> ## 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.

# Callbacks

> Receive run results via HTTP webhook. Signing, retries, redelivery.

Long-running models push their final payload to a URL you control. You provide the URL on the request; Runflow POSTs the result when the run finishes.

## Pattern

1. Call `POST /v1/models/{model_id}/runs` with `callback_url`, where `{model_id}` is the slash-delimited provider/model path shown on the model page.
2. Save the returned `id`. Callback payloads call the same value `run_id`.
3. Run finishes in the background.
4. Runflow POSTs to `callback_url`.
5. Your handler returns `200 OK` within a few seconds.

## Callback payload

The callback body is an event envelope, frozen at delivery time:

```json theme={"dark"}
{
  "event": "run.completed",
  "run_id": "01J0...",
  "status": "succeeded",
  "output": { "image_urls": ["https://..."] },
  "duration_ms": 102345,
  "created_at": "2026-05-06T10:00:00.000+00:00",
  "completed_at": "2026-05-06T10:01:42.000+00:00",
  "metadata": null
}
```

| Field                        | Notes                                                                                                                                                                                                                            |
| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `event`                      | One of `run.completed`, `run.failed`, `run.cancelled`, `run.partial_succeeded`. Event names use UK spelling (`cancelled`, two `l`s); the run's `status_code` field uses US spelling (`canceled`). Both literals are stable.      |
| `run_id`                     | UUIDv7 of the run. Match against the `id` from your `POST /runs` response.                                                                                                                                                       |
| `status`                     | Terminal callback status. Run records expose the same value as `status_code` on [`GET /v1/runs/{id}`](/api-reference/runs/get-run).                                                                                              |
| `output`                     | The normalized model output on success, an error envelope on failure, or `null` if cancelled. Batch callbacks use a self-describing `{items, succeeded, failed, cancelled, total}` envelope, see the OpenAPI for the full shape. |
| `duration_ms`                | Total run wall-clock time.                                                                                                                                                                                                       |
| `created_at`, `completed_at` | ISO 8601, `+00:00` offset (not `Z`) so HMAC verification stays byte-equivalent.                                                                                                                                                  |
| `metadata`                   | Whatever you passed at run creation, verbatim.                                                                                                                                                                                   |

Output URLs are presigned and time-limited. Download or re-upload them to your own storage on receipt. Batch callbacks ship the same envelope but with `batch_id` instead of `run_id`.

## Verify the source

Create a callback secret and Runflow signs every callback with HMAC-SHA256:

```bash theme={"dark"}
curl -X POST https://api.runflow.io/v1/callback-secrets \
  -H "Authorization: Bearer $RUNFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"label": "production"}'
```

Save the returned `plain_secret`. Runflow then sends `Runflow-Signature: <hmac-hex>` on every callback (alongside a `Runflow-Request-Id` for log correlation). See [Verify callback signatures](/guides/verify-callback-signatures) for handler code.

## Retries

Runflow retries failed callbacks (non-2xx response, timeout, connection error) on an exponential schedule. Inspect attempts with [`GET /v1/runs/{run_id}/callback`](/api-reference/runs/get-callback-delivery-history).

## Manual redelivery

Replay a callback at any time:

```bash theme={"dark"}
curl -X POST https://api.runflow.io/v1/runs/{run_id}/callback-redeliveries \
  -H "Authorization: Bearer $RUNFLOW_API_KEY"
```

## Local development

Your laptop is not reachable from the public internet. Tunnel it:

```bash theme={"dark"}
ngrok http 3000
# use the https://abc123.ngrok-free.app URL as callback_url
```

## Related

<CardGroup cols={2}>
  <Card title="Verify signatures" icon="shield-check" href="/guides/verify-callback-signatures">HMAC verify in Node and Python.</Card>
  <Card title="Callback delivery API" icon="webhook" href="/api-reference/runs/get-callback-delivery-history">History, redelivery, and secrets.</Card>
</CardGroup>
