# Replay and retry

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

> Recover from a failed or missed delivery by retrying one delivery or replaying a whole event, and know which one costs quota.

SolidHooks gives you two ways to send something again. They look similar and behave very differently, so pick on purpose.

**Retry** re-sends one delivery to one destination. It reuses the same delivery row and is free. **Replay** takes a stored event and runs it through its connections again, as if it had just arrived. It creates new deliveries, and every new delivery is billable.

## How they differ

|                      | Retry                            | Replay                                                 |
| -------------------- | -------------------------------- | ------------------------------------------------------ |
| Endpoint             | `POST /v1/deliveries/{id}/retry` | `POST /v1/events/{id}/replay`                          |
| Acts on              | One delivery                     | One event, all its connections                         |
| Delivery rows        | Same row, new attempt            | New delivery per matching connection                   |
| Billed               | No                               | Yes, one delivery each                                 |
| Quota gate           | None                             | 429 `QUOTA_EXCEEDED` on hard-capped plans at the limit |
| Uses current routing | No, same destination as before   | Yes, connections that are active now                   |
| Permission           | `delivery: retry`                | `event: replay`                                        |

Both are member-tier actions. A member, or an unscoped API key, can call them. A scoped key needs the matching grant.

## Retry one delivery

Use retry when a destination was down or misbehaving and you want the same delivery sent again.

```bash
curl -s -X POST "$API/v1/deliveries/dlv_.../retry" \
  -H "authorization: Bearer $SDHK_KEY"
```

The response is `{ "ok": true }`. The delivery goes back to `pending` and a worker picks it up. Its next attempt is recorded with `trigger: "manual_retry"`, and that attempt also records who asked for it (`actorType` of `user`, `api_key` or `agent`). Only that first attempt carries `manual_retry`. If it fails, the attempts after it follow the normal [retry schedule](https://solidhooks.com/docs/retries) and are marked `auto_retry`.

Read the history with `GET /v1/deliveries/{deliveryId}/attempts`.

> **A delivery that is already queued returns 409**
>
> Retry is refused with `409 CONFLICT` while the delivery still owns a live job. That includes a delivery with `status: "throttled"`, which is waiting behind a rate limit and will send on its own, and one that is already scheduled for its next automatic attempt. Do not loop on the 409. Watch the delivery's `status`, or the queue through `GET /v1/deliveries/stats`, and retry once it has finished.

A retried delivery appears in the [delivery stream](https://solidhooks.com/docs/streams) when its next attempt starts, not at the moment you call retry.

**API**

```bash
curl -s -X POST "$API/v1/deliveries/dlv_.../retry" \
  -H "authorization: Bearer $SDHK_KEY"
```

**Dashboard**

Open a delivery and use the **Retry delivery** action. It is enabled only for failed deliveries, and a successful call shows "Delivery queued for retry".

## Replay an event

Use replay when the event itself needs to go out again: you fixed a bug in the receiver and want to reprocess history, or you added a connection after the event arrived.

```bash
curl -s -X POST "$API/v1/events/evt_.../replay" \
  -H "authorization: Bearer $SDHK_KEY"
```

```json
{ "ok": true, "deliveriesCreated": 2 }
```

Replay routes the stored event through every connection that is active now. It uses the event type saved at ingest, not the source's current `eventTypeField`, so connection filters apply exactly as they did the first time. If a connection has since been disabled or its filter no longer matches, it creates no delivery, and `deliveriesCreated` can be `0`.

### What can be replayed

* A `duplicate` event is refused with `400 BAD_REQUEST`. It never had deliveries of its own.
* A `rejected` event is refused with `400 BAD_REQUEST`. It failed signature verification, so replaying it would push unverified content to your destinations.
* An event past your plan's `retentionDays` is gone. Older events and their deliveries are purged nightly and cannot be replayed.
* A hard-capped plan at its delivery quota gets `429 QUOTA_EXCEEDED` with `data: { limit, current, resetAt }`. Plans with overage enabled keep accepting and meter the excess. Check `GET /v1/billing/usage` first if you are about to replay a lot.

### Telling a replay from the original

Each replay call is recorded with who ran it, even when it creates no deliveries. Every delivery it creates carries a `replayId`. On `GET /v1/events/{id}/timeline`, each delivery branch has a `replay` object, or `null` for the original fan-out:

```json
{
  "replay": {
    "id": "rpl_...",
    "at": "2026-09-30T10:12:00.000Z",
    "actorType": "api_key",
    "actorEmail": null
  }
}
```

`actorType` is `user`, `api_key` or `agent`. `actorEmail` is the user's email at the time, and `null` for keys and agents. There is no endpoint that lists replays on their own. You read them through the timeline or the `replayId` on a delivery.

**API**

```bash
curl -s -X POST "$API/v1/events/evt_.../replay" \
  -H "authorization: Bearer $SDHK_KEY"
```

**Dashboard**

Open an event and use the **Replay event** action. A successful call shows "Event queued for replay".

There is no bulk replay endpoint. To replay many events, list them with `GET /v1/events` and call replay for each, watching your quota.

## Incident playbook

A destination was down for an hour and you want everything it missed.

1. Confirm the destination is healthy again. `POST /v1/destinations/{id}/test-delivery` sends a signed sample without creating an event or delivery.
2. Find the damage. `GET /v1/deliveries?status=failed&destinationId=dest_...` shows what gave up.
3. Retry those deliveries one by one. It is free, and each keeps its history. Skip any that return `409`, because they are still scheduled.
4. Replay only if retry cannot work: the delivery is gone, or the event never reached this destination because the connection did not exist yet. Check `GET /v1/billing/usage` first, since replays are billed.
5. Do both inside your retention window. After `retentionDays`, neither is possible.

> **Replays are new deliveries**
>
> A replayed event reaches your receiver under a new delivery id, so a receiver that deduplicates on the delivery id (`webhook-id` on Standard Webhooks destinations) will process it again. That is usually what you want from a replay. If it is not, deduplicate on something inside the payload instead.

## Next steps

* [Retries](https://solidhooks.com/docs/retries) — the automatic schedule that runs before you step in
* [Deliveries](https://solidhooks.com/docs/deliveries) — statuses and attempt history
* [Events](https://solidhooks.com/docs/events) — event statuses and what is stored
* [Live streams](https://solidhooks.com/docs/streams) — watch deliveries land in real time