Parseo

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 typeFired when
invoice.processedThe invoice is ready: OCR succeeded and the full pipeline completed. For an invoice that was blocked for assignment review, this fires only after the review is resolved and deferred processing completes
invoice.review_requiredOCR succeeded but the invoice was blocked by the client-assignment gate (assignment.status = "needs_review"). Resolve it via POST /invoices/{id}/assignment/{approve,move,discard} or in the dashboard — invoice.processed follows once processing completes
invoice.awaiting_creditsAn uploaded file was admitted but parked: your team's credit balance did not cover it. Nothing was charged and nothing was rejected — the job resumes automatically when the balance rises. Fires once, at admission. Never fires for test-mode keys
invoice.failedProcessing terminates without producing an invoice, or a reprocess run fails on an existing invoice
invoice.updatedA team member edits an invoice field in the Parseo UI
invoice.deletedA team member deletes an invoice in the Parseo UI

Treat invoice.processed as "the invoice is ready", not "the upload finished". A blocked invoice fires invoice.review_required instead at parse time; discarding it means invoice.processed never fires for that invoice.

invoice.awaiting_credits is not terminal. It announces a pause, not an outcome — the same job still fires invoice.processed or invoice.failed later, once it resumes and runs. Do not treat it as a failure and do not re-upload the file; the job already exists and holds its place in the queue.

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.review_required, invoice.updated, invoice.deleted, reprocess-failure invoice.failed)

{
  "id": "evt_7G6f5E4d3C2b1A0z",
  "type": "invoice.processed",
  "apiVersion": "v1",
  "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.awaiting_credits, and invoice.failed when OCR never produced an invoice)

{
  "id": "evt_7G6f5E4d3C2b1A0z",
  "type": "invoice.failed",
  "apiVersion": "v1",
  "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" }
    }
  }
}

invoice.awaiting_credits uses the same shape, with status: "awaiting_credits" and no error:

{
  "id": "evt_7G6f5E4d3C2b1A0z",
  "type": "invoice.awaiting_credits",
  "apiVersion": "v1",
  "createdAt": "2026-04-14T12:34:56.789Z",
  "data": {
    "job": {
      "id": "job_abc123",
      "status": "awaiting_credits",
      "sourceFile": "invoice.pdf",
      "createdAt": "2026-04-14T12:34:50.000Z"
    }
  }
}

The job-shaped payload is emitted whenever no invoice row exists yet: because the pipeline failed before one could be materialised (OCR provider outage, extraction timeout, payload rejected), or because the job is parked and has not run at all. There is no invoice to fetch in either case. 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) {
  // No invoice row yet: parked awaiting credits, or a failure before OCR
  // could produce one. `status` tells you which.
  handleJobEvent(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": "v1",
  "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:

ValueReceives 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.

On this page