Webhooks
Configure delivery in Workspace, then consume signed events when a plan run drains, a participant acts, a file seals, or a claim changes.
Webhooks notify your backend as a plan run progresses. They complement the public read endpoints: an event says something changed, and the API returns the current record when you need to reconcile it.
Create the endpoint, choose its event subscriptions, test delivery, rotate its secret, and inspect failed attempts in Keepable Workspace. Webhook administration is intentionally not exposed to API keys.
Workspace shows the signing secret once when an endpoint is created or rotated. Store it in server-side secret storage and use it to verify every delivery.
Events to wire first
type | Fires when |
|---|---|
correspondence.send_completed | The queued delivery work for a plan run has drained. Its counts show delivered, held, and undeliverable outcomes. |
participant.acted | Delivery, read, approval, signature, consent decision, or form submission. data.act narrows it. |
correspondence.sealed | A file completed and sealed into a covenant. |
correspondence.withdrawn | A live run was withdrawn. |
form.response_received | One participant submitted a form response. |
claim.claimed | A claim code was redeemed. |
claim.expired | A claim code expired without redemption. |
correspondence.send_completed closes the asynchronous loop opened by POST /plans/{plan_id}/runs. The run endpoint answers 202; this event says its
queued delivery work finished, even if one or more recipients were not reached.
The envelope
{
"event_id": "evt_01J8ZQ4T",
"type": "participant.acted",
"at": "2026-09-18T10:00:00Z",
"data": {
"correspondence_id": "cor_01J8ZQ4T",
"participant_id": "prt_01J8ZQ4T",
"act": "approved",
"ref": "CUST-100401"
}
}Branch on type, and persist event_id for idempotent processing. Treat the
event as a notification rather than a complete database row: re-read the
correspondence, participants, responses, claims, or permission feed when your
decision needs current state.
Each delivery also carries:
| Header | Carries |
|---|---|
X-Keepable-Signature | t=<unix>,v1=<hex>. Verify it before parsing or acting. |
X-Keepable-Event-Id | The same id as the envelope, for de-duplication. |
X-Keepable-Event-Type | The event type, for routing before body parsing. |
Acknowledge quickly
Return any 2xx after durably accepting the event, ideally after placing it on
your own queue. Do not hold the response open while performing business work.
Delivery is at least once. Keepable retries non-2xx responses and timeouts,
so the same event_id may arrive more than once. De-duplicate before applying
effects. If Workspace shows a dead delivery, inspect it there and reconcile
from the public read operation named by the event.
Next
Verify webhook signatures
Recompute the HMAC over the raw body, enforce a timestamp window, and process each event idempotently.
Permission changes
Resolve the current consent state for one subject and consume a replay-safe feed of changes recorded through Keepable.
Verify signatures
Every webhook delivery is HMAC-SHA256 signed with your endpoint's secret. Verify the signature, enforce a timestamp window, and de-duplicate on the event id before you trust a payload.