WooCommerce webhooks: order topics, headers and signature checks

To test a WooCommerce webhook, set its Delivery URL to a free webhooks.sh bin (no signup, 48 hours, 100 requests a day) and each order or product payload shows up live with its X-WC-Webhook headers. WooCommerce waits up to 60 seconds per delivery and disables it after more than 5 failures in a row.

A WooCommerce webhook POSTs the same JSON the REST API returns for the order, product, customer or coupon, plus X-WC-Webhook headers, to the Delivery URL. Pointing that Delivery URL at a free bin and placing a test order shows you the exact request before you wire it to your handler. This page covers setting up a test webhook, the unsigned save ping, delivery headers, topics, X-WC-Webhook-Signature verification, and common reasons deliveries go missing.

Point a webhook at a free bin

  1. Create a free bin in the browser at view.webhooks.page or run curl -X POST https://webhooks.sh/v1/bins to get an ingest_url and view_url.
  2. In your WordPress admin, go to WooCommerce > Settings > Advanced > Webhooks > Add webhook.
  3. Set Status to Active, Topic to Order created, the bin's ingest URL as the Delivery URL (for example https://<bin>.webhooks.page/woocommerce), enter a Secret, and leave API Version on WP REST API Integration v3.
  4. Save the webhook, and watch the initial save ping show up in the bin viewer.
  5. Place a test order in your store and watch the order JSON arrive.

You can also create the same webhook through the REST API at /wp-json/wc/v3/webhooks.

# 1. Create a free bin (or open view.webhooks.page)
curl -X POST https://webhooks.sh/v1/bins

# 2. Or create the webhook with the REST API
curl -X POST https://<store>/wp-json/wc/v3/webhooks \
  -u <consumer_key>:<consumer_secret> \
  -H "Content-Type: application/json" \
  -d '{"name": "Order created to bin",
       "topic": "order.created",
       "delivery_url": "https://<bin>.webhooks.page/woocommerce",
       "secret": "<secret>"}'

The save ping and the delivery headers

The first time you save a webhook with Status set to Active, WooCommerce sends a ping to the Delivery URL: a POST request with the body webhook_id=<id> and only a user agent, so it carries no X-WC-Webhook headers and no signature. If your URL doesn't answer with a 2xx status code, the admin shows Error: Delivery URL returned response code: <code>, so your handler needs to let this unsigned ping through and answer it with a 200.

Real deliveries are application/json POST requests sent with the user agent WooCommerce/<version> Hookshot (WordPress/<version>). Their body matches the REST API response for that resource in the API Version you picked (WP REST API Integration v3 by default, or v2 or v1; the deprecated Legacy API v3 option only appears when the legacy REST API is available).

HeaderExampleUse it for
X-WC-Webhook-Sourcehttps://example.com/Which store sent it
X-WC-Webhook-Topicorder.createdRouting in your handler
X-WC-Webhook-ResourceorderResource type
X-WC-Webhook-EventcreatedEvent type
X-WC-Webhook-SignatureBase64 HMAC-SHA256Verifying the sender
X-WC-Webhook-ID42The webhook's ID
X-WC-Webhook-Delivery-IDA hashMatching log entries

Topics: orders, products and custom actions

  • The core topics are created, updated and deleted for coupons, customers, orders and products, plus coupon.restored, order.restored, product.restored and product.published.
  • The Action topic fires on a WordPress action hook you name, such as action.woocommerce_add_to_cart, and its payload holds the hook name and the hook's first argument.
  • Plugins can register custom topics using the woocommerce_webhook_topic_hooks filter.

Verify X-WC-Webhook-Signature

The X-WC-Webhook-Signature header is the base64-encoded HMAC-SHA256 hash of the raw request body, keyed with the webhook's Secret (if you leave Secret blank in the admin, WooCommerce saves a randomly generated secret, which then shows in the Secret field). Read the raw body and verify the signature before parsing the JSON, compare the digests in constant time, and remember to let the unsigned webhook_id=<id> save ping through on your endpoint.

