Shopify

PennyLens auto-detects Shopify stores — including Shopify Plus, hydrogen-fronted storefronts, and the standard Online Store 2.0 themes. Drop the SDK snippet onto your storefront and product_view and cart_add start flowing without any further wiring; connect the order-creation webhook below for reliable purchase capture.

This page covers what auto-detect actually does, what to expect on Shopify's hosted checkout, and how to recover when events aren't flowing.

When auto-detect activates

When the SDK loads, it checks two signals. Either one enables the Shopify hooks:

  • window.Shopify.shop is a non-empty string — present on virtually every Shopify storefront, including custom-coded themes and headless setups that ship with the Shopify JS.
  • A <meta name="shopify-checkout-api-token"> tag — present on Shopify's checkout pages, catching themes where the window.Shopify global is stripped elsewhere on the site.

The check short-circuits on the first positive and silently no-ops on non-Shopify sites. The auto-detect logic ships in the core 8 KB SDK bundle.

Cart events

PennyLens intercepts the Shopify Ajax Cart API to capture cart_add without any theme modification:

  • /cart/add.js (and the GraphQL Storefront equivalent on hydrogen sites) — every line item added to the cart emits a cart_add event with product_id, variant_id, quantity, and the line total.
  • /cart/update.js and /cart/change.js — quantity edits and removals update the cart-state model used for abandonment analysis, but don't emit duplicate cart_add events.

Exactly one network-level interceptor is installed per page, shared with any other PennyLens e-commerce integration to avoid stacking patches.

Checkout and purchase

Shopify's checkout runs on Shopify-owned domains (or a checkout.* subdomain on Plus), inside a sandboxed context the storefront SDK can't reach. That limits what auto-detect can capture there:

  • checkout_step — step-by-step checkout progress is manual-only today. Shopify's checkout.liquid sandbox blocks the SDK from injecting into the checkout iframe, so auto-detect cannot fire this event pre-Aug-2026. If you need step-level dropoff before then, wire it manually with pennylens.track('checkout_step', ...). (Shopify is sunsetting checkout.liquid on Aug 26, 2026 in favor of the Web Pixels API, which will let PennyLens auto-capture this — this doc will update when that ships.)
  • purchase (client-side, best-effort) — emitted when a /checkout/.../thank_you (or /checkouts/{token}/thank_you) URL loads, reading window.Shopify.checkout for total, currency, and order_id. That object is not guaranteed to be present on every store, so treat this as a best-effort signal, not a reliable one.
  • purchase (server-side, recommended) — a Shopify order creation webhook delivered straight to PennyLens's ingestion service. This is the reliable path: it fires from Shopify's servers regardless of what happens in the customer's browser, and it's the only path that carries a hashed customer identity for per-customer attribution.

Without the webhook connected, purchases still capture via the client-side signal above, but customer identity stays anonymous beyond the order ID.

Connect the order-creation webhook

Set this up once per project for reliable, per-customer purchase capture:

  1. In PennyLens, open your project's Settings → Shopify Webhook panel.
  2. Copy the Webhook URL shown there.
  3. In Shopify Admin, go to Settings → Notifications → Webhooks and click Create webhook.
  4. Set Event to Order creation and Format to JSON.
  5. Paste the Webhook URL from step 2 into Shopify's URL field, then save.
  6. Shopify displays a Signing secret back to you — copy it, paste it into the Signing Secret field in PennyLens Settings → Shopify Webhook, then click Regenerate to save it.

PennyLens verifies every delivery against that signing secret (HMAC-SHA256) before accepting it, and de-dupes retries automatically — Shopify's own retry behavior on a slow response won't double-count an order.

Mutual exclusion with WooCommerce

If both Shopify and WooCommerce signals appear on the same page — usually only on agency development sites — Shopify takes precedence and the WooCommerce auto-hook silently no-ops. Manual wiring still works on dual-signal sites.

Zero-event fallback

If 24 hours after install you don't see any events in the PennyLens dashboard, walk through these in order:

  1. Confirm the SDK is loaded. Open your storefront's browser developer console and run window.PennyLens. It should be defined. If it isn't, check that the snippet is in your theme's theme.liquid <head> block — not just on /products/* pages.
  2. Confirm Shopify is detected. In the same console, run window.Shopify?.shop. It should be a non-empty string. If it's undefined, your theme has stripped the standard Shopify JS — fall back to manual wiring.
  3. Check the order-creation webhook. If the dashboard shows cart_add events but no purchase events, the order status page redirect may be skipping the standard /thank_you route — or you haven't connected the webhook yet. Connect the order-creation webhook (above); it captures purchases directly from Shopify's servers and doesn't depend on the customer's browser reaching /thank_you.
  4. Headless storefronts. Hydrogen and other custom storefronts that don't set window.Shopify.shop or render the shopify-checkout-api-token meta tag won't trigger auto-detect. Use the manual snippet in the SDK reference — same API as any custom event.

Confidence indicator

Once at least one cart_add or purchase event has arrived, the dashboard Overview displays a confidence badge above the KPI row:

Shopify detected — 1,247 events captured

The badge is informational — no action required when it appears. If you don't see it after 24 hours, return to the Zero-event fallback above.

Manual wiring

If your storefront falls outside the auto-detect signals — headless hydrogen sites, custom-coded themes that strip Shopify's standard globals, or storefronts behind a CDN that hides Shopify origin scripts — wire the events manually:

pennylens.track("cart_add", {
  product_id: 7281928392,
  variant_id: 41827382,
  qty: 1,
  total: 49.0,
  currency: "USD",
});

Same API as any custom event. See Event Tracking for the full reference.

Next steps