# Retries and recovery

What Dispatch does when a customer's server is slow or down, and how to resend what they missed.

Customers' servers go down. Dispatch assumes it, so a failed delivery is retried automatically, and a broken endpoint is protected from a flood of retries.

## Retry schedule

Dispatch uses the same engine as Gateway. A delivery is attempted immediately, then retried up to **7 more times**, after 5 seconds, 30 seconds, 2 minutes, 10 minutes, 1 hour, 2 hours and 4 hours: eight attempts across about seven hours. Each wait is spread randomly so retries don't land together.

| Customer's response | What Dispatch does |
|---|---|
| `2xx` | Delivered. |
| `408`, `409`, `425`, `429`, `5xx`, timeout, network error | Retries on the schedule. |
| Other `4xx` | Fails at once; the endpoint rejected it. |
| `3xx` | Fails. Redirects are not followed. |
| `Retry-After` on `429` or `503` | Waits at least that long, never less. |
| `Retry-After: -1` | Fails and stops retrying. |

Every attempt has a timeout (15 seconds by default).

## When an endpoint stays down

After **10 failed attempts in a row** an endpoint's [circuit breaker](/gateway/circuit-breaker/) opens. Dispatch stops sending to it, keeps the queue, and probes it after 1 minute, 5 minutes, 30 minutes and then hourly. When a probe succeeds, sending resumes and the queue drains. Other customers are never affected.

An endpoint whose breaker is open shows as such in **Customers**, so you can tell the customer before they find out themselves. Reset it from the dashboard or with `POST /v1/destinations/:id/breaker/reset`; changing the endpoint's URL resets it too.

## Retry one message

Resend a message to all of its endpoints, or to a single one:

```bash
curl -X POST https://api.getensue.com/v1/dispatch/events/msg_…/retry \
  -H "authorization: Bearer ens_<key>" -H "content-type: application/json" \
  -d '{ "endpoint_id": "dst_…" }'
```

`endpoint_id` is optional. A retry creates a new attempt on the same delivery, so the history stays in one place. Retrying a delivery whose endpoint's breaker is open is refused with `409 breaker_open`: reset the breaker first.

## Recover an endpoint

When a customer has fixed their server, resend **everything it missed** in one call:

```bash
curl -X POST https://api.getensue.com/v1/dispatch/endpoints/dst_…/recover \
  -H "authorization: Bearer ens_<key>" -H "content-type: application/json" \
  -d '{ "since": "2026-11-09T18:00:00Z" }'
```

| Field | Notes |
|---|---|
| `since` | Required. An ISO 8601 time or milliseconds since the epoch. Goes back at most **14 days**. |

It retries every failed or cancelled delivery to that endpoint since then, oldest first, up to 500 per call. If there are more, the response sets `more: true` and gives `next_since`; call again from there.

```json
{ "retried": 500, "more": true, "next_since": 1767225600000 }
```

Deliveries then run through the normal delivery policy, at the endpoint's rate.

## Make customers' lives easier

- Tell customers to return `2xx` as soon as they have **verified and stored** the webhook, and to process it afterwards.
- Tell them to de-duplicate on `webhook-id`; retries and recoveries reuse it.
- Watch for `destination.breaker_opened` [notifications](/gateway/notifications/) and reach out first.