Webhooks
Register an https endpoint under Developers → Webhooks and InvoiceJet POSTs signed events when extraction finishes, whether the invoice arrived through the API, the app, or email forwarding.
Events
| Event | When |
|---|---|
invoice.completed | Extraction finished; the invoice resource is complete. |
invoice.failed | Extraction failed; the resource carries status failed. |
Payload
data.invoice is exactly what GET /v1/invoices/:id returns; there is no separate shape to parse.
{
"id": "evt_5f0c…",
"type": "invoice.completed",
"created_at": "2026-07-10T14:03:22.114Z",
"data": { "invoice": { "id": "9b2f…", "object": "invoice", "status": "complete", "…": "…" } }
}Signatures
Every delivery is signed with your endpoint's whsec_… secret (reveal it in Developers → Webhooks). Headers: webhook-id, webhook-timestamp (unix seconds), and webhook-signature: an HMAC-SHA256 of {id}.{timestamp}.{body} keyed with the base64-decoded secret, formatted v1,<base64>. Reject anything older than 5 minutes.
import { verifyWebhookSignature } from "@invoicejet/node";
app.post("/webhooks/invoicejet", express.raw({ type: "*/*" }), async (req, res) => {
const ok = await verifyWebhookSignature({
payload: req.body.toString("utf8"),
headers: req.headers,
secret: process.env.INVOICEJET_WEBHOOK_SECRET,
});
if (!ok) return res.status(401).end();
const event = JSON.parse(req.body);
if (event.type === "invoice.completed") {
// event.data.invoice
}
res.status(200).end();
});Delivery & retries
Respond with any 2xx within 10 seconds. Failed deliveries (network errors, 5xx, and 4xx alike) retry automatically on a backoff schedule, re-signed with a fresh timestamp on each attempt. Every attempt is logged in Developers → Webhooks → Recent deliveries (status, response code, duration), where failed deliveries also show their next scheduled retry and a Redeliver button for an immediate manual attempt. Endpoints can be paused with the active toggle without losing configuration; paused endpoints do not receive retries.
| Attempt | When |
|---|---|
| 1 and 2 | At dispatch (immediate, 2s apart). |
| 3 | 5 minutes later. |
| 4 | 30 minutes. |
| 5 | 2 hours. |
| 6 | 8 hours. |
| 7 | 24 hours, then delivery is abandoned. |
Reconciling: GET /v1/events
Webhooks are at-least-once but not guaranteed: if your endpoint is down past the last retry, the delivery is abandoned. The event itself is never lost. Every event is stored and listable at GET /v1/events (newest first, same payload shape as the webhook body), so a periodic sweep after downtime catches anything you missed. Filter with type, created_after, and page with starting_after.
// After downtime: replay everything since the last event you processed
const { data } = await client.events.list({ created_after: lastSeenAt });
for (const event of data) {
if (event.type === "invoice.completed") handleCompleted(event.data.invoice);
}