# Webhook delivery receipts API

Source: https://docs.settlemint.com/docs/api-reference/reference/webhook-receipts
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 [#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](/docs/api-reference/reference/organization-system-scope). The submit call is the consumer's own and authenticates differently, as described under [Submit a receipt](#submit-a-receipt). To configure delivery and the per-endpoint receipts setting, see [Webhook endpoints](/docs/api-reference/webhooks/webhook-endpoints).

## Route handler semantics [#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](/docs/api-reference/reference/organization-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 [#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 [#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`.

```bash
curl --globoff "https://your-platform.example.com/api/v2/webhook-receipts?filter[endpointId]=whe_01HXYZ" \
  -H "x-api-key: YOUR_API_KEY"
```

```json
{
  "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 [#read-one-receipt]

`GET /api/v2/webhook-receipts/{id}` returns a single receipt by its identifier, scoped to the active tenant.

```bash
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 [#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.

```bash
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"
  }'
```

```json
{
  "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 [#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 [#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 [#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 [#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 `verificationFailureClass` filter.
* 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](/docs/api-reference/webhooks/webhook-endpoints).
