CommerceHook

WooCommerce webhooks: how they behave, and how to see them

WooCommerce's webhooks are capable and badly instrumented: the admin shows a delivery log per webhook, but not the payloads your handler is failing on, and several behaviours surprise everyone the first time. This page is the missing manual.

The lifecycle

A webhook is created in the admin (WooCommerce, then Settings, Advanced, Webhooks) or through the REST API (POST /wp-json/wc/v3/webhooks with a topic, a delivery URL, and optionally a secret). The moment it is created, WooCommerce sends a ping: a form-encoded webhook_id body, just to see the URL answer. From then on, each matching event queues a delivery.

Topics cover four resources (orders, products, customers, coupons), each with created, updated and deleted, plus custom topics mapping any WordPress action hook as action.hook_name. The payload is the same JSON the REST API returns for that resource.

Delivery is wp-cron

Deliveries go out in background processing via wp-cron, not inline with the request that caused them. Two consequences: a busy store delivers within seconds, while a quiet one can sit on a delivery until the next page view wakes wp-cron; and delivery timing is not ordering, so an order.updated can arrive before the order.created it follows. Handlers should key on the payload's own data, never on arrival order.

Failure, retries, and the five-strikes rule

A delivery counts as failed when the destination answers anything outside 2xx. After five consecutive failures, WooCommerce sets the webhook's status to disabled and stops delivering, without an email or a dashboard banner. This is the behaviour behind most "orders silently stopped syncing" incidents: the handler 500ed for an hour once, the webhook died, and nobody knew until the reconciliation report.

The blunt fix is a destination that cannot fail. A CommerceHook endpoint answers 2xx to every delivery and captures it, so the webhook stays alive no matter what state your real handler is in; you then work from the captured copy, replaying deliveries at the handler while you fix it.

The headers that matter

Debugging questions

Why did my WooCommerce webhook stop firing?
The most common cause is auto-disable: after five consecutive deliveries that did not get a 2xx response, WooCommerce sets the webhook to disabled and stops trying. Check WooCommerce > Settings > Advanced > Webhooks for its status. Pointing the webhook at an inspector endpoint that always answers 2xx prevents the counter ever tripping.
Why are deliveries delayed?
WooCommerce delivers webhooks in the background through wp-cron, which only runs when the site gets traffic. A quiet store can hold deliveries until the next visit. If timing matters, run a real cron job for wp-cron instead of relying on page loads.
What is the webhook_id delivery with no event type?
The ping. When a webhook is created, WooCommerce immediately POSTs a small form-encoded body (webhook_id=123) to the delivery URL to check it answers. It is expected, and it is the only non-JSON delivery a healthy webhook sends.
How do I verify the signature?
Each delivery carries X-WC-Webhook-Signature: a base64-encoded HMAC-SHA256 of the raw request body, keyed with the webhook secret set at creation. Compute the same HMAC over the exact bytes received and compare. A mismatch on an otherwise valid delivery usually means something between the store and your handler rewrote the body.

Seeing the deliveries

CommerceHook gives a WooCommerce store a stable HTTPS destination and shows every delivery in an inspector: the topic badge, every header including the signature, and the payload as a JSON tree or raw. Registration happens from the dashboard, deliveries can be replayed at any handler, and commercehook listen streams them into the one on your laptop. Free for one endpoint, no card.