Webhooks and events
A webhook is Kontier calling your system when something happens, so you do not have to poll for it. You register an endpoint, choose the events you care about, and verify the signature on every delivery.
Verify the signature
Every delivery is signed with a secret you hold. Check it before you trust the body: an unverified endpoint is a URL anyone who learns it can post to.
Rotate the secret rather than replacing the endpoint. Rotation lets both secrets validate while you deploy.
Retries mean be idempotent
A delivery that does not get a 2xx is retried. That means your handler will sometimes see the same event twice, and it must reach the same result the second time.
Use the event id as an idempotency key. Return 2xx as soon as you have safely recorded the event, and do the slow work afterwards, because a handler that takes too long is a handler that gets retried.
Wrong types are refused
You cannot subscribe to an event that does not exist. Registration validates every value against the catalog below and rejects anything outside it, naming the offending array element and suggesting near matches from the same family.
This matters more than it sounds. Delivery matches event types exactly, so an accepted typo would register cleanly and then deliver nothing, forever, with no feedback. Registration is the last point at which anyone can tell you.
Listed means emitted
An event appears below only if it is actually emitted. Constants that exist in the code but are never constructed are deliberately excluded, because subscribing to one would silently deliver nothing.
Every event type carries its own payload shape revision, delivered as the envelope's payload_version and currently 1 for all of them. Versions advance per event, so one event's payload can change without touching any other's. The live number for each type is in the OpenAPI webhooks section and in GET /v1/webhook-endpoints/event-catalog; see Payload versioning for how to handle a bump.
Every event we emit
119 events across 24 families, generated from the platform's own event catalog, so this is the complete list rather than a selection.
| Family | Covers | Events |
|---|---|---|
| agreement | A negotiated term running out, and what was applied when it did. term_ending_soon is the notice signal, emitted once, the agreed number of days before the end, so the renewal conversation happens before the price changes rather than after | agreement.term_ended, agreement.term_ending_soon |
| allowance | Included-allowance consumption | allowance.rolled_over |
| approval | Approval workflow steps | approval.request_approved, approval.request_cancelled, approval.request_created, approval.request_expired, approval.request_rejected |
| credit_note | Credit notes issued and applied | credit_note.applied, credit_note.created, credit_note.issued, credit_note.voided |
| customer | Customer records | customer.corrective_invoice_review_required, customer.created, customer.currency_changed, customer.mode_changed, customer.type_changed |
| dispute | Chargebacks and disputes | dispute.changed |
| document | Generated PDFs | document.failed, document.generated, document.requested |
| dunning | Overdue collection | dunning.step.cancel, dunning.step.email, dunning.step.grace, dunning.step.suspend |
| e_invoicing | Structured invoice generation | e_invoicing.document.generated, e_invoicing.document.quarantined |
| invoice | The invoice lifecycle | invoice.created, invoice.finalized, invoice.generated, invoice.line_added, invoice.line_deleted, invoice.line_updated, invoice.manual_collection_link_requested, invoice.payment_attempt_failed, invoice.payment_failed_terminal, invoice.payment_recorded, invoice.updated, invoice.voided |
| meter | Usage metering | meter.threshold_breached |
| mutation | Bulk record changes | mutation.recorded |
| payment | Payments | payment.changed |
| plan | Plan catalog and versions | plan.published, plan.subscribers_migrated, plan.version_rebased |
| product | Product catalog | product.activated, product.archived, product.copied, product.created, product.deleted, product.updated, product.version_rebased |
| promotion | Promotions and redemptions | promotion.activated, promotion.archived, promotion.budget_exhausted, promotion.created, promotion.effect_applied, promotion.expired, promotion.phase_changed, promotion.redeemed, promotion.redemption_cap_reached, promotion.redemption_dormant, promotion.redemption_free_periods_exhausted, promotion.redemption_reactivated, promotion.redemption_revoked, promotion.updated |
| quote | Quotes and acceptance | quote.approved, quote.created, quote.declined, quote.expired, quote.orchestrated, quote.rejected, quote.reminded, quote.sent, quote.signed, quote.viewed |
| reserve_outcome | Wallet reservations | reserve_outcome.captured, reserve_outcome.failed, reserve_outcome.queued |
| scheduled_change | Scheduled subscription changes | scheduled_change.cancelled, scheduled_change.created, scheduled_change.reconfirmed, scheduled_change.released, scheduled_change.rolled_back, scheduled_change.superseded, scheduled_change.upcoming |
| state | State transitions | state.transitioned |
| subscription | The subscription lifecycle | subscription.activated, subscription.allowance_included_quantity_changed, subscription.allowance_refresh_changed, subscription.cancelled, subscription.created, subscription.dunning_entered, subscription.dunning_stage_changed, subscription.item_added, subscription.item_removed, subscription.negotiated_price_expired, subscription.paused, subscription.payment_failed, subscription.plan_changed, subscription.product_pool_configured, subscription.quantity_changed, subscription.recovered, subscription.refund_required, subscription.renewal_upcoming, subscription.resumed |
| tag | Tagging | tag.applied, tag.deleted, tag.removed, tag.renamed |
| tax | Tax rules and resolution | tax.oss_threshold_approaching, tax.oss_threshold_exceeded |
| wallet | Prepaid balances | wallet.apply_to_invoice, wallet.auto_topup_triggered, wallet.hold_expired, wallet.low_balance, wallet.payment_skipped, wallet.promotional_balance_expired, wallet.transferred |
There is no invoice.paid
An invoice being paid arrives as invoice.payment_recorded. The identifier
invoice.paid exists internally for mail triggers and product analytics, but it
is not a webhook event and never reaches an endpoint.
Reference
| Topic | When you need it |
|---|---|
| Payment gateways | Callbacks inbound to Kontier, the opposite direction |
| Invoices | What the invoice family describes |
| Subscriptions | The largest event family |
| Agreements | What the two agreement.* events are about |
| Webhooks API | Registering an endpoint and rotating a secret |