Payload structure
Every webhook delivery follows the same envelope format:
The payload is intentionally minimal. When your handler needs more context (parties, legs, timestamps, transitions), fetch the settlement with
GET /v1/settlements/{settlement_id} or its event history with GET /v1/settlements/{settlement_id}/events.
When webhooks fire
Settlement webhooks are dispatched by KeyStone’s settlement engine: each time an engine run advances a settlement and leaves it in a new state, one event fires, named for the state the run ended in (settlement.state. plus the lowercased state name).
Two consequences worth designing around:
- Intermediate states are skipped. A single engine run can pass through several states; only the final one emits an event. On the happy path, compliance passes and on-chain registration completes in one run, so the first event you receive is
awaiting_deposits(notinstructedorcompliance_cleared). - States written outside the engine do not emit their own event. On-chain outcomes (
SETTLED,ROLLED_BACK,TIMED_OUT) and operator actions are applied directly and are visible immediately via the API; a webhook follows only where the engine runs afterwards (execution is followed by an engine run that finalizes the settlement and emitsfinalized). See states without their own webhook below.
Events you will receive
settlement.state.awaiting_deposits
Fired when compliance has passed and the settlement is registered on-chain, ready for escrow deposits. On the happy path this is the first event for a new settlement.
settlement.state.compliance_checking
Fired when screening flags a party and the settlement parks in COMPLIANCE_CHECKING awaiting a manual compliance decision. (When screening passes outright, the settlement continues to AWAITING_DEPOSITS in the same engine run and no compliance_checking event fires.)
settlement.state.rejected
Fired when compliance screening fails outright and the settlement is auto-rejected before any deposits. Nothing is locked on-chain at this point. (A settlement rejected by a manual compliance decision reaches the same REJECTED state - poll or check the settlement after submitting a decision.)
settlement.state.compliance_cleared
Fired only when compliance passed but the engine run halted at COMPLIANCE_CLEARED (for example, on-chain setup needed a retry). Normally you will not see this event - the settlement continues to AWAITING_DEPOSITS in the same run.
settlement.state.finalized
Fired when the settlement has executed on-chain and the record is finalized. This is the success event.
test.ping
Fired when you test a webhook endpoint via the Dashboard or the API. Used to verify your endpoint is reachable and correctly verifying signatures.
States without their own webhook
Thesettlement.state.<state> namespace covers every state in the settlement state machine, but five states are currently never delivered as webhooks:
Event filtering
When registering a webhook endpoint, you specify which events to receive using glob-style patterns.
You can subscribe to multiple patterns per endpoint:
Subscribing to
settlement.state.* is the safe default: you receive every event that fires today and automatically pick up any states that gain webhooks later.Signature verification
Every delivery includes anX-Keystone-Signature header containing an HMAC-SHA256 hex digest of the raw request body, signed with your webhook secret.
Secret rotation
When you rotate a webhook secret, KeyStone provides a 24-hour grace period where both the old and new secrets are valid. During this window:X-Keystone-Signatureis signed with the new secretX-Keystone-Signature-Previousis signed with the old secret
X-Keystone-Signature is sent.
Retry behavior
Each delivery is attempted with up to 3 retries in quick succession (short, jittered backoff; aRetry-After header is honored on 429 responses). A non-2xx response or a timeout after the final attempt marks the delivery as failed in the delivery log - there is no delayed redelivery queue.
Because failed deliveries are not replayed later, treat the delivery log as your recovery tool: inspect failures in the Dashboard or via the delivery log API, and reconcile missed events by fetching the settlement’s current state.
Idempotency
The same event may be delivered more than once. Your webhook handler must be idempotent. Recommended patterns:- Check current state before acting. If you receive
settlement.state.finalizedbut the trade is already marked as settled in your system, skip processing. - Use the settlement ID plus state as a deduplication key. Track which settlement/state combinations you have already processed.
- Make downstream calls idempotent. If your handler triggers a transfer or notification, ensure the downstream system also handles duplicates.
Full handler example
A complete webhook handler in TypeScript that covers signature verification, idempotency, and event routing:For SDK-based webhook handling with built-in signature verification, see the TypeScript SDK webhooks guide or Python SDK webhooks guide.