Skip to main content
This is the single reference page to integrate Cativa webhooks. A webhook is a POST that Cativa sends to a URL of yours every time an event happens in the tenant (a user signs up, a payment clears, a post is published). Instead of polling the API, Cativa notifies you the moment the event occurs. Here you’ll find: how to register the listener, the list of events, how to verify the signature, and how to handle retries and duplicates.
One event per listener. Each subscription listens to one event name (e.g. user_created). To listen to several events, create several listeners, pointing them all at the same URL if you want. Each listener has its own secret.

Two ways to subscribe

Via the Console

Visual, no code. Best to configure quickly and reveal the secret on screen.

Via the management API

Programmatic and versionable. Best to provision listeners at scale or through infrastructure as code.

Subscribe via the Console

In the Cativa dashboard, open Console > Webhooks and click Add listener. The flow asks for 3 things:
  1. URL — your app’s public endpoint that will receive the POST (e.g. https://myapp.com/webhooks/cativa).
  2. Event — the event name you want to listen to (snake_case, e.g. user_received_badge).
  3. Secret — generated automatically. After you create the listener, open it and click Reveal secret to copy the value (format whsec_ + 64 hex characters). Only the tenant admin can reveal the secret. Store it safely in your credentials vault.

Subscribe via the management API

The webhook management endpoints live under the admin/integration/{customerId}/webhooks resource. {customerId} is your tenant’s ID.
  • Base URL: https://apis.cativalab.digital/tenant/api/v2
  • Authentication: header Authorization: Bearer YOUR_API_KEY (the API key starts with cativa_).
1

Create the subscription (and store the secret)

Send a POST with the EventName and Url. The response returns the Secret once — copy and store it before closing the connection. After that it can’t be retrieved via the API (only revealed in the Console).
Response:
If you omit GroupId, the listener fires for the event across the whole tenant. Provide a GroupId to listen to a single group only.
2

Receive the POST at your endpoint

Every delivery arrives as a POST with Content-Type: application/json and the headers X-Cativa-Signature, X-Cativa-Execution-Id, X-Cativa-Automation-Id and X-Cativa-Idempotency-Key (detailed in Delivery headers).
3

Verify the HMAC signature

Before trusting the payload, recompute the HMAC-SHA256 with your Secret and compare it in constant time against the X-Cativa-Signature header. Reject deliveries with a timestamp outside the 5-minute window. Ready-to-use code in Node and Python in the Verifying the HMAC signature section.
4

Respond 2xx fast

Respond 200 (any 2xx works) as soon as you validate the signature. Don’t do heavy work inside the request. Queue the processing and answer fast, otherwise the delivery times out and enters a needless retry.
5

Deduplicate redeliveries

Delivery is at-least-once. Use the X-Cativa-Idempotency-Key header (or X-Cativa-Execution-Id) to drop duplicates. Details and code in Idempotency.
6

Let Cativa retry failures

If you respond 5xx/408/429 or the connection drops, Cativa retries on its own on the backoff curve. You don’t have to do anything beyond going back to responding 2xx. Details in Retries and permanent failures.

Management endpoints

All require Authorization: Bearer YOUR_API_KEY and use the base https://apis.cativalab.digital/tenant/api/v2.
Temporarily disable it or swap the URL without recreating the listener (the secret is preserved):
Logs for a specific subscription:
All tenant executions, filtering by failures of one event in the last 24h:

Available events

Use exactly these snake_case names in the EventName field when subscribing — that’s how Cativa matches.

Verifying the HMAC signature

The signature is what proves the delivery really came from Cativa (and not from someone who found your URL). Always verify before processing. Every delivery ships the header:
  • t — Unix timestamp (seconds) at delivery time.
  • v1 — HMAC-SHA256 (hex, lowercase) over the string "<t>.<rawBody>", using the listener’s secret as the key.
Verification takes three steps:
  1. Parse the header into t and v1.
  2. Anti-replay — reject the request if |now - t| > 300 seconds (5 minutes is the industry standard). This blocks the replay of an old captured request.
  3. Recompute the HMAC and compare in constant time (timingSafeEqual / hmac.compare_digest), never with ==.
Compute the HMAC over the raw body (the exact bytes received), never over re-serialized JSON. Re-serializing changes whitespace and key order and invalidates the signature. In Express use express.raw; in Flask use request.get_data().

Delivery headers

Retries and permanent failures

If your endpoint doesn’t reply with 2xx, Cativa retries on this curve:
That’s 6 retries after the initial attempt (7 deliveries total), covering roughly 33 hours. Every retry of the same delivery carries the same X-Cativa-Idempotency-Key and X-Cativa-Execution-Id. Cativa honors the Retry-After header you return (capped at the next backoff window’s max).

Behavior table by status

The logic: status in the 4xx range (except 408 and 429) means “the request is wrong and retrying won’t help” — typically a client bug, deactivated URL or wrong auth. Status 5xx, 408, 429 and transport errors mean “try again later”.

When all retries fail

If the 7th attempt also fails, the delivery becomes a dead-letter and is recorded in the logs (queryable via GET .../webhooks/{webhookId}/logs and GET .../executions). Monitor your endpoint’s uptime on your own side, and if you suspect missed events, check the logs or open a ticket at dev@cativa.digital.

Idempotency

Delivery is at-least-once. The same event can arrive more than once (retries after a timeout, connection loss while replying). You have to detect duplicates on your side. The canonical key is the X-Cativa-Idempotency-Key header: it’s deterministic per delivery and stays the same across all retries (the X-Cativa-Execution-Id also works). Save that ID in the same transaction as your business logic:
That guarantees either everything happened or nothing happened — no chance of double-processing.

Best practices

Validate the signature, enqueue the payload (queue, outbox table, topic) and respond 2xx right away. Heavy processing inside the request times out and generates needless retries.
Never act on an unverified payload. Your URL can leak (logs, proxies) and anyone can POST to it. The HMAC is what separates a legitimate delivery from a forged one.
Record the X-Cativa-Idempotency-Key and drop redeliveries. Assume every event can arrive twice.
External calls inside the handler (CRM, email) should have short timeouts. If a third-party provider hangs, you hang the response and it becomes a retry.
Don’t reuse a secret across environments. A staging listener and a production listener have distinct secrets by design — treat them as separate credentials.

Webhooks (overview)

Why webhooks, delivery guarantees and the payload format.

user_received_badge

Full reference page for an event — payload, headers and example receiver.