Dev Pattern Labs logo
Dev Pattern Labs Blog
WooCommerce 12 min readOct 14, 2026

WooCommerce Webhooks: Complete Setup and Integration Guide

Master WooCommerce webhooks from setup to integration. Learn webhook topics, payload structure, HMAC-SHA256 signature verification, building a Next.js receiver, and handling retries.

Dev Pattern Labs Author

Dev Pattern Labs

Peer-reviewed technical tutorial · Production tested

Webhook integration flow diagram on a whiteboard
Executive Summary & Key Insight

Master WooCommerce webhooks from setup to integration. Learn webhook topics, payload structure, HMAC-SHA256 signature verification, building a Next.js receiver, and handling retries. This practical guide covers step-by-step setup, real-world code snippets, security hardening, and common production edge cases.

Advertisement

WooCommerce powers a significant portion of the world's e-commerce stores, and one of its most powerful — yet frequently underutilized — features is its webhook system. Webhooks allow your WooCommerce store to push real-time event notifications to external services the moment something meaningful happens: a new order is placed, a product is updated, a customer registers, or a subscription renews. Instead of constantly polling your store's REST API for changes, webhooks let your applications react instantly, saving server resources and dramatically reducing latency.

If you've ever built a fulfillment integration, synced inventory to a warehouse management system, triggered transactional emails through a third-party provider, or fed order data into an analytics pipeline, you've already experienced the problem webhooks solve. Without them, you're left writing polling loops — fragile, expensive, and often delayed by minutes. With WooCommerce webhooks, your external systems become first-class observers of your store's event stream, receiving structured JSON payloads the moment state changes occur.

This guide is a complete end-to-end walkthrough. You will learn how to configure webhooks inside the WooCommerce admin panel, understand every available webhook topic, dissect the payload structure, verify incoming requests using HMAC-SHA256 signature headers, build a production-ready receiver endpoint in Next.js, and implement retry-safe handling. By the end, you'll have a solid foundation for any webhook-driven integration your project demands.


Prerequisites

Before diving in, make sure you have the following in place:

  • A WordPress installation with WooCommerce 3.x or later installed and active
  • Administrator access to the WooCommerce dashboard
  • Node.js 18+ and a Next.js 14+ project (App Router)
  • Basic familiarity with TypeScript and REST API concepts
  • A publicly accessible HTTPS URL for your receiver endpoint (use ngrok for local development)
  • Optional: a tool like Postman or curl for manual testing

1. What Are Webhooks and Why Use Them?

A webhook is an HTTP callback — your server registers a URL, and when a predefined event fires, the source system (WooCommerce in this case) sends an HTTP POST request to that URL with a JSON body describing the event. This is the "don't call us, we'll call you" pattern of API design.

Compared to REST polling, webhooks are:

DimensionREST PollingWebhooks
TriggerTime-based (interval)Event-based (real-time)
LatencyUp to poll interval (e.g., 60s)Near-instant (milliseconds)
Server loadHigh — repeated requestsLow — only on events
Missed eventsPossible if polling skipsHandled by retry logic
ImplementationSimple but wastefulRequires receiver endpoint
ScalabilityDegrades with volumeScales with event volume

WooCommerce webhooks are built directly into the core plugin (no extra extensions needed) and support a wide range of resource events across orders, products, customers, coupons, and more. Each outbound request is signed with an HMAC-SHA256 hash, giving your receiver the ability to cryptographically verify that the payload came from your store and was not tampered with in transit.


2. Setting Up Webhooks in WooCommerce Admin

Configuring a webhook takes less than two minutes in the WooCommerce dashboard:

  1. Log in to your WordPress admin panel.
  2. Navigate to WooCommerce → Settings → Advanced → Webhooks.
  3. Click Add webhook.
  4. Fill in the following fields:
    • Name — A descriptive label (e.g., Order Created → Fulfillment API).
    • Status — Set to Active when ready; use Paused during development.
    • Topic — The event to listen for (e.g., Order created).
    • Delivery URL — Your publicly reachable HTTPS endpoint.
    • Secret — A strong random string used to sign the payload (store this securely).
    • API Version — Use WP REST API Integration v3 for the most complete payloads.
  5. Click Save webhook.

WooCommerce will immediately send a ping event to your delivery URL to confirm it is reachable. Your endpoint must return a 2xx HTTP status within 10 seconds, or WooCommerce will consider the delivery a failure.

