Cosmovex Tools

WooCommerce webhook missing or signature wrong? Check X-WC-Webhook-Signature here

WooCommerce with Hookwatch Webhook Tester

This inbox is set up as a WooCommerce webhook Delivery URL. Save the webhook and the ping WooCommerce sends appears first; place a test order and the order.created delivery follows with X-WC-Webhook-Topic, Resource, Event, Source and Signature headers. The Signature tab is already configured for WooCommerce: base64 HMAC-SHA256 of the body with your webhook secret.

  1. Copy the inbox URL. It ends in /woocommerce so store deliveries stand out.
  2. In WordPress go to WooCommerce → Settings → Advanced → Webhooks → Add webhook. Set Status to Active, pick a Topic such as Order created, paste the URL as Delivery URL and note the Secret.
  3. Save. The first request in the list is the ping: a form post with webhook_id=<id> and no signature header. That is expected.
  4. Create a test order, or press Send a signed test WooCommerce event here. Select the request: the Signature tab is set to X-WC-Webhook-Signature, HMAC-SHA256, base64.
  5. Paste the Secret. Valid confirms your secret; if your PHP or Node handler still rejects it, it is hashing a decoded or re-encoded body instead of the raw JSON.
woocommerce webhook signature woocommerce webhook testerwoocommerce webhook secretx-wc-webhook-signaturewoocommerce order created webhookwoocommerce webhook payload
Open Hookwatch Webhook Tester Free · Pro $5/mo · no account

What to know

WooCommerce signs each delivery with HMAC-SHA256 over the JSON request body, keyed with the webhook's Secret, and sends the raw digest base64-encoded in X-WC-Webhook-Signature. In PHP the matching check is base64_encode(hash_hmac('sha256', $raw, $secret, true)) compared with hash_equals; note the true argument, which returns raw bytes before base64. Forgetting it base64-encodes the hex string instead and never matches. In WordPress read the body with file_get_contents('php://input') or $request->get_body() before decoding it.

Alongside the signature each delivery carries X-WC-Webhook-Topic (order.created), X-WC-Webhook-Resource (order), X-WC-Webhook-Event (created), X-WC-Webhook-Source (the store home URL), X-WC-Webhook-ID and X-WC-Webhook-Delivery-ID. The first request after saving an active webhook is different: a ping with the form body webhook_id=<id>, no X-WC-Webhook-* headers and no signature. Handlers that reject unsigned requests should return 200 for it anyway, because WooCommerce only accepts the URL when the ping gets a 200.

If deliveries never arrive, check how they are scheduled. WooCommerce queues webhook deliveries through Action Scheduler by default, so they depend on WP-Cron or a real cron hitting the site; on a quiet staging site they can lag until the next page load. WooCommerce → Status → Scheduled Actions lists pending woocommerce_deliver_webhook_async jobs and their logs. A webhook is disabled automatically after more than five consecutive failed deliveries; any response outside 200–302 counts as a failure.

The body is the same JSON as the REST API resource for the version selected in the webhook's API version field, so an order.created payload includes line_items, billing, shipping, meta_data and totals as strings like "58.00". order.updated fires whenever the order is saved, including status changes, so expect several deliveries per order; open them one after another here, or use the Compare view (Pro) to see which fields changed between two of them.

Updated · Cosmovex

Questions

Why does my WooCommerce webhook signature not match?

Usually the digest is built from hex instead of raw bytes, or from a decoded body. Use base64_encode(hash_hmac('sha256', $raw_body, $secret, true)) on the raw request body. If this page shows Valid with your Secret, the secret is right and your handler's body handling needs fixing.

Why is the first WooCommerce request unsigned?

It is the ping WooCommerce sends when you save an active webhook. Its body is webhook_id=<id>, form-encoded, without X-WC-Webhook-* headers or a signature. Answer it with 200; real deliveries that follow are signed.

Why did WooCommerce disable my webhook?

It had more than five consecutive failed deliveries. Anything other than a 200–302 response, or a timeout, counts as a failure. Fix the endpoint, set the webhook back to Active and save.

Why are WooCommerce webhooks delayed?

Deliveries are queued with Action Scheduler and run when WP-Cron runs. On low-traffic sites that can take minutes. Check WooCommerce → Status → Scheduled Actions for pending delivery jobs.

Is the webhook Secret sent to your server?

No. The HMAC is computed in your browser with WebCrypto and the Secret stays in this browser's storage. The delivery itself, which can contain customer details, is stored on our server so it appears in your inbox; delete it when you are done.

The free plan covers everything on this page. Hookwatch Webhook Tester Pro ($5/mo, billed monthly) is described on the Hookwatch Webhook Tester page.