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

# Delivery & Retries

> The full WalletSuite webhook delivery contract - acknowledgement, retry schedule, at-least-once semantics, idempotency, and failure handling.

## Lifecycle of an event

From on-chain transfer to your `2xx`:

```mermaid theme={null}
sequenceDiagram
    autonumber
    participant Chain as Blockchain
    participant WS as WalletSuite
    participant You as Your endpoint

    Chain->>WS: Transfer to a watched address lands in a block
    WS->>WS: First confirmation observed
    WS->>WS: Build payload, sign with HMAC-SHA256
    WS->>You: POST webhookUrl (Webhook-Id, Webhook-Timestamp, Webhook-Signature)
    You->>You: Verify signature against the raw body
    You-->>WS: 2xx acknowledgement
    You->>You: Dedupe on Webhook-Id, process async
    Note over WS,You: No timely 2xx -> the event enters the retry schedule
```

## How a delivery looks

Every delivery is a single HTTP `POST` to the `webhookUrl` you registered on the subscription.

| Property          | Value                                                  |
| ----------------- | ------------------------------------------------------ |
| Method            | `POST`                                                 |
| Content-Type      | `application/json; charset=utf-8`                      |
| User-Agent        | `WalletSuite-Webhooks/1.0`                             |
| Body              | One [event envelope](/webhooks/events), JSON           |
| Signature headers | `Webhook-Id`, `Webhook-Timestamp`, `Webhook-Signature` |

```http Example request theme={null}
POST /your/webhook/path HTTP/1.1
Host: hooks.yourapp.com
Content-Type: application/json; charset=utf-8
User-Agent: WalletSuite-Webhooks/1.0
Webhook-Id: 3f0a8d52-7e14-4b9c-a6d2-c8e1f4b09a7d
Webhook-Timestamp: 1779805371
Webhook-Signature: v1,g0gqp1234EXAMPLEsignatureBASE64==

{"schema_version":"1.0","event_id":"3f0a8d52-7e14-4b9c-a6d2-c8e1f4b09a7d","event_type":"transfer.received","subscription_id":"9b7c1e2a-4f6d-4c3b-8a1e-2d5f7a9c0b13","occurred_at":"2026-05-26T14:22:48.123Z","data":{...}}
```

<Warning>
  Authenticate every delivery by verifying its signature - see the [verification guide](/webhooks/verify). Delivery source IPs vary by plan; see [Egress IPs](#egress-ips).
</Warning>

## Acknowledgement

Your endpoint acknowledges a delivery by responding with **any `2xx` status within 18 seconds**.

* **Success:** any `2xx` response - the body is ignored.
* **Failure:** anything else - a `3xx` (redirects are not followed), `4xx`, or `5xx`, a connection or TLS error, or no response in time. Failures enter the [retry schedule](#retry-behavior).

Respond first, process async - handler design patterns are in [Best Practices](/webhooks/best-practices).

## Retry behavior

A delivery that is not [acknowledged](#acknowledgement) is retried - with no status-code exceptions - on the following schedule, where each period starts after the failure of the preceding attempt:

* Immediately (the initial delivery)
* 30 seconds
* 2 minutes
* 10 minutes
* 1 hour
* 6 hours
* 12 hours
* 24 hours

After the final attempt, the event is **permanently abandoned** - it will not be delivered again. The full schedule spans roughly 43 hours, and each delay is jittered ±25% so retries don't bunch up after an outage on your side - treat timings as approximate, not a contract.

<Warning>
  If a delivery is critical, make your endpoint resilient enough to acknowledge within one of the eight attempts. Missed state can always be retrieved from the chain or the REST API.
</Warning>

An endpoint that fails persistently is **automatically paused**. Pausing is separate from per-event abandonment: while paused, the subscription stops receiving deliveries and any pending deliveries are **held with their retry budget frozen**, so a pause never consumes attempts or abandons the backlog. After a cooldown the subscription resumes automatically and delivers the held events with their remaining attempts. Events that occur while the subscription is paused are not captured - reconcile them via the [REST API](/getting-started/api-reference/overview). See the [status lifecycle](#subscription-status-lifecycle).

## Delivery semantics

WalletSuite webhooks are delivered **at-least-once**, in **best-effort order**.

* **At-least-once:** every event is delivered at least once. In rare cases the same event can arrive again - for example, when your endpoint processed a delivery but its `2xx` response was lost in transit, so we retry. Dedupe on `Webhook-Id` and your handler stays correct (see [Idempotency](#idempotency)).
* **Best-effort ordering:** events are sent in the order they occur, but ordering is not a contractual guarantee - retries can interleave with fresh deliveries. For sequence-sensitive logic, order by `occurred_at`.

<Tip>
  [`occurred_at`](/webhooks/events) is the on-chain event time; the `Webhook-Timestamp` header is the delivery time, used only for [replay protection](/webhooks/verify#replay-protection).
</Tip>

## Idempotency

Every delivery carries a built-in idempotency key: the **`Webhook-Id`** header (= `event_id` in the payload), stable across all retries of the same event. One check against it gives you exactly-once processing on top of at-least-once delivery:

* Record each `Webhook-Id` as you process it.
* Seen before? Acknowledge with `2xx` and skip - the work is already done.

The race-safe claim-then-process implementation is in [Best Practices](/webhooks/best-practices).

## Egress IPs

Deliveries are authenticated by their [signature](/webhooks/verify), not by source IP.

**Static egress IPs are an Enterprise option.** For strict network perimeters, deliveries can originate from a fixed set of addresses in your regional environment (US, EU). The regional IP list is provided during enterprise onboarding. Static IPs are a network control layered on top of signature verification, not a replacement for it.

## Subscription status lifecycle

A subscription's `status` tells you whether it is delivering.

| Status    | Delivering? | Meaning                                                                                          |
| --------- | ----------- | ------------------------------------------------------------------------------------------------ |
| `pending` | Not yet     | Momentary state while a new subscription registers - it flips to `active` automatically.         |
| `active`  | Yes         | Fully registered and delivering events as on-chain transfers occur.                              |
| `failed`  | No          | Registration failed. Delete the subscription and create a fresh one.                             |
| `paused`  | No          | Applied automatically to persistently failing endpoints. Resumes automatically after a cooldown. |
| `deleted` | No          | The subscription was deleted. Create a new subscription to resume.                               |

## Next steps

<CardGroup cols={2}>
  <Card title="Verify Signatures" icon="shield-check" href="/webhooks/verify">
    Authenticate every delivery with Standard Webhooks HMAC-SHA256.
  </Card>

  <Card title="Events & Payloads" icon="brackets-curly" href="/webhooks/events">
    The full envelope, field semantics, and `transfer.received` payload.
  </Card>

  <Card title="Best Practices" icon="list-check" href="/webhooks/best-practices">
    Handler design: queues, idempotency stores, and resilient processing.
  </Card>

  <Card title="Troubleshooting" icon="wrench" href="/webhooks/troubleshooting">
    Diagnose subscriptions, delivery failures, and signature mismatches.
  </Card>
</CardGroup>
