Send data out
Webhooks
Send every extracted result to your own endpoint. Configuration, payload, authentication, and the two failure modes that matter.
A Webhook posts the full result to an endpoint you control, the moment a document finishes. It is the general-purpose route: whatever Parsli does not connect to directly, a webhook reaches.
Setting one up
Under the parser's Outbound, add a Webhook integration.
| Setting | Notes |
|---|---|
| URL | Your HTTPS endpoint |
| Method | POST or PUT |
| Auth type | None, bearer token, or basic |
| Auth token | Sent in the Authorization header |
Use the test action before relying on it. A webhook pointing at a typo fails silently — extraction keeps working and the data simply never arrives.
The payload
{
"event": "document.processed",
"parser_id": "6f3a1c88-...",
"parser_name": "Supplier invoices",
"document_id": "b71c9d02-...",
"timestamp": "2026-08-07T09:14:22.481Z",
"data": {
"invoice_number": "INV-2026-0417",
"vendor_name": "Northgate Supplies",
"total_amount": 1284.5,
"line_items": [
{ "description": "Steel brackets", "quantity": 40, "unit_price": 12.5 }
]
},
"metadata": {
"file_name": "northgate-0417.pdf",
"mime_type": "application/pdf",
"source_type": "email",
"page_count": 2
}
}
Field-by-field reference: receiving results.
data is keyed by your field names and typed to your field types. It is the same shape the API returns, so one handler covers both.
It fires for everything
A webhook catches every completed document on the parser, however it arrived — uploaded, emailed, sent through the inbound webhook, or submitted over the API.
That makes it the single most useful integration point. One endpoint sees everything.
metadata.source_type tells you which route a document took, so one handler can treat an emailed invoice differently from an uploaded one without a second parser.
Authentication
Set an auth type. Without one the endpoint accepts anything that finds the URL.
Bearer token — Parsli sends Authorization: Bearer <token>. Compare it against your secret and reject mismatches.
Basic — standard HTTP basic auth.
if (req.headers.authorization !== `Bearer ${process.env.PARSLI_WEBHOOK_SECRET}`) {
return res.sendStatus(401)
}
The two failure modes that matter
Duplicates. Delivery is at-least-once: a network problem between our send and your acknowledgement causes a retry, and your handler sees the same document twice. Deduplicate on document_id. This bug is silent — you get duplicate rows, never an error.
Empty values treated as zero. A field the engine could not ground in the document is null. If a total arrives null and your code does amount || 0, you have just recorded a zero-value invoice. Check explicitly and route it to a human.
app.post("/parsli", async (req, res) => {
if (req.headers.authorization !== `Bearer ${process.env.PARSLI_WEBHOOK_SECRET}`) {
return res.sendStatus(401)
}
const { document_id, data, metadata } = req.body
res.sendStatus(200) // acknowledge first
if (await alreadyProcessed(document_id)) return // at-least-once delivery
if (data.total_amount == null) return flagForReview(document_id, metadata)
await recordInvoice({ documentId: document_id, ...data })
})
Acknowledge before you work. Return 200 immediately and queue the processing. A handler that does its work inline will eventually be slow enough to look like a failure and trigger a retry.
Developing against it
A webhook needs a publicly reachable URL, which localhost is not. Either use a tunnel, or poll the API while developing and switch to the webhook in production.
When it fails
A failing webhook does not fail the extraction — the document still processes, the result is still stored, and other integrations still fire. The failure is recorded against the integration.
To recover missed results, fetch them with GET /api/v1/documents/{id}. Nothing is lost.
Something here wrong or missing? Tell us — we treat it as a bug.