Commerce & blogs
Read Shopify and WooCommerce orders in one shape, with line items, tax, refunds and attribution, and keep them in sync with webhooks instead of polling.
Orders from Shopify and WooCommerce come back in the same shape: line items with unit prices, tax per line, discounts, shipping and fee lines, billing details, payment status, refunds and where the buyer came from. That is enough to rebuild an invoice, feed an analytics tool or attribute a sale to a post without knowing which store it came from. Orders are read-only.
#Before you start
| Store | What it needs |
|---|---|
| Shopify | The orders.read capability, which needs the optional scopes read_orders and read_all_orders. Without read_all_orders, Shopify returns only the last 60 days. Check with GET /v1/commerce/store and send the owner to grantPermissionsUrl |
| WooCommerce | Nothing extra: any user who can manage WooCommerce can read orders |
Shopify also gates buyer details behind its protected customer data approval. Where the approval does not cover them, customer, billingAddress and shippingAddress are null and the rest of the order is complete.
#List orders
Lists come newest first. Filter by createdAfter and createdBefore, or pass updatedAfter to page forward from the last change you saw: results then come oldest first. Shopify pages hold up to 25 orders, WooCommerce pages up to 100.
curl "https://api.pozy.ai/v1/commerce/orders?accountId=6710c4b2e4b0d2f1a8c95e3a&updatedAfter=2026-10-29T00:00:00Z&limit=25" \
-H "Authorization: Bearer $POZY_API_KEY"const res = await fetch("https://api.pozy.ai/v1/commerce/orders?accountId=6710c4b2e4b0d2f1a8c95e3a&updatedAfter=2026-10-29T00:00:00Z&limit=25", {
headers: { Authorization: `Bearer ${process.env.POZY_API_KEY}` },
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
for (const o of data.orders) console.log(o.number, o.status, o.total.amount, o.total.currency);import os, requests
orders = requests.get(
"https://api.pozy.ai/v1/commerce/orders",
headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
params={"accountId": "6710c4b2e4b0d2f1a8c95e3a", "updatedAfter": "2026-10-29T00:00:00Z", "limit": 25},
).json()["orders"]{
"id": "6148123456",
"accountId": "6710c4b2e4b0d2f1a8c95e3a",
"platform": "shopify",
"number": "#1042",
"status": "awaiting_fulfillment",
"platformStatus": "unfulfilled",
"paymentStatus": "paid",
"currency": "EUR",
"taxesIncluded": true,
"subtotal": { "amount": "58.00", "currency": "EUR" },
"discountTotal": { "amount": "8.70", "currency": "EUR" },
"shippingTotal": { "amount": "4.90", "currency": "EUR" },
"taxTotal": { "amount": "9.40", "currency": "EUR" },
"total": { "amount": "54.20", "currency": "EUR" },
"discountCodes": ["INSTA15"],
"lineItems": [
{
"id": "15011223344",
"productId": "8214569812345",
"variantId": "45123456789012",
"sku": "BEANIE-GRY",
"title": "Merino beanie",
"quantity": 2,
"unitPrice": { "amount": "29.00", "currency": "EUR" },
"total": { "amount": "49.30", "currency": "EUR" }
}
],
"refunds": [],
"createdAt": "2026-10-29T14:02:11Z",
"updatedAt": "2026-10-29T14:02:15Z"
}Get one order with GET /v1/commerce/orders/{orderId}?accountId=....
#Read the fields
| Field | Meaning |
|---|---|
status |
pending_payment, on_hold, awaiting_fulfillment, partially_fulfilled, in_transit, delivered, completed or cancelled. Raw value in platformStatus |
paymentStatus |
pending, authorized, partially_paid, paid, partially_refunded, refunded, voided or failed |
subtotal, unitPrice |
Before discounts |
total |
After discounts. taxesIncluded says whether amounts include tax |
lineItems[].taxLines, shippingLines, feeLines, taxLines |
Tax and charges per line, each with title, ratePercent and amount |
refunds |
Each refund with amount, note and the refunded line items. refundedTotal sums them |
fulfillments |
Carrier, tracking number and tracking URL |
customer, billingAddress, shippingAddress |
Buyer details, or null where the store does not share them |
buyerNote, discountCodes, paymentMethods |
As entered at checkout |
All amounts are in the store currency as decimal strings.
#Attribution
attribution says where the order came from. Use it to credit a sale to a post, a campaign or a channel.
"attribution": {
"ready": true,
"firstVisit": null,
"lastVisit": {
"occurredAt": "2026-10-29T13:55:00Z",
"landingUrl": "https://acme.com/products/merino-beanie?utm_source=instagram&utm_campaign=winter",
"referrerUrl": "https://l.instagram.com/",
"source": "instagram",
"sourceType": "SOCIAL",
"utm": { "source": "instagram", "medium": "social", "campaign": "winter", "term": null, "content": null }
},
"customAttributes": [{ "key": "gift_message", "value": "Happy birthday" }]
}| Store | What it records |
|---|---|
| Shopify | First and last visit from the customer journey. Shopify attributes a new order shortly after it is placed: until then ready is false and both visits are null. No event fires when it becomes ready, so read the order again later |
| WooCommerce | The last visit only (firstVisit and occurredAt are always null). Needs WooCommerce 8.5 or later with Order Attribution turned on, for orders placed after that |
customAttributes holds cart and note attributes on Shopify and public order meta on WooCommerce.
Attribution is only as good as your UTM parameters. Put utm_source, utm_medium and utm_campaign on every link you publish, and you can group orders by post or campaign straight from lastVisit.utm.
#Keep in sync with webhooks
Polling works, but webhooks are cheaper. When a store grants order access, Pozy subscribes to its order changes (on WooCommerce, when the site is connected). Subscribe your webhook endpoint to the three events below. They carry the store and the order id; fetch the full order by id.
{
"id": "evt_6f1c2a9b8d7e4c3a",
"event": "commerce.order.created",
"timestamp": "2026-10-29T14:02:16.000Z",
"store": { "accountId": "6710c4b2e4b0d2f1a8c95e3a", "platform": "shopify", "shopId": "64822345678" },
"resource": { "type": "order", "id": "6148123456", "status": "awaiting_fulfillment", "platformStatus": "unfulfilled" }
}A refund usually fires commerce.order.updated as well as commerce.order.refunded. A WooCommerce site connected before order webhooks were available gets them the next time it reconnects or a catalog sync is created.
| Event | When it fires |
|---|---|
commerce.order.created |
Commerce order created. |
commerce.order.updated |
Commerce order updated. |
commerce.order.refunded |
Commerce order refunded. |
#Sync without gaps
To backfill or recover after downtime, page forward with updatedAfter:
- Store the newest
updatedAtyou have processed. - Start each sync a few minutes before it, because an order edited while you page can shift the list.
- Drop duplicates by
id.
#Endpoints
| Endpoint | What it does |
|---|---|
GET /v1/commerce/orders |
List orders |
GET /v1/commerce/orders/{orderId} |
Get an order |
GET /v1/commerce/store |
Get a store |