# Webhook delivery records and resend

> How to read a stream event delivery record, how its queued, delivered, and failed states are decided, what the attempt counter means, and how to page and resend deliveries.

Every time a live input or an asset emits an event, ClutchCall records
a **delivery**: one row per attempt to hand that event to one of your
endpoints. The **Request logs** screen renders those rows. This page
explains the record's fields, how its state is decided, and what a
resend does.

The log is read-only history plus a per-row resend. Endpoint
configuration (URLs, which event types an endpoint subscribes to) is not
part of the delivery record and is managed on the Webhooks page.

## The delivery record and its fields

Each row returned by `streams.eventDeliveries.list` is a single
delivery record:

| Field          | Column in the console | Meaning                                                                 |
| -------------- | --------------------- | ----------------------------------------------------------------------- |
| `id`           | (row key)             | Delivery id. This is what a resend targets.                             |
| `event_type`   | Event                 | The event name that fired.                                              |
| `obj_id`       | Object                | Id of the object the event is about — a live input or an asset.          |
| `endpoint_url` | Endpoint              | Destination URL. The console falls back to `webhook_url`, then to `—`.   |
| `response_code`| Response              | HTTP status returned by your endpoint. `null` when no response yet.     |
| `status`       | (not shown directly)  | Feeds the state decision below. `pending` and `failed` are significant. |
| `attempt`      | Attempt               | Attempt number for this delivery. The console shows `1` when unset.     |
| `ts`           | Time                  | Delivery timestamp, parsed as a date.                                   |

Rows in this log routinely span several days, so the console formats
`ts` with month and day as well as hour and minute rather than time
alone.

## Queued, delivered and failed: how a state is decided

There is no single state column. The console derives the badge from
`response_code` and `status` together:

| Badge                     | Condition                                                              |
| ------------------------- | ---------------------------------------------------------------------- |
| **Queued** (grey)         | `status === "pending"`, or `response_code` is null and `status` is not `failed`. |
| **`2xx` OK** (green)      | `response_code` is a number in the range 200–299.                      |
| **`<code>` ERR** (red)    | Anything else that is not queued — a response code outside 200–299, or `status === "failed"`. |

Two consequences are worth internalising:

- A delivery that has not been attempted yet, or is mid-flight, is
  **queued**, not failed. It is not painted red and it is not counted as
  a failure.
- A record with `status === "failed"` and no `response_code` renders as
  `— ERR`. That is the shape of a failure where no HTTP status came back
  at all (the request never completed), as opposed to a `4xx`/`5xx`
  answer from your server.

## What the attempt counter counts

`attempt` is the attempt number carried on the delivery record. A row
with an attempt greater than `1` tells you that this event took more
than one try to reach the endpoint. When the field is absent the console
displays `1`.

The record does not carry a retry schedule, a next-attempt time, or a
remaining-attempts budget — none of those are fields on the delivery, so
you cannot read "when will it retry again" off this screen. Treat the
attempt number as history, not as a prediction.

## Resending an event and what the consumer sees

The resend control on each row acts on a single delivery: it takes the
org id and that row's `id`. There is no bulk or time-range replay on this
screen, and a resend is scoped to your org.

Plan your handler around two properties:

- **At-least-once, not exactly-once.** A resend hands your endpoint the
  same `event_type` for the same `obj_id` again. A handler that creates
  records, sends notifications, or increments counters must be
  idempotent. Key it on identity from the event itself rather than on
  "have I seen a request before".
- **No ordering guarantee.** A resend is dispatched when you click it, so
  a replayed event arrives *after* events that were originally emitted
  later than it. Do not infer state transitions from arrival order;
  reconcile against the object's current state or the timestamps inside
  the event payload.

The screen re-queries the delivery list every 30 seconds, so the
outcome of a resend surfaces in the log on the next poll rather than
instantly.

## Listing and paging deliveries over the API

```ts
const deliveries = await trpc.streams.eventDeliveries.list.query({
  orgId,
  limit: 100,
  page: 1,
});
```

| Input   | Notes                                                                 |
| ------- | --------------------------------------------------------------------- |
| `orgId` | Required. Scopes the log to one org. The console does not issue the query until an org id is known. |
| `limit` | Page size. The console uses 100.                                      |
| `page`  | 1-based page number.                                                  |

The procedure returns a flat array of delivery records, newest first. It
returns **no total count and no cursor**. The console detects a further
page by checking whether the returned array is exactly `limit` long; if
it is shorter, this is the last page and **Next** is disabled. That is
also why the "All" filter label renders as `100+` rather than an exact
total when a full page comes back.

## Filters apply to the page you loaded

The **All / Delivered / Failed** buttons filter client-side over the
records already fetched for the current page. They are not query
parameters:

- **Delivered** keeps rows whose `response_code` is in 200–299.
- **Failed** keeps rows that are neither delivered nor queued.
- The failure count next to the **Failed** button counts failures in the
  loaded page only — not across your whole history.

To audit further back, page through with **Prev** / **Next** and apply
the filter per page.

## Which objects appear in the log

The `obj_id` column is the id of the live input or asset that emitted the
event, and the `event_type` column is the event's name. An org with no
deliveries yet sees an empty log: events from your live inputs and assets
appear here as they fire, once at least one endpoint is configured to
receive them.

## Related

- [Authentication](/concepts/authentication) — API keys for control-plane calls
- [Telemetry](/platform/telemetry) — server-side operational streams
