Webhooks Overview
Subscribe to real-time invoice events delivered directly to your server.
Overview
Parseo delivers webhook events to your HTTPS endpoints when invoices are processed, updated, or deleted. This eliminates the need to poll GET /invoices/:id.
Events
| Event type | Fired when |
|---|---|
invoice.processed | OCR succeeds and an invoice is materialised |
invoice.failed | Processing terminates without producing an invoice, or a reprocess run fails on an existing invoice |
invoice.updated | A team member edits an invoice field in the Parseo UI |
invoice.deleted | A team member deletes an invoice in the Parseo UI |
invoice.deleted only fires for deletions performed via the Parseo web UI. The API does not expose DELETE /invoices/:id in v1, so this event will not fire for API-initiated deletions.
Payload shapes
All events share the same envelope. data has one of two shapes depending on whether the invoice was materialised:
Invoice-shaped data.invoice (invoice.processed, invoice.updated, invoice.deleted, reprocess-failure invoice.failed)
{
"id": "evt_7G6f5E4d3C2b1A0z",
"type": "invoice.processed",
"apiVersion": "2026-04-01",
"createdAt": "2026-04-14T12:34:56.789Z",
"data": {
"invoice": { ... }
}
}data.invoice is the full invoice DTO, identical to the response from GET /invoices/:id. Partners can import directly from the webhook payload without a follow-up API call.
Job-shaped data.job (invoice.failed when OCR never produced an invoice)
{
"id": "evt_7G6f5E4d3C2b1A0z",
"type": "invoice.failed",
"apiVersion": "2026-04-01",
"createdAt": "2026-04-14T12:34:56.789Z",
"data": {
"job": {
"id": "job_abc123",
"status": "failed",
"sourceFile": "invoice.pdf",
"createdAt": "2026-04-14T12:34:50.000Z",
"error": { "message": "OCR provider returned error 502" }
}
}
}This shape is emitted when the processing pipeline failed before an invoice could be materialised — e.g. OCR provider outage, extraction timeout, payload rejected. There is no invoice to fetch. Your handler should check for data.job vs data.invoice to route accordingly:
if (event.data.invoice) {
// OCR succeeded OR this is a reprocess-of-existing failure
handleInvoiceEvent(event.data.invoice);
} else if (event.data.job) {
// OCR never produced an invoice
handleJobFailure(event.data.job);
}Idempotency
Each event carries a unique id (evt_ prefixed). Parseo delivers events at-least-once — retries on failure may result in the same event being delivered more than once. Store the id and deduplicate on your side.
The same id is used across all retry attempts of the same delivery, so deduplicating on id protects against retry duplicates.
Payload size cap
When the full invoice DTO exceeds 1 MB, Parseo delivers a minimal envelope instead:
{
"id": "evt_7G6f5E4d3C2b1A0z",
"type": "invoice.processed",
"apiVersion": "2026-04-01",
"createdAt": "2026-04-14T12:34:56.789Z",
"data": {
"invoice": {
"id": "inv_abc123",
"status": "completed"
}
},
"oversized": true
}When oversized: true is present, fetch the full invoice via GET /invoices/:id.
Forward compatibility
Parseo may add new optional fields to any event payload at any time without bumping apiVersion. Your handler must tolerate unknown fields. Standard JSON parsers (JSON.parse in Node.js, json.loads in Python, encoding/json in Go) tolerate unknown fields by default.
apiVersion changes only when the payload structure changes in a backwards-incompatible way (field removed, renamed, or changes semantic meaning).
Delivery
- Parseo attempts delivery up to 8 times with exponential backoff.
- Your endpoint must return a 2xx status within 30 seconds.
- Redirects (3xx) are treated as failures.
- After 8 failed attempts, the event is marked
dead_lettered.
Source filtering
Each webhook endpoint has an optional sourceFilter to limit which events it receives:
| Value | Receives events from |
|---|---|
"any" (default) | All sources |
"api" | API-originated events only |
"ui" | UI-originated events only |
{ "apiKeyId": "..." } | A specific API key only |
Managing endpoints
See the Webhooks API Reference for CRUD operations on webhook endpoints.
Endpoint limit
Each team may register up to 20 webhook endpoints.
Testing
Use POST /webhooks/:id/test to send a test invoice.processed event to your endpoint immediately. Rate limited to 10 test deliveries per hour per endpoint.
