WooCommerce Webhook: Example Payload, Headers and Signature

What a WooCommerce store posts when an order, product or customer changes — the X-WC-Webhook-* headers, the REST-shaped JSON, and the rule that switches a failing webhook off.

No webhook URL yet. Creating one needs no account.

Request details
Requests sent to your webhook URL appear here: method, headers and the body exactly as it arrived.

The button posts the example below to your webhook URL from your browser. It is not sent by WooCommerce, it carries your browser's User-Agent, and its signature is a placeholder that will not verify. To see a real delivery, follow the steps further down.

Last updated . How this page is checked.

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

HeaderExampleWhat it is
X-WC-Webhook-Topicorder.createdResource and event joined with a dot.
X-WC-Webhook-ResourceorderThe resource half of the topic.
X-WC-Webhook-EventcreatedThe event half of the topic.
X-WC-Webhook-Signature4T7XwV0bq0aC0mAaB3o1bBCn0zXb8c8o4zqk3mXl1yE=HMAC-SHA256 of the raw body, base64-encoded, keyed with the webhook's secret.
X-WC-Webhook-ID3Id of the webhook in the store.
X-WC-Webhook-Delivery-ID36e0a4e1f1a54b1e9a3f6c2f6f6c1c7bId of this delivery.
X-WC-Webhook-Sourcehttps://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

  1. Press Create webhook URL at the top of this page and copy the address.
  2. In WordPress admin, open WooCommerce → Settings → Advanced → Webhooks and add a webhook.
  3. Set Status to Active, choose a topic such as Order created, paste the address as the Delivery URL, and save.
  4. The ping arrives straight away; then place a test order on a staging store.
  5. 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.updated is 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

Other webhooks signed with an HMAC of the body

References

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.