> ## Documentation Index
> Fetch the complete documentation index at: https://docs.basaltic.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Delivery, retries, and failures

> Handle webhook verification, retry policies, and notification dead-letter envelopes.

A notification produces one delivery record for each confirmed subscription
whose filter matches. Retries update that record. Consumers should make
processing idempotent, for example by remembering the notification message ID
alongside the business operation they completed.

## Webhook envelopes and raw delivery

The default HTTP request body is a JSON envelope with `type: "notification"`,
`message_id`, `topic_crn`, `topic_name`, `subscription_id`, `message`,
`timestamp`, and `unsubscribe_url`. Subject and attributes appear when supplied.
The `message` remains a string, even when your application uses JSON in it.

With `raw_message_delivery: true`, the request body is the selected message
payload. Webhook metadata remains in the `X-Basaltic-Notification-Type`,
`X-Basaltic-Message-Id`, `X-Basaltic-Subscription-Id`, `X-Basaltic-Topic-Crn`,
`X-Basaltic-Unsubscribe-Url`, and `X-Basaltic-Delivery-Attempt` headers.
The request content type remains `application/json`; choose a suitable payload.

Queue subscriptions always receive the envelope. Their queue message attributes
also include notification identifiers. When delivering to a FIFO queue,
Notifications supplies a group ID derived from the topic CRN and a deduplication
ID derived from the published message ID. FIFO ordering applies within the
queue; asynchronous notification fan-out does not guarantee publication order.

## Verify webhook signatures

Every signed webhook request carries:

```text theme={null}
X-Basaltic-Signature: v1,t=<unix-seconds>,s=<hex-hmac-sha256>
```

Use the subscription signing secret as the HMAC key, as its literal UTF-8 bytes.
The signed bytes are the timestamp, a dot, and the exact raw request body.
Do not parse and reserialize JSON before verification. Compare signatures in
constant time and reject timestamps more than five minutes in the past or
future. Signature verification does not replace deduplication of valid retries.

The same verification applies to the `subscription_confirmation` handshake.
After verification, the endpoint owner can follow its `confirm_url` to consent.
Returning a success status to the handshake only acknowledges its receipt.

Store the secret outside logs. Reading and rotating it require separate
permissions. Rotation replaces the key, so coordinate it with the endpoint
operator. A subscription description or list never returns the secret.

## Retry policy

| Field | Default | Allowed range | Meaning |
| - | - | - | - |
| `max_attempts` | 6 | 1–10 | Total attempts, including the first. Set 1 for no retries. |
| `min_delay_seconds` | 20 | 1–3,600 | Initial retry backoff. |
| `max_delay_seconds` | 1,200 | 1–3,600 | Cap on the exponential backoff before jitter. |
| `timeout_seconds` | 15 | 1–30 | Per-attempt webhook timeout; ignored for email. |

Subscription settings override topic settings field by field. Unspecified
values use platform defaults. Backoff doubles between retries and includes
jitter, so these are scheduling bounds and defaults, not exact delivery times.
When both delays are supplied, the maximum must not be less than the minimum.

For webhooks, HTTP `2xx` succeeds. Timeouts, connection failures, HTTP `408`,
`429`, and `5xx` can be retried. Other `4xx` and all `3xx` responses are terminal;
redirects are not followed. A destination that resolves to a blocked private
address is also a terminal failure. HTTP success does not confirm that an
application completed its business operation.

## Dead-letter queues

Set `redrive_policy.dead_letter_queue` on a subscription to retain an envelope
when a delivery ends in terminal failure. The queue can be a standard or FIFO
queue in the same account and region. Setting it requires `queue:SendMessage`.

The failure envelope contains the original notification, subscription and
delivery identifiers, attempts, failure time, and last error. The delivery
record remains failed after redrive. A full, deleted, or unavailable destination
can prevent the envelope from being stored, so monitor both delivery failures
and the dead-letter queue rather than treating it as a second delivery guarantee.

This handles a failure to deliver a notification to its endpoint. To handle a
consumer that receives but cannot process an already enqueued message, configure
that destination queue's [redrive policy](/queue/fifo-and-dead-letter-queues).

## Diagnose a missing notification

1. Confirm the publication was accepted and inspect its fan-out state.
2. Check that the subscription is confirmed.
3. Compare the published attributes against the entire filter policy.
4. Find the delivery record and inspect its attempts and last error.
5. For webhooks, check the registered URL, signature handling, and HTTP status.
6. For queues, check destination availability, encryption key state, and consumer leases.

Use topic [Telemetry metrics](/notifications#metrics) for trends and alerts.
Filtered-out subscriptions have no failed delivery attempt. Missing metrics
samples indicate an observation gap, not proof that no deliveries occurred.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.