Skip to main content

Consuming webhook events

How a receiver reads DALP webhook deliveries: outcomes, identifiers, ordering, retries, Resend, Replay, chain of custody, receipts and retractions.

The platform attempts each event it records for an endpoint at least once, and can send one event more than once. A Replay sends an earlier outcome again under a new event id. Automatic delivery stops at a terminal failure or after three days, and the platform records no events for an endpoint while it is switched off. Endpoint registration and signature verification are in the setup guide.

Outcome model

An API response that accepts a write request does not mean the transaction is final on chain. Many write routes queue the transaction, so the response confirms only that the platform accepted the instruction. The lifecycle state of an event tells you how far the fact has progressed. The lifecycle page defines each state. The idempotency and async settlement guide covers the write response.

A chain reorganisation replaces recent blocks with a competing set of blocks. The platform sends a final event for a chain log only after the block passes the network's finality depth, which Lifecycle states defines.

Signal
What it tells you
What it does not tell you
A write response that accepted the request
The platform accepted the instruction.
That the transaction reached the chain or is final.
A provisional event
The platform reports a fact that can still change. A chain reorganisation can still remove the block of a chain log.
That the fact is complete.
A final event
The platform reports the fact as complete. The block of a chain log passed the finality depth.
That no retraction can follow. A trade bust retracts a settled trade that the platform already reported as final.
A retracted event
An earlier event no longer stands.
That your endpoint received the earlier event. Retractions covers this case.

Identifiers and deduplication

Verify the exact raw body and its signature before trusting an event or using its identifiers. The setup guide documents verification. A lost response can cause a duplicate delivery, so a successful verification does not mean the event is new.

Identifier
Where it appears
Same across attempts
Use it to
webhook-id header and evt_id body field
Every delivery. Both hold the same value, the id of the event.
Yes. A retry and a Resend keep it. A Replay mints a new one.
Deduplicate deliveries of one event.
webhook-delivery-id header
Every delivery.
No. Each attempt gets a new value.
Identify one attempt, and name it in a counter-signed receipt.
source_id body field
Every event except a test event.
Yes. A Replay keeps it.
Recognise the same outcome of one event name when it returns under a new event id.
request.idempotency_key body field
Request correlation metadata when the transaction's caller attribution is unambiguous.
Yes, when present.
Correlate with a request when the stored event supplies a key. Never deduplicate deliveries on it.

The platform adds the headers webhook-delivery-id and webhook-endpoint-id to the three headers of the open Standard Webhooks specification: webhook-id, webhook-timestamp and webhook-signature. The webhook-endpoint-id header names your endpoint and stays the same for it. The setup guide lists every header.

Deduplicate deliveries on webhook-id. A retry, a Resend, and a send that the platform repeats when it stopped before recording the attempt all keep the webhook-id of the first attempt. Answer a webhook-id you already handled with a 2xx response.

A Replay is a new event with a new evt_id, so webhook-id does not match it. Its source_id and its payload identifiers match the original. When you recover through a Replay, deduplicate outcomes on source_id, or on payload identifiers such as the transactionHash and logIndex of a token transfer.

For each endpoint, the platform stores at most one event with a given source_id, excluding Replay and test events. A provisional event and the final event for the same fact have different source_id and evt_id values, because the source_id includes the event name and version. Match them through the payload identifiers.

The optional request.idempotency_key is a correlation field, not a delivery deduplication key. A chain-sourced event includes the caller's key when the transaction can be attributed to one organisation and one nonempty caller key, and that organisation owns the endpoint. Conflicting attribution, a missing key or an endpoint in another organisation leaves the field absent. A Replay or retraction preserves a key already present in the stored event. When the field is absent, correlate through the payload identifiers rather than inventing a key.

