Skip to content

Webhooks Overview

Webhooks tell you what changed, so you do not have to poll for it. When something happens to one of your orders, we send an HTTP request to a URL you have registered with us.

Each delivery is a small signed notification carrying identifiers and a link — never the business data itself. You verify the signature, then fetch the current state from the API. Because the payload holds no data, it cannot go stale between our sending it and your reading it: however long the notification sat in a queue, the fetch that follows returns what is true now.


Every event has this shape, and every future event type will too. The fields below are the whole contract — nothing else is sent.

{
"eventId": "84a4a78bd45d732c5241ee8180896267",
"eventType": "orders.order.placed",
"occurredAt": "2026-08-06T09:30:23.1945315+02:00",
"version": 1,
"entity": { "id": "2004148349", "type": "order" },
"resource": { "href": "/orders/v1/2004148349", "rel": "order:read" }
}
Field Type Description
eventId string 32 lowercase hex characters. Stable for a given logical event — use it as your idempotency key.
eventType string Lowercase dotted domain.entity.action.
occurredAt string ISO 8601 with an explicit offset, e.g. 2026-08-06T09:30:23.1945315+02:00. Carries the originating offset; it is not normalised to UTC.
version integer Envelope version. Currently always 1.
entity.id string Identifier of the thing that changed.
entity.type string Kind of thing that changed, e.g. order.
resource.href string Path to fetch the current state, relative to https://<store-url>/public.
resource.rel string What the link is, e.g. order:read.

Parse the envelope tolerantly. Ignore event types you do not recognise and ignore fields you do not know, so that new event types can be added without breaking your receiver.


Method POST
Scheme HTTPS only
Content type application/json
Timeout 10 seconds
Success Any 2xx
Redirects Not followed — a 3xx counts as a failure

Every delivery carries:

Header Value
webhook-id Same value as eventId
webhook-timestamp Unix time in seconds when the delivery was signed
webhook-signature One or more space-separated v1,<base64> signatures

Anything other than a 2xx inside the 10-second budget is a failed delivery, and we retry it on a backoff schedule spanning roughly 21 hours. See Retries and failures for the schedule, what happens when attempts run out, and how a subscription can be suspended.


Delivery is at-least-once. The same eventId can arrive more than once — a retry, or a redelivery of an event you have already handled. You must deduplicate on eventId and treat a repeat as a no-op. There is no deduplication on our side.

There is no ordering. Do not infer sequence from arrival order. Each event retries independently, so a later event can land before an earlier one. Do not sort by occurredAt either — event types take it from different clocks, so it can disagree with the order in which things actually happened. The envelope is thin precisely so that this does not matter: whatever order notifications arrive in, fetching the current state converges on the same answer.

One change can raise several events. Cancelling an order raises orders.order.cancelled and an orders.order.lines.changed for the cancelled lines; the final invoice on an order raises orders.order.invoiced and orders.order.completed. They arrive as separate deliveries with different eventIds, so deduplication does not collapse them into one. Treat any of them as a cue to re-fetch the order, and let the fetch settle what actually changed. See Follow an order lifecycle.

Only the SoldTo customer is notified. If you are ShipTo, BillTo, ContactTo or PlacedBy on an order but not SoldTo, you receive nothing for that order.


Ten event types are published, all of them about orders. Every one carries the same identifiers, so a receiver that can handle one can handle all ten:

Field Value
entity.type order
entity.id The order number
resource.rel order:read
resource.href /orders/v1/{orderId}

Fetch the order by resolving resource.href against https://<store-url>/public, with a token carrying the order:read scope. See Orders overview for the response shape. What differs between the event types is only why you were told, and therefore what is worth re-reading.

The order has been created and confirmed in our order system. Raised once per order.

Re-read the whole order.

This fires after the order is confirmed in our order system, which happens later than your checkout call returning. It is not a synchronous confirmation of your checkout submission — treat it as an asynchronous notification, and never block a checkout response waiting for it.

A line was added, removed, substituted, repriced, quantity-changed or cancelled. Raised any number of times, and a single change can raise it more than once.

Re-read orderLines[].

Expected delivery dates have been set or revised. Raised any number of times, and normally once shortly after the order is placed.

Re-read expectedDeliveries[].

A delivery has been created for some or all of the lines. Raised once per delivery, so an order that ships in parts raises it more than once.

Re-read deliveries[].

Picking has started on a delivery in the warehouse. Raised once per delivery, so an order that ships in parts raises it more than once.

Re-read deliveries[], in particular events.Picked.

Picking the first delivery closes the cancel-request path for the whole order, not just for that delivery’s lines — a request made after this has fired will not succeed. That does not preclude orders.order.cancelled firing later for other reasons. See Follow an order lifecycle.

The delivery has left the warehouse. Raised once per delivery.

Re-read deliveries[], in particular events.Sent and trackingNumber.

An invoice has been issued. Raised once per invoice, so an order invoiced in parts raises it more than once.

Re-read billings[] where type is Invoice. This is also what moves orderLines[].quantity.fulfilled.

A credit note has been issued. Raised any number of times, and it can arrive long after the order is finished.

Re-read billings[] where type is CreditNote.

Every line has been invoiced or cancelled. Raised at most once per order.

Re-read the events map for Completed.

The whole order has been cancelled. Raised at most once per order.

Re-read the events map for Cancelled.

For a full walkthrough of a single event — verify, deduplicate, acknowledge, then fetch the order — see Handle an order placed event. For how the types follow one another over an order’s life, see Follow an order lifecycle.


Each subscription has its own signing secret, which you receive from Komplett when the subscription is set up. There is no API that returns it.

Treat it as you would any other credential: store it somewhere secret, keep it out of source control, and keep it out of your logs.

We can rotate the secret on request — ask if one is ever lost or exposed. During a rotation the webhook-signature header carries both the new and the previous signature, space-separated, so you can move to the new secret with no downtime and no dropped deliveries.

See Verify a signature for working C# and Node verifiers, and for the mistakes that make verification fail.


Webhook subscriptions are not self-service. They are configured by Komplett — not something you register through the API.

Contact your Komplett Account Manager with:

  • the HTTPS URL deliveries should be sent to, and
  • the event types you want to receive.

Event types can be listed individually, or selected with a trailing wildcard — orders.order.* for every order event, or * for everything we publish. A wildcard keeps your subscription current as new event types are added, provided your receiver ignores the ones it does not recognise.

You get back a subscription identifier and a signing secret.

Webhooks need no scope of their own. The notification carries no business data, so the only authorization that matters is the scope on the token you use to fetch resource.href — order:read for an order.

Webhooks are delivered in the sandbox environment too, so you can prove your receiver works against a real order before going live. See Test your receiver.