Since X-WC-Webhook-Signature stays visible in the webhooks.sh bin viewer, you can copy a captured payload and signature from your bin into the HMAC signature verifier (set to Custom HMAC, SHA-256, and base64) to check that your secret produces the same hash.

<?php
$payload   = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_WC_WEBHOOK_SIGNATURE'] ?? '';
$expected  = base64_encode(
    hash_hmac('sha256', $payload, WC_WEBHOOK_SECRET, true)
);

if (!hash_equals($expected, $signature)) {
    http_response_code(401);
    exit;
}

Why deliveries go missing

  • Deliveries run in the background by default as Action Scheduler jobs in the woocommerce-webhooks group and only go out when the queue runs, and if a resource already has a delivery pending for that webhook, another change doesn't queue a second one.
  • WooCommerce counts 301 and 302 responses as successful deliveries but doesn't follow redirects, so a Delivery URL that redirects (like http to https, or a missing trailing slash) drops the payload while WooCommerce counts it as delivered.
  • On a free bin, request bodies over 100KB return 413 body_too_large and requests past 100 in a day return 429 daily_cap_reached, and WooCommerce counts both responses as failed deliveries.

You can check each request and response in WooCommerce > Status > Logs under the source webhooks-delivery, or walk through our webhook not received guide if nothing hits your endpoint.

RuleValue
Response timeout60 seconds
Counts as success2xx, 301 or 302
Redirects followedNone
Disabled afterMore than 5 consecutive failures
Ping on first Active savePOST body webhook_id=<id>, unsigned
Default API VersionWP REST API Integration v3

Keep the URL or move to localhost

Free bins expire after 48 hours, and if a bin expires while your webhook is still Active, deliveries will fail and WooCommerce will disable the webhook after more than 5 failures in a row. Pause or delete the test webhook when you're done, or claim the bin with a free Google sign-in before it expires to keep the same URL and 30 days of rolling history. If you need a persistent endpoint on webhooks.host, Solo ($12 a month, see pricing) gives you 250,000 requests a month, 5MB bodies, and the wsh CLI to relay live deliveries straight to a handler on localhost.

FAQ

Where are webhooks in WooCommerce?

You manage them in the WordPress admin at WooCommerce > Settings > Advanced > Webhooks, or through the REST API at /wp-json/wc/v3/webhooks using a consumer key and consumer secret.

Why was my WooCommerce webhook disabled?

WooCommerce sets a webhook's Status to Disabled after more than 5 consecutive failed deliveries, meaning any response other than 2xx, 301 or 302. Fix the issue on your endpoint, then edit the webhook and set Status back to Active.

How do I verify a WooCommerce webhook signature?

Compute the base64-encoded HMAC-SHA256 hash of the raw request body using the webhook's Secret (for example base64_encode(hash_hmac('sha256', $payload, $secret, true)) in PHP) and compare it to the X-WC-Webhook-Signature header.

What is the webhook_id request WooCommerce sends?

That webhook_id=<id> POST is the unsigned ping WooCommerce sends the first time you save a webhook with Status set to Active. Have your endpoint answer it with an HTTP 200 so the admin saves the webhook without an error.

Why is my WooCommerce webhook delayed?

By default, WooCommerce queues webhook deliveries in the background as Action Scheduler jobs in the woocommerce-webhooks group, so they only go out when the queue runs. You can switch to immediate delivery with the woocommerce_webhook_deliver_async filter.

Which API version should a WooCommerce webhook use?

Use WP REST API Integration v3, which is the default for new webhooks. The webhook payload matches the REST API response for whichever version (v3, v2 or v1) you select.

Last updated

Try it in 10 seconds.

Create a free bin at view.webhooks.page — no signup, no install. Point your sender at the URL and watch requests land live. Bins expire after 48 hours.