Webhook delivery receipts API
Read and submit counter-signed webhook delivery receipts through the DALP Platform API for non-repudiation proof that a consumer received and verified each event.
A delivery receipt is a signed record that a webhook consumer received a specific event and acknowledged it. When an integration needs an audit trail that proves delivery, not just that the platform sent an event but that the receiver got it and confirmed it, the consumer counter-signs each delivery and the platform stores the result. An auditor or operator then reads those receipts as the consumer-side half of the delivery chain of custody.
This surface has two roles. The consumer submits a receipt by counter-signing a delivery it received. The tenant operator reads the resulting receipts to confirm which deliveries were acknowledged and verified. Receipts are opt-in per endpoint: enable counter-signed receipts on the webhook endpoint before consumers can submit them.
Endpoints
| Endpoint | Use it for |
|---|---|
GET /api/v2/webhook-receipts | List the receipts recorded in the active tenant scope, newest first. |
GET /api/v2/webhook-receipts/{id} | Read one receipt by its identifier. |
POST /api/v2/webhook-receipts | Submit a consumer counter-signed receipt for a delivery the consumer received. |
The list endpoint uses the collection envelope with data, meta, and pagination links. The read and submit endpoints use the single-resource envelope with data and links.self. The two read calls run in the active organisation and system context, as described in Organization and system scope. The submit call is the consumer's own and authenticates differently, as described under Submit a receipt. To configure delivery and the per-endpoint receipts setting, see Webhook endpoints.
Route handler semantics
Each endpoint is served by a dedicated route handler with a specific scope and authentication method.
| Route | Handler scope | Authentication | Semantics |
|---|---|---|---|
GET /api/v2/webhook-receipts | Tenant-scoped | API key | Reads receipts from the active tenant, ordered newest first by receivedAt. Supports filtering, sorting, and global search across the receipt collection. |
GET /api/v2/webhook-receipts/{id} | Tenant-scoped | API key | Reads a single receipt by primary key, constrained to the active tenant. Returns DALP-0529 when the identifier does not exist in the current scope. |
POST /api/v2/webhook-receipts | Public (no tenant scope) | HMAC signature over the delivery | The consumer proves it holds the endpoint signing secret by counter-signing the delivered event. The handler verifies the signature against the active and previous secrets so rotation in flight does not reject valid receipts, then recomputes the canonical hash of the delivered payload to confirm the consumer signed the exact bytes that were dispatched. |
The list and read handlers are tenant-scoped: they enforce the organisation and system context from the API key session, as described in Organization and system scope. The submit handler is public: it does not use an API key. Instead, the HMAC signature over the (deliveryId, endpointId, evtId) capability triple is the authentication credential. The platform recognises the consumer because it signed the delivery with the endpoint's signing secret. The handler authorises against the delivery row's snapshot of counterSignedReceipts (not the live endpoint flag) so toggling the endpoint cannot retroactively change whether old deliveries accept receipts.
Receipt fields
Each receipt describes one consumer acknowledgement of one delivery.
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier for the receipt. |
deliveryId | string | The delivery this receipt acknowledges. One receipt exists per delivery. |
evtId | string | The event identifier carried by the delivery. |
endpointId | string | The webhook endpoint that received the delivery. |
consumerSignature | string | The consumer's HMAC signature submitted with the receipt. |
innerEventHash | string | The hash of the delivered event body that the consumer signed. |
receivedAt | string | When the platform recorded the consumer's submission. |
verifiedAt | string or null | When the submission verified. Set on a successful receipt, null when the receipt records a verification failure. |
verificationFailureClass | string or null | The failure reason when verification did not pass: RECEIPT_INVALID_SIG or RECEIPT_HASH_MISMATCH. null when verified. |
A receipt with verifiedAt set and verificationFailureClass of null is a confirmed acknowledgement. A receipt that instead carries a verificationFailureClass records a submission that reached the platform but did not verify, which is itself an audit signal.
List receipts
GET /api/v2/webhook-receipts returns the receipts in the active tenant scope, newest first by receivedAt.
The list supports pagination, sorting, filtering, and global search. Filter by evtId, endpointId, receivedAt, verifiedAt, or verificationFailureClass. Global search with filter[q] matches across the searchable fields. The default sort is newest first by receivedAt.
curl --globoff "https://your-platform.example.com/api/v2/webhook-receipts?filter[endpointId]=whe_01HXYZ" \
-H "x-api-key: YOUR_API_KEY"{
"data": [
{
"id": "whr_01HXYZ",
"deliveryId": "whd_01HABC",
"evtId": "evt_123abc",
"endpointId": "whe_01HXYZ",
"consumerSignature": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"innerEventHash": "a3bf4f1b2b0b822cd15d6c15b0f00a08884c7d659a2feaa0c55ad0159f86d081",
"receivedAt": "2024-01-01T00:00:01Z",
"verifiedAt": "2024-01-01T00:00:01Z",
"verificationFailureClass": null
}
],
"meta": {
"total": 1,
"facets": {}
},
"links": {
"self": "/v2/webhook-receipts?page[offset]=0&page[limit]=50",
"first": "/v2/webhook-receipts?page[offset]=0&page[limit]=50",
"prev": null,
"next": null,
"last": "/v2/webhook-receipts?page[offset]=0&page[limit]=50"
}
}The list returns 50 receipts per page by default, up to 200. Use page[offset] and page[limit] to page through longer histories. Filter on verificationFailureClass to isolate the submissions that did not verify, which surfaces consumers that received a delivery but signed it with the wrong secret or against a changed payload.
Read one receipt
GET /api/v2/webhook-receipts/{id} returns a single receipt by its identifier, scoped to the active tenant.
curl "https://your-platform.example.com/api/v2/webhook-receipts/whr_01HXYZ" \
-H "x-api-key: YOUR_API_KEY"A receipt identifier that does not resolve in the current tenant scope returns DALP-0529 with status 404. The response does not reveal whether the receipt exists in another tenant, so confirm the receipt identifier and the active organisation before retrying.
Submit a receipt
POST /api/v2/webhook-receipts is the consumer's call. After the consumer receives a delivery, it counter-signs the delivery and submits the receipt to confirm acknowledgement.
The submission carries no API key. The HMAC signature over the delivered event authenticates the request: the platform recognises the consumer because it signed the delivery with the endpoint's signing secret. Identify the delivery by the combination of deliveryId, endpointId, and evtId, then prove possession of the secret and the delivered bytes through consumerSignature and innerEventHash.
| Field | Type | Description |
|---|---|---|
deliveryId | string | The delivery being acknowledged. |
endpointId | string | The endpoint that received the delivery. |
evtId | string | The event identifier carried by the delivery. |
consumerSignature | string | The consumer's HMAC-SHA256 of innerEventHash under the endpoint signing secret, as a hex string. An optional sha256= prefix is accepted. |
innerEventHash | string | The hash of the delivered event body, used to prove the consumer signed the bytes it received. |
The platform verifies the submission in two steps. First it checks consumerSignature against the endpoint's signing secret, accepting both the active secret and the previous one during a rotation overlap so a rotation in flight does not reject a valid receipt. Then it recomputes the canonical hash of the delivered event body and compares it to innerEventHash, which proves the consumer signed the exact bytes that were delivered rather than only proving it holds the secret.
curl "https://your-platform.example.com/api/v2/webhook-receipts" \
-H "Content-Type: application/json" \
-d '{
"deliveryId": "whd_01HABC",
"endpointId": "whe_01HXYZ",
"evtId": "evt_123abc",
"consumerSignature": "sha256=9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"innerEventHash": "a3bf4f1b2b0b822cd15d6c15b0f00a08884c7d659a2feaa0c55ad0159f86d081"
}'{
"data": {
"id": "whr_01HXYZ",
"deliveryId": "whd_01HABC",
"evtId": "evt_123abc",
"endpointId": "whe_01HXYZ",
"consumerSignature": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"innerEventHash": "a3bf4f1b2b0b822cd15d6c15b0f00a08884c7d659a2feaa0c55ad0159f86d081",
"receivedAt": "2024-01-01T00:00:01Z",
"verifiedAt": "2024-01-01T00:00:01Z",
"verificationFailureClass": null
},
"links": {
"self": "/v2/webhook-receipts/whr_01HXYZ"
}
}Submission is idempotent
A consumer can retry the same submission safely. The platform records one receipt per delivery, so an at-least-once retry of an identical receipt converges to the same record rather than creating duplicates. A successful submission that follows an earlier hash-mismatch attempt for the same delivery clears the mismatch and confirms the delivery.
Submission outcomes
| Result | What it means |
|---|---|
DALP-0527 (404) | No delivery matches the deliveryId, endpointId, and evtId combination, or counter-signed receipts were not enabled on the endpoint when that delivery was dispatched. Confirm the three identifiers and that the endpoint has receipts enabled. |
DALP-0510 (401) | The signature did not verify, the payload hash did not match the delivered body, or the receipt window for the delivery has closed. Verify the signing secret and that the signed body matches the delivered bytes, then acknowledge the next retry attempt if the window has closed. |
The receipt window matters for late submissions. When a delivery's deadline passes before the consumer acknowledges it, the platform treats that delivery as timed out and schedules a retry. Submitting a receipt after the window has closed returns DALP-0510; acknowledge the next retry attempt instead.
Error registry
All errors that the webhook-receipts v2 routes can return are listed below. The list and read endpoints are tenant-scoped and can return the standard scope and not-found errors. The submit endpoint is public and returns authentication and delivery-specific errors.
| Code | Status | Endpoint | What it means | How to resolve |
|---|---|---|---|---|
DALP-0529 | 404 | Read | No receipt with that identifier exists in the active tenant scope. | Verify the receipt identifier belongs to the authenticated tenant. Use the list endpoint to retrieve valid identifiers. |
DALP-0527 | 404 | Submit | No delivery matches the submitted deliveryId, endpointId, and evtId combination, or counter-signed receipts were not enabled on the endpoint when that delivery was dispatched. | Confirm the three identifiers are correct and that the endpoint has receipts enabled. |
DALP-0510 | 401 | Submit | The signature did not verify, the payload hash did not match the delivered body, or the receipt window for the delivery has closed. | Verify the signing secret and that the signed body matches the delivered bytes, then acknowledge the next retry attempt if the window has closed. |
The read endpoint returns DALP-0529 when the receipt identifier does not resolve in the current tenant scope. The response does not reveal whether the receipt exists in another tenant, so confirm the receipt identifier and the active organisation before retrying.
The submit endpoint returns DALP-0527 for an unknown delivery or one that was not dispatched with counter-signed receipts enabled. It returns DALP-0510 when the consumer signature fails verification, the inner event hash does not match the delivered payload, or the delivery's deadline has already passed and the platform has scheduled a retry. In the timeout case, the consumer should wait for the next retry delivery and acknowledge that instead.
When to use it
Use this surface when you need to:
- Prove that a consumer received and acknowledged a specific event delivery, not just that the platform attempted to send it.
- Build the consumer-side half of a webhook delivery chain of custody for an audit.
- Isolate deliveries that reached a consumer but failed verification, through the
verificationFailureClassfilter. - Let an external receiver confirm deliveries without holding a platform API key, using its endpoint signing secret as proof.
To configure delivery, signing, retries, and the per-endpoint receipts setting, see Webhook endpoints.
Token factory registry API
Read the token factories deployed on your system through the DALP Platform API. List every factory and its asset type, read one by address, and check whether an address is available before you deploy.
Participant directory API
List an organization's participants for a given address purpose and classify their wallet addresses as externally owned or smart wallets through the DALP Platform API.