Skip to content

Set up webhooks

Last verified
Last verified Aug 29, 2026

Use this to notify an external system when something happens in KORONA Event, for example when an order settles or a customer changes newsletter consent. Webhooks push signed JSON events to an HTTPS URL you control.

Integration settings and connection status in the back office
Use the integrations area to verify credentials, connection state, and external-system setup.

Before you start

Confirm:

  • you have an HTTPS endpoint that can receive POST requests and respond quickly
  • you have back-office permission to manage webhook endpoints under Admin > Webhooks
  • you know which event types the receiving system needs

Create a webhook endpoint

  1. In the back office, open Admin > Webhooks.
  2. On the Webhook endpoints page, select Create.
  3. Enter the Endpoint URL. It must be a valid HTTPS URL.
  4. Select the Event types that should trigger this webhook.
  5. Optionally select a Point of sale to filter events for one point of sale. A point of sale is required when you select booking events.
  6. Leave Active selected; inactive webhooks do not receive events.
  7. Select Create webhook.
  8. Copy the secret from the Webhook secret dialog and store it securely.

Available event types

Select from these event types when creating or editing an endpoint:

  • Orders: Order Settled
  • Bookings: Booking Created, Booking Updated, Booking Cancelled, Quota Updated
  • Customers: Contact Created, Contact Updated, Contact Deleted, Contact Newsletter Consent Changed, Contact Subscription Added, Contact Subscription Removed
  • Segments: Segment Created, Segment Updated, Segment Deleted, Segment Membership Added

Using Order Settled for external analytics

Order Settled is an authoritative server-side notification that an order entered a settled payment state. Its payload includes the order number, payment state and method, currency, total and settled gross values, settled line items, vouchers, and these additive fiscal fields:

PathMeaning
data.order.total_netSum of the net values of settled invoice and receipt lines
data.order.total_taxSum of the tax values of settled invoice and receipt lines
data.order.shipping.gross_valueGross total of settled lines classified as shipping when each line was created
data.order.shipping.net_valueTax-exclusive total of those shipping lines
data.order.shipping.taxTax total of those shipping lines
data.line_items[].unit_gross_valueSettled line gross total divided by its quantity
data.line_items[].unit_net_valueSettled line net total divided by its quantity
data.line_items[].gross_valueSettled line total including included and additional taxes, after line-item discounts
data.line_items[].net_valueSettled line total excluding included and additional taxes, after line-item discounts
data.line_items[].taxDifference between the settled gross and net line totals
data.line_items[].tax_breakdown[]Tax component values and, when authoritative, their tax_rate and included status
data.line_items[].discountDiscount applied to a settled invoice line; omitted for receipt-backed lines
data.line_items[].is_shippingSnapshot of whether the line used the configured shipping article when it was created
data.vouchers[]Applied voucher id, number, and name

Every monetary value in the webhook uses an object with decimal-string amount, integer cents, and ISO currency. Use the line-total cents for exact reconciliation. A unit value is divided and rounded to the currency's minor unit when the line total does not divide evenly by quantity. When a settled line has quantity zero, both unit fields are omitted because no unit value can be calculated. Tax rates are numeric percentages, so 19 means 19%.

For receipt-backed lines with a verified stored net total, the stored receipt gross and net totals are authoritative, and tax is their difference. Rated components are included only when their reconstructed values sum exactly to that authoritative tax. Otherwise, the breakdown contains one component with the authoritative tax_value and sets tax_rate and included to null; receivers must not infer a rate allocation from that component.

Historical receipt rows created before net totals were persisted are not treated as zero-net purchases. Their additive fiscal line fields are omitted, and order-level net and tax aggregates that depend on those lines are omitted as well. Fiscal fields are also omitted when a receipt line's stored currencies do not match the order currency; receivers must not add differently denominated cents values.

Receipt items do not store an authoritative row-level discount. KORONA Event therefore omits discount from receipt-backed lines instead of repeating a pricing-level discount across multiple receipt rows. Invoice-backed lines continue to expose their persisted line discount.

Shipping classification is stored with each settled line. Changing the configured shipping article later does not reclassify these snapshots, and a newly configured article is treated as shipping only for lines created after that change. Lines that existed before the snapshot field was introduced were classified using the shipping article configured during the upgrade, so their classification might not reflect an earlier configuration.

The top-level net and tax values sum settled lines; vouchers, account transactions, and other order-level adjustments are not distributed across them. Keep data.order.total_gross and settled_gross when reconciling the complete order. The webhook remains contract version 1 because the existing fields and meanings are unchanged and the fiscal fields are additive. Receivers must ignore unknown fields and tolerate the new fields being absent in older or staged deliveries.

