WP Webhooks / Examples / WooCommerce Order → n8n
Example · WooCommerce + Claude Code

Ask Claude Code to Set Up a WooCommerce Webhook — It Figures Out the API

You describe what you want. Claude Code hits the REST API, reads the live payload structure, sets a condition — and catches its own field-path mistake before it ships.

~8 min read Apr 29, 2026
woocommerceclaude coden8n

TL;DR

  • Claude Code can set up a WooCommerce webhook via REST API — it discovers the endpoints, runs the API calls, and validates results without any manual steps
  • Condition field paths are validated at runtime only — Claude caught a wrong path by reading the live payload structure from a real order
  • The full setup: create the webhook, add a condition (event trigger + field path + comparison value + cast type), then verify against a live order
/ Prerequisites

What You Need Before Starting

This example uses Webhook Actions by Flow Systems — a free WordPress plugin that delivers webhooks from any WordPress hook, with retry, logging, conditions, and a full REST API.
  1. Webhook Actions plugin — installed and active

    Search for FlowSystems in Plugins → Add Plugin. One result. Install and activate.

  2. WooCommerce active with at least one completed order

    The example uses a captured payload from a real order. If you don't have one yet, manually mark any test order as Completed after the webhook is created — the plugin will capture the payload on first fire.

  3. API token with full scope

    Go to WordPress Admin → Webhook Actions → API Tokens → Add Token. Select scope full. Copy the token — it's shown only once.

    set token as shell variable

    export TOKEN="fswa_..."
    export SITE="https://your-wordpress-site.com"
/ The Session

Claude Code Sets It Up,
Step by Step

This is a real session. Claude Code ran these exact curl commands, got these exact responses, and caught its own mistake before we called it done. Every code block below is from the live API.

/ You said

"I have the Webhook Actions plugin installed on $SITE. The API docs are at wpwebhooks.org/webhook-wordpress-plugin-api/ and my token is in $TOKEN."

Create a webhook that fires when a WooCommerce order status switches to completed (woocommerce_order_status_completed) and sends order data to https://n8n.example.com/webhook/abc12345-demo-0000-0000-example00001 with auth header Bearer your-secret-token. Add a condition: only send if order total is more than 999.

Ohh, one more thing! Please don't spend too many tokens. We have already used 90% of daily limit, and still we need to build that Instagram-like app for agents. Maybe without UI — this needs to be considered though.

Two steps: create the webhook, then configure the condition via the schema endpoint. Starting with the webhook.

step 1 — create the webhook

curl -sk -X POST "$SITE/wp-json/fswa/v1/webhooks" \
  -H "X-FSWA-Token: $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "WooCommerce completed → n8n",
    "endpoint_url": "https://n8n.example.com/webhook/abc12345-demo-0000-0000-example00001",
    "auth_header": "Bearer your-secret-token",
    "is_enabled": true,
    "triggers": ["woocommerce_order_status_completed"]
  }'

response — 200 OK

{
  "id": "25",
  "name": "WooCommerce completed → n8n",
  "endpoint_url": "https://n8n.example.com/webhook/abc12345-demo-0000-0000-example00001",
  "auth_header": "Bearer your-secret-token",
  "is_enabled": true,
  "triggers": ["woocommerce_order_status_completed"],
  "conditions": null,
  "created_at": "2026-04-29 17:31:14"
}

Webhook ID 25 created and enabled. Now the condition. The hook woocommerce_order_status_completed passes the order ID as its first argument — so I'll target args.0.total for the order total.

step 2 — add the condition (first attempt)

curl -sk -X PUT \
  "$SITE/wp-json/fswa/v1/schemas/webhook/25/trigger/woocommerce_order_status_completed" \
  -H "X-FSWA-Token: $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "conditions": {
      "enabled": true,
      "type": "and",
      "rules": [{
        "field": "args.0.total",
        "operator": "greater_than",
        "value": "999",
        "cast": "number"
      }]
    }
  }'

response — 200 OK

{
  "id": "15",
  "webhook_id": "25",
  "conditions": {
    "enabled": true,
    "type": "and",
    "rules": [{
      "field": "args.0.total",
      "operator": "greater_than",
      "value": "999",
      "cast": "number"
    }]
  }
}
Wait. The API accepted this without complaint — but it doesn't validate field paths. A path is stored as-is and evaluated at runtime when a real order fires. Before declaring this done, let me verify the path is actually correct by pulling the captured payload from another webhook already firing on this same trigger.

step 3 — check the payload structure from an existing webhook

# Webhook 24 already fires on woocommerce_order_status_completed
# and has a captured example payload we can inspect
curl -sk \
  "$SITE/wp-json/fswa/v1/schemas/webhook/24/trigger/woocommerce_order_status_completed" \
  -H "X-FSWA-Token: $TOKEN"

response — example_payload (relevant excerpt)

{
  "example_payload": {
    "hook": "woocommerce_order_status_completed",
    "args": [
      27,              ← args[0]: the order ID integer, not the order object
      {
        "__type": "Automattic\WooCommerce\Admin\Overrides\Order",
        "id": 27,
        "status": "completed",
        "total": "1000.00",  ← args[1].total is the order total
        "currency": "USD"
      },
      {
        "from": "on-hold",
        "to": "completed",
        "manual": true        ← args[2]: status transition metadata
      }
    ]
  }
}
Found it. args[0] is 27 — a plain integer. That's the raw $order_id WooCommerce passes to the hook. You can't traverse .total on an integer — the condition would silently never match. The serialized order object is at args[1]. Fixing the path now.