The helper verifyWebhook in @settlemint/dalp-sdk/verify-webhook accepts an optional replayStore. With a store, it claims the webhook-id of each delivery whose signature verifies and returns REPLAYED_EVENT for an id that is already claimed. Answer REPLAYED_EVENT with a 2xx response. When your handler fails, call release on the verified result, so the platform's retry of that webhook-id verifies again. Your store must claim each id in one atomic write. Refuse a repeated delivery in the setup guide explains the store methods and the cases a store does not cover.

Ordering

The platform assigns each event a position in its endpoint's delivery sequence. A later eligible event waits while an earlier event remains pending, including during backoff. A successful attempt, a terminal failure, or exhaustion of the retry window releases the later events. A missing counter-signed receipt can reopen an event for retry.

A linked correction waits for its cause. The platform sends it after the event it replaces is delivered, or after that event can no longer be delivered. This permits a correction to identify an event your system never received.

The platform gives no guarantee of:

  • Order between endpoints. Each endpoint has its own sequence.
  • Delivery of a chain final event before the events recorded after it. An event that waits for chain finality does not hold back later events, so its delivery can follow theirs.

A Replay takes the next place in the endpoint's sequence, after every event the platform already recorded.

Retries

Your response decides what the platform does next.

Your endpoint answers
The platform
A 2xx status
Marks the attempt delivered and does not retry.
408 or 429
Retries. Use these statuses to ask for a later attempt.
Any other 4xx status
Treats the response as terminal, does not retry, and ends delivery of the event.
A 5xx status
Retries.
A redirect or any other status. The platform follows no redirects.
Retries.
No answer within 20 seconds, a refused or reset connection, a Domain Name System (DNS) lookup failure, or a Transport Layer Security (TLS) handshake failure
Retries.

The receipt rules also apply after a 2xx response when receipts are enabled.

When your receiver cannot process an event at that moment, answer 503 or 429, not another 4xx. A 4xx ends delivery of the event, and only a Resend recovers it.

The platform also checks the endpoint address before each attempt. When the address falls outside the destination policy, for example because its hostname now resolves to a private address, the platform sends nothing. It marks the attempt TARGET_REFUSED, does not retry, and ends delivery of the event. Correct the endpoint URL, then use Resend.

The platform waits before each retry. The base wait doubles with each failed attempt: 30 seconds after attempt 1, 60 seconds after attempt 2, 2 minutes after attempt 3, and so on, limited only by the retry window. The platform multiplies the base wait by a random factor from 0.7 to 1.3. When a failed response carries a Retry-After header, the platform waits at least that long. The wait never exceeds the time left in the retry window. An attempt times out after 20 seconds.

The retry window is three days. It starts when the platform records the event, not at the first attempt.

At exhaustion, the platform abandons automatic delivery, writes a warning log, and records a failed trace span. Later eligible events can proceed. The recorded attempts remain available in the endpoint's delivery history, under Event deliveries in the Console. A terminal failure also stops automatic delivery. Resend and Replay provide recovery actions after the receiver is repaired.

A run of failed attempts can also switch the endpoint off. After 30 consecutive failed attempts, test events excluded, the platform opens the endpoint's circuit breaker and switches the endpoint off. The platform queues no new events for an endpoint that is off, and no Replay recovers them later. It probes the endpoint and resumes delivery after two successful probes. Enabling the endpoint with PATCH /api/v2/webhooks/{webhookId} and disabled: false resets the breaker at once. The Webhook Delivery concept describes the circuit breaker in full.

Resend and replay

Resend and Replay are authenticated Platform API requests. The authentication guide describes the accepted credentials. The v2 API reference defines their request, response and error bodies. The event-delivery signing secret is not a replacement for API authentication.

Both actions send an event again. They differ in what they keep.

Property
Resend
Replay
Request
POST /api/v2/webhooks/{webhookId}/deliveries/{deliveryId}/retries
POST /api/v2/webhooks/{webhookId}/replays
What it selects
One attempt, which must be the latest attempt of its event.
A block range on one chain, or one event by evt_id.
Event id
The same evt_id and webhook-id.
A new evt_id for each event.
source_id
The same.
The same.
Attempt history
Adds one attempt to the event. Earlier attempts keep their recorded outcome.
Creates a new event with its own attempt history.
is_replay
Unchanged.
true on every replayed event. A live event has no is_replay field.

