How to send BigCommerce orders to ProfitMetrics using a Cloudflare Worker (free Zapier alternative)
This guide covers Part 1 of the Server-side Hybrid Universal Integration — sending orders to ProfitMetrics server-side — using a free Cloudflare Worker instead of Zapier.
NOTE: This is an experimental guide and has not been tested by ProfitMetrics. We recommend implementing this under the guidance of a developer and testing with caution. If you find any issues with the guide, please let us know at support@profitmetrics.io
There are 3 parts to this guide.
Part 1: Prerequisites & BigCommerce API credentials
Part 2: Deploy the Cloudflare Worker
Part 3: Create the BigCommerce webhook
After completing this guide, continue with:
Part 1: Prerequisites & BigCommerce API credentials
You will need the following before starting:
| Item | Where to find it |
|---|---|
ProfitMetrics Public ID (pid) |
ProfitMetrics → Settings → Website → Public ID |
| BigCommerce Store Hash | Your BigCommerce admin URL: https://store-**{STORE_HASH}**.mybigcommerce.com |
| BigCommerce API Access Token | Create one in BigCommerce → Settings → API → API Accounts (see below) |
| Cloudflare account (free) | Sign up at cloudflare.com |
| Node.js (v16+) and npm | Required locally to deploy the Worker via Wrangler CLI |
Create a BigCommerce API Account
- In your BigCommerce admin, go to Settings → API → API Accounts → Create API Account → Create V2/V3 API Token.
- Give it a descriptive name, e.g.
ProfitMetrics Worker. - Set the following OAuth scopes:
| Scope | Permission |
|---|---|
| Orders | Read-Only |
| Order Transactions | Read-Only |
| Products | Read-Only |
- Click Save. Copy the Access Token and Store Hash — you will need these in Part 2.
IMPORTANT: The Access Token is only shown once. Store it securely.
Part 2: Deploy the Cloudflare Worker
Step 1 — Install Wrangler CLI
Open a terminal and install the Cloudflare Wrangler CLI:
npm install -g wrangler
Authenticate with your Cloudflare account:
wrangler login
Step 2 — Create the project
Create a new directory and initialise the project:
mkdir bigcommerce-profitmetrics
cd bigcommerce-profitmetrics
Create a file called wrangler.toml with the following content:
name = "bigcommerce-profitmetrics"
main = "worker.js"
compatibility_date = "2024-01-01"
Step 3 — Add the Worker code
Create a file called worker.js and paste the full Worker code provided below.
Click to expand: worker.js (full source)
/**
* BigCommerce → ProfitMetrics Server-Side Hybrid Universal Integration
* Cloudflare Worker
*
* Environment variables (set via `wrangler secret put`):
* BC_STORE_HASH - Your BigCommerce store hash
* BC_ACCESS_TOKEN - BigCommerce API access token
* PM_PUBLIC_ID - ProfitMetrics website Public ID
*/
export default {
async fetch(request, env) {
if (request.method !== "POST") {
return new Response("Method not allowed", { status: 405 });
}
try {
const payload = await request.json();
const orderId = payload?.data?.id;
if (!orderId) {
return new Response("No order ID in payload", { status: 400 });
}
console.log(`Received webhook for order ${orderId}`);
// Fetch full order from BigCommerce V2 Orders API
const bcBase = `https://api.bigcommerce.com/stores/${env.BC_STORE_HASH}`;
const bcHeaders = {
"X-Auth-Token": env.BC_ACCESS_TOKEN,
"Accept": "application/json",
"Content-Type": "application/json",
};
const orderRes = await fetch(`${bcBase}/v2/orders/${orderId}`, {
headers: bcHeaders,
});
if (!orderRes.ok) {
console.error(`Order fetch failed: ${orderRes.status}`);
return new Response("Failed to fetch order", { status: 502 });
}
const order = await orderRes.json();
// Fetch order products
const productsRes = await fetch(
`${bcBase}/v2/orders/${orderId}/products`,
{ headers: bcHeaders }
);
if (!productsRes.ok) {
console.error(`Products fetch failed: ${productsRes.status}`);
return new Response("Failed to fetch order products", { status: 502 });
}
const products = await productsRes.json();
// Build ProfitMetrics orderspec
const orderspec = buildOrderspec(order, products);
// Send to ProfitMetrics
const pmUrl = buildProfitMetricsUrl(env.PM_PUBLIC_ID, orderspec);
const pmRes = await fetch(pmUrl);
if (!pmRes.ok) {
console.error(`ProfitMetrics request failed: ${pmRes.status}`);
return new Response("ProfitMetrics request failed", { status: 502 });
}
console.log(`Order ${orderId} sent to ProfitMetrics successfully`);
return new Response("OK", { status: 200 });
} catch (err) {
console.error("Worker error:", err);
return new Response("Internal error", { status: 500 });
}
},
};
function buildOrderspec(order, products) {
const email = order.billing_address?.email || "";
const rawPhone = order.billing_address?.phone || "";
const phone = rawPhone.replace(/[^0-9]/g, "") || null;
const totalInclVat = parseFloat(order.total_inc_tax) || 0;
const totalExVat = parseFloat(order.total_ex_tax) || 0;
const shippingExVat = parseFloat(order.shipping_cost_ex_tax) || 0;
const shippingCountry = order.shipping_addresses?.length
? order.shipping_addresses[0].country_iso2
: order.billing_address?.country_iso2 || null;
const shippingZipcode = order.shipping_addresses?.length
? order.shipping_addresses[0].zip
: order.billing_address?.zip || null;
const shippingState = order.shipping_addresses?.length
? order.shipping_addresses[0].state
: order.billing_address?.state || null;
// IMPORTANT: The "sku" field must match the product ID used in your
// ProfitMetrics product feed. Adjust if your feed uses product_id
// or variant_id instead of the actual SKU.
const pmProducts = products.map((product) => ({
sku: product.sku || String(product.product_id),
qty: product.quantity,
priceExVat: parseFloat(product.price_ex_tax) || 0,
}));
const orderspec = {
id: String(order.id),
ts: Math.floor(new Date(order.date_created).getTime() / 1000),
orderEmail: email,
shippingMethod: order.shipping_method || "Standard",
paymentMethod: order.payment_method || "Unknown",
currency: order.currency_code || "GBP",
priceShippingExVat: shippingExVat,
priceTotalExVat: totalExVat,
priceTotalInclVat: totalInclVat,
products: pmProducts,
};
if (phone) orderspec.customerPhone = phone;
if (shippingCountry) orderspec.shippingCountry = shippingCountry;
if (shippingZipcode) orderspec.shippingZipcode = shippingZipcode;
if (shippingState) orderspec.shippingState = shippingState;
const firstName = order.billing_address?.first_name || null;
const lastName = order.billing_address?.last_name || null;
if (firstName) orderspec.customerFirstname = firstName;
if (lastName) orderspec.customerLastname = lastName;
return orderspec;
}
function buildProfitMetricsUrl(publicId, orderspec) {
const params = new URLSearchParams({
v: "3uh",
pid: publicId,
o: JSON.stringify(orderspec),
});
return `https://my.profitmetrics.io/l.php?${params.toString()}`;
}
Step 4 — Add your secrets
Store your credentials as encrypted environment variables. Run each command and paste the value when prompted:
wrangler secret put BC_STORE_HASH
wrangler secret put BC_ACCESS_TOKEN
wrangler secret put PM_PUBLIC_ID
For PM_PUBLIC_ID, use your ProfitMetrics Public ID (e.g. PUBLIC_ID).
Step 5 — Deploy
wrangler deploy
Wrangler will output your Worker URL. It will look something like:
https://bigcommerce-profitmetrics.<your-subdomain>.workers.dev
Copy this URL — you will need it in Part 3.
Part 3: Create the BigCommerce webhook
Register a webhook so BigCommerce notifies your Worker whenever a new order is created.
Option A — Using the BigCommerce API directly (cURL)
Replace {STORE_HASH}, {ACCESS_TOKEN}, and {WORKER_URL} with your values:
curl -X POST \
https://api.bigcommerce.com/stores/{STORE_HASH}/v3/hooks \
-H "X-Auth-Token: {ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"scope": "store/order/created",
"destination": "{WORKER_URL}",
"is_active": true,
"headers": {}
}'
Option B — Using the BigCommerce Control Panel
- Go to Settings → API → Webhooks (if available in your BigCommerce plan).
- Click Create Webhook.
- Set Event to
store/order/created. - Set Destination URL to your Cloudflare Worker URL.
- Ensure Active is toggled on.
- Click Save.
Testing the integration
- Place a test order in your BigCommerce store.
- Check your Cloudflare Worker logs for confirmation:
wrangler tailYou should see output like:Received webhook for order 12345 Order 12345 sent to ProfitMetrics successfully - In ProfitMetrics, go to Orders and verify the test order appears with the correct data.
Troubleshooting
| Issue | Possible cause | Solution |
|---|---|---|
| Order not appearing in ProfitMetrics | Worker not receiving the webhook | Run wrangler tail and place a test order. If no log output, re-check the webhook destination URL and that the webhook is active. |
502 error in Worker logs |
BigCommerce API credentials incorrect | Verify BC_STORE_HASH and BC_ACCESS_TOKEN are correct. Re-run wrangler secret put if needed. |
| Order appears but products show wrong SKU | SKU field mismatch with product feed | The Worker uses product.sku by default. If your ProfitMetrics product feed uses product_id or variant_id, update the buildOrderspec function accordingly. |
| Order appears but with £0 values | BigCommerce returning string "0" for prices | Check that the order in BigCommerce has correct tax-exclusive prices. The Worker parses total_ex_tax and total_inc_tax from the V2 API. |
"shippingMethod" shows as "Standard" |
BigCommerce did not return a shipping method | This is a fallback default. Verify that the order in BigCommerce has a shipping method assigned. |
How it works (technical summary)
The Cloudflare Worker acts as middleware between BigCommerce and ProfitMetrics:
- BigCommerce fires a
store/order/createdwebhook containing the order ID. - The Worker calls the BigCommerce V2 Orders API to fetch the full order object and its line items.
- The Worker transforms the BigCommerce data into a ProfitMetrics orderspec JSON object, mapping fields like
total_ex_tax→priceTotalExVat,billing_address.email→orderEmail, etc. - The Worker sends the orderspec as a URL-encoded GET request to
https://my.profitmetrics.io/l.php?v=3uh&pid=PUBLIC_ID&o={orderspec}.
The Cloudflare Workers free tier allows 100,000 requests per day, which is more than sufficient for order volume.
Field mapping reference
The following table shows how BigCommerce order fields are mapped to ProfitMetrics orderspec fields:
| ProfitMetrics field | BigCommerce source | Notes |
|---|---|---|
id |
order.id |
Cast to string |
ts |
order.date_created |
Converted to Unix timestamp (UTC) |
orderEmail |
order.billing_address.email |
Sent as plaintext; ProfitMetrics handles hashing |
customerPhone |
order.billing_address.phone |
Digits only, non-numeric characters stripped |
customerFirstname |
order.billing_address.first_name |
For Google Ads Enhanced Conversions |
customerLastname |
order.billing_address.last_name |
For Google Ads Enhanced Conversions |
shippingMethod |
order.shipping_method |
Falls back to "Standard" |
shippingCountry |
shipping_addresses[0].country_iso2 |
ISO 3166-1 alpha-2; falls back to billing address |
shippingZipcode |
shipping_addresses[0].zip |
Falls back to billing address |
shippingState |
shipping_addresses[0].state |
Falls back to billing address |
paymentMethod |
order.payment_method |
Falls back to "Unknown" |
currency |
order.currency_code |
ISO 4217 (e.g. "GBP", "EUR") |
priceShippingExVat |
order.shipping_cost_ex_tax |
Parsed to number |
priceTotalExVat |
order.total_ex_tax |
Parsed to number |
priceTotalInclVat |
order.total_inc_tax |
Parsed to number |
products[].sku |
product.sku or product.product_id |
Must match your ProfitMetrics product feed ID |
products[].qty |
product.quantity |
— |
products[].priceExVat |
product.price_ex_tax |
Parsed to number |
Important notes
SKU matching: The sku field in the ProfitMetrics orderspec does NOT necessarily refer to the actual product SKU. It must match the product identifier used in your ProfitMetrics product feed. This could be the BigCommerce SKU, product ID, or variant ID. Check your feed and update the Worker code if needed.
Checkout timing: If your checkout process means the customer is only identified on the order confirmation page (e.g. using Stripe Checkout or a similar redirect flow), the order should not be transmitted until after the confirmation page has loaded and sent the identify event (setEmail) to ProfitMetrics. In most standard BigCommerce checkout flows this is not an issue, but if you are using a third-party checkout, you may need to use the store/order/statusUpdated webhook instead and add a short delay.
Null fields: Any ProfitMetrics fields that cannot be populated should be omitted from the orderspec entirely. They must not be sent as empty strings or undefined.
Next steps
After completing this guide, continue with the remaining steps of the Server-side Hybrid Universal Integration:
Step 2: Install the Server-side Hybrid Universal Integration script