> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ingestly.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Events

> Receive a signed request at your endpoint when a document arrives, a run ends or a review task changes.

Events tell your integration that something changed, so it does not have to poll. An [event subscription](/admin/event-subscriptions) sends each event it matches to your HTTPS endpoint as a signed `POST`. Events are thin: they carry ids, a status and your `reference`, and you fetch the full record (a run's output, a review task) with the [API](/api-reference/introduction) when you need it.

## Event types

| Type | Sent when | `data` |
| - | - | - |
| [`document.received`](/api-reference/event-document-received) | A trigger, an upload or an inbox stores a document | `documentId`, `workflowId`, `status` (`queued` or `held`), `reference` |
| [`run.completed`](/api-reference/event-run-completed) | A run finishes successfully | `runId`, `workflowId`, `documentId`, `sourceRunId`, `status`, `reference`, `startedOn`, `completedOn` |
| [`run.failed`](/api-reference/event-run-failed) | A run fails | The `run.completed` fields plus `error.code`, the failure category |
| [`run.cancelled`](/api-reference/event-run-cancelled) | A run is cancelled | The `run.completed` fields |
| [`review.created`](/api-reference/event-review-created) | A review task is created, or reset to pending for a new round | `reviewTaskId`, `runId`, `rootRunId`, `workflowId`, `documentId`, `round`, `reference`, `decision` (always `null`) |
| [`review.completed`](/api-reference/event-review-completed) | A review task is approved or rejected | The `review.created` fields plus `decision` (`approved` or `rejected`) |

Run and review events cover the runs your documents start, including reruns and retries, which name the run they came from in `sourceRunId`. Runs a workflow starts on its own (the child runs of a [Split](/nodes/split), Loop or Classify step, and a [reusable section](/guides/reusable-sections) it calls), runs restarted from a single step, [dry runs](/guides/test-mode) and node test runs send no events, and neither do the review tasks inside them. `run.failed` carries only the failure category, never the error text: [get the run](/api-reference/get-run) for the message.

## Envelope

Every event has the same envelope; only `data` differs by type.

```json theme={null}
{
  "id": "0199a6c4-2e3f-7a1b-8c2d-3e4f5a6b7c8d",
  "type": "run.completed",
  "createdOn": "2026-10-02T14:03:11+00:00",
  "environment": "prod",
  "data": {
    "runId": "0199a6c2-4b7d-7e01-8f3a-2c4d6e8f0a1b",
    "workflowId": "0199a6c1-9e2f-7c3d-8a4b-1c2d3e4f5a6b",
    "documentId": "0199a6c2-3f1e-7b2a-9c4d-5e6f7a8b9c0d",
    "sourceRunId": null,
    "status": "completed",
    "reference": "PO-1182",
    "startedOn": "2026-10-02T14:02:47+00:00",
    "completedOn": "2026-10-02T14:03:11+00:00",
    "error": null
  }
}
```

| Field | Description |
| - | - |
| `id` | The event id. It stays the same on every retry, every replay and in [`GET /events`](/api-reference/list-events) |
| `type` | One of the [event types](#event-types), or `ping` for a [test event](#test-events) |
| `createdOn` | When the event happened, in UTC |
| `environment` | `dev` or `prod`, the [environment](/workflows/stages) of the workflow |
| `data` | The type's fields, listed on each event type's page |

## Delivery

Each delivery is a `POST` with a JSON body and these headers:

| Header | Value |
| - | - |
| `X-Ingestly-Signature` | `t=<unix seconds>,v1=<signature>`, with one `v1` per active [webhook signing key](/admin/webhook-signing-keys). See [verifying signatures](/api-reference/verifying-signatures) |
| `X-Ingestly-Event-Id` | The event `id` |
| `Idempotency-Key` | The event `id` again, for endpoints that already dedupe on this header |
| `User-Agent` | `Ingestly-Webhooks/1.0` |

Answer with any `2xx` status within 10 seconds to acknowledge the event. Any other status, a timeout or a connection error counts as a failure and is retried. Redirects are not followed, so a `3xx` is a failure too. Acknowledge first and do slow work afterward, so a long job does not time out and trigger a retry.

<Warning>Deliveries are signed with your organization's webhook signing key. Without an active key, Ingestly cannot sign a delivery and it fails, so [generate a key](/admin/webhook-signing-keys#generating-a-signing-key) before you subscribe.</Warning>

### At least once, in no particular order

* **Delivery is at least once.** A retry, a replay or a lost acknowledgment can deliver the same event twice. Store the `id` of each event you process and skip one you have already seen.
* **Order is not guaranteed**, within a type or across types: `run.completed` can arrive before the `document.received` for the same document. Order by `createdOn` when it matters, and treat a fetch as the source of truth: when an event arrives, [get the run](/api-reference/get-run) or the [review task](/api-reference/get-review-task) for its current state.

### Retries

A failed delivery is retried with exponential backoff: the first retry comes 30 seconds after the failure, each wait is three times the previous one, no wait is longer than 6 hours, and retries stop 72 hours after the first attempt. Every attempt is signed again with a fresh timestamp.

The first time a delivery runs out of retries since the subscription's last successful delivery, your organization's admins get an in-app notification and an email. A successful delivery resets this, so a long outage sends one notification, not one per event.

### Responding with 410 Gone

Answer `410 Gone` to stop receiving events: Ingestly stops retrying and disables the subscription at once. [Re-enable](/admin/event-subscriptions#re-enabling-a-subscription) it when your endpoint is ready again.

### Automatic disabling

A subscription that keeps failing is disabled automatically after 20 consecutive failed attempts with no successful delivery in the last 72 hours. A subscription created or re-enabled within the last 72 hours is not disabled. Your organization's admins get an in-app notification and an email when it happens, and the subscription shows the reason in **Settings** → **Event Subscriptions**.

## Replay and re-enable

Events and their delivery history are kept for 7 days. Within that window you can, from the [subscription](/admin/event-subscriptions):

* **Replay** a single delivery, or every delivery since a point in time. A replay since a time sends again the deliveries that failed or were skipped (a delivery is skipped when its subscription is disabled before its next attempt) and the events the subscription never received (for example while it was disabled), oldest first, with their original `id`. Only an active subscription can be replayed.
* **Re-enable** a disabled subscription. Re-enabling resets its failure count and offers to replay from its earliest failed or skipped delivery, or from when it was disabled if that is earlier.

A replay since a time follows the subscription's current settings: an event type or workflow you removed from it is not replayed. Replaying a single delivery resends that event as it is, whatever the subscription's settings are now.

## Test events

**Send test event** on a subscription delivers a `ping` event right away, signed like any other, and shows your endpoint's response. A `ping` has a fresh `id` every time and its `data` holds only the `eventSubscriptionId`:

```json theme={null}
{
  "id": "0199a6c5-0a1b-7c2d-8e3f-4a5b6c7d8e9f",
  "type": "ping",
  "createdOn": "2026-10-02T14:05:00+00:00",
  "environment": "prod",
  "data": { "eventSubscriptionId": "0199a6c0-5d6e-7f80-9a1b-2c3d4e5f6a7b" }
}
```

A `ping` is sent once and never retried, never counts toward automatic disabling, and is never listed in `GET /events`.

## Reading events with the API

[`GET /events`](/api-reference/list-events) returns the last 7 days of events in the shape a subscription delivers them, oldest first by `id`. It needs the `events:read` [scope](/api-reference/authentication#scopes) and lists only the events of your key's environment. Use it to catch up after an outage, or instead of a subscription when your system cannot receive requests.

Event ids reflect when an event was created, not when it became visible, so an event can appear after you have read one with a higher `id`. To poll without missing events:

1. Pass `after` set to an `id` you saw a few minutes before your newest one, not the newest `id` itself.
2. Read from `page=1` and follow the pages until the last one.
3. Skip events whose `id` you have already processed.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.