---
title: "List recent deliveries for a subscription"
method: GET
path: "/webhooks/{webhookId}/events"
tags: ["Webhooks"]
---

# List recent deliveries for a subscription

`GET /webhooks/{webhookId}/events`

## Request

- Base URL: `https://inkseal.net/api/v1`
- URL: `https://inkseal.net/api/v1/webhooks/{webhookId}/events`
- Auth: HTTP bearer

The delivery history for one subscription: what was sent, how it went, how many attempts it took, and what the endpoint answered.

**This endpoint returns at most the 50 newest deliveries and there is no pager.** It accepts no `page`, `limit`, or cursor parameter, and there is no way to reach anything older than those 50 rows through the API. A subscription that receives a high volume of events will roll past older history within minutes. If you need durable delivery records, capture them as they arrive rather than relying on reading them back here.

`responseBody` is truncated to the first 500 characters of whatever the endpoint returned, or holds the transport error when the request never got a response.

`terminal` is the field to branch on. It is true when the delivery has failed and no further automatic attempt is scheduled, which is exactly the state that `POST /webhooks/{webhookId}/events/{eventId}/retry` can reopen.

The subscription is resolved inside the workspace first, and its events are then read through both that subscription and the same workspace predicate, so nothing here can reach another tenant's deliveries.

## Response `200`

Up to 50 deliveries, newest first. Not paginated: older history is unreachable.

- object
  - `data` WebhookDelivery[], required
    - `id` integer, required: The delivery's id. A sequential integer, and the value to pass as `eventId` when retrying. It is not the `X-InkSeal-Event-Id` the receiving endpoint sees.
    - `eventType` 'envelope.sent' | 'envelope.completed' | 'envelope.declined' | 'envelope.voided' | 'envelope.expired' | 'signer.viewed' | 'signer.signed' | 'signer.declined' | 'envelope.created' | 'email_envelope.approved' | 'email_envelope.rejected' | 'envelope.send_failed' | 'document.ready' | 'document.failed' | 'detection.completed' | 'detection.failed' | 'signer.approved' | 'signer.acknowledged', required: An event a subscription can ask for. New names are appended over time and never renamed or removed, so a subscription pinned to an explicit list keeps working and simply misses anything added later.
    - `status` 'pending' | 'delivered' | 'failed', required: `pending` is queued or between attempts; `failed` may still have an automatic attempt scheduled. Check `terminal` before deciding a delivery has given up.
    - `attempts` integer, required: Attempts made so far, of a maximum of 6. A manual retry resets this to 0.
    - `responseStatus` integer, nullable, required: The HTTP status the endpoint returned on the last attempt, or null when no response was received.
    - `responseBody` string, nullable, required: The first 500 characters of the endpoint's response body, or the transport error when the request never got a response.
    - `deliveredAt` string, date-time, nullable, required: When the endpoint accepted the delivery. Null unless `status` is `delivered`.
    - `lastAttemptAt` string, date-time, nullable, required
    - `nextRetryAt` string, date-time, nullable, required: When the next automatic attempt is due. Null when none is scheduled, either because the delivery succeeded or because it has given up.
    - `terminal` boolean, required: True when the delivery has failed with no further automatic attempt scheduled. This is the only state a manual retry can reopen.
    - `createdAt` string, date-time, required

## Other responses

- `401`: The credential is missing, malformed, unknown, expired, revoked, or past its rotation grace window. Code: `unauthorized`. Every authentication failure is deliberately indistinguishable.
- `403`: The credential is valid but may not do this. Codes: `insufficient_scope` (the route's scopes are not all held; `details` names `requiredScopes` and `missingScopes`), `forbidden` (the capability check on the resolved resource failed), `scope_escalation` (a grant wider than the caller's own was requested).
- `404`: The resource does not exist, was deleted, or belongs to another workspace. Code: `not_found`. The three cases are answered identically on purpose, so a 404 never confirms that an identifier exists elsewhere.
- `429`: A limit was reached. Codes: `rate_limited` (the credential's per-bucket HTTP limit, or the per-workspace detection throttle), `workspace_limit_exceeded` (a workspace content or send limit; `details.limit` names it), `daily_budget_exceeded` (the workspace's daily detection budget). The four rate-limit headers are sent on this response and on no successful one, so a client cannot read its remaining budget without being refused. Retrying after `Retry-After` is always safe: a rate-limit refusal happens before the handler runs.
- `503`: A dependency the request needs is unreachable and nothing was changed. Codes: `queue_unavailable` (a background job could not be enqueued; retry the call), `billing_unavailable` (Stripe would not open a checkout session; nothing was charged, retry the call), `service_unavailable` (the rate-limit store is down, so the request was refused before running).

---

[API](https://skmtc.dev/inkseal/apis/inkseal-api.md) · [All operations](https://skmtc.dev/inkseal/apis/inkseal-api/llms.txt) · [OpenAPI document](https://skmtc.dev/inkseal/apis/inkseal-api/revisions/c440c981d578?raw)