A Resend queues one more attempt. It runs even when an earlier attempt succeeded, so your endpoint can receive an event it already acknowledged. The call returns deliveryId and scheduled. The value scheduled is false, and the platform changes nothing, when any of these holds:

  • The attempt is not the latest attempt of its event.
  • The event was replaced or cancelled after a chain reorganisation, so the platform no longer delivers it.
  • A newer attempt is already claimed, or an earlier Resend has not produced an attempt.

A retry waiting in backoff does not block a Resend.

A Resend does not restart the retry window. The platform measures the window from when it recorded the event. A failed Resend attempt on an event older than three days gets no retry.

A Replay takes either a block range or one event. The block range holds fromBlock, an optional toBlock, and chainId. Without toBlock, the range ends at the highest block of an event recorded for the endpoint on that chain. The single event holds evtId and an optional chainId. The response returns replayId, eventsEnqueued and snapshotToBlock, the last block the range covered. The platform resends the stored payload with its lifecycle state, so a replayed provisional event is still provisional.

Do not use Replay arrival order or a global ranking of lifecycle_state values to replace a business record. Follow the event family's correlation and transition rules in the catalogue. A replayed provisional transfer does not undo its final confirmation. Retain explicit supersedes relationships so that an older observation cannot restore a fact already withdrawn. If the Replay's relationship to your record is unclear, reconcile the resource before overwriting that record.

A Replay is subject to these rules:

  • The platform rejects it while the endpoint is switched off or its circuit breaker is open.
  • A range whose toBlock is before its fromBlock is rejected with WEBHOOK_REPLAY_RANGE_REVERSED.
  • A range wider than 10,000 blocks needs confirmLargeRange set to true. Without it, the platform rejects the range with WEBHOOK_REPLAY_RANGE_TOO_LARGE.
  • A chainId the organisation has no network for is rejected with WEBHOOK_REPLAY_CHAIN_NOT_AVAILABLE, before anything is queued. A network that the organisation registered can still be replayed after it is turned off.
  • An organisation can start at most 10 Replays per minute on each API server, because each server counts its own requests.
  • The platform does not replay an event that a chain reorganisation replaced or cancelled.

If a Replay response is lost, repeating the request can queue the same outcomes again with new event ids. Deduplicate those outcomes by source_id, and do not assume the second request repeats the first response. The Resend conditions still apply to its original deliveryId. Treat neither Resend nor Replay as safe to repeat after an API failure.

Chain of custody

GET /api/v2/webhooks/events/{evtId}/chain-of-custody returns the recorded dispatch history of one event. The record is the platform's own account of what it sent. It is not signed, it is stored with the event, and the retention cleanup deletes it 30 days after delivery ends. Keep your own copy of evidence you must retain, or enable counter-signed receipts. The route needs webhook management permission. The Console shows the same record under “Chain of custody” in the “Webhook event” sheet.

Field
Contents
hops
One hop for each successful delivery attempt, with stage set to dispatch-attempt, contentHash, signedBy holding the endpoint id, and recordedAt. contentHash is sha256: followed by the SHA-256 digest of the exact body the platform sent.
merkleRoot
The SHA-256 digest of the hops' contentHash values joined with :, in hexadecimal.
platformSignature
dalp-platform: followed by merkleRoot. It labels the root and is not a cryptographic signature.

A hop's contentHash equals the body hash a counter-signed receipt submits for the same body, so the two records can be matched. An event with no successful attempt has no hops.

Delivery history retention

The platform periodically deletes settled delivery history more than 30 days old. It measures the age from when delivery ended, not from when the event was recorded. The event's delivery attempts and counter-signed receipts are removed with it.

