WooCommerce Webhooks + Next.js: Real-Time Order and Inventory Sync
WooCommerce can push real-time notifications to your Next.js frontend every time a product changes, an order is placed, or inventory levels shift. These notifications are called WooCommerce webhooks, and they are the missing piece that turns a polling-based headless architecture into a genuinely reactive one. Instead of waiting for your ISR timer to expire or manually purging caches, your storefront updates within seconds of a change in WordPress.
This guide covers the full implementation: configuring webhooks in WooCommerce, building a secure Next.js API route to receive them, verifying webhook signatures, triggering on-demand ISR revalidation, syncing inventory in real time, and debugging the issues that trip up most developers. If you have already read our WooCommerce REST API + Next.js integration guide, this is the natural next step.
TL;DR
What WooCommerce webhooks are (and why they matter for headless)
A webhook is an outbound HTTP request that WooCommerce sends to a URL you specify whenever a particular event happens. Think of it as the inverse of an API call: instead of your frontend asking WooCommerce "has anything changed?" every few seconds, WooCommerce tells your frontend the moment something changes.
In a traditional WooCommerce setup, this is irrelevant — the PHP frontend renders data directly from the database on every page load. But in a headless WooCommerce architecture, your Next.js frontend is decoupled from WordPress. It fetches data via the REST API and caches it (via ISR, static generation, or an edge cache). Without webhooks, your storefront only updates when the cache expires — which could be 60 seconds, 5 minutes, or longer.
Webhooks eliminate that delay. A store manager updates a product price in WooCommerce admin, and the webhook fires immediately, hitting your Next.js API route, which triggers on-demand revalidation for that specific product page. The customer sees the correct price within seconds.
<2s
Storefront update after webhook-triggered revalidation
60-300s
Typical ISR revalidation interval without webhooks
30+
Webhook topics available in WooCommerce
Available webhook topics
WooCommerce organises webhooks by topic — the combination of a resource and an action. You choose which events trigger notifications. Here are the topics most relevant to a headless Next.js storefront:
- product.created — a new product is published
- product.updated — product data changes (price, description, images, stock)
- product.deleted — a product is moved to the bin or permanently deleted
- order.created — a new order is placed (useful for real-time dashboards)
- order.updated — order status changes (processing, completed, refunded)
- coupon.created / coupon.updated — discount codes change
- customer.created — a new customer registers
- action.woocommerce_product_set_stock — inventory level changes directly
For most headless storefronts, product.updated and product.created are the essential ones. These cover price changes, stock status updates, new products going live, and any edit made in the WooCommerce product editor. If you are building a real-time order dashboard, add order.created and order.updated.
Custom webhook topics
woocommerce_product_set_stock), you can create a webhook with the topic action.woocommerce_product_set_stock to receive notifications when inventory changes via bulk import tools or third-party integrations that bypass the standard product update flow.Configuring webhooks in WooCommerce admin
Setting up a webhook takes two minutes in the WordPress dashboard. Go to WooCommerce → Settings → Advanced → Webhooks and click Add webhook.
Fill in the following fields:
- Name: A descriptive label (e.g. "Product updates → Next.js")
- Status: Active
- Topic: Select from the dropdown (e.g. "Product updated") or choose "Action" for custom topics
- Delivery URL: Your Next.js API endpoint (e.g.
https://yourstore.com/api/webhooks/woocommerce) - Secret: A strong random string used to sign payloads (generate one with
openssl rand -hex 32) - API version: WC API v3 (the latest stable version)
Save the webhook. WooCommerce will immediately send a ping request to your delivery URL to verify it is reachable. If the ping fails, WooCommerce marks the webhook as inactive. Your endpoint must respond with a 200 status code.
Your endpoint must be publicly reachable
localhost:3000 will not work unless you use a tunnelling tool like ngrok during development. For production, your Next.js app must have a public URL with a valid SSL certificate — WooCommerce rejects non-HTTPS delivery URLs by default.Building the Next.js API route
Your Next.js application needs an API route that receives the webhook POST request, verifies the signature, processes the payload, and responds with a 200 status. Here is the complete implementation using the App Router:
// app/api/webhooks/woocommerce/route.js
import crypto from 'crypto';
import { revalidatePath, revalidateTag } from 'next/cache';
const WEBHOOK_SECRET = process.env.WC_WEBHOOK_SECRET;
function verifySignature(payload, signature) {
const hash = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(payload, 'utf8')
.digest('base64');
return crypto.timingSafeEqual(
Buffer.from(hash),
Buffer.from(signature)
);
}
export async function POST(request) {
const rawBody = await request.text();
const signature = request.headers.get('x-wc-webhook-signature');
const topic = request.headers.get('x-wc-webhook-topic');
// Verify the webhook signature
if (!signature || !verifySignature(rawBody, signature)) {
return new Response('Invalid signature', { status: 401 });
}
const payload = JSON.parse(rawBody);
// Handle different webhook topics
switch (topic) {
case 'product.updated':
case 'product.created':
// Revalidate the specific product page
revalidatePath(`/products/${payload.slug}`);
// Also revalidate collection/category pages
revalidateTag('products');
break;
case 'product.deleted':
revalidateTag('products');
break;
case 'order.created':
// Trigger inventory revalidation
revalidateTag('products');
break;
default:
// Unknown topic — log and accept
console.log(`Unhandled webhook topic: ${topic}`);
}
return new Response('OK', { status: 200 });
}Add the webhook secret to your .env.local file. This must match the secret you entered in WooCommerce admin:
WC_WEBHOOK_SECRET=your_64_character_hex_secret_hereNever skip signature verification
Signature verification in detail
WooCommerce signs every webhook payload using HMAC-SHA256. The process works as follows: WooCommerce takes the raw JSON payload body, hashes it with your webhook secret using the SHA-256 algorithm, Base64-encodes the result, and sends it in the X-WC-Webhook-Signature header.
Your API route must reproduce this process exactly. Take the raw request body (as a UTF-8 string, not a parsed object), hash it with the same secret, and compare the result to the header value. Use crypto.timingSafeEqual for the comparison — a standard === check is vulnerable to timing attacks that can leak the secret.
A common mistake is parsing the body as JSON first and then stringifying it for the hash. JSON serialisation is not guaranteed to produce the same string — key ordering and whitespace may differ. Always hash the raw request body exactly as received.
Triggering ISR revalidation with webhooks
On-demand revalidation is the killer feature that makes webhooks essential for headless WooCommerce. Next.js provides two revalidation functions: revalidatePath (invalidates a specific URL path) and revalidateTag (invalidates all pages that used a specific cache tag when fetching data).
Path-based revalidation
When a single product changes, revalidate its specific page. The webhook payload includes the product slug, so you can target precisely:
revalidatePath(`/products/${payload.slug}`);This tells Next.js to regenerate that one page on the next request. Every other page remains cached. For a store with 5,000 products, this is dramatically more efficient than rebuilding the entire site.
Tag-based revalidation
For changes that affect multiple pages (a new product, a deleted product, or a category reassignment), use cache tags. When you fetch data in your server components, tag the requests:
// In your server component or data-fetching function
const res = await fetch(apiUrl, {
next: { tags: ['products'] }
});Then in your webhook handler, invalidate the tag:
revalidateTag('products');Every page that fetched data with the products tag will regenerate on the next request. This covers product listing pages, category pages, search results, and any component that displays product data.
1 page
Revalidated with revalidatePath (targeted)
All tagged
Revalidated with revalidateTag (broad)
0
Full rebuilds needed with on-demand ISR
Inventory sync strategies
Inventory is the most time-sensitive data in any e-commerce store. Showing a product as "in stock" when it has sold out leads to failed orders, customer frustration, and wasted ad spend. Webhooks make real-time inventory sync achievable.
Strategy 1: Revalidate on product.updated
The simplest approach. Every stock change triggers a product.updated webhook because WooCommerce updates the product's stock_quantity and stock_status fields. Your webhook handler revalidates the product page, and the next visitor sees the correct stock level.
Strategy 2: Custom action for stock changes
For stores with high order volume, product updates fire frequently for non-stock reasons (description edits, image changes). To target stock changes specifically, create a webhook with the topic action.woocommerce_product_set_stock. This fires only when inventory levels change — whether from orders, manual edits, or bulk imports via the WooCommerce REST API.
Strategy 3: Edge-side stock validation
For stores where stock accuracy is critical (limited editions, flash sales), combine webhooks with a client-side stock check. Use webhooks to keep the rendered page current, but also make a lightweight API call when the customer clicks "Add to cart" to confirm the item is still available. This prevents race conditions where two customers see the same cached page showing one item in stock.
Webhook + client check = best of both worlds
Handling webhook failures and retries
Webhooks are not guaranteed to succeed on the first attempt. Network issues, server restarts, deployment windows, and rate limits can all cause delivery failures. WooCommerce handles this with an automatic retry mechanism.
When a webhook delivery fails (non-2xx response or timeout), WooCommerce retries up to 5 times with increasing delays. After 5 consecutive failures, WooCommerce automatically disables the webhook and marks it as "Disabled" in the admin panel. You will need to manually re-enable it after fixing the underlying issue.
- Respond with 200 quickly — process heavy work asynchronously if needed
- Log incoming webhook payloads for debugging failed deliveries
- Monitor webhook status in WooCommerce → Settings → Advanced → Webhooks
- Set up uptime monitoring on your webhook endpoint URL
- Use a queue (Redis, SQS) for processing if webhook volume is high
- Implement idempotency — the same webhook may be delivered more than once
Idempotency deserves emphasis. If WooCommerce retries a webhook because it did not receive a timely 200 response (but your endpoint did process it), you will receive the same payload twice. Your handler must cope with this gracefully. For ISR revalidation this is harmless — revalidating the same path twice is a no-op. But if your webhook handler triggers emails, writes to a database, or calls external APIs, you need to deduplicate based on the webhook delivery ID or payload content.
Debugging common webhook issues
Webhook debugging is notoriously frustrating because the request originates from your WordPress server, not from your browser. You cannot see it in browser DevTools. Here are the most common problems and their solutions.
Webhook delivery fails immediately
Check whether your Next.js endpoint is publicly accessible. Hit the URL directly in your browser — you should get a 405 (Method Not Allowed) because it only accepts POST. If you get a 404, the route file is in the wrong location. If you get a connection timeout, check your DNS, firewall rules, and whether your hosting provider blocks incoming requests to API routes.
SSL certificate errors
WooCommerce validates SSL certificates by default. If your Next.js app is behind a self-signed certificate, an expired certificate, or a certificate with a hostname mismatch, webhook delivery will fail silently. Check the WooCommerce webhook log (in the database's wc_webhooks table) for SSL-related error messages. During development, use ngrok which provides a valid SSL certificate automatically.
Signature verification fails
If your endpoint returns 401 on every webhook, the signature verification is failing. Common causes: the secret in your .env.local does not match the secret in WooCommerce admin, you are hashing the parsed JSON instead of the raw body, or your hosting provider is modifying the request body (some WAFs and proxies do this). Log both the received signature and your computed hash to compare them.
Webhooks fire but pages do not update
If the webhook is received and processed successfully but the page content does not change, the issue is usually with revalidation paths. Verify that the path you pass to revalidatePath matches the actual URL of your product page, including any prefix (e.g. /products/ vs /shop/). Also confirm that your data-fetching functions use the next: { tags } option if you are relying on revalidateTag.
Firewall and WAF gotchas
X-WC-Webhook-Signature header must arrive intact for verification to succeed.Development workflow with ngrok
Testing webhooks locally requires exposing your development server to the internet. The simplest tool for this is ngrok. Run your Next.js dev server on port 3000, then start an ngrok tunnel:
ngrok http 3000Ngrok gives you a public HTTPS URL (e.g. https://abc123.ngrok-free.app). Use this as your webhook delivery URL in WooCommerce during development. Every webhook delivery appears in the ngrok dashboard, showing the full request and response — which makes debugging significantly easier than reading server logs.
Remember to update the delivery URL to your production domain before going live. A common mistake is leaving the ngrok URL in place after the tunnel expires, which silently breaks all webhook deliveries.
Security best practices
Your webhook endpoint is a publicly accessible URL that accepts POST requests and triggers server-side actions. It is an attack surface. Treat it with the same seriousness as any authentication endpoint.
- Always verify the HMAC-SHA256 signature before processing any payload
- Use a cryptographically strong secret (64+ hex characters)
- Never log the full webhook secret — log only the first 8 characters for debugging
- Rate-limit the endpoint to prevent denial-of-service via flood
- Validate the payload structure before acting on it
- Use environment variables for secrets — never hardcode them
- Rotate webhook secrets periodically (update both WooCommerce and Next.js)
- Return generic error messages — do not leak internal state to callers
If you are running your WooCommerce backend on a known IP address, add an IP allowlist to your webhook route as an additional layer. This is particularly relevant for stores on dedicated hosting where the server IP is static. For guidance on backend infrastructure, see our WooCommerce hosting strategy guide.
Webhooks and headless SEO
Webhooks have a direct impact on headless WooCommerce SEO. Search engines crawl your product pages and index the content they find. If a product's price changes in WooCommerce but your Next.js page still shows the old price (because ISR has not revalidated yet), Google may index stale data. Worse, if your structured data (JSON-LD Product schema) shows a different price than the visible page content, Google may drop your rich snippets.
Webhook-triggered revalidation ensures that crawlers always see current data. When Google visits a product page, it gets the latest price, stock status, and availability — because the page was revalidated within seconds of the change, not minutes or hours later.
Putting it all together
A complete WooCommerce webhook + Next.js implementation involves three webhooks (product created, updated, deleted), one secured API route with signature verification, and targeted ISR revalidation. The total code is under 60 lines. The operational benefit is enormous: your headless storefront stays in sync with WooCommerce in real time, without polling, without manual cache purges, and without waiting for timer-based revalidation.
If you are planning a migration to headless WooCommerce, webhooks should be part of your architecture from day one — not bolted on later. They are the difference between a headless store that feels static and one that feels alive.
WPBundle includes webhook integration out of the box. Product updates, inventory changes, and order events automatically trigger revalidation across your storefront — no manual configuration required. The webhook endpoint, signature verification, and revalidation logic are all pre-built, so you can focus on your store rather than infrastructure plumbing.
Ready to go headless?
Join the WPBundle waitlist and get beta access completely free.
Join the Waitlist