Source: https://dinakar-pageloop-knowledge-base.docs-staging.pageloop.ai/concepts/delivery-and-retries

# Delivery and retries

# Delivery and retries

## What counts as success

A delivery succeeds when your endpoint returns any `2xx` status within the response
timeout. Everything else — a `4xx`, a `5xx`, a connection reset, a TLS error, a timeout —
is a failed delivery.

> [!NOTE]
>
> Redirects are not followed. A `301` or `302` is recorded as a failed delivery, so point
> subscriptions at the final URL.

## Timeouts

Kestrel waits **10 seconds** for response headers. The clock starts when the request is
written, not when the connection is opened, so a slow TLS handshake does not eat into it.

## The retry schedule

A failed delivery is retried up to **5 times** with exponential backoff and full jitter.
The base delay is 2 seconds and the cap is 32 seconds.

![Timeline of delivery attempts with 2s, 4s, 8s, 16s and 32s gaps](https://d2qvcb9ffq0p5e.cloudfront.net/faf0698b-4136-4317-868c-d1aef034d8f4/images/f2f3ce1cce15ad5ccd059fc1593ebf93d58a19ea5c84fa6e529a12b8d1a68f42.svg)

_Six attempts in total: the initial delivery plus five retries, roughly a minute end to end._

| Attempt     | Nominal delay after previous attempt |
| ----------- | ------------------------------------ |
| 1 (initial) | —                                    |
| 2           | 2s                                   |
| 3           | 4s                                   |
| 4           | 8s                                   |
| 5           | 16s                                  |
| 6           | 32s                                  |

After the sixth attempt the delivery is marked `exhausted` and no further attempts are
made. The event itself is untouched — you can still replay it.

> [!WARNING]
>
> Jitter means the delays above are upper bounds, not exact times. Do not build logic
> that assumes a retry lands at a particular second.

## Circuit breaking

If an endpoint fails **20 consecutive deliveries**, Kestrel disables the subscription and
emails the account owner. Deliveries queued while a subscription is disabled are held for
the plan's retention window and can be replayed once you re-enable it.

Re-enable a subscription from **Dashboard → Subscriptions**, or with:

```bash
curl -X POST https://api.kestrel.dev/v1/subscriptions/sub_8213/enable \
  -H "Authorization: Bearer $KESTREL_API_KEY"
```

## Idempotency

Retries reuse the event `id`, so your endpoint will see the same `id` more than once
whenever a response is lost after you processed it. Deduplicate on `id`.

```js
const seen = await redis.set(`kestrel:${event.id}`, '1', 'NX', 'EX', 60 * 60 * 24);
if (!seen) return res.sendStatus(200); // already handled
```

> [!TIP]
>
> A 24-hour dedup window comfortably covers the retry schedule, which finishes within
> about a minute of the first attempt.

## Replay

Any delivery inside your retention window can be replayed. Replays are recorded separately
and do not count against the original delivery's attempt budget.

See [Handling failures](/guides/handling-failures) for the operational playbook.