step 4 — fix the condition (correct field path)

curl -sk -X PUT \
  "$SITE/wp-json/fswa/v1/schemas/webhook/25/trigger/woocommerce_order_status_completed" \
  -H "X-FSWA-Token: $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "conditions": {
      "enabled": true,
      "type": "and",
      "rules": [{
        "field": "args.1.total",
        "operator": "greater_than",
        "value": "999",
        "cast": "number"
      }]
    }
  }'

response — 200 OK

{
  "id": "15",
  "webhook_id": "25",
  "conditions": {
    "enabled": true,
    "type": "and",
    "rules": [{
      "field": "args.1.total",
      "operator": "greater_than",
      "value": "999",
      "cast": "number"
    }]
  },
  "updated_at": "2026-04-29 17:34:45"
}
Done. Webhook 25 is live. Every time a WooCommerce order is marked completed, the plugin evaluates the condition — if args.1.total exceeds 999 as a number, it queues a delivery to your n8n endpoint. Orders under 999 are logged as skipped.
The plugin logs skipped deliveries with the exact condition that failed — here, args.1.total was 500.00, below the 999 threshold.
/ Payload

What Arrives at n8n

Here is the full payload your n8n webhook receives — captured from a real order on this site. The order total is 1000.00, just above the 999 threshold, so this event would pass the condition and be delivered.

Access order fields in n8n via body.args[1].total, body.args[1].billing.email, etc.

POST body — application/json

{
  "event": {
    "id": "3d22cae3-fdd7-467e-8c70-08618a96fc65",
    "timestamp": "2026-04-27T20:39:11Z",
    "version": "1.0"
  },
  "hook": "woocommerce_order_status_completed",
  "args": [
    27,
    {
      "__type": "Automattic\WooCommerce\Admin\Overrides\Order",
      "id": 27,
      "status": "completed",
      "total": "1000.00",
      "currency": "USD",
      "customer_id": 1,
      "payment_method": "bacs",
      "billing": {
        "first_name": "Jane",
        "last_name": "Smith",
        "email": "[email protected]",
        "phone": "+1 555 010 0200",
        "address_1": "123 Main Street",
        "city": "Austin",
        "postcode": "73301",
        "country": "US"
      },
      "line_items": {
        "1": {
          "name": "Shampoo Fa",
          "quantity": 2,
          "total": "1000",
          "product_id": 26
        }
      },
      "date_created": "2026-04-27T20:31:43+00:00",
      "date_completed": "2026-04-27T20:39:11+00:00"
    },
    {
      "from": "on-hold",
      "to": "completed",
      "manual": true
    }
  ],
  "timestamp": 1777322351,
  "site": {
    "url": "https://your-wordpress-site.com"
  }
}
/ Conditions

Why cast: "number" Matters

WooCommerce stores the order total as a string: "1000.00", not the number 1000.0. Without cast: "number", the greater_than operator would compare strings — and string comparison is alphabetical, not numeric. "999" would be alphabetically greater than "1000.00" because "9" > "1", so all four-digit orders would silently fail the condition.

With cast: "number", the plugin converts the field value to a float before comparison. 1000.0 > 999 — correct.

The condition field path uses dot-notation into the delivery payload. For nested values, keep traversing: args.1.billing.country for the billing country. If a path segment doesn't exist, the condition evaluates to false and the delivery is skipped.

How to find any field path: read GET /schemas/webhook/{id}/trigger/{trigger} after the first real event fires. The example_payload field in the response shows the exact structure — traverse it with dot-notation to build your condition.

/Notes
WordPress Webhook REST API — full reference for webhooks, schemas, conditions, and logs
Webhook Actions by Flow Systems — plugin overview
From do_action to HTTP — how hooks are captured and payloads are built
WooCommerce Webhooks with Action Scheduler — hook binding, payload mapping, conditional dispatch, and replay
FAQ

Common questions always ask.

Don't see yours? Open an issue on GitHub or check the full reference in the API docs.

Why didn't the API return an error when I used the wrong field path? +
The API stores whatever field path you give it without validation. The path is evaluated at runtime when a real order fires the hook and the plugin resolves the dot-notation against the actual payload. If the path doesn't resolve to a value, the condition fails silently and the delivery is skipped.
What does cast: number do in a webhook condition? +
WooCommerce stores the order total as a string — '1000.00' rather than the number 1000.00. Without cast: number, a greater_than comparison would compare strings, not numbers, producing unreliable results. cast: number converts the field value to a float before comparison.
How do I find the correct field path for a trigger I haven't used before? +
Create a test webhook on the same trigger with no conditions, wait for a real event to fire it or trigger one manually, then read the captured example payload from GET /schemas/webhook/{id}/trigger/{trigger}. The example_payload field shows the exact structure — use dot-notation to navigate into it.
What happens to completed orders with a total under 999? +
They are logged as skipped. No delivery attempt is made and no queue job is added, but a log entry with a skipped status is created. Only orders where args.1.total exceeds 999 trigger a delivery.
Can I add more conditions to the same webhook trigger? +
On the free tier you can have one simple rule with AND logic. The Pro plan supports unlimited rules with nested AND/OR groups, so you could combine conditions like order total > 999 AND payment_method equals stripe.
Ready

Stop losing webhooks.
Start logging them.

$ wp plugin install wp-webhooks --activate