WooCommerce webhooks
This page is about setting a WooCommerce webhook integration up. If one already exists and is misbehaving, the WooCommerce webhook inspector covers why deliveries fail, arrive late, or stop entirely.
Two ways in
WooCommerce gives you the same webhook two ways, and which you pick mostly depends on whether you will do it again.
In the admin
Go to WooCommerce, then Settings, Advanced, Webhooks, and Add webhook. It is the quickest route for one or two, and the delivery log that appears afterwards is per webhook.
Over the REST API
POST /wp-json/wc/v3/webhooks, authenticated with a consumer key and
secret created under Settings, Advanced, REST
API. This is the route worth learning if you manage more than one store,
because it is scriptable and the same call works against every install.
curl -X POST "https://example.com/wp-json/wc/v3/webhooks" \
-u "$WC_CONSUMER_KEY:$WC_CONSUMER_SECRET" \
-H "Content-Type: application/json" \
-d '{
"name": "Orders to CommerceHook",
"topic": "order.created",
"delivery_url": "https://hooks.commercehook.app/{endpoint_id}",
"secret": "a-long-random-string",
"status": "active"
}'
The four fields that matter
Topic
The event. The built-in resources are orders, products,
customers and coupons, each with created, updated and deleted, so
order.created and product.updated are the shapes you will use most.
Anything WordPress fires as an action hook can also be a topic, written
action.hook_name, which is how you reach behaviour a plugin adds
rather than WooCommerce itself.
Delivery URL
Where it goes. It has to be reachable from the
store, which is the part that catches people developing locally: a
localhost URL is reachable from your laptop and from nowhere else.
Secret
What signs the delivery. It is optional, and it should not be: set one explicitly, a long random string, and keep it where your handler can read it. Leaving it to chance is how you end up unable to tell a real delivery from anything else that can reach a public URL.
Status
active, paused or disabled. Worth knowing now
because WooCommerce will change it for you later, and that is the single
most common reason a working integration stops.
You will see a delivery straight away
The moment a webhook is created, WooCommerce pings the delivery URL with
a small form-encoded body, webhook_id=123, to check something answers.
It is expected. It carries no event type and it is the only non-JSON delivery a healthy webhook sends, so a handler that assumes JSON on every request will throw on the very first one it ever receives. Handle it or ignore it, but know it is coming.
Verifying a delivery
Every delivery carries X-WC-Webhook-Signature: a base64-encoded
HMAC-SHA256 of the raw request body, keyed with the secret you set.
Compute the same HMAC over the exact bytes you received and compare. Over the raw bytes, not a re-serialised object: parsing the JSON and stringifying it again changes the whitespace, which changes the digest, and produces a mismatch that looks like an attack and is actually your own framework.
Three things that will bite you
Deliveries run through wp-cron
WooCommerce queues a delivery and sends it in the background, and wp-cron only runs when somebody visits the site. A quiet store can sit on a delivery until the next page load. If timing matters, disable the pseudo-cron and run a real one on a schedule.
Five consecutive failures disables the webhook
Anything that is not a 2xx counts. After the fifth, WooCommerce sets the status to disabled and stops trying, without telling you. The webhook is still listed, it simply never fires again, which is why an integration that worked in testing can be silently dead by the time it matters.
Staging gates and basic auth count as failures
A staging site behind a password answers 401 to WooCommerce exactly as it would to anybody else, and five of those are enough.
Pointing it at CommerceHook
An endpoint answers 2xx immediately and always, so the five-failure counter never starts counting, and the delivery is kept whether or not whatever you are building behind it was up at the time.
Create an endpoint, then register from the dashboard by saving the
store’s consumer key and secret, and tick the topics you want. We create
the webhooks over the REST API, set the signing secret, and verify
signatures on delivery from then on. If you would rather not save
credentials, the dashboard prints the same curl command for you to run
yourself.
From there every delivery is readable in full: the payload as a JSON
tree or raw, every header including the signature, the method, and the
query string it arrived with. Deliveries can be replayed at your own
handler once it is ready, and commercehook listen streams them
straight to a handler on your laptop, which is the part that removes the
tunnel from the loop.
Free for one endpoint, no card. The quick start is the five-minute version, and /woocommerce covers what the integration does once it is running.