# Retries and delivery (/developers/webhooks/retries-and-delivery)

Fianto delivers each event to your application's verified URL at least once, in no particular
order. This page covers what counts as delivered, the retry schedule, what happens when your route
keeps failing, and how to get events back.

**When Fianto retries a webhook delivery**

1. Fianto makes up to 8 attempts to deliver each event to your endpoint.
2. An attempt succeeds when your endpoint answers with any 2xx within 10 seconds; a 3xx counts as a failure.
3. Between attempts Fianto waits 1 minute, 5 minutes, 30 minutes, 2 hours, 5 hours, 10 hours, 10 hours, each wait ±20 %: about 28 hours in all.
4. After the 8th failed attempt the delivery is `EXHAUSTED`.
5. Delivery is at least once and unordered; every attempt carries the same `webhook-id`.
6. After 5 exhausted deliveries in a row, the endpoint is suspended back to pending (`delivery_failures`). A single exhausted delivery does not suspend it.
7. While it is suspended, new and waiting deliveries are held, up to 10,000, and sent after the URL is verified again.

## What counts as delivered [#what-counts-as-delivered]

* Any `2xx` response within **10 seconds** is a success.
* Anything else is a failed attempt: a `3xx` (redirects are not followed), a `4xx` or `5xx`, no
  answer within 10 seconds, or a connection that fails.

Answer first and do slow work afterwards, or in a queue: a route that takes longer than 10 seconds
fails even if it finishes its work.

## Retry schedule [#retry-schedule]

Each event gets up to **8 attempts**. Between them Fianto waits:

| After attempt | Wait       |
| ------------- | ---------- |
| 1             | 1 minute   |
| 2             | 5 minutes  |
| 3             | 30 minutes |
| 4             | 2 hours    |
| 5             | 5 hours    |
| 6             | 10 hours   |
| 7             | 10 hours   |

Each wait is stretched or shortened by up to 20 %; in all, the waits add up to about 28 hours. If the
8th attempt fails too, the delivery is `EXHAUSTED` and Fianto stops trying.

Every attempt carries the same `webhook-id` (the event id) and the same body. It is signed again when
it is sent, so its `webhook-timestamp` is the time of that attempt.

## Delivery statuses [#delivery-statuses]

The dashboard lists each application's deliveries with these labels:

| Status | Meaning | What you can do |
|---|---|---|
| `PENDING` | Shown as "Queued" before the first attempt, then "Retrying" while attempts remain. | Nothing: Fianto keeps trying on the schedule above. |
| `SUCCEEDED` | Shown as "Delivered": your endpoint answered 2xx within 10 seconds. | Redeliver it if you need it again. |
| `EXHAUSTED` | Shown as "Failed": all 8 attempts failed. | Fix your route, then redeliver it. |
| `CANCELLED` | Shown as "Cancelled": the application was disabled before the delivery finished. | Nothing: a disabled application stays disabled. |

A delivery held while its endpoint is suspended is shown as "Held" (see below).

## Suspension after 5 exhausted deliveries in a row [#suspension-after-5-exhausted-deliveries-in-a-row]

A single exhausted delivery does not suspend anything. After **5 exhausted deliveries in a row**, the
endpoint is suspended: its URL goes back to pending, with the reason `delivery_failures`, and stops
receiving. Any successful delivery resets the count to zero.

While the endpoint is suspended:

* new deliveries, and deliveries that were still waiting for a retry, are **held**, up to 10,000;
* past 10,000 held deliveries, new events are still stored but get no delivery;
* a held event is still deleted after 90 days.

To resume, fix your route and click **Verify again** in the application's webhook settings. If you had
already set a newer URL that was still waiting for verification, that newer URL is the one checked.
Once a URL passes the verification challenge, Fianto sends the held deliveries.

> **Held is not the same as never verified:**
>
> Deliveries are held only for a URL that was verified and then suspended. Events published while your
> application has never had a verified URL are stored but never delivered; read them with
> `GET /v1/events`.

## Redeliver an event [#redeliver-an-event]

In the dashboard's delivery list, **Redeliver** sends a delivery that is no longer pending again, as a
new delivery with the same `webhook-id`. A pending delivery cannot be redelivered.

* Redelivery is in the dashboard only; there is no API for it.
* The endpoint must be verified.
* Up to 60 redeliveries per hour per application.

Because the `webhook-id` is the same, a route that skips event ids it has already handled ignores a
redelivery of an event it processed. Delete that id from your records first if you want it handled
again.

## Recover events from the API [#recover-events-from-the-api]

Fianto keeps events for **90 days** and deletes older ones every day. Within that window,
`GET /v1/events` lists your application's events, newest first, with the same envelope a delivery
carries plus `object: "event"`, and `GET /v1/events/{id}` reads one. Use them to catch up after an
outage, or to reconcile what you processed.

```bash
curl "https://api.fianto.xyz/v1/events?limit=100" \
  --user "$FIANTO_APP_ID:$FIANTO_APP_SECRET"
```

## See also [#see-also]

- [Webhooks](/developers/webhooks/overview): Endpoints, the verification challenge and the envelope.

- [Verify webhooks](/developers/webhooks/verify): What your route answers, and why deliveries fail verification.

- [Pagination and rate limits](/developers/pagination-and-rate-limits): Page through GET /v1/events.