Overview
kurnl sends HTTP POST requests to yourwebhook_url after significant events. The webhook_url lives on your provider integration settings (tenant-config); the signing secret is the Shared webhook secret from the dashboard Checkout page.
All events share a common envelope structure with event-specific fields merged in at the top level.
Event reference
On every event:
subscription_id, external_subscription_id (your own
identifier; null when the line has none) and is_anonymous. They are
additive: nothing you already read has moved.What is not emitted: there is no subscription.suspended event, and
a restore you request through
the status call sends no new
subscription.activated event. Also mind the
payload variants: subscription.activated from a provider switch carries
kind, activated_at and plan_version_id like every activation, plus
started_at and via: "provider_switch", but no location_hash or
subscriber_id, so don’t key on those unconditionally.subscription.activated used to be silent on the home-drop and confirm-install
paths. It now fires there too, with kind: "first". A line you cancelled and
kurnl later brings back sends it again with kind: "reactivated".
An example of the new visit event:
Backup e-mail for anonymous lines
For an anonymous line, kurnl can also send every one of these events to an e-mail address of your choice, next to the webhook. It is a safety net, for the day your endpoint is down or a delivery is dropped. Set the address under Backup notification e-mail on the dashboard Checkout page; the Send notification e-mails switch turns it off. It works without awebhook_url
too. The mails come from kurnl’s status address. Lines of named subscribers
send no backup mail, and neither do port offline or port restored.
Payload structure
event, provider_document_id, and timestamp. Event-specific fields are merged in alongside them.
Verifying signatures
When you have awebhook_secret configured, kurnl signs every request body with HMAC-SHA256 and sends the signature in the X-Webhook-Signature header as sha256=<hex_digest>.
Always verify this signature before processing any event.
Delivery behaviour
- kurnl first attempts delivery up to 3 times inline, with exponential back-off (2s, then up to 30s between retries) and a 15 second timeout per attempt
- If all inline attempts fail, the event moves to a dead-letter queue: a scheduler (every 10 minutes) redelivers it with growing back-off until 5 total attempts have been made, then the event is dropped and kurnl operations is alerted
- Every delivery carries an
X-Webhook-Eventheader with the event name; DLQ redeliveries additionally carryX-Webhook-Retry: true— use it for dedup awareness - Network errors and 5xx responses are retried; 4xx responses are not retried (a 4xx during DLQ redelivery ends retrying immediately)
- Return any
2xxstatus to acknowledge — kurnl does not inspect the response body
Idempotency
kurnl may deliver the same event more than once after transient failures. Usejob_id or subscription_id as an idempotency key to deduplicate in your handler:
Testing webhooks locally
During development you can use a tunnelling tool to expose your local server to kurnl:Debugging delivery failures
The sandbox has its own webhook event feed — but it is a different pipeline with different event names than the production events on this page. See Sandbox for how to configure, list, and replay sandbox webhook events. For production issues, remember the delivery behaviour above: failed events are redelivered from the dead-letter queue withX-Webhook-Retry: true, and events
that exhaust all attempts alert kurnl operations.