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.
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
What You Need Before Starting
- Webhook Actions plugin — installed and active
Search for FlowSystems in Plugins → Add Plugin. One result. Install and activate.
- 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.
- API token with
fullscopeGo 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"
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.
"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" }] } }
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 } ] } }
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" }
args.1.total exceeds 999 as a number, it queues a delivery to your n8n endpoint. Orders under 999 are logged as skipped.args.1.total was 500.00, below the 999 threshold.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" } }
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.
Common questions always ask.
Don't see yours? Open an issue on GitHub or check the full reference in the API docs.