Webhooks
A webhook tells your system that something happened in Kontier: an invoice was finalised, a payment attempt failed, a subscription was cancelled. You register a URL, choose which events you care about, and Kontier posts to it.
Use them instead of polling. An invoice becomes overdue at a moment you cannot predict, and asking every minute is both slower and more expensive than being told.
Registering an endpoint
url and event_types are required. Nothing else is.
Code
The response carries signing_key once: without a secret_id, Kontier
provisions a signing secret for the endpoint and never shows the key again, so
store it now.
Subscribe to what you will act on, not everything. GET /v1/webhook-endpoints/event-catalog lists every subscribable type; there are
135 of them, all listed in Webhooks and events.
At least once
This is the design fact everything else follows from.
A delivery that fails is retried. A delivery that succeeds but whose response Kontier never sees is also retried, because from the sender's side those two look identical. Your endpoint will therefore receive the same event twice occasionally, and it must not double-act.
Make handlers idempotent: key on the envelope's idempotency_key (stable
across retries and endpoints; id changes per delivery), record what you have
processed, and make a repeat a no-op. This is the same reasoning as the required
idempotency_key on usage ingest, from the other direction.
Return 2xx fast, then work
Kontier treats a slow response as a failure and retries. Acknowledge with a 2xx immediately, queue the work, and process it outside the request. An endpoint that does its work inline is an endpoint that gets duplicate deliveries under load.
Verifying the signature
Every delivery is signed. Your endpoint is public, so anyone can post to it; the signature is what distinguishes Kontier from anyone else.
Verify it on every request, before doing anything with the body. An endpoint
signed with HMAC (the default, hmac_sha256) receives:
| Header | Content |
|---|---|
X-Webhook-Timestamp | The send time, Unix epoch seconds |
X-Webhook-Signature-V1 | v1= + lowercase hex of HMAC-SHA256(signing_key, timestamp + "." + raw_body) |
X-Webhook-Signature | Deprecated, still sent: sha256= + hex of HMAC-SHA256(signing_key, raw_body) |
Verify X-Webhook-Signature-V1: recompute it over the raw request bytes,
compare in constant time, and reject a timestamp more than about 5 minutes from
your clock. Binding the timestamp is what stops a captured delivery being replayed
at you later. The older X-Webhook-Signature signs the body alone, so it stays
valid on a replay forever; it is still emitted for existing receivers and will be
removed only after an announced deprecation window.
An endpoint whose secret uses jwt_hs256 receives X-Webhook-Signature-JWT
instead: an HS256 token with iat/exp and a phash claim (hex SHA-256 of the
body), and no HMAC headers.
Secrets are managed separately at /v1/webhook-secrets.
POST /v1/webhook-secrets/{id}/rotate issues a new key and returns it once. The
old key stops working immediately; there is no grace window, so have the new key
ready to deploy when you rotate. Deliveries that fail verification in between are
retried on the normal schedule.
Payload versions
Every delivery is an envelope: id (unique per delivery), type, schema
(the envelope version, "v1"), payload_version (this event type's payload
shape, currently 1 for every event), idempotency_key, created_at and
payload. A payload_version bump means a breaking change for that one event
type only; additive fields do not bump it, so ignore keys you do not know.
Retries
A delivery that gets no 2xx within 30 seconds is retried 1 minute, 5 minutes,
30 minutes, 2 hours and then every 12 hours after the previous attempt, up to the
workspace's attempt cap (6 by default). After that it is exhausted.
When something goes wrong
| Endpoint | What it gives you |
|---|---|
GET /webhook-endpoints/{id}/deliveries | Every attempt, with status and response |
POST .../deliveries/{delivery_id}/replay | Requeue one delivery by hand |
Replay is the tool for the case where your endpoint was down for an hour, or a bug meant you dropped events you should have kept. It re-sends the original payload rather than a fresh one, so late processing still sees what happened at the time.
Terms and API names
| Term | In the API |
|---|---|
| an endpoint | /v1/webhook-endpoints |
| the events an endpoint receives | event_types[] |
| delivery attempts | GET /webhook-endpoints/{id}/deliveries |
| sending one delivery again | POST .../deliveries/{id}/replay |
| the signing secret | /v1/webhook-secrets, rotatable |
Where webhooks go next
- React to failures.
invoice.payment_attempt_failedandsubscription.dunning_enteredare the events most worth wiring first; they tell you a payment did not go through and dunning has started. - Keep your own copy. Persist the events you care about rather than relying on the delivery log for history.
- Check the catalog before subscribing. Event names are specific, and guessing one silently subscribes you to nothing.
Reference
| Topic | When you need it |
|---|---|
| Dunning | What the payment-failure events mean |
| Invoices | The lifecycle behind the invoice events |
| Webhooks and events | Every event type you can subscribe to |
| Webhooks API | Endpoints, secrets, deliveries and replay |