# Retries

Source: https://solidhooks.com/docs/retries

> How SolidHooks retries a failed delivery, what ends the schedule, and how each kind of response from your endpoint is treated.

When your endpoint does not return a `2xx`, SolidHooks retries the same delivery automatically. The retry reuses the delivery row, so it adds attempts to the attempts log rather than creating new deliveries, and it never counts against your delivery quota.

How many attempts you get, and over how long, is set by your plan.

## How it works

The first attempt happens immediately. After that, the gaps between attempts follow an exponential schedule:

| Attempt | Waits before it | Roughly after the first attempt |
| ------- | --------------- | ------------------------------- |
| 1       | none            | 0                               |
| 2       | 5 seconds       | 5 s                             |
| 3       | 5 minutes       | 5 min                           |
| 4       | 30 minutes      | 35 min                          |
| 5       | 2 hours         | 2 h 35 min                      |
| 6       | 5 hours         | 7 h 35 min                      |
| 7       | 10 hours        | 17 h 35 min                     |
| 8       | 10 hours        | 27 h 35 min                     |

Each gap carries up to 10% of random jitter so a burst of failures does not retry in lockstep. Two plan entitlements bound the schedule, and whichever runs out first ends it:

* `maxDeliveryAttempts`: the total number of attempts, first attempt included.
* `maxRetryHours`: a wall-clock ceiling measured from the first attempt.

| Plan       | `maxDeliveryAttempts` | `maxRetryHours` |
| ---------- | --------------------- | --------------- |
| Hobby      | 3                     | 1               |
| Launch     | 8                     | 36              |
| Scale      | 8                     | 36              |
| Enterprise | 8                     | 36              |

Both limits are also clamped by the plan's `retentionDays`, because a retry cannot outlive the delivery record it belongs to. Check your own values with `GET /v1/billing/plan`; enterprise organisations can have them changed.

While a delivery is still being retried, `nextAttemptAt` on the delivery tells you when the next attempt is due. `null` means no further attempts are scheduled.

## How your endpoint's response is interpreted

| Response                 | What happens                                                                                                                                                                     |
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `2xx`                    | Success.                                                                                                                                                                         |
| `3xx`                    | Failure, retried on the schedule. Redirects are never followed.                                                                                                                  |
| `410 Gone`               | Failure. Two consecutive `410` responses disable the destination and email the organisation's owners and admins. Any other response in between resets the count.                 |
| `429`, `502`, `504`      | Parked, not failed. SolidHooks honours `Retry-After` (seconds or an HTTP date, capped at 1 hour), the park costs no retry attempt, and a delivery can be parked at most 5 times. |
| Any other `4xx` or `5xx` | Failure, retried on the schedule.                                                                                                                                                |

A parked delivery has `status: "throttled"` and `throttledReason: "destination_busy"`. It is queued, not lost.

Parking uses no attempt, so the attempt count alone cannot bound it. `maxRetryHours` does: a park that would wake the delivery after the ceiling is not taken, and the response is treated as an ordinary failure instead.

> **Why redirects are not followed**
>
> A redirect would send the signed payload, and your destination's custom headers, to a URL you never configured. Treating `3xx` as a failure keeps deliveries going only where you pointed them. Set the destination URL to the final address.

## Timeouts

A request to your endpoint times out after 20 seconds by default. A timeout is a failure and follows the schedule above. To change it, set `timeoutMs` on the destination to a value between 1000 and 30000.

```bash
curl -s -X PATCH "$API/v1/destinations/dest_..." \
  -H "authorization: Bearer $SDHK_KEY" \
  -H 'content-type: application/json' \
  -d '{ "data": { "timeoutMs": 10000 } }'
```

## Manual retries and replays

* `POST /v1/deliveries/{id}/retry` retries one delivery. It is free and creates no new delivery. It returns `409 CONFLICT` while the delivery is `throttled`, because it is already queued and will send on its own.
* `POST /v1/events/{id}/replay` re-runs an event through its connections and creates new deliveries, which are billable.

## Next steps

* [Deliveries](https://solidhooks.com/docs/deliveries) — statuses and the attempts log
* [Throughput and rate limits](https://solidhooks.com/docs/rate-limits) — why a delivery can read `throttled`
* [Verifying deliveries](https://solidhooks.com/docs/delivery-signatures) — check the signature on each attempt
* [Replay](https://solidhooks.com/docs/replay) — re-run an event