Tip: During local development, run ngrok http 3000 to expose your Next.js dev server and use the generated HTTPS URL as your delivery URL.


3. Available Webhook Topics

WooCommerce organizes webhook events into topics that follow the pattern {resource}.{action}. Below is the full reference table:

TopicTrigger Description
order.createdA new order is placed
order.updatedAn existing order is modified
order.deletedAn order is permanently deleted
order.restoredA trashed order is restored
product.createdA new product is published
product.updatedA product's data or stock changes
product.deletedA product is permanently deleted
product.restoredA trashed product is restored
customer.createdA new customer account is registered
customer.updatedA customer's profile is modified
customer.deletedA customer account is deleted
coupon.createdA new coupon code is created
coupon.updatedA coupon is modified
coupon.deletedA coupon is permanently deleted
action.woocommerce_checkout_order_processedCustom action hook fires at checkout

You can also register custom action topics by hooking into any WooCommerce or WordPress action using the action.{hook_name} format in the Topic field. This gives you granular control beyond the default resource events.


4. Understanding the Webhook Payload Structure

Every WooCommerce webhook delivers a standard HTTP POST request with the following headers:

Content-Type: application/json
X-WC-Webhook-Source: https://your-store.com/
X-WC-Webhook-Topic: order.created
X-WC-Webhook-Resource: order
X-WC-Webhook-Event: created
X-WC-Webhook-Signature: <base64-encoded HMAC-SHA256>
X-WC-Webhook-ID: 42
X-WC-Webhook-Delivery-ID: 1234567

The request body is a JSON object matching the WooCommerce REST API v3 response schema for the given resource. Here is an abbreviated order.created payload:

{
  "id": 1024,
  "status": "processing",
  "currency": "USD",
  "total": "149.99",
  "billing": {
    "first_name": "Jane",
    "last_name": "Doe",
    "email": "jane@example.com",
    "address_1": "123 Main St",
    "city": "Austin",
    "state": "TX",
    "postcode": "78701",
    "country": "US"
  },
  "line_items": [
    {
      "id": 1,
      "name": "Premium Widget",
      "product_id": 88,
      "quantity": 2,
      "total": "149.99"
    }
  ],
  "date_created": "2026-09-18T09:00:00",
  "meta_data": []
}

The payload schema is identical to what you would receive from a GET /wp-json/wc/v3/orders/{id} API call, so any existing WooCommerce REST client types you have will transfer directly.


5. Verifying the X-WC-Webhook-Signature Header with HMAC-SHA256

Security is non-negotiable. Before processing any webhook payload, you must verify the X-WC-Webhook-Signature header. WooCommerce computes this by applying HMAC-SHA256 over the raw request body using the webhook secret you configured, then base64-encoding the result.

Here is a reusable TypeScript utility for signature verification:

// lib/woocommerce/verify-webhook.ts
import { createHmac, timingSafeEqual } from "crypto";

/**
 * Verifies an incoming WooCommerce webhook signature.
 *
 * @param rawBody   - The raw, unparsed request body as a Buffer or string
 * @param signature - The value of the X-WC-Webhook-Signature header
 * @param secret    - The webhook secret configured in WooCommerce admin
 * @returns true if the signature is valid, false otherwise
 */
export function verifyWooCommerceWebhook(
  rawBody: Buffer | string,
  signature: string,
  secret: string
): boolean {
  if (!signature || !secret) return false;

  const body = typeof rawBody === "string" ? Buffer.from(rawBody, "utf-8") : rawBody;
  const expectedSignature = createHmac("sha256", secret)
    .update(body)
    .digest("base64");

  const expectedBuffer = Buffer.from(expectedSignature, "utf-8");
  const receivedBuffer = Buffer.from(signature, "utf-8");

  // Use timing-safe comparison to prevent timing attacks
  if (expectedBuffer.length !== receivedBuffer.length) return false;
  return timingSafeEqual(expectedBuffer, receivedBuffer);
}

Using timingSafeEqual from Node's built-in crypto module is critical — a naive string comparison (===) leaks timing information that can be exploited to forge signatures.


6. Building a Next.js Webhook Receiver Endpoint

With verification in place, let's build the actual API route. In Next.js 14 with the App Router, create the file at app/api/webhooks/woocommerce/route.ts.

First, define the TypeScript types for common webhook payloads:

// types/woocommerce-webhooks.ts

export type WooWebhookTopic =
  | "order.created"
  | "order.updated"
  | "order.deleted"
  | "product.created"
  | "product.updated"
  | "customer.created"
  | "customer.updated";

