---
title: "Ask Claude Code to Set Up a WooCommerce Webhook"
description: "Claude Code sets up a WooCommerce order webhook with an order total condition — REST API calls, field-path correction, and live fix."
url: "https://wpwebhooks.org/examples/woocommerce-order-webhook-claude-code/"
date: "2026-04-29"
---

[WP Webhooks](https://wpwebhooks.org/) / [Examples](https://wpwebhooks.org/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](https://wpwebhooks.org/wordpress-webhook-plugin/) — a free WordPress plugin that delivers webhooks from any WordPress hook, with retry, logging, conditions, and a full REST API.

1.  1
    
    **Webhook Actions plugin — installed and active**
    
    Search for **FlowSystems** in **Plugins → Add Plugin**. One result. Install and activate.
    
2.  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.  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/](https://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. The n8n bearer secret already sits in the Credentials Vault as credential `3` — the webhook references it via `auth_credential_id` (preferred over the legacy plaintext `auth_header`), and an agent-scope token can assign it but never read it. 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_credential_id": 3,
    "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_credential_id": 3,
  "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": "jane.smith@example.com",
        "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](https://wpwebhooks.org/webhook-wordpress-plugin-api/) — full reference for webhooks, schemas, conditions, and logs

→ [Webhook Actions by Flow Systems](https://wpwebhooks.org/wordpress-webhook-plugin/) — plugin overview

→ [From do\_action to HTTP](https://wpwebhooks.org/blog/wordpress-webhook-delivery-engineering/) — how hooks are captured and payloads are built

→ [Why WP-Cron is not enough for production webhook delivery](https://wpwebhooks.org/blog/async-webhooks-wordpress-wp-cron-not-enough/)

→ [WooCommerce Webhooks with Action Scheduler](https://wpwebhooks.org/blog/woocommerce-webhooks-action-scheduler/) — hook binding, payload mapping, conditional dispatch, and replay

→ [All WordPress webhook automation examples](https://wpwebhooks.org/examples/)

/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.

/More examples

## Related integrations.

Example

CF7 to Webhook

Send Contact Form 7 submissions to n8n with retry and logging.

[Read →](https://wpwebhooks.org/examples/cf7-to-webhook/)

Example

Gravity Forms → Webhook

Send Gravity Forms entries to any webhook endpoint on submission.

[Read →](https://wpwebhooks.org/examples/gravity-forms-webhooks/)

HubSpot Series

HubSpot WooCommerce Integration: Create & Update Deals

Full HubSpot deal creation from WC orders — same REST API, Code Glue, and 2026-03 endpoint.

[Read →](https://wpwebhooks.org/examples/hubspot-create-deal-woocommerce/)

## Structured data

```json
{"@context":"https://schema.org","@type":"Article","headline":"Ask Claude Code to Set Up a WooCommerce Webhook","description":"Claude Code sets up a WooCommerce order webhook with an order total condition — REST API calls, field-path correction, and live fix.","datePublished":"2026-04-29","dateModified":"2026-04-29","author":{"@type":"Person","name":"Mateusz Skorupa","url":"https://wpwebhooks.org/about/"},"publisher":{"@type":"Organization","name":"WP Webhooks","url":"https://wpwebhooks.org","sameAs":["https://flowsystems.pl"]},"url":"https://wpwebhooks.org/examples/woocommerce-order-webhook-claude-code/","image":{"@type":"ImageObject","url":"https://wpwebhooks.org/examples/woocommerce-order-webhook-claude-code/og_image.jpg"},"keywords":["woocommerce webhook","woocommerce order completed webhook","claude code webhook","woocommerce n8n","wordpress webhook rest api","woocommerce automation","ai agent webhook setup"]}

{"@context":"https://schema.org","@type":"FAQPage","mainEntity":[{"@type":"Question","name":"Why didn't the API return an error when I used the wrong field path?","acceptedAnswer":{"@type":"Answer","text":"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."}},{"@type":"Question","name":"What does cast: number do in a webhook condition?","acceptedAnswer":{"@type":"Answer","text":"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."}},{"@type":"Question","name":"How do I find the correct field path for a trigger I haven't used before?","acceptedAnswer":{"@type":"Answer","text":"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."}},{"@type":"Question","name":"What happens to completed orders with a total under 999?","acceptedAnswer":{"@type":"Answer","text":"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."}},{"@type":"Question","name":"Can I add more conditions to the same webhook trigger?","acceptedAnswer":{"@type":"Answer","text":"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."}}]}

{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://wpwebhooks.org/"},{"@type":"ListItem","position":2,"name":"Examples","item":"https://wpwebhooks.org/examples/"},{"@type":"ListItem","position":3,"name":"WooCommerce Order Webhook with Claude Code","item":"https://wpwebhooks.org/examples/woocommerce-order-webhook-claude-code/"}]}

{"@context":"https://schema.org","@type":"HowTo","name":"How to Set Up a WooCommerce Order Webhook Using Claude Code","description":"Use Claude Code to set up a WooCommerce order webhook with async delivery and automatic retry, without writing the integration manually.","step":[{"@type":"HowToStep","name":"Open Claude Code in your project","text":"Launch Claude Code (claude command) in your WordPress project directory."},{"@type":"HowToStep","name":"Describe the integration you need","text":"Tell Claude Code: set up a webhook that fires on WooCommerce order status changes and POSTs to [your endpoint URL] with retry on failure."},{"@type":"HowToStep","name":"Review the generated configuration","text":"Claude Code identifies the correct WooCommerce action hooks, configures the Webhook Actions plugin, and sets up payload mapping for the order data."},{"@type":"HowToStep","name":"Test and verify the webhook","text":"Place a test order and confirm delivery in the plugin log. Claude Code can also help write REST API calls to inspect logs or trigger replays."}]}
```
