Webhooks
Register a URL to be called the moment something happens, and know the three things that will surprise you.
Manage → Settings → Webhooks.
"Register a URL to receive events as they happen, instead of polling the API for them."
This is why a store is reachable through the API at all rather than through a polling loop. An agency running fifty stores registers fifty times with fifty calls, instead of clicking through fifty settings screens.
Adding an endpoint
New endpoint.
Endpoint URL — your system's address, e.g.
https://example.com/hooks/web-builder.
Description — an internal label, e.g. "Order sync to our warehouse system".
Events — at least one. There are eleven:
| Event | Fires when |
|---|---|
order.created |
An order is placed |
order.updated |
An order's status, payment or fulfillment changes |
customer.created |
A customer is added |
customer.updated |
A customer record changes |
product.created |
A product is created |
product.updated |
A product changes |
product.deleted |
A product is removed |
page.published |
A page goes live |
course.enrolled |
A student is granted a course |
course.revoked |
Course access is withdrawn |
form.submitted |
A form submission is stored |
form.submitted fires for every stored submission of every form on the
site — contact, order, booking — never for a refused one. The full payload
and how it relates to the inspector's action chains are in
Form actions.
The next-to-last two belong to The Courses app, and they come as a pair on purpose: an integration that only hears enrolments keeps a refunded student in your LMS forever, and it has no way to poll for the absence of something.
Enabled — the switch.
An endpoint subscribed to nothing would never fire, with no error anywhere to notice by — which is why the form requires at least one.
Three things that will surprise you
1. The signing secret appears once you save — and only to people who can edit
"The signing secret is shown once you save." Need it again later, open the endpoint's menu: Reveal signing secret returns the same one, Rotate signing secret mints a new one — and "the current secret stops working immediately", so update your receiver before the next event lands.
All three routes — create, reveal, rotate — require write access to
webhooks. The list and the detail never carry the secret, not even as an
empty field, because webhooks.read reaches the lowest role on a store — and a
viewer who could read the signing key could forge deliveries into your own
system.
2. Duplicates will happen
Delivery is at-least-once, never exactly-once. A delivery the platform believes failed — a timeout, a dropped connection, a 5xx — is retried in full, even when your system actually processed it and only the response back was lost.
If your receiver turns order.created straight into a charge, an email, or a
stock decrement without checking anything first, it will eventually do it twice.
The defence: every delivery carries an id that is stable across every
attempt (also the X-WB-Event-Id header). Record the ones already handled,
and no-op a repeat.
3. There is no ordering
Deliveries go out in the order they become due, not the order the events happened in. A retry sits behind its backoff, so a fresher event for the same resource can arrive first.
Do not infer sequence from arrival order. If you need one, read the resource's
own updatedAt.
Status
| Status | Means |
|---|---|
| Active | Normal |
| Disabled | You switched it off |
| Failing | Three consecutive delivery failures |
Failing does not mean dead: "Still trying — one success clears this."
The retry schedule is seven attempts spanning about 32.6 hours: immediately, +1 minute, +5 minutes, +30 minutes, +2 hours, +6 hours, +24 hours. Long enough to outlast a deploy, an expired certificate or a night's outage; short enough that nothing is still being retried next week. After the seventh, that delivery is marked Dead.
An endpoint is not disabled by this — it keeps receiving new events, because an integration that comes back on its own should catch up by itself rather than be switched off silently.
Checking whether it works
Send test event fires a real delivery at your URL.
View deliveries shows recent attempts with their status codes and errors; each is Pending, Delivered or Dead. The Last success column in the list answers "is it alive" at a glance.
The delivery history never includes the payload. If you need to know exactly what was sent, the envelope your receiver already got is it.
Blocked URLs
A URL pointing at a private, loopback or link-local address is refused
immediately with 400 blocked_url. The address is checked again at dial
time, because a hostname's DNS answer can change after the first check — and
redirects are never followed.
There is nothing to debug: the answer comes back at once.
Read-only
If your role only has webhooks.read, the screen says so: "You can see
registered endpoints, but not create or change them."
Verifying signatures
Every delivery carries X-WB-Signature, along with X-WB-Timestamp,
X-WB-Event-Id, X-WB-Event-Type and X-WB-Attempt. How to check it —
including the detail people get wrong (the signed string uses Unix seconds,
not the RFC3339 value in the header) — is in
The public API.
Updated 26/09/2026