# Deliveries

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

> Follow one event to one destination, read its attempt history, and retry it when it fails.

A delivery is one event sent to one destination through one connection, with a full record of every attempt. If an event matches three connections it produces three deliveries, each with its own status and history.

Delivery IDs start with `dlv_` and attempt IDs with `att_`. The delivery is the billable unit. See [Plans](https://solidhooks.com/docs/plans).

## How it works

A delivery is created `pending` when an event is routed. A worker sends it to the destination, records the attempt, and moves the delivery to a new status. If the attempt fails, the delivery is retried on a schedule until it succeeds or the schedule runs out. The delivery row keeps only the latest attempt's response, so the attempt list is the only record of earlier attempts.

## Statuses

| Status      | Meaning                                                                                                                |
| ----------- | ---------------------------------------------------------------------------------------------------------------------- |
| `pending`   | Queued, awaiting its next attempt.                                                                                     |
| `throttled` | Held back and queued. See `throttledReason`.                                                                           |
| `success`   | The destination answered with a `2xx`.                                                                                 |
| `failed`    | The latest attempt failed. Check `nextAttemptAt`: a date means it will be retried, `null` means the schedule is spent. |
| `skipped`   | Terminal, nothing sent. The route was disabled: the destination, its connector or the connection.                      |
| `filtered`  | Terminal, nothing sent. The event type is no longer in the connection's allowlist.                                     |

`pending` and `throttled` are transient. `success` and `failed` come from an attempt that was actually made. `skipped` and `filtered` are deliberate non-sends and are not failures.

### Throttled

A throttled delivery is queued, not lost, and waiting consumes no retry attempt. `throttledReason` says why:

| `throttledReason`  | Cause                                                                                       |
| ------------------ | ------------------------------------------------------------------------------------------- |
| `rate_limit`       | SolidHooks is pacing delivery: your plan's throughput or the destination's own `rateLimit`. |
| `destination_busy` | The destination answered `429`, `502` or `504`. SolidHooks honours its `Retry-After`.       |

`throttledAt` is set the first time a delivery is held and never cleared, so a delivery that was held and then succeeded still shows it. It is the historical record, while `status` is the current state. `throttledReason` is `null` when the delivery is not parked.

## Fields

| Field                                                  | Notes                                                                                                               |
| ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------- |
| `eventId`, `sourceId`, `destinationId`, `connectionId` | What the delivery came from and went to. `connectionId` can be `null` if the connection was deleted.                |
| `replayId`                                             | The replay that created this delivery, or `null` for the original fan-out.                                          |
| `status`                                               | One of the statuses above.                                                                                          |
| `attempts`                                             | Number of attempts made.                                                                                            |
| `lastAttemptAt`                                        | Time of the most recent attempt.                                                                                    |
| `nextAttemptAt`                                        | When the next attempt is due. `null` means no further attempts are scheduled: finished, or the retry plan is spent. |
| `throttledAt`, `throttledReason`                       | See above.                                                                                                          |
| `responseStatus`, `responseBody`, `responseHeaders`    | The latest attempt's response.                                                                                      |
| `requestBody`, `requestHeaders`                        | What was sent.                                                                                                      |
| `error`                                                | Why the latest attempt failed, if it did.                                                                           |
| `createdAt`, `updatedAt`                               | Timestamps.                                                                                                         |

## Attempts

`GET /v1/deliveries/{deliveryId}/attempts` lists every attempt for a delivery, oldest first, with `items`, `nextCursor` and `hasMore`. Pass `cursor` and `limit` (1 to 100, default 20) to page.

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

Each attempt records:

| Field                                | Notes                                                                                                                                   |
| ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
| `attemptNumber`                      | 1-based. Not unique: a throttled attempt consumes no retry budget and shares its number with the attempt that follows it.               |
| `status`                             | `pending`, `success`, `failed`, `throttled`, `skipped`, `filtered` or `interrupted`.                                                    |
| `trigger`                            | `initial`, `auto_retry` or `manual_retry`. Only the first attempt of a manually retried delivery is `manual_retry`.                     |
| `startedAt`, `finishedAt`            | `finishedAt` is `null` while in flight.                                                                                                 |
| `responseStatus`, `responseTimeMs`   | What the destination returned and how long it took.                                                                                     |
| `responseHeaders`                    | Sensitive values redacted.                                                                                                              |
| `responseBody`                       | Truncated to 4 KB.                                                                                                                      |
| `error`                              | Why the attempt failed.                                                                                                                 |
| `nextRetryAt`                        | When the next attempt was scheduled. `null` on the final attempt, which separates a delivery still retrying from one that has given up. |
| `actorType`, `actorId`, `actorEmail` | Who asked for a manual retry: `user`, `api_key` or `agent`. Unset on every other attempt.                                               |

Order by `startedAt`, not by `attemptNumber`. `interrupted` is not a destination failure: it means no outcome was recorded because the worker stopped, and a resumed execution can replace it with its real outcome. Deliveries created before attempt history was recorded return no attempts.

In the dashboard, open a delivery to see **Overview**, **Request**, **Response** and **Timeline** tabs. The timeline shows each attempt, including parked and not-sent ones, and who retried it.

## Retry schedule

A failed delivery is retried automatically on an exponential schedule, and the number of attempts and the time window depend on your plan. Retries are free: they reuse the same delivery. The full schedule, `Retry-After` handling and how responses are classified are on [Retries](https://solidhooks.com/docs/retries).

## Retry a delivery

`POST /v1/deliveries/{id}/retry` re-queues a single delivery. It creates no new delivery and is not billed.

**API**

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

```json
{ "ok": true }
```

**Dashboard**

Use the retry icon on the delivery's row in **Deliveries**, or **Retry delivery** in its detail panel. It is only enabled for failed deliveries.

A delivery that still has a live job returns `409 CONFLICT`. For a `throttled` delivery the message says it is queued behind a rate limit and will send automatically, so wait instead of retrying. A delivery already scheduled for its next attempt is also refused.

Retry resends one delivery to one destination. To run an event through all of its connections again, [replay](https://solidhooks.com/docs/replay) the event. A replay creates new deliveries and is billed.

## List deliveries

`GET /v1/deliveries` returns a page of deliveries, newest first by default.

| Parameter                     | Filters by                                          |
| ----------------------------- | --------------------------------------------------- |
| `eventId`                     | Deliveries of one event.                            |
| `sourceId`                    | Deliveries from one source.                         |
| `destinationId`               | Deliveries to one destination.                      |
| `connectionId`                | Deliveries through one connection.                  |
| `status`                      | One of the statuses above.                          |
| `from`, `to`                  | Created on or after, or on or before, a date.       |
| `cursor`, `limit`             | Paging. `limit` is 1 to 100, default 20.            |
| `search`, `sortBy`, `sortDir` | Search term and sort. `sortDir` is `asc` or `desc`. |

```bash
curl -s "$API/v1/deliveries?status=failed&destinationId=dest_...&limit=50" \
  -H "authorization: Bearer $SDHK_KEY"
```

The response is `{ items, nextCursor, hasMore }`. Pass `nextCursor` back as `cursor` for the next page.

In the dashboard, **Deliveries** filters by status, source, destination and connection.

## Queue snapshot

`GET /v1/deliveries/stats` returns what is queued right now:

```json
{ "pending": 12, "throttled": 3 }
```

`pending` is deliveries moving and awaiting their next attempt. `throttled` is deliveries held by a rate limit. This is a live snapshot, not lifetime totals. The dashboard shows it as queued and rate-limited counts on the **Deliveries** page.

For live updates without polling, `GET /v1/deliveries/stream` tails delivery activity as Server-Sent Events. See [Streams](https://solidhooks.com/docs/streams).

## What the destination receives

Every delivery request carries `x-source-id`, `x-event-id`, `x-organization-id`, `x-destination-id` and `x-delivery-id`, your configured headers, and the signature headers for the destination's scheme. See [Delivery signatures](https://solidhooks.com/docs/delivery-signatures) for how to verify them.

## Next steps

* [Retries](https://solidhooks.com/docs/retries) — the schedule and how responses are interpreted
* [Replay](https://solidhooks.com/docs/replay) — re-run an event through its connections
* [Destinations](https://solidhooks.com/docs/destinations) — rate limits and timeouts that shape delivery
* [Delivery signatures](https://solidhooks.com/docs/delivery-signatures) — verify what you receive
* [Streams](https://solidhooks.com/docs/streams) — tail deliveries live

API reference: [Deliveries](https://api.solidhooks.com/v1/#tag/deliveries).