Automate WooCommerce order processing by verifying HMAC-SHA256 signatures on order.created webhooks, fetching order details with GET /wp-json/wc/v3/orders/{id}, and updating fulfillment status with PUT /wp-json/wc/v3/orders/{id}. WooCommerce uses consumer key/secret auth (not Application Passwords). Rate limit: 25 requests per 10-second window. The Checkout endpoint is capped at just 3 requests per 60 seconds.
API Quick Reference
HTTP Basic Auth (Consumer Key/Secret)
25 requests per 10 seconds (Store API)
JSON
REST only
Understanding the WooCommerce REST API
WooCommerce adds its own REST API to WordPress at /wp-json/wc/v3/. Unlike the WordPress core REST API which uses Application Passwords, WooCommerce uses consumer key and consumer secret pairs (generated at WooCommerce > Settings > Advanced > REST API). Authentication is HTTP Basic Auth over HTTPS with consumer_key as the username and consumer_secret as the password.
For order automation, WooCommerce provides a first-class webhook system — the only first-party webhook source in WordPress. Webhooks are configured at WooCommerce > Settings > Advanced > Webhooks and fire for order.created, order.updated, and other events. Each delivery includes an X-WC-Webhook-Signature header containing HMAC-SHA256 of the raw payload body, base64-encoded, keyed by the webhook secret.
WooCommerce's Store API has a documented rate limit: 25 requests per 10-second window, with the Checkout endpoint capped at 3 requests per 60 seconds. The REST API (wc/v3) shares the WordPress host's WAF policies. Official documentation: woocommerce.com/document/woocommerce-rest-api/.
https://yourwordpresssite.com/wp-jsonSetting Up WooCommerce API Authentication
WooCommerce REST API keys are completely separate from WordPress user accounts. Generate a key pair at WooCommerce > Settings > Advanced > REST API. Set permissions to 'Read/Write' for order automation. The consumer_secret is shown only once at generation time — copy it immediately.
- 1Log into wp-admin and go to WooCommerce > Settings
- 2Click the 'Advanced' tab, then 'REST API'
- 3Click 'Add Key', enter a description (e.g., 'Order Automation'), set user to an admin, permissions to 'Read/Write'
- 4Click 'Generate API Key'
- 5Copy both the Consumer Key (ck_...) and Consumer Secret (cs_...) — the secret is shown only once
- 6Set environment variables: WC_CONSUMER_KEY and WC_CONSUMER_SECRET
- 7Test: curl 'https://yoursite.com/wp-json/wc/v3/orders?status=processing' -u ck_xxx:cs_xxx
1import os2import requests34WP_BASE = os.environ['WP_BASE_URL'] # e.g. https://yoursite.com/wp-json5WC_KEY = os.environ['WC_CONSUMER_KEY'] # ck_...6WC_SECRET = os.environ['WC_CONSUMER_SECRET'] # cs_...7WC_AUTH = (WC_KEY, WC_SECRET)89# Verify connection10resp = requests.get(f'{WP_BASE}/wc/v3/orders?per_page=1', auth=WC_AUTH)11print('WooCommerce API connected:', resp.status_code == 200)Security notes
- •Store consumer key and secret in environment variables, never in source code
- •Consumer secrets are shown only once at generation time — store immediately in your secret manager
- •Always use HTTPS — WooCommerce consumer key auth is only valid over encrypted connections
- •Create a dedicated API key per integration so you can revoke individual keys without disrupting others
- •Validate the X-WC-Webhook-Signature header on every incoming webhook before processing
Key endpoints
/wc/v3/ordersList orders with filtering by status, date, customer, and more. Use this for polling or backfill operations. Default returns 10 orders; max per_page is 100.
| Parameter | Type | Required | Description |
|---|---|---|---|
status | string | optional | Filter by order status: pending, processing, on-hold, completed, cancelled, refunded, failed. |
per_page | number | optional | Orders per page, max 100. Default 10. |
after | string | optional | ISO 8601 date to filter orders created after this date. |
page | number | optional | Page number for pagination. Check X-WP-TotalPages header for total pages. |
Response
1[{"id":1001,"status":"processing","billing":{"first_name":"Jane","last_name":"Doe","email":"jane@example.com"},"line_items":[{"name":"Product A","quantity":2,"total":"49.98"}],"total":"49.98","date_created":"2026-05-07T10:00:00"}]/wc/v3/orders/{id}Retrieve a single order by ID with full detail including line items, billing/shipping address, payment method, and status.
| Parameter | Type | Required | Description |
|---|---|---|---|
id | number | required | WooCommerce order ID. |
Response
1{"id":1001,"status":"processing","payment_method":"stripe","billing":{"first_name":"Jane","email":"jane@example.com","address_1":"123 Main St","city":"Boston","state":"MA","postcode":"02101"},"line_items":[{"id":5,"name":"Product A","sku":"PROD-A","quantity":2,"total":"49.98"}],"shipping_lines":[{"method_title":"Flat Rate","total":"5.00"}],"total":"54.98"}/wc/v3/orders/{id}Update an order's status, tracking information, or notes. Use this to move orders from 'processing' to 'completed' after shipping.
| Parameter | Type | Required | Description |
|---|---|---|---|
status | string | optional | New order status: pending, processing, on-hold, completed, cancelled, refunded, failed. |
customer_note | string | optional | Note visible to the customer. |
Request
1{"status":"completed"}Response
1{"id":1001,"status":"completed","date_completed":"2026-05-07T12:00:00"}Step-by-step automation
Configure WooCommerce Webhook
Why: WooCommerce webhooks provide real-time order notifications — far more efficient than polling the orders endpoint every minute.
In wp-admin, go to WooCommerce > Settings > Advanced > Webhooks and click 'Add webhook'. Set topic to 'Order created', enter your HTTPS handler URL, and set status to Active. Copy the webhook secret. Alternatively, create programmatically via POST /wc/v3/webhooks. Each delivery includes X-WC-Webhook-Signature (HMAC-SHA256, base64-encoded) using your webhook secret.
1# Create webhook via API2curl -X POST 'https://yoursite.com/wp-json/wc/v3/webhooks' \3 -u 'ck_YOUR_KEY:cs_YOUR_SECRET' \4 -H 'Content-Type: application/json' \5 -d '{6 "name": "Order Created",7 "topic": "order.created",8 "delivery_url": "https://yourapp.com/webhooks/woocommerce",9 "secret": "YOUR_WEBHOOK_SECRET"10 }'Pro tip: WooCommerce signatures use HMAC-SHA256 base64-encoded, not hex-encoded like most webhook systems. If your signature verification fails, check that you are base64-encoding the digest, not hex-encoding it.
Expected result: Your webhook handler receives POST requests for each new WooCommerce order with the full order JSON payload.
Extract Order Details and Validate
Why: Validating the order status and line items before processing prevents duplicate fulfillment of already-shipped orders.
Parse the webhook payload to extract order ID, status, billing address, and line items. For critical operations, re-fetch the order from the API to get the authoritative state — webhook payloads can occasionally be slightly stale.
1curl -X GET 'https://yoursite.com/wp-json/wc/v3/orders/1001' \2 -u 'ck_YOUR_KEY:cs_YOUR_SECRET'Pro tip: WooCommerce order statuses follow a flow: pending → processing → completed (or cancelled/refunded). Only process orders in 'processing' status for fulfillment — 'pending' orders have not yet been paid.
Expected result: Order data is validated and enriched with fresh API data. The order is ready for shipping service integration.
Update Order Status to Completed
Why: Marking orders as completed in WooCommerce triggers customer completion emails and removes them from the active orders queue.
After fulfillment, send PUT /wc/v3/orders/{id} with the new status. You can also add a customer_note to the order update to include tracking information.
1curl -X PUT 'https://yoursite.com/wp-json/wc/v3/orders/1001' \2 -u 'ck_YOUR_KEY:cs_YOUR_SECRET' \3 -H 'Content-Type: application/json' \4 -d '{"status":"completed","customer_note":"Your order has been shipped. Tracking: 1Z999AA10123456784"}'Pro tip: WooCommerce order status changes trigger internal hooks that run email notifications. Do not send your own completion email from the automation — WooCommerce handles it. Focus on status updates and tracking notes.
Expected result: The WooCommerce order status updates to 'completed'. WooCommerce automatically sends the order completion email to the customer.
Sync Order to External Shipping Service
Why: Most fulfillment automations need to send order data to a shipping API (ShipStation, EasyPost, etc.) before marking the order complete.
Extract the shipping address and line items from the WooCommerce order and POST them to your shipping service API. Receive the tracking number, then update the WooCommerce order with tracking info. The exact shipping API call depends on your provider.
1# Example: Create shipment in ShipStation2curl -X POST 'https://ssapi.shipstation.com/orders/createshipment' \3 -H 'Authorization: Basic BASE64_SHIPSTATION_CREDS' \4 -H 'Content-Type: application/json' \5 -d '{"orderId": 12345, "carrierCode": "fedex", "serviceCode": "fedex_ground"}'Pro tip: Always update the WooCommerce order status AFTER the shipping API call succeeds, not before. If the shipping API fails, the order should remain in 'processing' so it can be retried.
Expected result: A tracking number is returned from the shipping service and added to the WooCommerce order as a customer note.
Complete working code
Complete WooCommerce order webhook handler: verifies HMAC-SHA256 signature, processes 'processing' orders, re-fetches for fresh data, calls a shipping service, updates order status to 'completed' with tracking note. Handles retries for transient failures.
1import os2import hmac3import hashlib4import base645import threading6import requests7from flask import Flask, request, jsonify89app = Flask(__name__)1011WP_BASE = os.environ['WP_BASE_URL']12WC_AUTH = (os.environ['WC_CONSUMER_KEY'], os.environ['WC_CONSUMER_SECRET'])13WEBHOOK_SECRET = os.environ['WC_WEBHOOK_SECRET']1415def verify_sig(body: bytes, sig: str) -> bool:16 expected = base64.b64encode(hmac.new(WEBHOOK_SECRET.encode(), body, hashlib.sha256).digest()).decode()17 return hmac.compare_digest(expected, sig)1819def wc_get(path):20 resp = requests.get(f'{WP_BASE}{path}', auth=WC_AUTH)21 resp.raise_for_status()22 return resp.json()2324def wc_put(path, payload):25 resp = requests.put(f'{WP_BASE}{path}', auth=WC_AUTH,26 headers={'Content-Type': 'application/json'}, json=payload)27 resp.raise_for_status()28 return resp.json()2930def get_tracking_number(order):31 # Replace with your shipping API integration32 return f"TRACK{order['id']:08d}"3334def process_order(order_payload):35 order_id = order_payload['id']36 if order_payload.get('status') != 'processing':37 return38 try:39 order = wc_get(f'/wc/v3/orders/{order_id}')40 if order['status'] != 'processing':41 return42 tracking = get_tracking_number(order)43 wc_put(f'/wc/v3/orders/{order_id}', {44 'status': 'completed',45 'customer_note': f'Shipped! Tracking: {tracking}'46 })47 print(f'Order {order_id} completed, tracking: {tracking}')48 except Exception as e:49 print(f'Error processing order {order_id}: {e}')5051@app.route('/webhooks/woocommerce', methods=['POST'])52def webhook():53 raw = request.get_data()54 sig = request.headers.get('X-WC-Webhook-Signature', '')55 if not verify_sig(raw, sig):56 return jsonify({'error': 'bad sig'}), 40157 order = request.json58 threading.Thread(target=process_order, args=(order,), daemon=True).start()59 return jsonify({'ok': True})6061if __name__ == '__main__':62 app.run(port=3000)Error handling
{"code":"woocommerce_rest_cannot_view","message":"Sorry, you cannot list resources.","data":{"status":401}}Consumer key or secret is incorrect, or the API key does not have the required permissions.
Verify consumer key and secret from WooCommerce > Settings > Advanced > REST API. Regenerate if unsure. Ensure permissions are set to 'Read/Write' not 'Read'.
No retry — fix credentials.
Webhook signature mismatchThe X-WC-Webhook-Signature is HMAC-SHA256 base64-encoded, not hex-encoded. Most webhook verification examples use hex — this is a common mistake.
Ensure you are base64-encoding the HMAC-SHA256 digest, not hex-encoding it: base64.b64encode(hmac.digest()).decode() in Python, .digest('base64') in Node.js.
No retry — fix the signature verification code.
Too Many RequestsExceeded the WooCommerce Store API rate limit of 25 requests per 10-second window, or the managed host's WAF throttled your IP.
Add delays between order processing calls. For bulk operations, process at most 2 orders per second. Honor any Retry-After header.
Exponential backoff: 1s, 2s, 4s, 8s. Check Retry-After header.
{"code":"woocommerce_rest_order_invalid_id","message":"Invalid ID.","data":{"status":404}}The order ID does not exist or was permanently deleted.
Handle 404 gracefully — log the ID and skip. Orders can be deleted manually in the admin.
No retry — skip and log.
{"code":"woocommerce_rest_cannot_edit","message":"Sorry, you are not allowed to edit this resource."}The WooCommerce API key was created with 'Read' permissions only.
Regenerate the API key at WooCommerce > Settings > Advanced > REST API with 'Read/Write' permissions.
No retry — fix permissions.
Rate Limits for WooCommerce REST API
| Scope | Limit | Window |
|---|---|---|
| WooCommerce Store API (block-based) | 25 requests | per 10-second window |
| Checkout endpoint (Store API) | 3 requests | per 60-second window |
| WooCommerce REST API (wc/v3) | Host WAF dependent | No WordPress core limit; managed hosts vary |
1import time2import requests34def wc_request(method, url, auth, max_retries=5, **kwargs):5 for attempt in range(max_retries):6 resp = getattr(requests, method)(url, auth=auth, **kwargs)7 if resp.status_code == 429:8 wait = int(resp.headers.get('Retry-After', 2 ** attempt))9 time.sleep(wait)10 continue11 return resp12 raise Exception('Max retries exceeded')- Process webhook events asynchronously — return 200 immediately and process in a background thread
- Re-fetch orders from the API for authoritative state rather than relying solely on webhook payload data
- Update order status only AFTER the shipping API succeeds to prevent marking orders complete if shipping fails
- Add 500ms delays between sequential order processing calls when doing batch backfills
- Monitor the X-WP-Total header when listing orders to understand pagination requirements
Security checklist
- Always verify X-WC-Webhook-Signature using HMAC-SHA256 base64-encoded (not hex) before processing webhook payloads
- Store WC_CONSUMER_KEY, WC_CONSUMER_SECRET, and WC_WEBHOOK_SECRET in environment variables
- Use HTTPS only — WooCommerce consumer key auth requires encrypted transport
- Create separate API keys per integration and set minimum required permissions (Read for read-only, Read/Write for updates)
- Return 200 from webhook handler immediately — process asynchronously to prevent Notion retries
- Validate order status before processing — skip orders not in 'processing' state to prevent duplicate fulfillment
- Log all order processing actions with order IDs for audit trail and debugging
Automation use cases
Real-Time Order Fulfillment
intermediateProcess every new WooCommerce order immediately via webhook, submit to a shipping API, and mark complete with tracking within seconds of payment.
Daily Order Sync to Spreadsheet
beginnerPoll GET /wc/v3/orders?after=YESTERDAY for all orders from the past 24 hours and sync them to Google Sheets or Airtable for reporting.
Low Stock Backorder Manager
advancedCombine GET /wc/v3/orders (for pending orders) with GET /wc/v3/products (for stock levels) to identify and email customers about backorders automatically.
No-code alternatives
Don't want to write code? These platforms can automate the same workflows visually.
Zapier
Free tier available; paid plans from $19.99/monthZapier's WooCommerce integration supports order.created triggers and can update order status and sync to shipping tools without code.
- + No code required
- + Built-in WooCommerce trigger
- + Connects to ShipStation, Airtable, Sheets
- - Webhook processing delay of a few seconds
- - Cannot verify webhook signatures natively
- - Cost scales with order volume
Make
Free tier available; paid plans from $9/monthMake's WooCommerce module supports webhooks, order reading, and status updates with stronger data transformation capabilities than Zapier.
- + Better for complex order data transformations
- + More affordable at high volume
- + HTTP module handles shipping API calls
- - Learning curve higher than Zapier
- - WooCommerce module may lag updates
- - Webhook signature verification requires custom setup
n8n
Free self-hosted; cloud from €20/monthn8n has a WooCommerce node for order reading and status updates, plus a Webhook node for real-time order notifications.
- + Self-hostable
- + Full order lifecycle management
- + Code node for custom shipping API integration
- - WooCommerce node may not cover all edge cases
- - Requires server for webhook reception
- - More setup than SaaS tools
Best practices
- Verify X-WC-Webhook-Signature with base64-encoded HMAC-SHA256 on every webhook — this prevents processing forged orders
- Re-fetch the order via GET /wc/v3/orders/{id} in the webhook handler for the authoritative state
- Only process orders in 'processing' status — 'pending' orders have not been paid
- Update order status to 'completed' only after your shipping/fulfillment step succeeds
- Handle all order statuses: if an order is 'cancelled' in the re-fetch, abort processing
- Log order_id, shipping tracking numbers, and status changes for support and debugging
- Test the full webhook flow with a real WooCommerce test order before going live
Ask AI to help
Copy one of these prompts to get a personalized, working implementation.
I'm verifying WooCommerce webhook signatures in Python. The X-WC-Webhook-Signature header contains the signature. The webhook secret is stored as WC_WEBHOOK_SECRET. My verification code is: {paste code}. The verification is failing even though the secret is correct. WooCommerce uses HMAC-SHA256 with base64 encoding. What is the correct Python code to verify the signature?
Build a Next.js order management dashboard for WooCommerce. The app should: display recent WooCommerce orders fetched from GET /wp-json/wc/v3/orders?per_page=50 with WooCommerce consumer key/secret auth stored in env vars, show order ID, customer email, total, status, and date in a table with color-coded status badges, include a 'Mark Complete' button that calls PUT /wc/v3/orders/{id} with status: 'completed', and a webhook handler at POST /api/webhooks/woocommerce that verifies the X-WC-Webhook-Signature (HMAC-SHA256 base64) and updates a Supabase table with new orders. Use WC_CONSUMER_KEY, WC_CONSUMER_SECRET, and WC_WEBHOOK_SECRET from environment variables.
Frequently asked questions
What is the difference between WooCommerce consumer key/secret and WordPress Application Passwords?
They are completely separate authentication systems. WordPress Application Passwords (Users > Profile > Application Passwords) authenticate against the WordPress REST API at /wp-json/wp/v2/. WooCommerce consumer keys (WooCommerce > Settings > Advanced > REST API) authenticate against the WooCommerce REST API at /wp-json/wc/v3/. You cannot use one for the other.
Why is the X-WC-Webhook-Signature base64-encoded instead of hex like other webhook systems?
WooCommerce generates the signature using PHP's hash_hmac('sha256', $body, $secret, true) — the third parameter (true) returns raw binary output, which WooCommerce then base64-encodes with base64_encode(). Most other webhook systems use the hex representation. Always use base64-encoded HMAC for WooCommerce signature verification.
Can I create WooCommerce webhooks via the API instead of the admin UI?
Yes. Send POST /wp-json/wc/v3/webhooks with {name, topic, delivery_url, secret} using your consumer key/secret auth. Topics include: order.created, order.updated, order.deleted, customer.created, product.created, product.updated. The response includes the webhook ID and generated secret.
Is the WooCommerce REST API free?
Yes. WooCommerce is a free plugin and its REST API is free to use. WooCommerce does not charge for API calls. Your costs are hosting only.
What happens when I hit the WooCommerce Store API rate limit?
The WooCommerce Store API returns HTTP 429 when you exceed 25 requests per 10-second window. The wc/v3 REST API (used for order management) is subject to your host's WAF policies, not the documented Store API limits. Implement exponential backoff and add delays between sequential order processing calls.
How do I paginate through all orders?
Use GET /wc/v3/orders with per_page=100&page=1. The response headers include X-WP-Total (total order count) and X-WP-TotalPages (total pages). Increment the page parameter and continue until page > X-WP-TotalPages.
Can I add custom meta data to WooCommerce orders via the API?
Yes. Include a meta_data array in your PUT or POST payload: {"meta_data": [{"key": "tracking_number", "value": "TRACK123"}]}. Meta data is returned in subsequent GET requests under the meta_data array. This is the best way to store fulfillment data alongside the order.
Can RapidDev build a custom WooCommerce order automation?
Yes. RapidDev has built 600+ apps including WooCommerce fulfillment pipelines, multi-warehouse order routing, and order sync systems. We can build a custom integration connecting WooCommerce to your shipping provider, inventory system, or ERP. Contact us at rapidevelopers.com for a free consultation.
Need this automated?
Our team has built 600+ apps with API automations. We can build this for you.
Book a free consultation