It is not a ready-made Google Ads or GA4 conversion feed. The webhook does not contain browser campaign parameters or click identifiers such as gclid, fbclid, or UTM values. A server-side advertising integration must obtain the legally permitted click or session identifier separately, join it to the settled order, transform the documented fiscal fields for its destination, deduplicate webhook deliveries, and send the destination request itself.

For browser-side campaign attribution and purchase events, use Configure external analytics and consent.

Booking identity for grouped event templates

For offerable.booking.created, offerable.booking.updated, and offerable.booking.cancelled, the offerable_* fields identify the public offer that an external system can query or book. When an occurrence was created from an internal child event template in a group, these fields therefore identify the public parent event template rather than the internal child.

FieldGrouped event-template booking
offerable_idID of the public parent event template
offerable_nameName of the public parent event template
offerable_typeEVENT_TEMPLATE
on_demand_event_idID of the materialized occurrence
offerable_span, offerable_span_begin, offerable_span_endTime span of that occurrence

KORONA Event still calculates the quota and point-of-sale routing from the internal source template and the specific occurrence. It retains the child ID as source_event_template_id in the integration-event metadata for routing and diagnostics; this internal metadata is not an additional field in the delivered JSON body. Ungrouped event templates use their own ID in both roles.

Verify deliveries

Each delivery is a JSON POST. There is no universal envelope: order and segment-membership events use canonical event fields such as key, version, occurred_at, and tenant, while booking, quota, and legacy record-change variants use type, created_at, tenant_id, and tenant_slug. contact.created and contact.updated can be delivered in both canonical and legacy variants when both production subscription paths are active. Build your receiver against the event and payload family shown in the complete webhook reference, not against one sample body.

Every production delivery includes these common authoritative headers:

  • Content-Typeapplication/json
  • X-Webhook-Signature — HMAC-SHA256 over <timestamp>.<raw-json-body> using the endpoint secret, formatted as t=<timestamp>,v1=<hex>
  • X-Webhook-Event-Id — the database event ID for the delivery path; use it for deduplication
  • X-Webhook-Event-Type — the event key for this delivery
  • X-Webhook-Timestamp — the Unix timestamp used in signature verification

The integration and legacy delivery paths add compatibility aliases and path-specific metadata headers. The complete webhook reference lists them and identifies which path each event currently uses. Custom headers cannot replace any authoritative header, regardless of letter casing. You may use a custom Authorization header for your receiver.

Verify the signature on the raw request body for every delivery. Compare the expected and received HMAC with a constant-time comparison, and reject timestamps more than five minutes from your receiver's current time. Store X-Webhook-Event-Id with the processing result and make handling idempotent: a repeated event ID must be rejected or acknowledged without applying the event twice. This protects against both replayed requests and normal delivery retries.

To test the wiring, open the endpoint's edit page and select Send test event. This action always queues a sample through the legacy delivery route. Its body and path-specific headers can therefore differ from the production delivery for the same event; use it to test connectivity and signature verification, then validate your parser against the production variant in the reference.

Delivery, retries, and monitoring

A delivery has five total attempts: the initial attempt followed, when the failure is retryable, by attempts after 30 seconds, 60 seconds, 5 minutes, and 15 minutes. There is no fifth retry after 30 minutes. Integration delivery retries HTTP 408, 425, 429, and 5xx responses; the legacy route retries HTTP 408, 429, 499, and 5xx responses. Both routes retry connection and timeout failures. When all attempts are exhausted, the delivery is marked dead-lettered and is not retried again.

KORONA Event allows 5 seconds to establish the connection and 10 seconds to receive the response. Verify the signature, enqueue durable processing, and return a 2xx response within that 10-second response/read deadline. Do not perform long-running work before acknowledging the delivery; a connection or response timeout is treated as a failed attempt and can cause a retry.

The Webhook endpoints list shows Last delivery, Failures, and a Status of Healthy, Failing, or Disabled per endpoint, so you can spot a broken receiver quickly.

To stop new deliveries, archive the endpoint. Archiving removes it from active subscriptions and can be undone from the archived list. A request that was already queued or in progress can still reach your URL, so keep signature verification and idempotent handling active during the transition.

Expected result

Your endpoint receives signed JSON events for the selected event types shortly after they happen, a test event arrives on demand, and the endpoint shows Healthy in the list.

Troubleshooting

ProblemWhat to check
No events arriveThe endpoint is Active, not archived, the right Event types are selected, and no Point of sale filter excludes the events.
Endpoint shows FailingYour receiver returns a success status quickly; check Last delivery and Failures in the list and your server logs.
Signature verification failsYou verify HMAC-SHA256 over timestamp.payload with the stored secret; if the secret is lost, create a new endpoint.
Events stop after repeated failuresDead-lettered deliveries are not retried; fix the receiver and use Send test event to confirm recovery.