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

# Webhooks

> Org-level webhook subscriptions for platform events. Configure once, receive every matching event across all runs.

A **webhook** is an org-level subscription to one or more event types. Configure the URL once; Runflow delivers every matching event from any run in the organization. Use webhooks when you want a single endpoint to receive all activity instead of setting `callback_url` per run.

## Webhooks vs `callback_url`

| Mechanism                | Scope               | Where it's set                    | When to use                                                                                   |
| ------------------------ | ------------------- | --------------------------------- | --------------------------------------------------------------------------------------------- |
| `callback_url` (per run) | One run             | On `POST /v1/models/{model}/runs` | One-off pipelines, ephemeral workers, ngrok during dev. See [Callbacks](/concepts/callbacks). |
| Webhooks (org-level)     | All runs in the org | `POST /v1/webhooks` once          | Production. Consistent endpoint, signed deliveries, retry history, redelivery.                |

Both deliver the same `run.completed` / `run.failed` / `run.cancelled` payload shape documented under [Callbacks](/concepts/callbacks#callback-payload). Pick `callback_url` if you don't need history; pick webhooks if you do.

## Endpoints

| Method   | Path                                                               | Purpose                                                               |
| -------- | ------------------------------------------------------------------ | --------------------------------------------------------------------- |
| `GET`    | `/v1/webhooks`                                                     | [List webhooks](/api-reference/webhooks/search-webhooks) for the org. |
| `POST`   | `/v1/webhooks`                                                     | [Create a webhook](/api-reference/webhooks/create-webhook).           |
| `GET`    | `/v1/webhooks/{id}`                                                | Fetch a webhook by id.                                                |
| `PATCH`  | `/v1/webhooks/{id}`                                                | Update url, secret, or event subscriptions.                           |
| `DELETE` | `/v1/webhooks/{id}`                                                | Delete a webhook.                                                     |
| `GET`    | `/v1/webhooks/event-types`                                         | Reference list of every event you can subscribe to.                   |
| `GET`    | `/v1/webhooks/event-types/{code}`                                  | Detail for one event type.                                            |
| `GET`    | `/v1/webhooks/{webhook_id}/deliveries`                             | Delivery history (pass `any` as `webhook_id` to span all webhooks).   |
| `GET`    | `/v1/webhooks/{webhook_id}/deliveries/{id}`                        | One delivery, with the recorded payload.                              |
| `GET`    | `/v1/webhooks/{webhook_id}/deliveries/{delivery_id}/attempts`      | Per-attempt records (timestamps, HTTP status, response).              |
| `GET`    | `/v1/webhooks/{webhook_id}/deliveries/{delivery_id}/attempts/{id}` | One attempt.                                                          |

Every endpoint is `Authorization: Bearer $RUNFLOW_API_KEY`. The `X-Organization-Id` header is optional and defaults to the key's org.

## Create a webhook

`POST /v1/webhooks` takes three required fields plus an optional list of event subscriptions:

```bash theme={"dark"}
curl -X POST https://api.runflow.io/v1/webhooks \
  -H "Authorization: Bearer $RUNFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-server.com/runflow",
    "secret_ciphertext": "<encrypted-secret>",
    "secret_fingerprint": "abcd",
    "event_subscriptions": [
      { "event_type_code": "run.completed" },
      { "event_type_code": "run.failed" }
    ]
  }'
```

| Field                   | Type   | Required | Notes                                                           |
| ----------------------- | ------ | -------- | --------------------------------------------------------------- |
| `url`                   | string | yes      | HTTPS endpoint Runflow POSTs to. Max 2048 chars.                |
| `secret_ciphertext`     | string | yes      | Org-managed encrypted signing secret. Used to HMAC the payload. |
| `secret_fingerprint`    | string | yes      | 4-char display fingerprint for the dashboard.                   |
| `event_subscriptions[]` | array  | no       | One subscription per event type. Add or remove later via PATCH. |

Subscribing to zero events creates a webhook that never fires. Subscribe to at least one event type from `GET /v1/webhooks/event-types`.

## Event types

`GET /v1/webhooks/event-types` returns every code Runflow can emit, with the schema each event carries. Common ones today:

| Code                    | Fired when                                        |
| ----------------------- | ------------------------------------------------- |
| `run.completed`         | A run reached `succeeded`.                        |
| `run.failed`            | A run reached `failed`.                           |
| `run.cancelled`         | A run was cancelled (admin or auto).              |
| `run.partial_succeeded` | Batch run finished with at least one failed item. |

Event names use UK spelling (`cancelled`, two `l`s); the run's `status_code` field uses US spelling (`canceled`). Both literals are stable.

## Deliveries and attempts

Every event that matches a webhook produces a **delivery**. Each delivery has one or more **attempts** (retries on 5xx, timeouts, or network errors).

```bash theme={"dark"}
# List recent deliveries for one webhook
curl https://api.runflow.io/v1/webhooks/{webhook_id}/deliveries \
  -H "Authorization: Bearer $RUNFLOW_API_KEY"

# Across every webhook in the org
curl https://api.runflow.io/v1/webhooks/any/deliveries \
  -H "Authorization: Bearer $RUNFLOW_API_KEY"

# One delivery with its attempts
curl https://api.runflow.io/v1/webhooks/{webhook_id}/deliveries/{delivery_id} \
  -H "Authorization: Bearer $RUNFLOW_API_KEY"

# Per-attempt detail (HTTP status, response_body, next_retry_at)
curl https://api.runflow.io/v1/webhooks/{webhook_id}/deliveries/{delivery_id}/attempts \
  -H "Authorization: Bearer $RUNFLOW_API_KEY"
```

Each attempt records `status_code`, `response_time_ms`, `response_body`, `succeeded`, and `next_retry_at`. Use them to debug 5xx storms or a misconfigured receiver.

## Verify the signature

Runflow signs every webhook delivery with HMAC-SHA256 of the raw body using your webhook's secret. The signature ships in the `Runflow-Signature` header alongside `Runflow-Request-Id` for log correlation. The verification code is the same as for per-run callbacks - see [Verify callback signatures](/guides/verify-callback-signatures).

## Receiver checklist

* Return `2xx` within a few seconds. Runflow treats non-2xx, timeouts, and network errors as failures and retries on an exponential backoff.
* Verify `Runflow-Signature` before trusting the body.
* Be idempotent on `delivery.id` and `run_id`. Retries can deliver the same event more than once.
* Log `Runflow-Request-Id`. It is the join key against `GET /v1/webhooks/{webhook_id}/deliveries`.
* Use [`/v1/webhooks/any/deliveries`](/api-reference/webhooks/search-webhook-deliveries) to triage when you're not sure which webhook fired.

## Related

<CardGroup cols={2}>
  <Card title="Callbacks (per run)" icon="webhook" href="/concepts/callbacks">Use `callback_url` for one-off runs.</Card>
  <Card title="Verify signatures" icon="shield-check" href="/guides/verify-callback-signatures">HMAC verification in Node and Python.</Card>
  <Card title="Errors" icon="triangle-exclamation" href="/concepts/errors">Status codes and the error envelope.</Card>
  <Card title="API reference" icon="code" href="/api-reference/webhooks/search-webhooks">Full endpoint reference.</Card>
</CardGroup>
