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

EventWhen
invoice.completedExtraction finished; the invoice resource is complete.
invoice.failedExtraction failed; the resource carries status failed.

Payload

data.invoice is exactly what GET /v1/invoices/:id returns; there is no separate shape to parse.

Payload
{
  "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.

AttemptWhen
1 and 2At dispatch (immediate, 2s apart).
35 minutes later.
430 minutes.
52 hours.
68 hours.
724 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);
}