Webhooks — Overview
Webhooks are how Inferio tells your server when extraction starts, progresses, and lands. You register an endpoint at request-creation time (or set a global default in the dashboard); we POST signed JSON the moment each lifecycle event fires.
The three events
Every request emits the same three events, in order:
| Event | When | Fields you’d act on |
|---|---|---|
ocr.request.created | Right after POST /api/v1/ocr/requests accepts | request_id, total_images, created_at |
ocr.document.processing | Per-page, when the LLM picks it up | document_id, source_url |
ocr.document.completed | Per-page, when extraction lands (success OR rejected) | fields, confidence, status |
Most downstream pipelines only need to wire ocr.document.completed —
that’s the canonical “extraction done, act on it” event.
Payload envelope
// POST $webhook_url
// Headers:
// X-Inferio-Signature: t=1735634283,v1=4f5a89e2b1d2c8f3...
// X-Inferio-Event: ocr.document.completed
// X-Inferio-Delivery: whd_8a7c2e0fa912
// Content-Type: application/json
{
"event": "ocr.document.completed",
"delivery_id": "whd_8a7c2e0fa912",
"occurred_at": "2026-04-30T03:18:06.421Z",
"merchant_id": "merchant_abc123",
"request_id": "req_3fa1c4e2b9d1",
"document_id": "doc_8a7c2e0fa912",
"status": "completed",
"fields": { /* extracted fields per document_type schema */ },
"confidence": { /* per-field 0.0–1.0 scores */ }
}Delivery guarantees
- At-least-once. If your endpoint times out or returns non-2xx, Inferio retries with exponential backoff (6 attempts over ~24h: 30s, 2m, 10m, 1h, 6h, 24h).
- Idempotent. Dedupe on
delivery_id— the samedelivery_idmay arrive multiple times across the retry window. - Ordered per document_id, not globally. A multi-page request can have pages complete out of order — don’t assume page 1 fires before page 2.
After 6 failed attempts, deliveries land in the dead-letter outbox.
Replay them from the dashboard or via
POST /api/v1/ocr/webhooks/deliveries/{id}/replay.
Configuring endpoints
Two options:
Per-request (one-off integrations)
Pass webhook_url in the request body:
curl -X POST https://api-dev.inferio.xyz/api/v1/ocr/requests \
-H "Authorization: Bearer $INFERIO_TOKEN" \
-H "X-Merchant-ID: $MERCHANT_ID" \
-H "Content-Type: application/json" \
-d '{
"session_id": "f47ac10b-…",
"document_type": "trade_invoice_jp",
"webhook_url": "https://yourdomain.com/webhooks/inferio"
}'Global default (production)
Dashboard → Settings → Webhooks → Add endpoint.
- Set a URL — must be HTTPS
- Pick event types to subscribe (default: all three)
- The dashboard shows you the whsec_ signing secret — copy it immediately; you can’t see it again after closing the dialog
Requests that don’t specify webhook_url inherit the global default.
Requests that DO specify one override the default for that request only.
Test ping
Every endpoint has a Send test event button in the dashboard. It
fires a synthetic ocr.document.completed with deterministic fixture
data so you can verify your signature + JSON parsing without burning
a real OCR credit.
Next
- Signature verification — must-read before production
- Errors — what
LLM_REJECTEDlooks like in a webhook payload