export interface WooOrderPayload {
  id: number;
  status: string;
  currency: string;
  total: string;
  billing: {
    first_name: string;
    last_name: string;
    email: string;
    address_1: string;
    city: string;
    state: string;
    postcode: string;
    country: string;
  };
  line_items: Array<{
    id: number;
    name: string;
    product_id: number;
    quantity: number;
    total: string;
  }>;
  date_created: string;
}

export interface WooProductPayload {
  id: number;
  name: string;
  status: string;
  stock_quantity: number | null;
  regular_price: string;
  sale_price: string;
  sku: string;
}

Now, build the route handler:

// app/api/webhooks/woocommerce/route.ts
import { NextRequest, NextResponse } from "next/server";
import { verifyWooCommerceWebhook } from "@/lib/woocommerce/verify-webhook";
import type { WooWebhookTopic, WooOrderPayload, WooProductPayload } from "@/types/woocommerce-webhooks";

const WOO_WEBHOOK_SECRET = process.env.WOO_WEBHOOK_SECRET ?? "";

export async function POST(request: NextRequest): Promise<NextResponse> {
  // 1. Read the raw body as a buffer BEFORE any parsing
  const rawBody = Buffer.from(await request.arrayBuffer());

  // 2. Extract signature and topic from headers
  const signature = request.headers.get("x-wc-webhook-signature") ?? "";
  const topic = request.headers.get("x-wc-webhook-topic") as WooWebhookTopic | null;
  const source = request.headers.get("x-wc-webhook-source") ?? "unknown";

  // 3. Verify the signature — reject immediately if invalid
  if (!verifyWooCommerceWebhook(rawBody, signature, WOO_WEBHOOK_SECRET)) {
    console.warn(`[WooWebhook] Invalid signature from ${source}`);
    return NextResponse.json({ error: "Invalid signature" }, { status: 401 });
  }

  // 4. Parse the verified body
  let payload: unknown;
  try {
    payload = JSON.parse(rawBody.toString("utf-8"));
  } catch {
    return NextResponse.json({ error: "Malformed JSON" }, { status: 400 });
  }

  console.log(`[WooWebhook] Received topic: ${topic} from ${source}`);

  // 5. Route to the appropriate handler
  try {
    switch (topic) {
      case "order.created":
        await handleOrderCreated(payload as WooOrderPayload);
        break;
      case "order.updated":
        await handleOrderUpdated(payload as WooOrderPayload);
        break;
      case "product.updated":
        await handleProductUpdated(payload as WooProductPayload);
        break;
      default:
        console.log(`[WooWebhook] Unhandled topic: ${topic}`);
    }
  } catch (err) {
    console.error(`[WooWebhook] Handler error for topic ${topic}:`, err);
    // Return 500 so WooCommerce will retry the delivery
    return NextResponse.json({ error: "Handler failed" }, { status: 500 });
  }

  // 6. Always return 200 once successfully processed
  return NextResponse.json({ received: true }, { status: 200 });
}

async function handleOrderCreated(order: WooOrderPayload): Promise<void> {
  console.log(`[WooWebhook] New order #${order.id} — ${order.billing.email} — $${order.total}`);
  // TODO: Send to fulfillment API, update inventory, trigger email, etc.
}

async function handleOrderUpdated(order: WooOrderPayload): Promise<void> {
  console.log(`[WooWebhook] Order #${order.id} updated — status: ${order.status}`);
  // TODO: Sync status to CRM or analytics platform
}

async function handleProductUpdated(product: WooProductPayload): Promise<void> {
  console.log(`[WooWebhook] Product #${product.id} updated — stock: ${product.stock_quantity}`);
  // TODO: Sync inventory to warehouse management system
}

Add this to your .env.local:

WOO_WEBHOOK_SECRET=your_strong_random_secret_here

Important: Next.js App Router routes parse the body automatically for most content types. Reading request.arrayBuffer() directly ensures you get the raw bytes that WooCommerce signed — never pass a pre-parsed body to your verification function.


7. Retry Logic and Failure Handling

