Overview

Webhooks push events to your endpoint as they happen, so you don't have to poll. Billey POSTs a signed JSON payload to a URL you control whenever one of a fixed set of events fires.

Setting up a subscription

Webhooks are managed per book, in the app:

  1. Open the book's Settings → webhooks.
  2. Add your endpoint URL (must be HTTPS) and choose which events to receive.
  3. Billey shows a signing secret — it starts with whsec_ and is shown once. Copy it; you need it to verify signatures. Treat it like a password.

The event envelope

Every delivery has the same envelope:

{
  "id": "9f1b7c2a-3d4e-4f5a-8b6c-1d2e3f4a5b6c",
  "type": "invoice.paid",
  "created_at": "2026-07-21T10:15:30Z",
  "data": { "id": 42, "invoice_number": "2026-0042", "status": "paid", "total": "1250.00" }
}
  • id — the event id (a UUID). Use it as an idempotency key.
  • type — the event type, one of the six events.
  • created_at — when the event was generated (UTC ISO-8601, Z suffix).
  • data — the affected resource, byte-identical to what the REST GET for that resource returns. So the data of an invoice.paid event is exactly an invoice resource; parse it with the same code.

Delivery, retries, and auto-disable

  • Billey POSTs the payload to your URL and expects any 2xx response to acknowledge receipt. Reply 2xx fast and do slow work asynchronously.
  • A failed delivery (non-2xx, timeout, or connection error) is retried up to 4 times with exponential backoff.
  • If a subscription keeps failing across many deliveries, it is automatically disabled and the book owner is emailed. Re-enable it in settings once your endpoint is healthy.

Delivery headers

Each request carries these headers:

Header Purpose
X-Billey-Signature v1=<hex> HMAC-SHA256 over "{timestamp}.{rawBody}"verify this
X-Billey-Timestamp Unix timestamp the signature was computed with
X-Billey-Event-Id The event id — equals the body id
X-Billey-Event-Type Unsigned convenience mirror of the body type

The X-Billey-Event-Type and X-Billey-Event-Id headers are unsigned conveniences. The signed body always wins: route on the body's type, not the header. Always verify the signature before trusting a payload.

Not delivered

There are no *.updated or *.deleted events — only the six on the Events page fire. And a POS sale emits no webhook. If you need to know about changes those don't cover, poll the relevant endpoint with updated_after (see Pagination).

Something inaccurate or missing? support@billey.nl