| name | woocommerce |
| description | WooCommerce REST API - products, orders, customers, webhooks |
WooCommerce Development Skill
Load with: base.md + (typescript.md or python.md)
For integrating with WooCommerce stores via REST API - products, orders, customers, webhooks, and custom extensions.
Sources: WooCommerce REST API | Developer Docs
Prerequisites
Store Requirements
# WooCommerce store must have:
# 1. WordPress with WooCommerce plugin installed
# 2. HTTPS enabled (required for API auth)
# 3. Permalinks set to anything except "Plain"
# WordPress Admin → Settings → Permalinks → Post name (recommended)
Generate API Keys
- Go to WooCommerce → Settings → Advanced → REST API
- Click Add key
- Set Description, User (admin), and Permissions (Read/Write)
- Click Generate API key
- Copy Consumer Key and Consumer Secret (shown only once)
API Basics
Base URL
https://your-store.com/wp-json/wc/v3/
Authentication
// Node.js - Basic Auth (recommended)
const WooCommerceRestApi = require("@woocommerce/woocommerce-rest-api").default;
const api = new WooCommerceRestApi({
url: "https://your-store.com",
consumerKey: process.env.WC_CONSUMER_KEY,
consumerSecret: process.env.WC_CONSUMER_SECRET,
version: "wc/v3"
});
# Python
from woocommerce import API
wcapi = API(
url="https://your-store.com",
consumer_key=os.environ["WC_CONSUMER_KEY"],
consumer_secret=os.environ["WC_CONSUMER_SECRET"],
version="wc/v3"
)
Query String Auth (Fallback)
# Only use if Basic Auth fails (some hosting configurations)
curl https://your-store.com/wp-json/wc/v3/products \
?consumer_key=ck_xxx&consumer_secret=cs_xxx
Installation
Node.js
npm install @woocommerce/woocommerce-rest-api
// lib/woocommerce.ts
import WooCommerceRestApi from "@woocommerce/woocommerce-rest-api";
const api = new WooCommerceRestApi({
url: process.env.WC_STORE_URL!,
consumerKey: process.env.WC_CONSUMER_KEY!,
consumerSecret: process.env.WC_CONSUMER_SECRET!,
version: "wc/v3",
queryStringAuth: false, // Set true for HTTP (dev only)
});
export default api;
Python
pip install woocommerce
# lib/woocommerce.py
import os
from woocommerce import API
wcapi = API(
url=os.environ["WC_STORE_URL"],
consumer_key=os.environ["WC_CONSUMER_KEY"],
consumer_secret=os.environ["WC_CONSUMER_SECRET"],
version="wc/v3",
timeout=30
)
Products
List Products
// Node.js
async function getProducts(page = 1, perPage = 20) {
const response = await api.get("products", {
page,
per_page: perPage,
status: "publish",
});
return response.data;
}
// With filters
async function searchProducts(search: string, category?: number) {
const response = await api.get("products", {
search,
category: category || undefined,
orderby: "popularity",
order: "desc",
});
return response.data;
}
# Python
def get_products(page=1, per_page=20):
response = wcapi.get("products", params={
"page": page,
"per_page": per_page,
"status": "publish"
})
return response.json()
Get Single Product
async function getProduct(productId: number) {
const response = await api.get(`products/${productId}`);
return response.data;
}
Create Product
async function createProduct(data: ProductInput) {
const response = await api.post("products", {
name: data.name,
type: "simple", // simple, variable, grouped, external
regular_price: data.price.toString(),
description: data.description,
short_description: data.shortDescription,
categories: data.categoryIds.map(id => ({ id })),
images: data.images.map(url => ({ src: url })),
manage_stock: true,
stock_quantity: data.stockQuantity,
status: "publish",
});
return response.data;
}
Update Product
async function updateProduct(productId: number, data: Partial<ProductInput>) {
const response = await api.put(`products/${productId}`, data);
return response.data;
}
// Update stock only
async function updateStock(productId: number, quantity: number) {
const response = await api.put(`products/${productId}`, {
stock_quantity: quantity,
});
return response.data;
}
Delete Product
async function deleteProduct(productId: number, force = false) {
// force: true = permanent delete, false = move to trash
const response = await api.delete(`products/${productId}`, {
force,
});
return response.data;
}
Variable Products
// Create variable product
async function createVariableProduct(data: VariableProductInput) {
// 1. Create product with type "variable"
const product = await api.post("products", {
name: data.name,
type: "variable",
attributes: [
{
name: "Size",
visible: true,
variation: true,
options: ["Small", "Medium", "Large"],
},
{
name: "Color",
visible: true,
variation: true,
options: ["Red", "Blue"],
},
],
});
// 2. Create variations
for (const variant of data.variants) {
await api.post(`products/${product.data.id}/variations`, {
regular_price: variant.price.toString(),
stock_quantity: variant.stock,
attributes: [
{ name: "Size", option: variant.size },
{ name: "Color", option: variant.color },
],
});
}
return product.data;
}
// Get variations
async function getVariations(productId: number) {
const response = await api.get(`products/${productId}/variations`);
return response.data;
}
Orders
List Orders
async function getOrders(params: OrderQueryParams = {}) {
const response = await api.get("orders", {
page: params.page || 1,
per_page: params.perPage || 20,
status: params.status || "any", // pending, processing, completed, etc.
after: params.after, // ISO date string
before: params.before,
});
return response.data;
}
// Get recent orders
async function getRecentOrders(days = 7) {
const after = new Date();
after.setDate(after.getDate() - days);
const response = await api.get("orders", {
after: after.toISOString(),
orderby: "date",
order: "desc",
});
return response.data;
}
Get Single Order
async function getOrder(orderId: number) {
const response = await api.get(`orders/${orderId}`);
return response.data;
}
Create Order
async function createOrder(data: OrderInput) {
const response = await api.post("orders", {
payment_method: "stripe",
payment_method_title: "Credit Card",
set_paid: false,
billing: {
first_name: data.customer.firstName,
last_name: data.customer.lastName,
email: data.customer.email,
phone: data.customer.phone,
address_1: data.billing.address1,
city: data.billing.city,
state: data.billing.state,
postcode: data.billing.postcode,
country: data.billing.country,
},
shipping: {
first_name: data.customer.firstName,
last_name: data.customer.lastName,
address_1: data.shipping.address1,
city: data.shipping.city,
state: data.shipping.state,
postcode: data.shipping.postcode,
country: data.shipping.country,
},
line_items: data.items.map(item => ({
product_id: item.productId,
variation_id: item.variationId,
quantity: item.quantity,
})),
shipping_lines: [
{
method_id: "flat_rate",
method_title: "Flat Rate",
total: data.shippingCost.toString(),
},
],
});
return response.data;
}
Update Order Status
async function updateOrderStatus(orderId: number, status: OrderStatus) {
const response = await api.put(`orders/${orderId}`, {
status, // pending, processing, on-hold, completed, cancelled, refunded, failed
});
return response.data;
}
// Add order note
async function addOrderNote(orderId: number, note: string, customerNote = false) {
const response = await api.post(`orders/${orderId}/notes`, {
note,
customer_note: customerNote, // true = visible to customer
});
return response.data;
}
Order Statuses
| Status | Description |
|---|---|
pending |
Awaiting payment |
processing |
Payment received, awaiting fulfillment |
on-hold |
Awaiting action (stock, payment confirmation) |
completed |
Order fulfilled |
cancelled |
Cancelled by admin or customer |
refunded |
Refunded |
failed |
Payment failed |
Customers
List Customers
async function getCustomers(params: CustomerQueryParams = {}) {
const response = await api.get("customers", {
page: params.page || 1,
per_page: params.perPage || 20,
role: "customer",
orderby: "registered_date",
order: "desc",
});
return response.data;
}
// Search customers
async function searchCustomers(email: string) {
const response = await api.get("customers", {
email,
});
return response.data;
}
Create Customer
async function createCustomer(data: CustomerInput) {
const response = await api.post("customers", {
email: data.email,
first_name: data.firstName,
last_name: data.lastName,
username: data.email.split("@")[0],
billing: {
first_name: data.firstName,
last_name: data.lastName,
email: data.email,
phone: data.phone,
address_1: data.address1,
city: data.city,
state: data.state,
postcode: data.postcode,
country: data.country,
},
shipping: {
// Same as billing or different
},
});
return response.data;
}
Update Customer
async function updateCustomer(customerId: number, data: Partial<CustomerInput>) {
const response = await api.put(`customers/${customerId}`, data);
return response.data;
}
Webhooks
Create Webhook
async function createWebhook(topic: string, deliveryUrl: string) {
const response = await api.post("webhooks", {
name: `Webhook for ${topic}`,
topic, // order.created, order.updated, product.created, etc.
delivery_url: deliveryUrl,
status: "active",
secret: process.env.WC_WEBHOOK_SECRET,
});
return response.data;
}
Webhook Topics
| Topic | Trigger |
|---|---|
order.created |
New order placed |
order.updated |
Order status/details changed |
order.deleted |
Order deleted |
product.created |
New product created |
product.updated |
Product updated |
product.deleted |
Product deleted |
customer.created |
New customer registered |
customer.updated |
Customer updated |
coupon.created |
New coupon created |
Verify Webhook Signature
// Express.js webhook handler
import crypto from "crypto";
function verifyWooCommerceWebhook(req: Request): boolean {
const signature = req.headers["x-wc-webhook-signature"] as string;
const payload = JSON.stringify(req.body);
const expectedSignature = crypto
.createHmac("sha256", process.env.WC_WEBHOOK_SECRET!)
.update(payload)
.digest("base64");
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
// Route handler
app.post("/webhooks/woocommerce", (req, res) => {
if (!verifyWooCommerceWebhook(req)) {
return res.status(401).json({ error: "Invalid signature" });
}
const topic = req.headers["x-wc-webhook-topic"];
const payload = req.body;
switch (topic) {
case "order.created":
handleNewOrder(payload);
break;
case "order.updated":
handleOrderUpdate(payload);
break;
// ... other topics
}
res.status(200).json({ received: true });
});
# Python/Flask webhook handler
import hmac
import hashlib
import base64
@app.route("/webhooks/woocommerce", methods=["POST"])
def woocommerce_webhook():
signature = request.headers.get("X-WC-Webhook-Signature")
payload = request.get_data()
expected = base64.b64encode(
hmac.new(
os.environ["WC_WEBHOOK_SECRET"].encode(),
payload,
hashlib.sha256
).digest()
).decode()
if not hmac.compare_digest(signature, expected):
return {"error": "Invalid signature"}, 401
topic = request.headers.get("X-WC-Webhook-Topic")
data = request.json
if topic == "order.created":
handle_new_order(data)
elif topic == "order.updated":
handle_order_update(data)
return {"received": True}, 200
Categories & Tags
List Categories
async function getCategories() {
const response = await api.get("products/categories", {
per_page: 100,
orderby: "name",
});
return response.data;
}
// Create category
async function createCategory(name: string, parentId?: number) {
const response = await api.post("products/categories", {
name,
parent: parentId || 0,
});
return response.data;
}
List Tags
async function getTags() {
const response = await api.get("products/tags", {
per_page: 100,
});
return response.data;
}
Coupons
Create Coupon
async function createCoupon(data: CouponInput) {
const response = await api.post("coupons", {
code: data.code,
discount_type: data.type, // percent, fixed_cart, fixed_product
amount: data.amount.toString(),
individual_use: true,
exclude_sale_items: false,
minimum_amount: data.minimumAmount?.toString(),
maximum_amount: data.maximumAmount?.toString(),
usage_limit: data.usageLimit,
usage_limit_per_user: 1,
date_expires: data.expiresAt, // ISO date string
});
return response.data;
}
Reports
Sales Report
async function getSalesReport(period = "month") {
const response = await api.get("reports/sales", {
period, // day, week, month, year
});
return response.data;
}
// Top sellers
async function getTopSellers(period = "month") {
const response = await api.get("reports/top_sellers", {
period,
});
return response.data;
}
Pagination
Handle Large Datasets
async function getAllProducts() {
const allProducts = [];
let page = 1;
const perPage = 100;
while (true) {
const response = await api.get("products", {
page,
per_page: perPage,
});
allProducts.push(...response.data);
// Check headers for total pages
const totalPages = parseInt(response.headers["x-wp-totalpages"]);
if (page >= totalPages) break;
page++;
}
return allProducts;
}
Pagination Headers
| Header | Description |
|---|---|
X-WP-Total |
Total number of items |
X-WP-TotalPages |
Total number of pages |
Error Handling
import WooCommerceRestApi from "@woocommerce/woocommerce-rest-api";
async function safeApiCall<T>(
operation: () => Promise<{ data: T }>
): Promise<T> {
try {
const response = await operation();
return response.data;
} catch (error: any) {
if (error.response) {
// API returned an error
const { status, data } = error.response;
switch (status) {
case 400:
throw new Error(`Bad request: ${data.message}`);
case 401:
throw new Error("Invalid API credentials");
case 404:
throw new Error("Resource not found");
case 429:
// Rate limited - wait and retry
await new Promise(r => setTimeout(r, 5000));
return safeApiCall(operation);
default:
throw new Error(`API error: ${data.message}`);
}
}
throw error;
}
}
// Usage
const products = await safeApiCall(() => api.get("products"));
Environment Variables
# .env
WC_STORE_URL=https://your-store.com
WC_CONSUMER_KEY=ck_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
WC_CONSUMER_SECRET=cs_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
WC_WEBHOOK_SECRET=your_webhook_secret
Add to credentials.md:
'WC_CONSUMER_KEY': r'ck_[a-f0-9]{40}',
'WC_CONSUMER_SECRET': r'cs_[a-f0-9]{40}',
Checklist
Before Integration
- WooCommerce plugin installed and activated
- HTTPS enabled on store
- Permalinks set to non-Plain setting
- API keys generated with appropriate permissions
- Webhook secret configured
Security
- API keys stored in environment variables
- Webhook signatures verified
- HTTPS used for all API calls
- Rate limiting handled
Testing
- Test API connection
- Test product CRUD operations
- Test order creation/updates
- Test webhook delivery
- Test pagination for large datasets
Anti-Patterns
- Plain permalinks - API won't work without pretty permalinks
- HTTP in production - Always use HTTPS
- Ignoring rate limits - WooCommerce may throttle requests
- Large single requests - Use pagination for bulk operations
- Storing keys in code - Use environment variables
- Skipping webhook verification - Always verify signatures