Browse the docs
DocsDevelopers / APIDevelopers

Webhooks

Subscribe an HTTPS endpoint to organisation events, verify the signed payloads, and understand delivery and retries.

Webhooks push organisation events to an HTTPS endpoint you control, so you can react to changes without polling. Each subscription is signed with its own secret and delivered on a best-effort, retrying schedule.

Create a subscription

Create a subscription with a webhooks:write key whose member is an admin. A read scope is deliberately not enough to create one: minting a signing secret is a write action.

http
POST /api/v1/webhooks/subscriptions HTTP/1.1
Authorization: Bearer ck_live_a1b2c3d4_...
Content-Type: application/json
json
{
  "url": "https://hooks.example.com/cadence",
  "event_types": ["time_entry.created", "time_entry.updated"]
}

The URL must be HTTPS and resolve to a public host: private and local addresses are refused. The response returns the new subscription and, exactly once, its signing_secret (a whsec_ value). Store it now; it is never shown again.

json
{
  "data": {
    "id": "7c6b5a49-3827-4615-9304-2c1b0a9f8e7d",
    "url": "https://hooks.example.com/cadence",
    "event_types": ["time_entry.created", "time_entry.updated"],
    "status": "active",
    "signing_secret": "whsec_Zk9...redacted..."
  }
}

Manage subscriptions

GET /webhooks/subscriptions
List your organisation's subscriptions (integrations:read). The signing secret is never returned here.
PATCH /webhooks/subscriptions/{id}
Set status to active, paused, or disabled (webhooks:write).
DELETE /webhooks/subscriptions/{id}
Soft-disable a subscription (webhooks:write). Subscriptions are never hard-deleted, so the audit trail is kept.

Delivery payload

Cadence delivers each event as a JSON POST to your URL. The body carries the event id, type, the organisation it belongs to, when it occurred, and the changed record under data:

json
{
  "id": "0e1d2c3b-4a59-4687-9105-2d3e4f5a6b7c",
  "type": "time_entry.created",
  "org_id": "abcdef01-2345-6789-abcd-ef0123456789",
  "occurred_at": "2026-06-17T10:30:01Z",
  "data": { /* the changed record */ }
}

Verify authenticity

Every delivery carries a Cadence-Signature header so you can confirm it really came from Cadence and was not tampered with. The header has the form t=<timestamp>,v1=<hmac>, where the HMAC is SHA-256 over <timestamp>.<raw-body>keyed by your subscription's signing secret.

  1. Read the header
    Parse the t (timestamp) and v1 (signature) values from Cadence-Signature.
  2. Recompute the HMAC
    Compute HMAC-SHA256(secret, t + "." + rawBody) over the exact raw request body, and compare it to v1 with a constant-time comparison.
  3. Check the timestamp
    Reject the delivery if t is too far from now, so a captured request cannot be replayed later.
Respond fast, dedupe on the event id
Return a 2xx quickly and do your real work asynchronously: deliveries time out after a few seconds. Because delivery is at-least-once, the same event id can arrive more than once after a retry; treat the id as an idempotency key on your side.

Retries and delivery

A background worker drains pending events roughly every minute. A non-2xx response, a timeout, or an unreachable endpoint is retried on an exponential backoff (a few attempts over roughly the next several hours) before the event is marked exhausted. Repeated failures count against the subscription, and you can pause or disable it at any time. Each delivery is recorded once per attempt, so your audit and ours line up.