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:
- Open the book's Settings → webhooks.
- Add your endpoint URL (must be HTTPS) and choose which events to receive.
- 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,Zsuffix).data— the affected resource, byte-identical to what the RESTGETfor that resource returns. So thedataof aninvoice.paidevent 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. Reply2xxfast 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