Concepts
Webhooks
Subscribe to execution and adverse-action events, verify the HMAC signature, handle retries idempotently, and rotate secrets without downtime.
Cascades runs asynchronously, so the reliable way to learn that a run finished, failed, or produced an adverse-action notice is to be told. Subscribe an HTTPS endpoint once and every matching event is delivered as a signed POST.
Which events exist?
| Event | Fires when |
|---|---|
execution.completed |
An execution reached completed. Once per execution, including batch rows |
execution.failed |
An execution reached failed; the payload adds first_error_message. Not for cancellations |
adverse_action.triggered |
Adverse-action handling began |
adverse_action.completed |
Adverse-action obligations were fulfilled |
adverse_action.failed |
A notice email failed to deliver |
adverse_action.retry_scheduled |
A post-adverse notice retry was scheduled |
Events are emitted for API-triggered executions only (runs started from the Cascades UI have no API client and produce no webhooks). Test-mode runs emit them too; execution.* payloads carry execution_mode: "test" (adverse-action payloads do not — look the execution up by workflow_execution_id and read input_context.execution_mode).
How do I subscribe?
curl -X POST "https://cascades.crscreditapi.com/api/v1/webhook_subscriptions" \
-H "Authorization: Bearer {{API_KEY}}" \
-H "Content-Type: application/json" \
-d '{
"url": "https://hooks.partner.example/cascades",
"event_filter": ["execution.completed", "execution.failed"]
}'The 201 response includes signing_secret exactly once; store it. An empty event_filter subscribes to every event; unknown event names are 422 validation_failed. The URL must be http or https, public (no private, loopback, link-local, metadata or localhost addresses) and free of userinfo. Needs webhooks:write.
PATCH /webhook_subscriptions/{id} changes the URL, filter or active flag; DELETE removes it; GET never re-exposes the secret.
What does a delivery look like?
POST /cascades HTTP/1.1
Host: hooks.partner.example
Content-Type: application/json
User-Agent: Cascades-Webhook/1.0
X-Cascades-Event: execution.completed
X-Cascades-Delivery: 9081
X-Cascades-Signature: t=1715191234,v1=4f1b2…
{
"id": "6d1f1a5e-4a2b-4c7d-9e8f-0a1b2c3d4e5f",
"event": "execution.completed",
"created_at": "2026-09-09T14:03:21Z",
"data": {
"workflow_execution_id": 1842,
"workflow_id": 27,
"batch_execution_id": null,
"external_reference": "rv-app-20491",
"execution_mode": "test",
"status": "completed",
"review_status": "approved",
"completed_at": "2026-09-09T14:03:21Z"
}
}id is the envelope id (the same across retries of one event); X-Cascades-Delivery is the delivery row id — your idempotency key. Branch on X-Cascades-Event.
How do I verify the signature?
X-Cascades-Signature is t=<unix timestamp>,v1=<hex HMAC-SHA256> where the HMAC is computed over "<t>.<raw request body>" with your signing_secret. Verify against the raw bytes — any reformatting changes the digest — compare in constant time, and reject timestamps outside your tolerance (60 seconds is a reasonable cap). Drop-in verifiers for Ruby, Python and Node are in Verify webhook signatures.
Rotating the secret
POST /webhook_subscriptions/{id}/rotate_secret mints a new secret and returns it once. For the next 24 hours every delivery is signed with both: v1= with the new secret and v0= with the old one. A verifier that accepts a match on any v?= value handles rotation with no deploy coordination; after the grace window only v1= ships.
What if my endpoint is down?
Any non-2xx response, connection error or timeout (5 s connect, 10 s read) is a failure. Cascades retries with backoff:
attempt 1 → +1m → 2 → +5m → 3 → +30m → 4 → +2h → 5 → +8h → 6 → +24h → deadAfter six attempts the delivery is dead. GET /webhook_deliveries shows every delivery with status (pending, retrying, succeeded, failed, dead), attempts, last_response_status and next_retry_at — filter by subscription, event type or status. POST /webhook_deliveries/{id}/retry re-enqueues a failed or dead row immediately; an already-succeeded one is 409 delivery_not_retryable. The delivery payload itself is not exposed through the API; re-read the execution instead.
Your endpoint should return 2xx as soon as the event is durably stored, do the real work on a queue, be idempotent on X-Cascades-Delivery, and answer within 10 seconds.
Related
- Webhook events reference — payload schema per event
- Verify webhook signatures
- Executions and Adverse action