How the webhook URL works. It is a public address that records whatever is sent to it and
answers 200. An inbox keeps the 50 most recent requests, accepts bodies up to 256 KB, and is
deleted 24 hours after it was created — or sooner if you press Delete URL or our server
restarts, because requests are held in memory and never written to disk.
Anyone who has the URL can read what it received. Use sandbox and test-mode accounts, do not route production traffic or customer data through it, and delete it when you are done. Privacy details.
What is a WooCommerce webhook?
A WooCommerce webhook is an HTTP POST sent by the store's own WordPress site to a delivery URL when a chosen topic fires: order.created, order.updated, product.updated, customer.created, coupon.deleted. The body is the same JSON the WooCommerce REST API returns for that resource, in the API version selected on the webhook.
The sender is whatever server hosts the shop, not a central platform. That is the important difference from a hosted provider: delivery speed and reliability depend on that site's hosting and on its background job runner actually running.
What a WooCommerce webhook request looks like
The request line and headers as they reach your server, followed by the body. Ids, tokens and the signature are placeholders.
POST /your/webhook/path HTTP/1.1
Host: your-server.example
X-WC-Webhook-Topic: order.created
X-WC-Webhook-Resource: order
X-WC-Webhook-Event: created
X-WC-Webhook-Signature: 4T7XwV0bq0aC0mAaB3o1bBCn0zXb8c8o4zqk3mXl1yE=
X-WC-Webhook-ID: 3
X-WC-Webhook-Delivery-ID: 36e0a4e1f1a54b1e9a3f6c2f6f6c1c7b
X-WC-Webhook-Source: https://shop.example.com/
{
"id": 6735,
"status": "processing",
"currency": "USD",
"date_created": "2026-01-01T12:00:00",
"total": "59.90",
"total_tax": "0.00",
"customer_id": 12,
"payment_method": "stripe",
"payment_method_title": "Credit card",
"billing": {
"first_name": "Jon",
"last_name": "Doe",
"email": "jon@example.com",
"country": "US"
},
"shipping": {
"first_name": "Jon",
"last_name": "Doe",
"city": "New York",
"country": "US"
},
"line_items": [
{
"id": 315,
"name": "Short Sleeve T-Shirt",
"product_id": 93,
"quantity": 1,
"total": "49.90",
"sku": "TS-BLK-M"
}
],
"shipping_lines": [
{
"id": 316,
"method_title": "Flat rate",
"total": "10.00"
}
],
"meta_data": []
}
Money values are decimal strings, and date_created is in the site's timezone with a separate date_created_gmt beside it in a full payload. Plugins add their own entries to meta_data, so two stores rarely send identical shapes.
Headers WooCommerce sends
| Header | Example | What it is |
|---|---|---|
X-WC-Webhook-Topic | order.created | Resource and event joined with a dot. |
X-WC-Webhook-Resource | order | The resource half of the topic. |
X-WC-Webhook-Event | created | The event half of the topic. |
X-WC-Webhook-Signature | 4T7XwV0bq0aC0mAaB3o1bBCn0zXb8c8o4zqk3mXl1yE= | HMAC-SHA256 of the raw body, base64-encoded, keyed with the webhook's secret. |
X-WC-Webhook-ID | 3 | Id of the webhook in the store. |
X-WC-Webhook-Delivery-ID | 36e0a4e1f1a54b1e9a3f6c2f6f6c1c7b | Id of this delivery. |
X-WC-Webhook-Source | https://shop.example.com/ | URL of the store that sent it. |
Header names are case-insensitive. Your framework may show them lower-cased, and HTTP/2 always does.
How WooCommerce signs a webhook
Each webhook has a Secret field; left blank, WooCommerce fills it with the creating user's API consumer secret. The store computes an HMAC-SHA256 of the request body with that secret and sends it base64-encoded in X-WC-Webhook-Signature.
Compute it over the raw request body exactly as received — before any JSON parsing — and compare with a constant-time function. The digest is base64, not hex.
const crypto = require('crypto');
// rawBody: Buffer of the body exactly as received
function verifyWooCommerce(rawBody, header, secret) {
const expected = crypto.createHmac('sha256', secret)
.update(rawBody).digest('base64');
const a = Buffer.from(header || '');
const b = Buffer.from(expected);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
Delivery, failures and the ping
Deliveries are queued through Action Scheduler and sent in the background, so they depend on WP-Cron (or a real cron job) firing on the store. A quiet site with cron disabled can sit on a webhook for a long time.
WooCommerce counts consecutive failures — any response that is not a 2xx, 301 or 302 — and disables the webhook after five in a row. It does not retry the individual delivery. When a webhook is first saved as active, the store also sends a small form-encoded ping, webhook_id=…, with no signature worth checking; a strict handler that rejects it can get the webhook disabled before a real order ever arrives.
Catch a real WooCommerce webhook
- Press Create webhook URL at the top of this page and copy the address.
- In WordPress admin, open WooCommerce → Settings → Advanced → Webhooks and add a webhook.
- Set Status to Active, choose a topic such as Order created, paste the address as the Delivery URL, and save.
- The ping arrives straight away; then place a test order on a staging store.
- Read the headers and order JSON in the inbox above.
To run a caught request against your own code, select it and press Open in API Tester: the method, headers and body are loaded into the request console, where you enter your server's public URL and send it. A body you edit will no longer match its signature — that is the signature doing its job.
Things that catch people out
order.updatedis noisy. A single checkout can fire it several times as status, payment and stock change. Compare the fields you care about instead of reacting to every delivery.- Caching and security plugins interfere. A firewall plugin or host-level rule that blocks outbound requests produces failures that look like your endpoint's fault. The webhook's log entries under WooCommerce → Status → Logs show what the store saw.
- The secret can be someone's API key. If the Secret field was left empty, the signing secret is that user's consumer secret — rotating the API key silently changes your webhook signature.
api-tester.com is independent and is not affiliated with WooCommerce. Providers change header names, limits and retry schedules; treat the official WooCommerce documentation as the source of truth.
Related examples and tools
Mailchimp Webhook Example
Mailchimp audience webhooks are form posts with bracketed field names, and they carry no signature at all.
HubSpot Webhook Example
HubSpot delivers CRM events in batches: one request, a JSON array, each element a small notification that names a record without containing it.
Square Webhook Example
Square signs the notification URL together with the body, so a request that is perfectly genuine fails verification when your server disagrees about its own address.
Bitbucket Webhook Example
Bitbucket Cloud webhooks look like GitHub's at first glance and differ in the details that break copied code: the event name header, the signature header's name, and the shape of a push.
Jira Webhook Example
A Jira webhook delivers the whole issue plus a changelog of exactly what was edited.
Shopify Webhook Example
The hosted-platform equivalent: same base64 HMAC idea, very different delivery rules.
Curl POST JSON
Send a JSON body like this one from a terminal.
Webhook Tester
The inbox on its own page, plus a guide to signatures, retries and idempotent handlers.
All Webhook Examples
30 providers side by side: content type, signature scheme and retry behaviour.
Other webhooks signed with an HMAC of the body
References
- Official WooCommerce documentation — The provider's own guide to the requests described on this page.
- WooCommerce REST API: webhooks — The webhook resource, its properties and the API for managing it.
- RFC 2104: HMAC — The keyed-hash construction this signature is built on.
- Node.js: crypto.timingSafeEqual() — The constant-time comparison used in the code on this page.
These links leave api-tester.com, which is independent of the companies and projects they point to.
WooCommerce webhook FAQ
What does a WooCommerce webhook payload look like?
The same JSON the WooCommerce REST API returns for the resource — for order.created, a full order with id, status, totals, billing, shipping, line_items and meta_data.
How do I verify a WooCommerce webhook signature?
Compute an HMAC-SHA256 of the raw request body with the webhook's secret, base64-encode the digest and compare it to the X-WC-Webhook-Signature header in constant time.
Why did my WooCommerce webhook get disabled?
WooCommerce disables a webhook after five consecutive failed deliveries. Any response other than a 2xx, 301 or 302 counts as a failure.
What headers does WooCommerce send?
X-WC-Webhook-Topic, X-WC-Webhook-Resource, X-WC-Webhook-Event, X-WC-Webhook-Signature, X-WC-Webhook-ID, X-WC-Webhook-Delivery-ID and X-WC-Webhook-Source, with a User-Agent beginning WooCommerce/ and containing Hookshot.
Why are my WooCommerce webhooks delayed?
They are sent by a background queue that relies on WP-Cron. If cron is disabled or the site has little traffic, deliveries wait until the scheduler next runs.