This cleanup includes final and retracted events. It also includes provisional events recorded inside the platform, such as trading-venue.order-status.provisional and trading-venue.price-alert.triggered: those events do not become final. Pending deliveries, test events and provisional observations from chain logs are outside this cleanup.

The 30-day retention period does not extend the three-day retry window. Keep your receiver's business records and acknowledgements for as long as your audit policy requires. The platform's delivery history is not a permanent archive.

Counter-signed receipts

The platform signs each delivery over webhook-id, webhook-timestamp and the exact body, as the setup guide describes. A 2xx response records that your endpoint answered. A counter-signed receipt records your system's acknowledgement of the exact body, signed with the endpoint's webhook signing secret. The endpoint setting counterSignedReceipts enables this acknowledgement. Enable it when you must show that your system, not only a proxy in front of it, accepted the exact body. Your receiver then posts a receipt within the receipt window after each 2xx response, or the platform retries the event. After a successful attempt, the platform schedules a deadline for the receipt.

Receipt submission requires Platform API authentication as well as the consumer signature. The v2 API reference defines the request and response contract for POST /api/v2/webhook-receipts.

A successful submission returns the receipt resource. A repeated valid submission within the receipt window updates the receipt for that delivery, including its verification timestamp. The response need not be identical. A delivery already marked as receipt-timed-out is rejected rather than reopened.

You post the receipt to POST /api/v2/webhook-receipts with deliveryId, evtId, endpointId, consumerSignature and innerEventHash. The platform accepts a valid signature made with the endpoint's active secret or its previous secret during rotation. The signing string has five lines, joined with \n, with no trailing newline.

Line
Value
1
The scheme name dalp.whr.v1.
2
The webhook-delivery-id of the delivery you answer.
3
The webhook-endpoint-id of the delivery.
4
The webhook-id of the delivery.
5
The body hash: sha256: followed by the lowercase hexadecimal SHA-256 digest of the exact body you received.

SHA-256 means Secure Hash Algorithm 256. The receipt signature is a keyed-hash message authentication code (HMAC) with SHA-256 over the signing string, written in hexadecimal. The key is the endpoint signing secret without its dalp_whsk_ prefix, decoded from base64. The SDK functions signWebhookReceipt and buildReceiptSigningString in @settlemint/dalp-sdk/sign-webhook-receipt produce the signature and the string.

The receipt window is 30 seconds by default. The endpoint field receiptTimeoutSeconds sets it to a whole number from 1 to 60. The window starts after your 2xx response.

Receipt
The platform
Verifies and matches the body
Records the receipt.
Has a signature that does not verify
Rejects it with status 401 and the failure class RECEIPT_INVALID_SIG.
Arrives after the window closed
Rejects it with status 401 and the failure class RECEIPT_WINDOW_CLOSED.
Has a hash that differs from the sent body
Rejects it with status 401 and RECEIPT_HASH_MISMATCH. It marks the attempt RECEIPT_HASH_MISMATCH, schedules no retry, settles the event, and counts the attempt toward the circuit breaker.
Does not arrive within the window
Marks the attempt RECEIPT_TIMEOUT and retries on the normal backoff. The retry repeats the same webhook-id.

An invalid signature rejects the receipt submission without settling the event or scheduling a new delivery. Correct the signature and submit it again while the receipt window is open. If no valid receipt arrives by the deadline, the receipt-timeout path retries delivery within the event's retry window.

A correctly signed receipt with the right hash can still replace a hash-mismatched receipt for the same delivery. The platform then records the receipt and restores the attempt as delivered, as long as the attempt has no recorded receipt timeout.

GET /api/v2/webhook-receipts lists the stored receipts, and GET /api/v2/webhook-receipts/{id} reads one. Both need webhook management permission. A receipt submission for a delivery that did not require a receipt when the platform sent it answers status 404 with WEBHOOK_DELIVERY_NOT_FOUND.

A late submission does not reopen its receipt window or schedule another attempt. Submit a receipt for the next delivery if the platform retries it. A rejected receipt submission and a retry of the webhook delivery are separate operations.

