# BigCommerce > Forward BigCommerce orders when they reach Awaiting Fulfillment — status webhook, fetch the order, one production order per shipping address — and create shipments with tracking. Source: https://swaglists.com/docs/bigcommerce BigCommerce webhooks are thin (they carry only the order id) and unsigned, so the recipe is: authenticate the webhook with a secret header you choose, fetch the order from the BigCommerce API, and forward it. ```text BigCommerce ── store/order/statusUpdated ──► your server ── GET order, products, addresses (BigCommerce API) └─ POST /orders (one per shipping address) ──► Swaglists BigCommerce ◄── POST /v2/orders/{id}/shipments ── your server ◄── shipment.created webhook ``` **Before you start:** your BigCommerce variants need our SKUs (see [Products & SKUs](https://swaglists.com/docs/products.md#skus)). Create a BigCommerce API account (**Settings → API → Store-level API accounts**) with **Orders: modify**, and note the store hash and access token. ## Register the webhook Choose a long random secret; BigCommerce sends your custom headers with every callback: ```bash title="cURL" curl -X POST https://api.bigcommerce.com/stores/$STORE_HASH/v3/hooks \ -H "X-Auth-Token: $BC_TOKEN" -H "Content-Type: application/json" -H "Accept: application/json" \ -d '{ "scope": "store/order/statusUpdated", "destination": "https://your-server.example.com/webhooks/bigcommerce", "is_active": true, "headers": { "X-Glue-Secret": "a-long-random-secret" } }' ``` The destination must be HTTPS on port 443. ## The server ```js title="bigcommerce.mjs" // Node.js 20+, npm install express import crypto from "node:crypto"; import express from "express"; const { STORE_HASH, BC_TOKEN, GLUE_SECRET, PRINT_API_KEY } = process.env; const BC = `https://api.bigcommerce.com/stores/${STORE_HASH}`; const API = "https://swaglists.com/api/v1"; const AWAITING_FULFILLMENT = 11; // paid and ready — status ids are stable even if you rename statuses const CANCELLED = 5; const bc = async (path) => { const res = await fetch(BC + path, { headers: { "X-Auth-Token": BC_TOKEN, Accept: "application/json" } }); if (!res.ok) throw new Error(`BigCommerce ${path}: ${res.status}`); return res.json(); }; const safeEqual = (a = "", b = "") => { const x = Buffer.from(a), y = Buffer.from(b); return x.length === y.length && crypto.timingSafeEqual(x, y); }; async function printApi(method, path, body, idempotencyKey) { const res = await fetch(API + path, { method, headers: { Authorization: `Bearer ${PRINT_API_KEY}`, "Content-Type": "application/json", ...(idempotencyKey ? { "Idempotency-Key": idempotencyKey } : {}) }, body: body === undefined ? undefined : JSON.stringify(body), }); const json = await res.json(); if (!res.ok && json.error?.code !== "duplicate_external_id") throw Object.assign(new Error(json.error.message), { status: res.status, error: json.error }); return json; } const app = express(); app.use(express.json()); app.post("/webhooks/bigcommerce", (req, res) => { if (!safeEqual(req.get("x-glue-secret"), GLUE_SECRET)) return res.sendStatus(401); res.sendStatus(200); // respond fast; BigCommerce retries failures for ~48 hours const { id, status } = req.body.data ?? {}; handle(id, status?.new_status_id).catch((err) => console.error(`BigCommerce order ${id}:`, err.error ?? err)); }); async function handle(orderId, newStatus) { if (newStatus === CANCELLED) { const { data } = await printApi("GET", `/orders?q=bc:${STORE_HASH}:${orderId}:&limit=10`); for (const o of data) await printApi("POST", `/orders/${o.id}/cancel`, { reason: "Canceled in BigCommerce" }).catch(() => {}); return; } if (newStatus !== AWAITING_FULFILLMENT) return; const [order, products, addresses] = await Promise.all([ bc(`/v2/orders/${orderId}`), bc(`/v2/orders/${orderId}/products`), bc(`/v2/orders/${orderId}/shipping_addresses`), ]); // One production order per shipping address (BigCommerce orders can ship to several addresses). for (const address of addresses) { const items = products .filter((p) => p.order_address_id === address.id && p.type === "physical" && p.sku) .map((p) => ({ sku: p.sku, quantity: p.quantity, external_line_id: String(p.id) })); if (!items.length) continue; const externalId = `bc:${STORE_HASH}:${orderId}:${address.id}`; await printApi( "POST", "/orders", { external_id: externalId, recipient: { name: `${address.first_name} ${address.last_name}`.trim(), company: address.company || null, address1: address.street_1, address2: address.street_2 || null, city: address.city, state_code: address.state || null, // the full name — US, Canadian and Australian names are converted to ISO codes for you postal_code: address.zip || null, country_code: address.country_iso2, phone: address.phone || null, email: address.email || order.billing_address?.email || null, }, shipping_method: /express/i.test(address.shipping_method ?? "") ? "express" : "standard", items, packing_slip: { customer_order_number: `#${orderId}` }, }, externalId, ); } } app.listen(process.env.PORT ?? 3000); ``` ## Push tracking back When our `shipment.created` webhook arrives ([verify it](https://swaglists.com/docs/webhooks.md#verify-signatures)), fetch the order (`GET /orders/{order_id}`) and parse its `external_id` (`bc:::
`). Then create the shipment in BigCommerce — `external_line_id` holds the BigCommerce order product id: ```js await fetch(`${BC}/v2/orders/${bcOrderId}/shipments`, { method: "POST", headers: { "X-Auth-Token": BC_TOKEN, "Content-Type": "application/json", Accept: "application/json" }, body: JSON.stringify({ order_address_id: Number(bcAddressId), tracking_number: shipment.tracking_number, tracking_link: shipment.tracking_url, shipping_provider: "", // or one of BigCommerce's providers (usps, ups, fedex, …) items: shipment.items.map((part) => ({ order_product_id: Number(order.items.find((i) => i.id === part.order_item_id).external_line_id), quantity: part.quantity, })), }), }); ``` ## Field mapping | Our field | BigCommerce | | --- | --- | | trigger | `store/order/statusUpdated` with `data.status.new_status_id == 11` (Awaiting Fulfillment) | | cancel trigger | `new_status_id == 5` (Cancelled) | | `external_id` | `bc:::` | | `recipient.name` | `first_name` + `last_name` of the shipping address | | `address1` / `address2` | `street_1` / `street_2` | | `state_code` | `state` (a full name such as `New South Wales` — sent as is; we convert US, Canadian and Australian names to the ISO code) | | `postal_code` / `country_code` | `zip` / `country_iso2` | | `items[].sku` / `quantity` / `external_line_id` | order products `sku` / `quantity` / `id`, filtered by `order_address_id` | ## Things to know - **No signatures** on BigCommerce webhooks: the secret header is the authentication — compare it in constant time and keep it secret. - **Thin payloads**: always re-fetch the order; never trust state from the webhook body alone. - **Statuses are renameable** in BigCommerce — match on ids, not names. - **New hooks** can take about a minute to activate. Hooks are deactivated after repeated failures — check `GET /v3/hooks`.