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

# Webhook Delivery

> How events are queued, delivered, and retried

My Crypto Server uses [Inngest](https://www.inngest.com) for durable webhook delivery. Each outbound delivery is a separate Inngest step — if your server is down or returns an error, Inngest retries with backoff automatically.

## Delivery flow

```
session.paid event
       │
       ▼
  webhookDeliver (Inngest function)
       │
       ├── POST to endpoint URL
       │         │
       │         ├── 2xx → delivered ✓
       │         │
       │         └── non-2xx / timeout → retry (up to 6 attempts)
       │
       └── all retries exhausted → delivery marked failed
```

## Retry schedule

Inngest retries with exponential backoff. 6 attempts span approximately 31 hours total.

| Attempt | Approximate delay |
| ------- | ----------------- |
| 1       | Immediate         |
| 2       | \~30 seconds      |
| 3       | \~5 minutes       |
| 4       | \~30 minutes      |
| 5       | \~3 hours         |
| 6       | \~12 hours        |

## One function per delivery

Each `(event, endpoint)` pair is a separate Inngest function invocation. If you have 3 webhook endpoints subscribed to `session.paid`, a single payment spawns 3 independent `webhookDeliver` runs. A failure on one endpoint doesn't affect delivery to the others.

## Delivery headers

Every POST includes:

```
Content-Type: application/json
X-Webhook-Signature: t=<unix_timestamp>,v1=<hmac_hex>
X-Webhook-Timestamp: <unix_timestamp>
X-Webhook-Event-Id: <event_uuid>
```

Always verify the signature. See [Webhooks → Verifying signatures](/api/webhooks#verifying-signatures).

## Timeout

Your server must respond within **30 seconds**. If it doesn't, the delivery is counted as failed and retried.

## Manual replay

From the dashboard under **Webhooks → Deliveries**, you can see every delivery attempt and replay any of them — including successful ones. This is useful for:

* Replaying a delivery your handler accidentally acknowledged without processing.
* Replaying events after you fix a bug in your webhook handler.
* Debugging delivery failures.

## Viewing delivery history via API

```bash theme={null}
GET /api/v1/events
```

The Events API shows all events and their delivery status. For delivery-level detail (per-endpoint status codes), use the dashboard.

## Event deduplication

Your webhook handler may receive the same event more than once (e.g. if your server returned a 200 but Inngest didn't receive the response). Always make your handler **idempotent** — use the `event.id` field to deduplicate:

```ts theme={null}
const processed = new Set<string>();

app.post("/webhooks/crypto", async (req, res) => {
  const event = verifyWebhook({ ... });

  if (processed.has(event.id)) {
    return res.json({ received: true }); // already handled
  }

  await handleEvent(event);
  processed.add(event.id);
  res.json({ received: true });
});
```

In production, store processed IDs in a database rather than an in-memory set.
