# Webhook endpoints

Source: https://docs.settlemint.com/docs/developers/api-integration/webhook-endpoints
Configure DALP webhook endpoints, delivery privacy, idempotent mutations, signing secrets, retries, and chain-of-custody proofs.



## Overview [#overview]

Webhook endpoints deliver selected DALP events to an external HTTPS URL. Use them when an integration needs pushed event delivery instead of polling token or account collections.

DALP also exposes inbound provider callback endpoints when an external provider must report decisions back to the platform. Provider callbacks are not webhook endpoint subscriptions. DALP owns these fixed intake routes and processes them before any customer-facing event delivery happens.

Use the [webhook events reference](/docs/events) and the [AsyncAPI manifest](/.well-known/dalp-events.json) to check event names, lifecycle states, and payload schemas before choosing endpoint subscriptions.

## Endpoint model [#endpoint-model]

Create endpoints with `POST /api/v2/webhooks`. The request includes:

| Field                   | Behaviour                                                                                                                      |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `url`                   | Required HTTPS target URL for delivery.                                                                                        |
| `displayName`           | Optional label, up to 200 characters.                                                                                          |
| `subscriptions`         | Event patterns to deliver. Defaults to `*.final`, `*.retracted`, and `*.recalled`.                                             |
| `defaultPayloadShape`   | Must be `thin` when creating an endpoint. Switch to `fat` later with a `PATCH` request and the required field acknowledgement. |
| `counterSignedReceipts` | Optional flag for endpoints that return signed delivery receipts.                                                              |

The create and rotate-secret responses reveal the signing secret once. Later reads return endpoint metadata and secret status, not the cleartext signing secret. If a client retries the same create or rotate request with the same idempotency key, DALP returns the same response envelope with `signingSecret: null` so the cleartext secret is not exposed again. Store the first `dalp_whsk_...` value securely, and do not overwrite it with `null` from an idempotent retry.

### Mutation idempotency [#mutation-idempotency]

Webhook endpoint mutations accept the `Idempotency-Key` header. Send a stable key when creating an endpoint, updating endpoint settings, rotating secrets, retrying deliveries, replaying events, recalling events, or changing secret state from an automated workflow. If the network drops after DALP accepts the request, retry the same request with the same key instead of submitting a second mutation.

Reuse an idempotency key only for the same request body. Reusing the key for a different body is rejected so one key cannot accidentally describe two different operations. Keep request idempotency separate from consumer-side event deduplication: the `Idempotency-Key` protects the API mutation, while webhook receivers should still dedupe delivered events by the `webhook-id` header and any stable business fields carried by the event.

## Payload privacy [#payload-privacy]

DALP delivers thin payloads by default. Thin payloads omit configured personal-data fields for event types such as identity registration, access-control role changes, asset issuance, compliance freeze recalls, and token transfers.

Create the endpoint as `thin` first. Switching an endpoint to `fat` requires a later `PATCH /api/v2/webhooks/{id}` request with a `fatEventsAcknowledgment.fieldsAcknowledged` list that covers every additional field implied by the endpoint's subscriptions. DALP rejects the update when the acknowledgement does not match the subscription set.

## Delivery operations [#delivery-operations]

| Operation                                                                 | API route                                                    |
| ------------------------------------------------------------------------- | ------------------------------------------------------------ |
| List endpoints                                                            | `GET /api/v2/webhooks`                                       |
| Read endpoint metadata                                                    | `GET /api/v2/webhooks/{id}`                                  |
| Update URL, subscriptions, payload shape, receipt mode, or disabled state | `PATCH /api/v2/webhooks/{id}`                                |
| Disable an endpoint                                                       | `DELETE /api/v2/webhooks/{id}`                               |
| Enqueue a test event                                                      | `POST /api/v2/webhooks/{id}/test-events`                     |
| List delivery attempts                                                    | `GET /api/v2/webhooks/{id}/deliveries`                       |
| Read one delivery attempt                                                 | `GET /api/v2/webhooks/{id}/deliveries/{deliveryId}`          |
| Retry one delivery event                                                  | `POST /api/v2/webhooks/{id}/deliveries/{deliveryId}/retries` |
| Replay historical events                                                  | `POST /api/v2/webhooks/{id}/replays`                         |
| Recall an event                                                           | `POST /api/v2/webhooks/events/{evtId}/recall`                |
| Get chain-of-custody proof                                                | `GET /api/v2/webhooks/events/{evtId}/chain-of-custody`       |
| Rotate the signing secret                                                 | `POST /api/v2/webhooks/{id}/rotate-secret`                   |
| Revoke the previous signing secret                                        | `POST /api/v2/webhooks/{id}/revoke-previous-secret`          |
| Read delivery statistics                                                  | `GET /api/v2/webhooks/stats`                                 |

