We’re upgrading our email infrastructure — for immediate response, email andrewjgaber@gmail.com meanwhile.
Skip to main content
Part of Digital Empire
Product analytics / CDP

Mixpanel mixpanel.track('Purchase') + people.track_charge at Shopify checkout

Mixpanel · Web Pixels API replacement · confidence: high

Dies 2026-08-26

Paste-ready snippet
// Wave4-Hotel7 (2026-08-20): Mixpanel Purchase event + Revenue charge
// replacement. Mixpanel is the second-most-common product-analytics
// platform after Amplitude for DTC + subscription commerce, especially
// for stores instrumenting subscription-billing / churn funnels. The
// legacy install pastes mixpanel.init(TOKEN) + mixpanel.track('Purchase',
// {...}) + mixpanel.people.track_charge(amount, {...}) into Shopify
// Additional Scripts. All three calls stop firing 2026-08-26.
//
// Two variants: pick ONE.
//
// ============================================================
// VARIANT A -- BROWSER-SIDE (Shopify Web Pixels sandbox).
// Loads the current Mixpanel JS library (mixpanel-2-latest.min.js) from
// cdn.mxpnl.com inside the pixel sandbox. MIXPANEL_PROJECT_TOKEN is the
// documented client-safe token (write-only, per-project rate-limited).
// ============================================================
(function loadMixpanel() {
  if (window.mixpanel && window.mixpanel.track) return;
  const s = document.createElement('script');
  s.async = true;
  s.src = 'https://cdn.mxpnl.com/libs/mixpanel-2-latest.min.js';
  s.onload = () => window.mixpanel.init('MIXPANEL_PROJECT_TOKEN', { track_pageview: false, persistence: 'localStorage' });
  document.head.appendChild(s);
})();

analytics.subscribe('checkout_completed', (event) => {
  const { checkout } = event.data;
  if (!checkout || !checkout.email || !window.mixpanel) return;

  const distinctId = checkout.order
    ? String(checkout.order.customerId || checkout.order.id)
    : checkout.email;

  // Identify BEFORE track() so the Purchase event is attached to the
  // logged-in profile (not the anonymous device_id).
  window.mixpanel.identify(distinctId);
  window.mixpanel.people.set({
    $email: checkout.email,
    $last_purchase: new Date().toISOString(),
  });

  // Revenue: track_charge populates the Mixpanel Revenue report and per-
  // profile Lifetime Value. Fire it BEFORE the Purchase event so revenue
  // is attached to the same profile+session.
  window.mixpanel.people.track_charge(Number(checkout.totalPrice.amount), {
    order_id: checkout.order ? String(checkout.order.id) : checkout.token,
    currency: checkout.totalPrice.currencyCode,
  });

  window.mixpanel.track('Purchase', {
    order_id: checkout.order ? String(checkout.order.id) : checkout.token,
    total: Number(checkout.totalPrice.amount),
    currency: checkout.totalPrice.currencyCode,
    item_count: (checkout.lineItems || []).length,
    // Mixpanel best-practice: flatten arrays of primitives, not deep object arrays --
    // deep arrays cost more per event and cannot be filtered in Mixpanel's UI.
    product_ids: (checkout.lineItems || []).map((li) => li.variant && li.variant.product ? String(li.variant.product.id) : null).filter(Boolean),
    product_skus: (checkout.lineItems || []).map((li) => li.variant ? li.variant.sku : null).filter(Boolean),
    product_names: (checkout.lineItems || []).map((li) => li.title),
  });
});

// ============================================================
// VARIANT B -- SERVER-SIDE (Mixpanel Import API + Shopify webhook).
// Recommended -- Mixpanel's Import API is the authoritative path for
// financial events (invoicing, subscription renewals) and gives you
// audit-grade dedupe via $insert_id.
// Docs: https://developer.mixpanel.com/reference/import-events
// ============================================================
//
// // Shopify webhook: orders/paid
// export async function handleShopifyOrderPaidForMixpanel(order) {
//   const auth = Buffer.from(process.env.MIXPANEL_SERVICE_ACCOUNT_USER + ':' + process.env.MIXPANEL_SERVICE_ACCOUNT_SECRET).toString('base64');
//   await fetch('https://api.mixpanel.com/import?strict=1&project_id=' + process.env.MIXPANEL_PROJECT_ID, {
//     method: 'POST',
//     headers: {
//       'Authorization': `Basic ${auth}`,
//       'Content-Type': 'application/json',
//     },
//     body: JSON.stringify([{
//       event: 'Purchase',
//       properties: {
//         distinct_id: String(order.customer ? order.customer.id : order.id),
//         time: Math.floor(new Date(order.created_at).getTime() / 1000),
//         $insert_id: 'shopify-order-' + order.id,  // dedupe key
//         order_id: String(order.id),
//         total: Number(order.total_price),
//         currency: order.currency,
//         revenue: Number(order.total_price),
//       },
//     }]),
//   });
// }

Legacy pattern detected via: mixpanel\.track\(\s*['"]Purchase['"]|mixpanel\.track\(\s*['"]Order Completed['"]|mixpanel\.people\.track_charge

What this does NOT cover

What this does NOT cover: Mixpanel Group Analytics (B2B account-level rollups), Mixpanel Session Replay (separate script), pre-purchase funnel events (Product Viewed / Added to Cart / Checkout Started -- those belong in theme.liquid or separate Web Pixels subscribers), or Mixpanel's Alias() flow for merging pre-login anonymous ids to a signed-in user (that must happen at login, not at checkout_completed).

Test-event verification checklist

  • Fire `people.track_charge` BEFORE `track('Purchase', ...)` in Variant A -- track_charge is what populates the Revenue report and per-profile LTV, and it must happen while the identify() call is still resolving the profile. Track_charge without identify() attaches revenue to an anonymous device_id and never merges with the user record.
  • Verify a live purchase in Mixpanel Live View (Data > Live View) within 30 seconds. Confirm distinct_id matches the customer id or email, the Purchase event has all expected properties, and the profile card (Users > lookup) shows the new charge on the Revenue tab. If Live View shows the event but Revenue is empty, track_charge was not called.
  • Duplicate-check: if Mixpanel is a destination from Segment (via the segment-analytics-track-order-completed snippet) OR from an ETL like Rudderstack/Hightouch that already forwards Shopify orders, do NOT also ship this snippet -- Purchase will double-count and skew LTV cohorts. Choose exactly one ingestion path per event.
  • Verify live against the platform's own test-event tool: https://docs.mixpanel.com/docs/data-structure/events-and-properties

Related paste-ready snippets

Product-analytics + CDP + CRM · HubSpot, Segment, Amplitude, Mixpanel, PostHog — every DTC brand ships at least one of these and all break the same way Aug 26.