WooCommerce has a built-in retry mechanism for failed webhook deliveries. Understanding it is essential for building resilient integrations:

  • If your endpoint returns a non-2xx status, or if the connection times out (> 10 seconds), WooCommerce marks the delivery as failed.
  • WooCommerce retries failed deliveries up to 5 times, with exponential back-off (approximately 1 minute, 2 minutes, 4 minutes, 8 minutes, 16 minutes).
  • After 5 consecutive failures, WooCommerce automatically disables the webhook and logs the failure under WooCommerce → Status → Logs.
  • The X-WC-Webhook-Delivery-ID header provides a unique ID per delivery attempt — log this for deduplication.

To make your handler idempotent (safe to call multiple times with the same payload), use the order or resource id from the payload as an idempotency key:

// lib/woocommerce/idempotency.ts
const processedDeliveries = new Set<string>();

/**
 * Returns true if this delivery has already been processed.
 * In production, replace the in-memory Set with a Redis key or database record.
 */
export function isDuplicateDelivery(deliveryId: string): boolean {
  if (processedDeliveries.has(deliveryId)) return true;
  processedDeliveries.add(deliveryId);
  return false;
}

In your route handler, add this check immediately after signature verification:

const deliveryId = request.headers.get("x-wc-webhook-delivery-id") ?? "";

if (isDuplicateDelivery(deliveryId)) {
  console.log(`[WooWebhook] Duplicate delivery ${deliveryId} — skipping`);
  return NextResponse.json({ received: true }, { status: 200 });
}

In production, back the deduplication store with Redis (e.g., using ioredis) and set a TTL of 24–48 hours on each key to avoid unbounded memory growth.


8. Testing Your Webhook Integration

Local testing with ngrok:

# Terminal 1 — start your Next.js dev server
npm run dev

# Terminal 2 — expose it publicly
ngrok http 3000

Copy the https:// forwarding URL from ngrok and paste it as the Delivery URL in your WooCommerce webhook settings.

Simulating a webhook with curl:

# Compute signature (replace MY_SECRET with your webhook secret)
BODY='{"id":1,"status":"processing","total":"99.00","billing":{"email":"test@example.com"}}'
SECRET="MY_SECRET"
SIG=$(echo -n "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -binary | base64)

curl -X POST https://your-ngrok-url/api/webhooks/woocommerce \
  -H "Content-Type: application/json" \
  -H "X-WC-Webhook-Topic: order.created" \
  -H "X-WC-Webhook-Signature: $SIG" \
  -H "X-WC-Webhook-Source: https://your-store.com/" \
  -H "X-WC-Webhook-Delivery-ID: test-001" \
  -d "$BODY"

Checking WooCommerce delivery logs:

Navigate to WooCommerce → Settings → Advanced → Webhooks, click your webhook, and scroll to the Recent Delivery section. Each entry shows the HTTP response code, response body, and delivery timestamp — invaluable for debugging.


Conclusion

WooCommerce webhooks are a powerful, built-in mechanism for building real-time integrations without the overhead of polling. Here are the key takeaways from this guide:

  1. Configure webhooks in WooCommerce admin — Set a strong secret, choose the right topic, and provide an HTTPS delivery URL.
  2. Know your topics — WooCommerce covers orders, products, customers, and coupons, and supports custom action hooks for advanced use cases.
  3. Always verify the signature — Use HMAC-SHA256 with timingSafeEqual to authenticate every incoming request before processing.
  4. Read the raw body first — Parse after verification, never before, to ensure the signature check operates on the exact bytes WooCommerce signed.
  5. Return 2xx immediately — Complete any heavy processing asynchronously (via a queue) and respond within 10 seconds to avoid timeouts.
  6. Implement idempotency — Use the Delivery ID for deduplication to handle WooCommerce's built-in retries gracefully.
  7. Monitor delivery logs — WooCommerce disables webhooks after 5 failures; set up alerting so you catch failures before they silently break your integration.

With these patterns in place, you have a production-ready foundation for any WooCommerce webhook-driven integration — whether it's order fulfillment, inventory synchronization, CRM updates, or real-time analytics pipelines.

Tags:#WooCommerce#Webhooks#Next.js#Integration
Dev Pattern Labs Engineering Team

Written by Dev Pattern LabsCore Engineering

Dev Pattern Labs is an engineering lab and digital consultancy specializing in custom Next.js web applications, WooCommerce REST API integrations, and workflow automation for growing businesses.

Advertisement

Recommended Companion Articles

E-commerce dashboard showing orders and inventory management
WooCommerce

WooCommerce REST API: Managing Orders and Inventory

Learn to manage WooCommerce orders and inventory programmatically via the REST API. Covers listing orders, updating status, adding notes, processing refunds, and batch stock management.

11 min read