When updating an endpoint URL while deliveries are pending, pass `acknowledgePending=true` only when you intend DALP to retarget those queued attempts to the new URL.

Secret rotation keeps the previous signing secret valid for a 24-hour overlap. Revoke the previous secret after DALP has observed delivery under the new secret.

### Retry, replay, and consumer idempotency [#retry-replay-and-consumer-idempotency]

Use the retry and replay routes for different recovery jobs:

| Recovery job         | Use it when                                                                          | Request shape                                                                                                                  | Consumer expectation                                                                                                                                                                        |
| -------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Retry one delivery   | One recorded delivery attempt needs to be scheduled again for the same event.        | `POST /api/v2/webhooks/{id}/deliveries/{deliveryId}/retries`                                                                   | Dedupe by the `webhook-id` header before applying side effects.                                                                                                                             |
| Replay an event      | A known event must be enqueued for the endpoint again.                               | `POST /api/v2/webhooks/{id}/replays` with `evtId` and optional `chainId`                                                       | Treat the replay as another delivery of the same event, not as a new business event.                                                                                                        |
| Replay a block range | A receiver was offline or a new endpoint must catch up from historical chain events. | `POST /api/v2/webhooks/{id}/replays` with `fromBlock`, optional `toBlock`, `chainId`, and `confirmLargeRange` for large ranges | Dedupe delivery attempts by `webhook-id`. Dedupe business side effects by stable payload fields such as transaction hash, log index, and lifecycle state when the event type includes them. |

Replay responses return a `replayId`, the `endpointId`, and `eventsEnqueued`. The response also includes `snapshotToBlock` when DALP bounded the replay to a chain snapshot. Replays enqueue delivery work; they do not change the original event's lifecycle state.

For request-to-chain reconciliation, keep DAPI idempotency and webhook outcome handling separate. A cached mutation response proves that DALP accepted the synchronous request for that idempotency key. The webhook event proves the later chain outcome. See [Idempotency and on-chain outcome](/docs/events/idempotency-and-on-chain-outcome) for the consumer-side reconciliation pattern.

## Ripple Custody inbound callbacks [#ripple-custody-inbound-callbacks]

`POST /api/webhooks/ripple` is the inbound callback boundary for Ripple Custody intent events. Ripple calls this route when a custody intent changes state. DALP parses the provider envelope and acknowledges intermediate events. Terminal approval or rejection events are dispatched to the custody approval monitor for the matching intent.

This route is separate from outbound webhook endpoints created with `POST /api/v2/webhooks`:

| Direction                 | Purpose                                                    | Who configures the URL                                                 | Public API surface                  |
| ------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------------------- | ----------------------------------- |
| Outbound webhook endpoint | DALP sends selected platform events to your HTTPS receiver | You create and manage the receiver URL                                 | `/api/v2/webhooks` and child routes |
| Ripple Custody callback   | Ripple Custody sends intent state changes back to DALP     | The platform exposes the DALP callback URL to the provider integration | `POST /api/webhooks/ripple`         |

### Accepted provider envelope [#accepted-provider-envelope]

DALP reads the Ripple Custody event type from `payload.type`, not from a top-level `type` field. The envelope can include provider event metadata such as `domainId`, event `id`, `savedAt`, and `sequenceNumber`.

```json
{
  "domainId": "25aaec0d-e8dc-44b6-8070-9231f1ddadf0",
  "id": "03424a3f-bdfc-4521-b8b2-933a8ff13cce",
  "payload": {
    "id": "35e4d8d9-f943-484b-865c-c736679ba0cc",
    "type": "IntentApproved"
  },
  "savedAt": "2026-05-19T07:32:00.000Z",
  "sequenceNumber": 369
}
```

Terminal approval events resolve the custody approval as approved. Terminal rejection, failure, expiry, closed, or denied events resolve it as rejected. Intermediate events are acknowledged without changing the approval monitor. Malformed or unrecognised events are also acknowledged without a monitor update.

### Authentication and request limits [#authentication-and-request-limits]

The Ripple callback body is capped at 64 KB before DALP verifies a signature or parses JSON. Requests over the cap return `413`.

