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

# Delivery and retries

> What counts as delivered, the retry schedule, ordering, and the answers that pause, slow down or disable an endpoint.

Flow Payroll delivers every event **at least once**: an event can occasionally arrive more than once, but it is never silently lost. A delivery that is not acknowledged is retried until it succeeds or the schedule runs out, and nothing is dropped silently: every attempt is in the [delivery log](/api-reference/webhooks/replay-and-recovery#the-delivery-log).

## What counts as delivered

Your server must answer with a success status, any status from `200` to `299` (a "2xx" status), within **15 seconds**. Fifteen seconds is ample for storing an event and answering; it is not meant for doing the work. Anything else is a failure and is retried:

* any other status, including `3xx`: redirects are never followed, so register the final URL;
* no answer within 15 seconds, or no connection within 5;
* a TLS error, such as an expired or self-signed certificate.

Answer quickly and do the work afterwards. See [Best practices](/api-reference/webhooks/best-practices#answer-quickly).

## Retry schedule

| Attempt | Sent | Time since attempt 1 |
| - | - | - |
| 1 | Straight away | 0 |
| 2 | 5 seconds after attempt 1 | 5 seconds |
| 3 | 5 minutes later | 5 minutes 5 seconds |
| 4 | 30 minutes later | 35 minutes |
| 5 | 2 hours later | 2 hours 35 minutes |
| 6 | 5 hours later | 7 hours 35 minutes |
| 7 | 10 hours later | 17 hours 35 minutes |
| 8 | 14 hours later | 31 hours 35 minutes |
| 9 | 20 hours later | 51 hours 35 minutes |
| 10 | 24 hours later | 75 hours 35 minutes |

This is the example schedule from the Standard Webhooks specification. Each wait varies by up to 20% either way. When a busy receiver fails many deliveries at once, this spreads the retries out so they do not all return at the same moment. The whole schedule covers about 75.6 hours (just over three days), long enough to ride out a weekend outage. If attempt 10 fails, the delivery is `Failed` and not retried again. You can still [resend it](/api-reference/webhooks/replay-and-recovery#resend-one-delivery) at any time.

Every attempt of the same delivery carries the same `webhook-id` and the same body. `X-Flow-Delivery-Attempt` says which attempt it is.

## Answers that change what happens next

| Your answer | What Flow Payroll does |
| - | - |
| Any failure with `Retry-After` | The next attempt waits for whichever is longer: your `Retry-After` (capped at one hour) or the next wait in the retry schedule. It never brings a retry forward. |
| `429`, `502`, `503`, `504` or a timeout | Halves the rate it sends to this endpoint, then speeds up again as deliveries succeed. |
| `410 Gone` | Stops for good: the delivery fails, the endpoint is disabled, and we email the people who get [alert emails](/api-reference/webhooks/manage-endpoints#alert-emails). Use this when an endpoint is being retired. |
| Header `webhook-delivery: abort-message` | Stops retrying this one delivery. The endpoint stays active. Use it for an event you will never be able to accept. |

## Ordering

By default deliveries are **not ordered**: events are sent as they happen, and a retry can arrive after a later event. Use `timestamp` to tell which came first, or fetch the latest state from `resourceUrl`.

If you need strict order, create the endpoint with `"ordering": "Fifo"`. Its deliveries then arrive one at a time, in the order the events happened. The cost is that a delivery your server keeps rejecting holds up every event behind it until it succeeds or runs out of retries, so only choose it if order matters more than speed.

## Rate limits

Set `rateLimitPerSecond` (1 to 1,000) on an endpoint to cap how fast Flow Payroll sends to it. Deliveries over the limit wait, not fail. Remove the cap with `PATCH /endpoints/{endpointId}` and `{"rateLimitPerSecond": null}`.

## When an endpoint keeps failing

* **Ten failures in a row** pause deliveries to the endpoint for a minute, then for longer each time it happens again, up to 30 minutes. One delivery is tried after each pause, and when it succeeds everything resumes. Waiting deliveries do not use up their attempts.
* **A day of continuous failure** sends an email, so someone can fix your receiver before it is disabled. It goes to the endpoint's alert contacts, or else your organisation's or account's default alert contacts, or else the person who created the endpoint and the account owner. See [Alert emails](/api-reference/webhooks/manage-endpoints#alert-emails). You get one email for each run of failures.
* **Five days of continuous failure** disable the endpoint. Its status becomes `DisabledBySystem` with `statusReason` `failing_for_5_days`, new events are recorded as `Skipped`, and the same people are emailed again.

To bring a disabled endpoint back, fix your receiver, then `PATCH /endpoints/{endpointId}` with `{"status": "Active"}`. It starts with a clean record. Then [replay](/api-reference/webhooks/replay-and-recovery#replay-a-window) what it missed.

## Operational events

Emails suit one organisation. If you run payroll for hundreds, you want to act on a failing endpoint in code: open a ticket, page whoever is on call, or re-enable it once it is fixed. Subscribe another endpoint to these events and Flow Payroll sends them to it:

| Event | Sent when |
| - | - |
| [`webhook.endpoint.failing`](/api-reference/webhooks/events/webhook-endpoint-failing) | An endpoint has failed every attempt for 24 hours. Sent once for each run of failures, at the same moment as the first email. |
| [`webhook.endpoint.disabled`](/api-reference/webhooks/events/webhook-endpoint-disabled) | An endpoint is disabled. `statusReason` is `failing_for_5_days` or `endpoint_gone` (it answered `410 Gone`). |
| [`webhook.delivery.exhausted`](/api-reference/webhooks/events/webhook-delivery-exhausted) | A delivery failed its last retry. `deliveryId`, `eventId` and `eventType` say what was not delivered. No email is sent for this one. |

* **Who receives them.** For an organisation's endpoint: that organisation's other endpoints and your account-wide endpoints. For an account-wide endpoint: your other account-wide endpoints. Never the endpoint the event is about, because it is the one that cannot receive it.
* **What they carry.** The endpoint's id, URL and description, `organisationId`, `customerId`, `failingSince`, the last status code and error token, and `metadata.resourceUrl` pointing at the endpoint (or, for an exhausted delivery, the delivery). Fields that do not apply are `null`.
* **They never cascade.** If the endpoint receiving these events fails too, its failures are emailed as usual but raise no further operational events.

Subscribe with the exact names, or with `webhook.*` for all three. They are recorded, listed and replayable like any other event.

## Pausing an endpoint yourself

`PATCH /endpoints/{endpointId}` with `{"status": "Paused"}` stops deliveries while you work on your receiver. Events that happen meanwhile are recorded as `Skipped`, not queued. Set the status back to `Active` and replay the window with `"statuses": ["Skipped"]` to receive them.

## Delivery statuses

| Status | Meaning |
| - | - |
| `Pending` | Not delivered yet: waiting for its first attempt or its next retry. |
| `Succeeded` | Your server answered `2xx`. |
| `Failed` | Every retry failed, your server answered `410` or asked us to stop, or the endpoint was deleted. `lastErrorToken` says why. |
| `Skipped` | Not sent, because the endpoint was paused or disabled when the event happened. |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.