Claron
    Documentation
    WEBHOOKS
    Settings
    Reviewed

    Outbound Webhooks

    Configure workspace webhook delivery from the settings surface for annotation and build events.

    Create a webhook and test delivery

    API-key setup needs webhooks:create to create, webhooks:write to update, webhooks:delete to remove, and webhooks:read to list deliveries. See API Keys.

    1. Prepare a publicly reachable HTTPS receiver

      It must not point at localhost or a private network address and should acknowledge within 10 seconds.

    2. For a workspace webhook, open Settings → Webhooks with workspace webhook-management permission

      Workspace management and project management are different grants.

    3. Enter the receiver URL, choose events, and save

      Optionally configure a signing secret; if you do, store the same secret securely at the receiver and verify every delivery's signature.

    4. For API setup, use /api/v1/webhooks/outbound; a user account creating a workspace hook must supply orgId

      Project-specific hooks use /api/v1/webhooks/outbound/project/:projectId and require permitted staff access to that project.

    5. Trigger a controlled subscribed event in a test project, then open delivery history and confirm a 2xx result and the expected receiver action

      Check both sides rather than assuming saving the hook tests it.

    Payload and signature contract

    A delivery is a JSON object with event (string), data (event-specific object), and ts (UTC epoch milliseconds for that delivery attempt). Do not treat data as an identical schema for every event or assume every optional field is present.

    When a signing secret is configured, X-Claron-Signature is sha256=<hex>. Verify HMAC-SHA256 over the exact received body bytes before parsing JSON. Parsing and re-serializing can change the signed bytes.

    The SLA outbound event name is annotation:sla_breached. It differs from the annotation:sla_breach notification-preference name; use the outbound name for webhook subscriptions. Confirm the subscribed event in an actual delivery when troubleshooting.

    Example receiver with raw-body verification

    This Node/Express example requires express and WEBHOOK_SECRET in the server environment. Mount this route before any JSON body parser that consumes the same request. Replace the marked handler with durable queuing or your own idempotent processing.

    EXPLORER
    webhook receiver×
    +
    javascript
    1import express from 'express'
    2import { createHmac, timingSafeEqual } from 'node:crypto'
    3
    4const app = express()
    5const secret = process.env.WEBHOOK_SECRET
    6if (!secret) throw new Error('WEBHOOK_SECRET is required')
    7
    8app.post('/claron-webhook', express.raw({ type: 'application/json', limit: '1mb' }), async (req, res) => {
    9 const header = req.get('X-Claron-Signature') ?? ''
    10 if (!/^sha256=[a-f0-9]{64}$/i.test(header) || !Buffer.isBuffer(req.body)) {
    11 return res.sendStatus(401)
    12 }
    13 const actual = Buffer.from(header.slice(7), 'hex')
    14 const expected = createHmac('sha256', secret).update(req.body).digest()
    15 if (actual.length !== expected.length || !timingSafeEqual(actual, expected)) {
    16 return res.sendStatus(401)
    17 }
    18 let delivery
    19 try { delivery = JSON.parse(req.body.toString('utf8')) }
    20 catch { return res.sendStatus(400) }
    21 if (typeof delivery.event !== 'string' || typeof delivery.ts !== 'number' || !delivery.data || typeof delivery.data !== 'object' || Array.isArray(delivery.data)) {
    22 return res.sendStatus(400)
    23 }
    24 try {
    25 await enqueueDelivery(delivery) // Implement durable, idempotent queuing.
    26 return res.sendStatus(202)
    27 } catch {
    28 return res.sendStatus(503)
    29 }
    30})
    31
    32app.listen(3000)
    webhook receiverLn 32, Col 1JAVASCRIPTUTF-8

    Retries and duplicate handling

    Timeouts and non-2xx responses are failures. Claron attempts delivery up to five times with exponential retry delays starting at one minute. A receiver may receive the same business event more than once.

    Make the business action idempotent using the resource identity and the event's business state. The envelope does not advertise a stable delivery ID, and ts is generated per attempt, so it is not a unique deduplication key.

    Delivery history records HTTP status, success, duration, and a bounded receiver response excerpt. For 401, compare the secret and raw bytes; for timeout, queue work promptly; for 4xx/5xx, check the receiver's route and logs. Avoid returning credentials or private payloads in error bodies.

    SETTING UP A WEBHOOK

    Workspace-level webhooks are configured from Dashboard sidebar Settings / Outbound Webhooks.

    1Go to Dashboard sidebar Settings / Outbound Webhooks
    2Add a webhook URL
    3Optionally set a signing secret
    4Choose the event types shown in the UI
    5Save and inspect delivery logs from the webhook row

    EVENT TYPES

    PAYLOAD SHAPE

    annotation:created
    json
    {
    "event": "annotation:created",
    "data": {
    "annotationId": "ann_01HZ...",
    "title": "Link is dead on iPad",
    "createdByUserId": "usr_..."
    },
    "ts": 1760000000000
    }

    Signing and retries

    When a secret is configured, Claron sends X-Claron-Signature: sha256=<hex>, an HMAC-SHA256 of the exact JSON request body. Delivery uses a 10-second timeout; network failures and non-2xx responses are retried up to five attempts with exponential backoff. Each attempt is recorded in the delivery log.

    DELIVERY LOGS

    Status
    Per-attempt response status in the delivery drawer
    Duration
    Per-attempt delivery timing
    Response body
    Captured response content when available
    Scope
    Workspace-level webhook configuration