Webhooks

A webhook endpoint (POST /webhooks) receives the public events of your account (the same ones returned by GET /events) as HTTPS POST requests, one event per request, signed with a signing secret specific to that endpoint. Delivery is at least once, so your receiver must be idempotent on the event id. Events are not guaranteed to arrive in order.

For every field and parameter, see the Webhooks reference. For the event catalog, see Events.

Registering an endpoint

  • url must use https and point to a public host on port 443, 80, or 8443. Private IP addresses, loopback, link-local, and metadata addresses, as well as any other port, are rejected both when you register the endpoint and when a delivery is sent. http://localhost is accepted only in sandbox accounts.
  • event_types[] lists the event types to subscribe to, taken from the catalog documented by GET /events. resource_filters optionally narrows deliveries by resource type and IDs. description is free text.
  • The create response includes the secret (whsec_…) only once. It cannot be retrieved afterwards. POST /webhooks/{webhook_id}/secret-rotations generates a new signing secret and keeps the previous one valid for overlap_hours (24 by default, up to 72). During that period, each delivery carries one signature per active secret.
  • status is active or inactive. Changing it with PATCH requires the webhooks.activate permission. DELETE cancels pending deliveries; the delivery history is kept for the retention period.

Payload and headers

Body: {"id": "evt_…", "type": "…", "version": 1, "occurred_at": "…", "data": {...}, "delivery_id": "whd_…", "attempt": n}.

Headers: Cademi-Signature: t=<unix>,v1=<hex>[,v1=<hex>], Cademi-Signature-Version: 1, Cademi-Webhook-Id, Cademi-Delivery-Id, Cademi-Event-Type, User-Agent: Cademi-Webhooks/3, Content-Type: application/json.

Verifying the signature

t        = value of "t" in Cademi-Signature
expected = HMAC_SHA256(secret, t + "." + raw_body)   # lowercase hex
valid    = any "v1" in the header == expected (constant-time comparison)
           and |now - t| <= 300 seconds

Use the raw body exactly as received, without re-serializing the JSON. During a secret rotation, check every v1 in the header against the secret you hold; a single match is enough.

Responses and retries

Endpoint responseResult
2xxDelivery becomes delivered
429 or 5xx, timeout, network errorNew attempt with exponential backoff and jitter (capped at 6 hours; Retry-After is honored up to 6 hours)
Any other 4xxDelivery becomes failed (not retried)
Destination rejected by the network policyDelivery becomes failed, and the attempt records a transport_error
10 attempts or 72 hoursDelivery becomes dead

The connection timeout is 5 seconds and the response timeout is 10 seconds. Up to 64 KiB of the response is read, and a masked excerpt (response_excerpt, up to 4 KiB) is stored for troubleshooting. Redirects are not followed.

Inspecting and resending deliveries

  • GET /webhooks/{webhook_id}/deliveries, GET .../deliveries/{webhook_delivery_id}, GET .../attempts, and GET /webhooks/deliveries (across all webhook endpoints accessible with the current credentials) require the webhooks.deliveries.read permission.
  • POST .../deliveries/{webhook_delivery_id}/attempts resends a delivery manually (same event id, new attempt number) and requires the webhooks.replay permission.
  • POST /webhooks/{webhook_id}/replays creates a webhook.replay operation over a set of retained events (selected by IDs, time range, or event types). Track the per-event results with GET /operations/{operation_id}. See Operations.
  • GET /webhooks/{webhook_id} includes health for the last 24 hours: delivered, failed, dead, and pending counts, the last success and failure, and consecutive failures.

Retention and sandbox

Events and deliveries are retained for 30 days, and attempts are retained with their deliveries. An event is removed only after its deliveries. In a sandbox account, deliveries to webhook endpoints registered in that sandbox are real, so you can use them as your test receiver. See Sandbox.

On this page