Webhooks
Webhooks deliver real-time HTTP notifications to your server when processing events occur, eliminating the need for polling.
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
- Create a webhook with your HTTPS endpoint URL
- Save the
secret from the creation response (it is only returned once)
- When processing completes or fails, your endpoint receives a POST request
- Verify the request signature using the secret
Payload Structure
Every webhook delivery sends a JSON POST request with this structure:
{
"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:
{
"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
}
}
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:
- Read the raw request body (the JSON string exactly as received)
- Compute
HMAC-SHA256(secret, body) using the secret from webhook creation
- Compare the result with the value after
sha256= in the X-Webhook-Signature header
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)
Always use a constant-time comparison (hmac.compare_digest) to prevent timing attacks. Never use == to compare signatures.
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.
To avoid missed events during outages, we recommend periodically listing your files with GET /v2/files/ and checking for any with status: "processed" or status: "error" that you haven’t handled.
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 |
| GET | /v2/webhooks/ | List webhooks |
| DELETE | /v2/webhooks/{id}/ | Delete a webhook |