Skip to main content

Webhooks

Webhooks deliver real-time HTTP notifications to your server when processing events occur, eliminating the need for polling.

Event Types

EventDescription
extraction.completedProcessing finished successfully, results are available
extraction.failedProcessing 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 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:
{
  "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"
  }
}
FieldTypeDescription
eventstringextraction.completed or extraction.failed
timestampstringISO 8601 timestamp of the event
data.extraction_idstringUUID of the extraction
data.statusstringprocessed (completed) or error (failed)
data.workflow_idstringUUID of the workflow used
data.processed_atstring or nullISO 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
  }
}

Delivery Headers

Each webhook request includes these headers:
HeaderDescription
Content-Typeapplication/json
X-Webhook-Signaturesha256={hex_digest} — HMAC-SHA256 signature for verification
X-Webhook-EventThe event type (extraction.completed or extraction.failed)
X-Webhook-Delivery-IdUnique UUID for this delivery attempt (useful for deduplication)
User-AgentAnyFormat-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
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

SettingValue
Max attempts3 (initial + 2 retries)
Delay between retries1 second (fixed)
Request timeout5 seconds per attempt
Connection timeout3 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

MethodEndpointDescription
POST/v2/webhooks/Create a webhook
GET/v2/webhooks/List webhooks
DELETE/v2/webhooks/{id}/Delete a webhook