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

# Delivery & retries (/webhooks/delivery-and-retries)

import { Step, Steps } from "fumadocs-ui/components/steps";

This page describes exactly how we call your endpoint, so you can size your timeouts, make your handler
idempotent, and understand what you see in the delivery log.

## How we call your endpoint

Every event is sent as a single `POST` request with a JSON body:

```json title="Request body"
{
  "event": "subscriptions.new",
  "account_id": "acct_123",
  "payload": {
    // Event-specific data — see the event catalog
  }
}
```

| Header                    | Value                                                                                                                       |
| ------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `Content-Type`            | `application/json`                                                                                                          |
| `User-Agent`              | `OnlyFansAPI.com/Webhook-Client`                                                                                            |
| `Signature`               | HMAC SHA256 of the raw request body — only sent when the webhook has a [signing secret](/webhooks/protecting-your-webhooks) |
| `X-OFAPI-Idempotency-Key` | Stable identifier for the event, e.g. `evt_9c1f0a4b…`. Use it to deduplicate. Not sent for ephemeral events (see below)     |
| `X-OFAPI-Redelivery-Of`   | Only present on a [manual redelivery](#replaying-a-delivery); contains the delivery ID that was replayed                    |

<Callout title="Requests may not come from a fixed IP">
  Outbound webhook requests are routed through our edge network, so source IP addresses are not
  stable and cannot be allowlisted. Verify the `Signature` header instead — see
  [Protecting your webhooks](/webhooks/protecting-your-webhooks).
</Callout>

## What counts as a successful delivery

A delivery attempt succeeds when your endpoint returns any **2xx** status code within our timeouts:

* **5 seconds** to establish the TCP/TLS connection
* **10 seconds** total for the request, including your response

An attempt is treated as **failed** when:

* your endpoint returns any non-2xx status code (3xx redirects are **not** followed as success), or
* the connection or the response exceeds the timeouts above, or
* the connection fails (DNS, TLS, refused connection).

The response body you return is ignored — only the status code matters.

<Callout type="warn" title="Answer first, process later">
  Do your processing **after** you respond. Acknowledge with a `200` as soon as you have persisted
  the payload (e.g. onto your own queue). Handlers that do heavy work inline are the single most
  common cause of duplicate events, because they blow the 10-second budget and trigger a retry.
</Callout>

## Retry schedule

A failed delivery is retried up to **3 attempts in total** with exponential backoff:

| Attempt | Sent                                |
| ------- | ----------------------------------- |
| 1       | Immediately when the event occurs   |
| 2       | \~10 seconds after attempt 1 fails  |
| 3       | \~100 seconds after attempt 2 fails |

If attempt 3 fails, the delivery is abandoned — we do not retry it again later. The whole retry window
spans roughly **2 minutes**. Backoff delays are a minimum, not a guarantee: retries run on a separate
queue, so a retry can land slightly later than the values above under load.

Two status codes short-circuit the schedule. If your endpoint responds with **404 Not Found** or
**410 Gone**, we treat the endpoint as intentionally removed and stop retrying immediately.

## At-least-once delivery

We guarantee **at-least-once** delivery, not exactly-once. Your endpoint can receive the same event
more than once — a retry after a response that timed out on our side but succeeded on yours, a
network-level duplicate, or a manual redelivery.

Deduplicate on the `X-OFAPI-Idempotency-Key` header: it stays identical across every attempt of the
same event, including manual redeliveries. Store the key, and ignore an event you have already
processed.

<Callout>
  Ephemeral events such as [`users.typing`](/webhooks/available-events#userstyping),
  [`users.online`](/webhooks/available-events#usersonline) and
  [`users.offline`](/webhooks/available-events#usersoffline) carry no idempotency key — they are
  only meaningful at the moment they fire, so there is nothing to deduplicate against.
</Callout>

Events are also **not ordered**. Retries and independent queue workers mean a later event can arrive
before an earlier one. If ordering matters to your integration, sort on the timestamps inside the
payload rather than on arrival order.

## Endpoints that keep failing

To stop a broken endpoint from generating endless retry traffic, each webhook has a circuit breaker:

<Steps>
  <Step>
    After **20 consecutive failed deliveries** (each having exhausted its retries), the endpoint is
    automatically **paused** and your team admins receive an email.
  </Step>

  <Step>
    The endpoint cools down for **5 minutes**. It is then sent a single probe delivery to test
    whether it has recovered.
  </Step>

  <Step>
    If the probe succeeds, deliveries resume immediately and your admins are notified. If it fails,
    the cooldown doubles — 10, 20, 40 minutes, and so on, up to a maximum of **6 hours** — before
    the next probe.
  </Step>
</Steps>

<Callout type="warn" title="Events fired while an endpoint is paused are lost">
  Deliveries are not buffered during a pause. Events that occur while your endpoint is paused are
  never sent, and they are not available for redelivery afterwards, because no delivery was ever
  attempted. Backfill that window from the API instead.
</Callout>

You do not have to wait out a cooldown. Once your endpoint is healthy again, disable and re-enable the
webhook — in the console, or via the [Update Webhook](/api-reference/webhooks/update-webhook) endpoint —
which clears the pause and resumes deliveries right away.

## Inspecting deliveries

Every attempt is recorded, including intermediate retries, so a delivery that succeeded on its third
try appears as three records.

* **Console** — open the webhook in the [Webhooks](https://app.onlyfansapi.com) section to browse recent
  attempts, their status codes, and errors.
* **API** — [List Webhook Deliveries](/api-reference/webhooks/list-webhook-deliveries) returns the same
  history, newest first, filterable by event, date range and success. Attempts belonging to one delivery
  share a `delivery_uuid`.

<Callout title="7-day retention">
  Delivery records are pruned automatically after **7 days**. Anything older is no longer available in
  the console or through the API.
</Callout>

## Replaying a delivery

Any recorded delivery can be re-sent with
[Redeliver Webhook Delivery](/api-reference/webhooks/redeliver-webhook-delivery):

* The stored payload is replayed verbatim, and the **original** `X-OFAPI-Idempotency-Key` is preserved —
  so your deduplication logic keeps working. An `X-OFAPI-Redelivery-Of` header lets you tell a replay
  apart from a first delivery.
* Replays go to the webhook's **current** URL and are signed with its **current** signing secret, not the
  values recorded at the time of the original delivery.
* The redelivery is queued and follows the normal retry schedule above, so the endpoint returns as soon as
  it is accepted. Poll the deliveries list and match the returned `redelivery_id` against `delivery_uuid`
  for the outcome.
* Redelivery is refused with a `409` when the webhook is disabled, still cooling down after repeated
  failures, or already has a recovery probe in flight.

## Credits

Each delivery costs the same whether it succeeds on the first attempt or fails on the last: retries are
**not** billed separately, and a delivery is charged once, on its final outcome. Manual redeliveries are
billed like organic deliveries. See [Credits](/introduction/essentials/credits) for the exact rate.