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

# Webhook deliveries and retries

> What counts as a delivered webhook, the retry backoff schedule, delivery statuses, resending by hand and reading the delivery log.

Each event sent to an endpoint becomes a **delivery**, and a delivery has one or more **attempts**.
This page covers what Brudcast does with them, in the dashboard under **Developers > Webhooks** and
on the platform API (`https://core-service.prod.brudcast.com/api/v1/user`) with an
[API key](/developers/api-keys) carrying `webhooks:read` or `webhooks:write`.

## What counts as delivered

| Your server's reaction                              | Result                                                                                                                |
| --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| Any HTTP response with a status from `200` to `599` | **Delivered**. The status code and the first 1,000 characters of your response body are recorded. No further attempts |
| No response before the endpoint's timeout           | The attempt failed. Retried if attempts remain                                                                        |
| Connection refused or reset, DNS failure, TLS error | The attempt failed. Retried if attempts remain                                                                        |

Because a `4xx` or `5xx` response ends the delivery, a handler that crashes after it reads the
request doesn't get a second chance. Store the event, return `200`, then process it. Point the
endpoint at its final URL rather than at one that redirects.

## Attempts and backoff

**Max Retries** is the total number of attempts, including the first. The default is 5. The
dashboard offers 0 to 5, and `PATCH /webhook-endpoints/{id}` accepts up to 20.

After attempt *n* fails, the next attempt waits 5 × 2^(*n* − 1) seconds, up to a maximum of 24
hours. Brudcast checks for due deliveries every five seconds, so an attempt can start up to about
five seconds after it's due. The first attempt happens within a few seconds of the event.

| Attempt | Wait before this attempt | Time since the first attempt |
| ------- | ------------------------ | ---------------------------- |
| 2       | 5 s                      | 5 s                          |
| 3       | 10 s                     | 15 s                         |
| 4       | 20 s                     | 35 s                         |
| 5       | 40 s                     | 1 min 15 s                   |
| 10      | 21 min 20 s              | 42 min 35 s                  |
| 15      | 11 h 23 min              | 22 h 45 min                  |
| 20      | 24 h                     | 5 d 21 h 31 min              |

With the default of 5, Brudcast stops trying about 75 seconds after the first attempt. If your
endpoint can be down for longer than that, during a deploy for example, raise **Max Retries**.

<Warning>
  With **Max Retries** set to `0`, Brudcast makes no attempts. Every delivery, including test
  events, is marked failed without being sent. Use at least `1`.
</Warning>

## Delivery statuses

| Status       | Dashboard label | Meaning                                                                                                                           |
| ------------ | --------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `pending`    | Pending         | Waiting for its first attempt, or reset by a manual resend                                                                        |
| `processing` | Processing      | An attempt is in progress                                                                                                         |
| `delivered`  | Delivered       | Your server returned an HTTP response                                                                                             |
| `failed`     | Failed          | The last attempt got no response. If `nextRetryAt` has a time, another attempt is scheduled. If it's `null`, no attempts are left |
| `retrying`   | Retrying        | Accepted as a filter value. Deliveries waiting for a retry show as `failed`, with `nextRetryAt` set                               |

When the last attempt fails, `errorMessage` begins with `Max retries exceeded. Last error:`,
followed by the reason for that failure.

## Resend a delivery

On the endpoint's **Delivery Logs** tab, select the retry icon on a **Failed** row, or open any
delivery that isn't delivered and select **Resend**. With the API, call
`POST /webhook-deliveries/{id}/retry` (scope `webhooks:write`). A resend puts the delivery back to
`pending` and sends it straight away.

* **Delivered deliveries can't be resent.** The API returns `400` with
  `Cannot retry a delivery that has already been delivered`.
* **The attempt count isn't reset.** A delivery that has used all of its endpoint's attempts is
  marked failed again, without being sent. Raise the endpoint's **Max Retries** above the delivery's
  `attemptCount` first.
* **The body is the same.** A resend carries the original `delivery_id` and `timestamp`.

## Read the delivery log

The endpoint's **Overview** tab shows the last five deliveries under **Recent Deliveries**.
**Delivery Logs** has the full history, filtered by **Status** and **Event**. Its **HTTP** column
shows **Timeout** for any failed delivery that got no response. Select a row for its **Request
Body** (the event's `data`) and the **Response Body** your server returned.

With the API, `GET /webhook-deliveries` (scope `webhooks:read`) lists deliveries newest first.
Filter with `webhookEndpointId`, `status` (repeat the parameter for several values) and `type`, add
`includeAttempts=true` for each delivery's attempts, and page with `limit` and `cursor` until
`meta.hasMore` is `false`.

```bash theme={"system"}
curl "https://core-service.prod.brudcast.com/api/v1/user/webhook-deliveries?webhookEndpointId=01a08ff7-0c3e-7a21-9d4f-6b2e8c1f3a57&status=failed&includeAttempts=true&limit=50" \
  -H "X-API-Key: $BRUDCAST_API_KEY"
```

`GET /webhook-deliveries/{id}` returns one delivery with all of its attempts. A delivery record
carries `id` (sent to your server as `delivery_id`), `type`, `payload`, `status`, `attemptCount`,
`httpStatusCode`, `errorMessage`, `responseBody`, `nextRetryAt`, `deliveredAt` and `createdAt`.

## Related

<Columns cols={2}>
  <Card title="Create an endpoint" icon="plus" href="/developers/webhooks/create-an-endpoint">
    Register a URL, set the timeout and **Max Retries**, send a test.
  </Card>

  <Card title="Verify signatures" icon="shield-check" href="/developers/webhooks/verifying-signatures">
    Why a rejected delivery still counts as delivered.
  </Card>

  <Card title="Events and payloads" icon="braces" href="/developers/webhooks/events-and-payloads">
    What arrives in the body of every attempt.
  </Card>

  <Card title="Webhooks overview" icon="webhook" href="/developers/webhooks/overview">
    How a delivery works, and the endpoint API.
  </Card>
</Columns>
