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

# Webhook delivery and retries

> How mintfax delivers events, what triggers a retry, and how to make your handler safe to call more than once.

mintfax dispatches a webhook attempt as soon as the event is produced. Your endpoint has one job per attempt: return a 2xx response quickly. Anything else schedules a retry.

## What counts as a successful delivery

Any 2xx status code (200, 201, 202, 204) within the per-attempt timeout. Body content is ignored; only the status code matters.

## What triggers a retry

| Outcome                                | Result               |
| -------------------------------------- | -------------------- |
| 2xx response within the timeout        | Delivered. No retry. |
| 4xx response                           | Retried.             |
| 5xx response                           | Retried.             |
| Network failure (DNS, TCP, TLS)        | Retried.             |
| Endpoint timeout exceeded (30 seconds) | Retried.             |
| Invalid TLS certificate                | Retried.             |

A retry sends an identical event payload with a fresh `webhook-timestamp` header. The `webhook-id` stays the same so your idempotency layer recognizes it as a re-delivery.

## Retry schedule

Up to four attempts with exponential backoff. Total wall-clock window from first attempt to giving up is approximately 18 minutes.

| Attempt | Sent                                     |
| ------- | ---------------------------------------- |
| 1       | Immediately when the event fires         |
| 2       | 10 seconds after attempt 1 failed        |
| 3       | About 100 seconds after attempt 2 failed |
| 4       | About 17 minutes after attempt 3 failed  |

If attempt 4 fails, the event is marked permanently failed. The event itself is not lost - you can replay it from the dashboard or re-fetch it via the [Events API](/docs/api-reference/events).

## Make your handler safe to retry

Webhook handlers must be idempotent. The same event can arrive more than once: a timeout on your side, a network blip, a retry from a previous attempt that eventually succeeded after you also processed it.

Two approaches:

**Deduplicate on `webhook-id`.** Persist the event ID before doing the work. If you see the same ID twice, skip.

```javascript theme={null}
const eventId = req.headers["webhook-id"];
const existing = await db.events.findUnique({ where: { id: eventId } });
if (existing) {
  return res.status(200).end();
}
await db.events.create({ data: { id: eventId } });
// ... your handler logic
```

**Or design the handler to be replay-safe.** Update operations that compare-and-set on a known state work without a dedupe layer. For example: "set fax X to delivered if its current status is sending" is safe to run twice.

## Per-event semantics

`fax.delivered` and `fax.failed` are mutually exclusive and exactly-once per fax. `fax.sending` may fire multiple times if a fax retries against the destination. `fax.queued` fires exactly once.

`balance.low` may fire multiple times if your balance oscillates around the threshold. `balance.topup` fires once per credit addition.

See the per-event pages in [Event types](/docs/webhooks/events/overview) for the firing rules of each event.

## Timeouts and what your endpoint should do quickly

The per-attempt timeout is 30 seconds. If you cannot respond within that window, return 200 immediately and process the event asynchronously on your side.

A handler that does heavy work before responding (write to multiple databases, call third-party APIs, send email) is at risk of timing out. The pattern:

1. Receive the request, verify the signature, enqueue a job locally.
2. Return 200 within milliseconds.
3. Process the job out-of-band.

This is also how mintfax dispatches its own events internally, for the same reason.

## What "permanently failed" means

A permanently failed event is one that exhausted all six attempts. The event itself still exists in your account's event log - you can find it via the [Events API](/docs/api-reference/events) or in the dashboard. From the dashboard, you can replay it manually after fixing the issue on your side. See [Inspect delivery attempts](/docs/webhooks/inspect-attempts).

mintfax does not delete events that failed to deliver. If your endpoint went down for an extended outage, your event history is intact and replayable when you are ready.

## Next

* [Inspect delivery attempts](/docs/webhooks/inspect-attempts) - see exactly which attempts succeeded, failed, and why
* [Verify webhook signatures](/docs/webhooks/verify-requests) - confirm requests came from mintfax before processing
* [Event types](/docs/webhooks/events/overview) - per-event firing rules and payload shapes
