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

# Overview

> Receive real-time notifications when processing completes or fails

Webhooks eliminate the need for polling — your server gets an HTTP callback the moment a processing event fires.

## Event Types

| Event                  | Description                                             |
| ---------------------- | ------------------------------------------------------- |
| `extraction.completed` | Processing finished successfully, results are available |
| `extraction.failed`    | Processing encountered an error                         |

If you omit the `events` field when creating a webhook, it defaults to all supported events.

## Requirements

* **HTTPS required**: Webhook URLs must use HTTPS. HTTP URLs are rejected.
* **API version**: Webhook endpoints require the v2 API (`/v2/webhooks/`).
* **Limit**: Up to 50 active webhook subscriptions per organization.

## How It Works

1. [Create a webhook](/api-reference/webhooks/create) with your HTTPS endpoint URL
2. Save the `secret` from the creation response (it is only returned once)
3. When processing completes or fails, your endpoint receives a POST request
4. Verify the request signature using the secret

## Payload Structure

Every webhook delivery sends a JSON POST request with this structure:

```json theme={null}
{
  "event": "extraction.completed",
  "timestamp": "2024-03-24T12:02:30.000Z",
  "data": {
    "extraction_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "status": "processed",
    "workflow_id": "550e8400-e29b-41d4-a716-446655440000",
    "processed_at": "2024-03-24T12:02:30.000Z"
  }
}
```

| Field                | Type           | Description                                                    |
| -------------------- | -------------- | -------------------------------------------------------------- |
| `event`              | string         | `extraction.completed` or `extraction.failed`                  |
| `timestamp`          | string         | ISO 8601 timestamp of the event                                |
| `data.extraction_id` | string         | UUID of the extraction                                         |
| `data.status`        | string         | `processed` (completed) or `error` (failed)                    |
| `data.workflow_id`   | string         | UUID of the workflow used                                      |
| `data.processed_at`  | string or null | ISO 8601 timestamp when processing finished, `null` on failure |

For `extraction.failed`, the payload looks the same but with `status: "error"` and `processed_at: null`:

```json theme={null}
{
  "event": "extraction.failed",
  "timestamp": "2024-03-24T12:02:30.000Z",
  "data": {
    "extraction_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "status": "error",
    "workflow_id": "550e8400-e29b-41d4-a716-446655440000",
    "processed_at": null
  }
}
```

## Delivery Headers

Each webhook request includes these headers:

| Header                  | Description                                                      |
| ----------------------- | ---------------------------------------------------------------- |
| `Content-Type`          | `application/json`                                               |
| `X-Webhook-Signature`   | `sha256={hex_digest}` — HMAC-SHA256 signature for verification   |
| `X-Webhook-Event`       | The event type (`extraction.completed` or `extraction.failed`)   |
| `X-Webhook-Delivery-Id` | Unique UUID for this delivery attempt (useful for deduplication) |
| `User-Agent`            | `AnyFormat-Webhooks/1.0`                                         |

## Signature Verification

Webhook payloads are signed using **HMAC-SHA256**. To verify a delivery:

1. Read the raw request body (the JSON string exactly as received)
2. Compute `HMAC-SHA256(secret, body)` using the `secret` from webhook creation
3. Compare the result with the value after `sha256=` in the `X-Webhook-Signature` header

```python theme={null}
import hmac
import hashlib

def verify_webhook(payload_body: bytes, signature_header: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(), payload_body, hashlib.sha256
    ).hexdigest()
    received = signature_header.removeprefix("sha256=")
    return hmac.compare_digest(expected, received)
```

<Warning>
  Always use a constant-time comparison (`hmac.compare_digest`) to prevent timing attacks. Never use `==` to compare signatures.
</Warning>

## Retry Policy

| Setting               | Value                   |
| --------------------- | ----------------------- |
| Max attempts          | 3 (initial + 2 retries) |
| Delay between retries | 1 second (fixed)        |
| Request timeout       | 5 seconds per attempt   |
| Connection timeout    | 3 seconds               |

If all 3 attempts fail, the delivery is dropped and logged on our side. Webhook failures do not affect processing itself — delivery is best-effort.

<Note>
  To avoid missed events during outages, we recommend periodically listing your files with `GET /v2/workflows/{workflow_id}/files/` and checking for any with `status: "processed"` or `status: "error"` that you haven't handled.
</Note>

## Webhook Secret

When you create a webhook, the API returns a `secret` field (a 64-character hex string). This secret is only included in the creation response and is excluded from list responses. Store it securely.

## Endpoints

| Method | Endpoint             | Description                                        |
| ------ | -------------------- | -------------------------------------------------- |
| POST   | `/v2/webhooks/`      | [Create a webhook](/api-reference/webhooks/create) |
| GET    | `/v2/webhooks/`      | [List webhooks](/api-reference/webhooks/list)      |
| DELETE | `/v2/webhooks/{id}/` | [Delete a webhook](/api-reference/webhooks/delete) |
