
This hands-on guide shows you exactly how to capture key commerce events from Shopify and BigCommerce and deliver them reliably to Amplitude and Mixpanel—using both client-side (pixels/JS SDKs) and server-side (webhooks/HTTP APIs) pipelines. You’ll leave with a working setup, clear validation steps, and a plan to avoid duplicates and identity fragmentation.
- Difficulty: Intermediate (comfortable editing themes/scripts, basic JS/Node, API keys)
- Time: 2–6 hours depending on your store complexity
- What you’ll set up:
- Client-side tracking for engagement events (views, add to cart, checkout started)
- Server-side tracking for critical revenue events (purchases) to avoid ad blocker loss
- Identity strategy (anonymous → identified), consent gating, and idempotent deduplication
- Validation in Amplitude and Mixpanel UIs
Prerequisites
- Access to a test store or staging theme (Shopify or BigCommerce)
- Amplitude API key (US or EU project) and Mixpanel Project Token (US or EU)
- A lightweight middleware/server endpoint you control (Node/Express or serverless) for secure forwarding
Plan your tracking foundation (do this once)
- Define events and properties
- Core events: Product Viewed, Add to Cart, Checkout Started, Purchase Completed, Login, Signup
- Must-have properties: product_id/SKU, quantity, price, currency, cart_value, order_id, discount codes, channel/source
- Naming convention: use snake_case or lowerCamelCase consistently
- Identity strategy
- Client-side: start with anonymous device/browser IDs
- On login/checkout: attach a stable customer_id; map to:
- Amplitude: user_id (keep device_id too when available)
- Mixpanel: distinct_id; also use alias upon signup and identify after login per the JavaScript SDK guidance in the official Mixpanel JavaScript SDK docs (2025)
- Consent and privacy
- Shopify: gate analytics on customerPrivacy flags per the Shopify customerPrivacy API (2025)
- BigCommerce: respect the BODL consent update event; see BigCommerce BODL consent event (2025)
- Deduplication and idempotency
- Treat server-side as the source of truth for purchase
- Include a stable insert_id on every event (e.g., order_123) so Amplitude and Mixpanel can drop duplicates; see Amplitude’s HTTP API quickstart (2025) and Mixpanel’s server-side best practices (2025)
Shopify → Amplitude
A1) Client-side via Shopify Web Pixels (engagement + checkout events)
Use Shopify’s Web Pixels to subscribe to standard events and forward payloads to your server (recommended) or directly to Amplitude if appropriate. Shopify documents the checkout_completed standard event and payload fields in the official Shopify Web Pixels checkout_completed docs (2025).
Why forward to your server? It keeps your Amplitude API key secret, avoids CORS issues, and lets you enrich and deduplicate before sending.
Example app pixel (TypeScript/ESM):
import { register } from '@shopify/web-pixels-extension';
register(({ analytics, init, customerPrivacy }) => {
let privacy = init.customerPrivacy; // { analyticsProcessingAllowed, ... }
// Update consent in real time
customerPrivacy.subscribe('visitorConsentCollected', (event) => {
privacy = event.customerPrivacy;
});
const forward = (name, data) => {
if (!privacy?.analyticsProcessingAllowed) return; // gate by consent
navigator.sendBeacon(
'https://your-middleware.example.com/shopify/events',
JSON.stringify({ name, data, privacy })
);
};
analytics.subscribe('product_viewed', (evt) => forward(evt.name, evt.data));
analytics.subscribe('product_added_to_cart', (evt) => forward(evt.name, evt.data));
analytics.subscribe('checkout_started', (evt) => forward(evt.name, evt.data));
analytics.subscribe('checkout_completed', (evt) => forward(evt.name, evt.data));
});
Middleware → Amplitude HTTP V2:
// Node/Express example
import express from 'express';
import fetch from 'node-fetch';
const app = express();
app.use(express.json({ type: '*/*' }));
app.post('/shopify/events', async (req, res) => {
const { name, data } = req.body;
const amplitudeEvent = {
event_type: name,
user_id: data?.customer?.id || undefined, // set when known
device_id: req.headers['x-client-id'] || undefined, // optional
time: Date.now(),
insert_id: data?.checkout?.order?.id || `${name}-${Date.now()}`,
event_properties: {
currency: data?.checkout?.currencyCode || data?.product?.price?.currencyCode,
total: data?.checkout?.totalPrice?.amount,
productId: data?.product?.id,
},
};
const endpoint = 'https://api2.amplitude.com/2/httpapi'; // use EU host for EU projects
const resp = await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ api_key: process.env.AMPLITUDE_API_KEY, events: [amplitudeEvent] })
});
const text = await resp.text();
res.status(200).send(text);
});
app.listen(3000);
Amplitude endpoint details and payload fields are described in the Amplitude HTTP API quickstart (2025). To debug browser events locally, use the Amplitude Chrome Instrumentation Explorer (2025).
Validation checkpoint
- In Amplitude, go to Data → Events and open the live stream per the Validate events guide (2025)
- Trigger a product view and add-to-cart; confirm event_type, properties, and user_id/device_id
Watch out
- Shopify’s Thank You/Order Status contexts can change payload semantics; Shopify announced a change to subtotal fields in 2025 in the Shopify Web Pixels subtotalPrice change note (2025)
A2) Server-side via Shopify Server Pixel or Webhooks → Amplitude
For revenue-critical events (purchase), prefer a server path for reliability and to bypass ad blockers.
Options:
- Server Pixel streaming to AWS EventBridge or Google Pub/Sub, then your processor → Amplitude. See Shopify’s ServerPixel object (2025).
- GraphQL Admin webhooks or EventBridge target to your stack → Amplitude. Shopify’s webhooks are covered in the Shopify Webhooks overview (2025).
Send to Amplitude with idempotency:
curl https://api2.amplitude.com/2/httpapi \
-H 'Content-Type: application/json' \
-d '{
"api_key": "YOUR_API_KEY",
"events": [{
"event_type": "purchase_completed",
"user_id": "customer_123",
"device_id": "",
"time": 1731782400000, // ms
"insert_id": "order_100045",
"event_properties": {"order_id": "100045", "value": 129.99, "currency": "USD"}
}]
}'
Validation checkpoint
- Trigger a test order; confirm only one purchase event arrives with insert_id = order_id
Watch out
- Always include at least user_id or device_id; Amplitude rejects events without either, as discussed in community threads and aligned with API behavior noted in 2025 (see the Amplitude community note on HTTP API v2 user_id/device_id requirement (2025))
- Use the correct regional host (api2 vs api.eu) per your project, per the Amplitude HTTP API quickstart (2025)
Shopify → Mixpanel
B1) Client-side via Mixpanel JS SDK (with consent gating)
Initialize Mixpanel only after analytics consent is granted via Shopify’s customerPrivacy API; see the Shopify customerPrivacy API (2025).
<script>
// Load Mixpanel JS (self-host or CDN)
(function(f,b){if(!b.__SV){var a,e,i,g;window.mixpanel=b;b._i=[];b.init=function(a,e,d){function f(b,h){var a=h.split(".");2==a.length&&(b=b[a[0]],h=a[1]);b[h]=function(){b.push([h].concat(Array.prototype.slice.call(arguments,0)))}}var c=b;"undefined"!==typeof d?c=b[d]=[]:d="mixpanel";c.people=c.people||[];c.toString=function(b){var a="mixpanel";"mixpanel"!==d&&(a+="."+d);b||(a+=" (stub)");return a};c.people.toString=function(){return c.toString(1)+".people (stub)"};i="disable time_event track track_pageview track_links track_forms track_with_groups add_group set_group remove_group register register_once alias unregister identify name_tag set set_once union unset remove delete_user opt_out_tracking opt_in_tracking has_opted_out_tracking clear_opt_in_out_tracking reset".split(" ");for(g=0;g<i.length;g++)f(c,i[g]);b._i.push([a,e,d])};b.__SV=1.2; a=f.createElement("script");a.type="text/javascript";a.async=!0;a.src="https://cdn.mxpnl.com/libs/mixpanel-2-latest.min.js";e=f.getElementsByTagName("script")[0];e.parentNode.insertBefore(a,e)}})(document,window.mixpanel||[]);
// Gate by Shopify consent
document.addEventListener('DOMContentLoaded', async () => {
// Suppose you expose privacy flags from your pixel to window.__privacy
if (window.__privacy?.analyticsProcessingAllowed) {
mixpanel.init('YOUR_PROJECT_TOKEN', { api_host: 'https://api.mixpanel.com', opt_out_tracking_by_default: false });
mixpanel.track('Product Viewed');
}
});
</script>
Identity merging and event tracking basics are documented in the Mixpanel JavaScript SDK guide (2025).
Validation checkpoint
- In Mixpanel → Live View, trigger a product view and confirm the event and properties are present
B2) Server-side for purchases via Mixpanel Node/HTTP
Server-side guarantees delivery for critical events and lets you enforce idempotency with insert_id. The Node SDK is documented in the Mixpanel Node.js SDK docs (2025).
import Mixpanel from 'mixpanel';
const mixpanel = Mixpanel.init(process.env.MIXPANEL_TOKEN, {
api_host: 'https://api.mixpanel.com' // use https://api-eu.mixpanel.com for EU
});
export async function trackPurchase({ orderId, userId, value, currency }) {
// time is seconds for Mixpanel
mixpanel.track('Purchase Completed', {
distinct_id: userId, // stable ID
insert_id: orderId, // idempotency key
time: Math.floor(Date.now() / 1000),
value,
currency,
order_id: orderId
});
}
Watch out
- /track is intended for events up to roughly the last few days; older events require /import per Mixpanel SDK notes. See the Mixpanel PHP SDK page on /track vs /import (2025) and Node SDK reference in the Node.js SDK docs (2025)
- Use alias once on signup, then identify consistently thereafter (per the JavaScript SDK docs (2025))
Validation checkpoint
- In Mixpanel Live View, place a test order; confirm a single Purchase Completed event with insert_id = orderId
BigCommerce → Amplitude
C1) Client-side via Script Manager (for engagement)
Install a storefront script via BigCommerce Script Manager and gate analytics on consent. BigCommerce’s Script Manager and script categorization are described in the BigCommerce Scripts API and management docs (2025).
Consent gating with BODL:
window.addEventListener('bodl_v1_update_consent', (event) => {
const { analytics } = event.detail || {};
if (!analytics) return;
// now safe to init light analytics or forward to server
navigator.sendBeacon('/bc/events', JSON.stringify({ name: 'page_view', ts: Date.now() }));
});
Note: Payment pages require script authorization (nonce) under PCI DSS 4.0. Plan on server-side capture for purchases. See BigCommerce Script Authorization on Payment Pages (2025).
Forward to Amplitude via your server as in Shopify A1.
Validation checkpoint
- In Amplitude Live events, confirm engagement events arrive with expected properties
C2) Server-side via BigCommerce Webhooks → Amplitude
Configure webhooks for orders and customers; verify signature; forward to Amplitude. BigCommerce webhook fundamentals and event types are covered in the BigCommerce Webhooks overview (2025).
Signature verification (Node):
import crypto from 'crypto';
function isValidBigCommerceSignature(rawBody, signatureHeader, clientSecret) {
const digest = crypto.createHmac('sha256', clientSecret).update(rawBody, 'utf8').digest('base64');
return crypto.timingSafeEqual(Buffer.from(digest), Buffer.from(signatureHeader || ''));
}
Forwarding to Amplitude (HTTP V2):
app.post('/bc/webhooks', async (req, res) => {
const signature = req.headers['x-bc-signature'];
const raw = req.rawBody; // ensure you have the raw body
if (!isValidBigCommerceSignature(raw, signature, process.env.BC_CLIENT_SECRET)) return res.sendStatus(401);
const payload = JSON.parse(raw);
if (payload.scope?.startsWith('store/order/')) {
const orderId = payload.data?.id;
await fetch('https://api2.amplitude.com/2/httpapi', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
api_key: process.env.AMPLITUDE_API_KEY,
events: [{
event_type: 'purchase_completed',
user_id: payload.data?.customer_id ? String(payload.data.customer_id) : undefined,
time: Date.now(),
insert_id: `bc_${orderId}`,
event_properties: { order_id: orderId, source: 'bigcommerce' }
}]
})
});
}
res.sendStatus(200);
});
Validation checkpoint
- Create a test order; confirm a single Amplitude event with insert_id = bc_{orderId}
Watch out
- BigCommerce documents the signature header and HTTPS requirements; design for at-least-once delivery and idempotency per the Manage Webhooks (2025)
BigCommerce → Mixpanel
D1) Client-side via Script Manager (for engagement)
Add Mixpanel in Script Manager; only initialize after BODL analytics consent.
window.addEventListener('bodl_v1_update_consent', (event) => {
const { analytics } = event.detail || {};
if (!analytics) return;
mixpanel.init('YOUR_TOKEN', { api_host: 'https://api.mixpanel.com' });
mixpanel.track('Product Viewed');
});
Validation checkpoint
- In Mixpanel Live View, confirm engagement events fire only after consent
D2) Server-side via Webhooks → Mixpanel
Use the same BigCommerce webhook receiver as in C2, but forward purchase events to Mixpanel’s server API using the Node SDK. Reference the Mixpanel Node.js SDK docs (2025).
import Mixpanel from 'mixpanel';
const mixpanel = Mixpanel.init(process.env.MIXPANEL_TOKEN, { api_host: 'https://api.mixpanel.com' });
app.post('/bc/webhooks', async (req, res) => {
// ...verify signature and parse payload...
if (payload.scope?.startsWith('store/order/')) {
const orderId = payload.data?.id;
mixpanel.track('Purchase Completed', {
distinct_id: String(payload.data?.customer_id || `anon_order_${orderId}`),
insert_id: `bc_${orderId}`,
time: Math.floor(Date.now()/1000),
order_id: orderId,
source: 'bigcommerce'
});
}
res.sendStatus(200);
});
Watch out
- Use EU host if your project is in the EU (api-eu.mixpanel.com), as noted in Mixpanel SDK docs (2025)
Validation checkpoint
- Confirm a single Purchase Completed event with correct distinct_id and insert_id in Mixpanel Live View
Consent and privacy checklist
- Shopify
- Read consent via customerPrivacy and subscribe to updates per the Shopify customerPrivacy API (2025)
- Do not initialize analytics SDKs or send events unless analyticsProcessingAllowed is true
- BigCommerce
- Use BODL bodl_v1_update_consent to gate analytics and categorize scripts correctly as described in the BigCommerce Scripts docs (2025)
- On payment pages, rely on server-side for purchases due to script authorization constraints per Script Authorization (2025)
Identity and deduplication strategy
- Amplitude
- Each event must include user_id or device_id; include insert_id to deduplicate as recommended in the Amplitude HTTP API quickstart (2025)
- Keep device_id for anonymous sessions and set user_id once known; use the same insert_id (e.g., order_id) for purchase
- Mixpanel
- Use alias once at signup to merge anonymous → identified, then identify on every subsequent session; per the Mixpanel JavaScript SDK docs (2025)
- Always set insert_id on server events for idempotency
- Cross-cutting
- Avoid double firing purchases (client and server). If you must keep client events for UX analytics, suppress purchase client-side, or detect duplicates server-side using order_id as a key
From experience: Ship purchases server-side by default. Ad blockers and checkout domain changes can silently drop client events.
Validation and QA workflow
- Amplitude
- Live event stream in Data → Events per the Validate events guide (2025)
- If using a browser SDK, debug locally with the Chrome Instrumentation Explorer (2025)
- Common errors: wrong region host, missing user_id/device_id, timestamps in seconds instead of milliseconds
- Mixpanel
- Use Live View and verify distinct_id and properties match expectations
- If backfilling events older than ~5 days, use /import per SDK notes in the PHP SDK doc (2025)
- BigCommerce
- Inspect webhook deliveries and retries in your logs; implement idempotency by storing processed insert_ids
- Shopify
- Test Thank You and Order Status pages; note that pixels also run on customer accounts and order status pages since 2025 per the Shopify pixels on accounts & order status note (2025)
Troubleshooting (quick fixes)
- Duplicate purchases in destination
- Ensure only server-side sends purchase; add insert_id = order_id; dedupe in middleware by ignoring repeated order_ids
- No events in Amplitude
- Check api2 vs api.eu host; ensure user_id or device_id present; verify API key; inspect 4xx responses with your middleware logs per the Amplitude HTTP API quickstart (2025)
- No events in Mixpanel Live View
- Confirm project token and api_host; verify distinct_id is a non-empty string; ensure time is seconds
- Client events missing on checkout pages
- On Shopify/BigCommerce, some checkout/payment contexts restrict scripts; rely on server-side
- Signature verification failing (BigCommerce)
- Ensure you read the raw body exactly and compute HMAC-SHA256 Base64 with your client secret; header is X-BC-Signature per the Manage Webhooks guide (2025)
Alternatives and extensions
- Tag Manager (e.g., GTM)
- Pros: faster iteration for client-side events; Cons: still subject to blockers; avoid for purchases
- Shopify Server Pixel → EventBridge or Pub/Sub
- Pros: native streaming; Cons: adds cloud infra; see ServerPixel object (2025)
- Warehouse/ETL sync (daily backfill)
- Pros: historical completeness; Cons: near-real-time analysis limited; for Amplitude bulk imports, note batch APIs in the Amplitude Batch Upload docs (2025)
Next steps: harden and monitor
- Logging and alerts
- Log all outgoing payloads and responses; alert on 4xx/5xx from Amplitude or Mixpanel
- Schema governance
- Document event names and required properties; enforce via code review and linting
- Observability
- Dashboard daily purchase counts vs platform backend to catch drop-offs early
- Backfill plan
- If you need to re-send older events, use Amplitude batch import and Mixpanel /import paths per their docs
You now have a robust, consent-aware pipeline from Shopify and BigCommerce into Amplitude and Mixpanel—with identity coherence, idempotent purchases, and clear validation. Ship client-side for rich engagement, server-side for money events, and keep your schemas and monitors tight.