Payload shape

Use the machine-readable event definitions or the SDK's Webhook types for each event's envelope and payload schema. Those schemas do not hold the consent field-path list.

Each endpoint has a payload shape. The shape controls whether the personal data fields of an event reach your endpoint. Each event defines its own personal data fields, such as the payer address of a trade fee.

Shape
Personal data fields
How an endpoint gets it
thin
The platform removes them from the payload before it signs the payload.
Every new endpoint starts thin.
fat
The platform keeps only the fields you acknowledged by name.
An update sets defaultPayloadShape to fat and lists the fields in fatEventsAcknowledgment.

A removed field is absent from the payload. It is not null, so read personal data fields as optional. The platform shapes the payload when it records the event for the endpoint. A later change of shape affects new events only, and a Replay sends the stored payload.

The platform rejects a request that creates an endpoint as fat. A switch to fat needs fatEventsAcknowledgment.fieldsAcknowledged to list every personal data field of the events the subscriptions match, written as <eventType>.<fieldPath>. The list includes the fields of the retractions of those events. A list that misses a field is rejected with status 422 and the error WEBHOOK_FAT_ACK_INCOMPLETE, id DALP-0517. The same check runs when an update changes the subscriptions of a fat endpoint. The platform records who acknowledged the fields, and when.

The Console endpoint editor prepares and records the required acknowledgement for the selected subscriptions. An endpoint read returns its stored fatEventsAcknowledgment.fieldsAcknowledged, not a computed list for a proposed subscription change. The event manifest and the public DALP-0517 error body do not supply that complete consent list. Do not infer consent paths from a sample payload.

Retractions

A retraction withdraws an earlier reported fact. When present, supersedes holds the evt_id of the event it replaces. The named event can be one your endpoint never received.

  • After a chain reorganisation removes a block, the platform retracts only an event for which it recorded a successful delivery to your endpoint. Otherwise, it cancels the event and sends no retraction.
  • For committed-state retractions such as trading-venue.trade-settled.retracted, the platform looks up the earlier event id created for your endpoint. This lookup does not require a successful delivery. The supersedes field is absent when no earlier endpoint event id exists, for example because the endpoint did not exist.
  • When the platform recorded the earlier event but could not deliver it, the retraction names an event you never received. The platform sends the retraction once the earlier event can no longer be delivered.

Your handler can have applied a chain observation even when its HTTP acknowledgement was lost. Without a recorded successful delivery, the platform can cancel that observation without sending a retraction. Absence of a correction is therefore not confirmation of the canonical chain state. Keep uncertain effects provisional and reconcile them before confirming your business record.

Consumer handling follows these rules:

  • Match by supersedes. If it is absent or unknown, use the payload identifiers, such as tradeId. Reverse only effects your system applied.
  • Retain the retraction and audit-log any unknown supersedes reference. An unknown reference alone is not a processing failure. Do not invent a reversal for an action your system never applied.
  • Keep the superseding state if the original arrives later. A Replay can deliver an older observation after a newer one.

The .retracted name suffix does not always mean a retracted lifecycle state. For example, token.corporate-action.retracted has state final and no envelope supersedes link. Match this event through its payload identifiers. Read the event's type as well as lifecycle_state.

The lifecycle state recalled is reserved. The platform sends no new event with this state, and a Replay resends one only when DALP 3.1 stored it. Acknowledge such an event and change nothing.

  • Event catalogue: the events of every domain except token sales, trading venues and XvP settlements, when the platform sends each one and what to do with it.
  • Market and settlement events: the events of token sales, trading venues and XvP settlements, when the platform sends each one and what to do with it.
  • Lifecycle states: what each state means, with worked sequences.
  • Subscriptions: the patterns that select events, and the events the default subscriptions skip.
  • Setup guide: endpoint registration, headers and signature verification.
  • Webhook Delivery: the delivery model and endpoint recovery.

Was this page helpful?