If `RIPPLE_WEBHOOK_SECRET` is set, DALP verifies an HMAC-SHA256 signature from `x-ripple-webhook-signature`, with `x-webhook-signature` as a fallback header. A signature mismatch returns `410` so the provider stops retrying a permanently unauthenticated delivery.

In production, DALP refuses to process Ripple callbacks when `RIPPLE_WEBHOOK_SECRET` is not set unless the deployment explicitly opts out with `RIPPLE_INSECURE_NO_HMAC=1`. Non-production deployments can proceed without the HMAC secret, but the integration then relies on HTTPS and network allowlisting at the deployment boundary.

### Replay and retry behaviour [#replay-and-retry-behaviour]

When the provider envelope includes both `domainId` and `sequenceNumber`, DALP records the highest sequence number seen for each `(domainId, intent)` pair. A duplicate or stale sequence is acknowledged with `200` and is not dispatched again. If the deduplication write fails, DALP still dispatches the terminal event because the approval monitor is idempotent for repeated resolution signals.

DALP returns provider-facing status codes with these meanings:

| Condition                                                                   | Response | Effect                                         |
| --------------------------------------------------------------------------- | -------- | ---------------------------------------------- |
| Valid terminal event dispatched to the approval monitor                     | `200`    | Delivery accepted                              |
| Intermediate, malformed, unrecognised, duplicate, or stale event            | `200`    | Delivery acknowledged without a monitor update |
| Body exceeds 64 KB                                                          | `413`    | Delivery rejected as too large                 |
| Signature mismatch when the HMAC secret is configured                       | `410`    | Delivery rejected permanently                  |
| Approval monitor is not reachable yet, or a retryable dispatch error occurs | `503`    | Provider can retry delivery                    |
| Approval monitor rejects the signal as a permanent client error             | `410`    | Provider can stop retrying delivery            |

Related pages:

* [Compliance providers](/docs/architects/integrations/compliance-providers)
* [Operational integration patterns](/docs/developers/api-integration/operational-integration-patterns)
* [Webhook events](/docs/events)

## Counter-signed receipts [#counter-signed-receipts]

Enable `counterSignedReceipts` when the receiving system must prove that it accepted a delivered event. DALP accepts receipts only for deliveries queued with counter-signed receipts enabled. Each receipt record stores the submitted signature, inner event hash, receipt time, and verification status.

| Operation                                | API route                           |
| ---------------------------------------- | ----------------------------------- |
| List counter-signed webhook receipts     | `GET /api/v2/webhook-receipts`      |
| Read one counter-signed webhook receipt  | `GET /api/v2/webhook-receipts/{id}` |
| Submit a consumer counter-signed receipt | `POST /api/v2/webhook-receipts`     |

A receipt submission includes `deliveryId`, `evtId`, `endpointId`, `consumerSignature`, and `innerEventHash`. The consumer signature is an HMAC-SHA256 signature over the inner event hash. Generate it with the endpoint signing secret after removing the `dalp_whsk_` prefix, or use the DALP SDK helper that normalizes webhook secrets for Standard Webhooks. DALP also compares the submitted hash with the delivered event body, so a receipt only verifies when the signature and delivered payload hash both match.

Late receipts are rejected after the receipt window closes. A consumer that misses the window should acknowledge the next retry attempt instead of trying to repair the timed-out delivery.

## Audit proof [#audit-proof]

DALP records delivery rows with the fields that were redacted during delivery preparation. It also records hop hashes for the prepared payload.

Use `GET /api/v2/webhooks/events/{evtId}/chain-of-custody` when an audit workflow needs proof for one event. The response uses the standard single-resource envelope:

```json
{
  "data": {
    "evtId": "evt_01h...",
    "hops": [
      {
        "stage": "outbox-write",
        "contentHash": "sha256:4f7c...",
        "signedBy": "0x...",
        "recordedAt": "2026-05-13T07:32:00.000Z"
      }
    ],
    "merkleRoot": "7a1f...",
    "platformSignature": "dalp-platform:7a1f..."
  },
  "links": {
    "self": "/v2/webhooks/events/evt_01h.../chain-of-custody"
  }
}
```

The `hops` array lists the recorded delivery stages and content hashes. `signedBy` is present when a hop has a recorded signer. Downstream systems can compare the hop hashes with the `merkleRoot` and platform signature when they need evidence of what DALP prepared for delivery.

Related pages:

* [Webhook events](/docs/events)
* [Operational integration patterns](/docs/developers/api-integration/operational-integration-patterns)
* [Compliance providers](/docs/architects/integrations/compliance-providers)
* [API reference](/docs/developers/api-integration/api-reference)
