Skip to main content
Tracking ships as a theme app extension with two blocks: an app embed (the tracker, enabled once per theme) and the Live View Counter section block (added to the product template). The embed collects events; the counter only reads an aggregate count and tracks nothing itself. Merchants enable both in the theme editor — see Theme setup.

Event types

Cart detection wraps both fetch and XMLHttpRequest for cart mutation URLs, then compares the cart state before and after the call. Legacy non-AJAX form posts to cart add URLs are beaconed optimistically. Heartbeats are skipped when no product identity exists on the page.

Anonymous identity

  • The session id is a client-generated UUID stored in localStorage and regenerated after 30 minutes of inactivity. Pages that block storage fall back to a per-page random id.
  • Product identity is the numeric product id when the theme exposes it, otherwise the URL handle from /products/<handle>. The variant id is attached when present.
  • Device class (mobile, tablet, desktop) derives from the user agent.
  • Referrer host and UTM parameters are attached only to the first event of a session; later events carry no source data.

View dedupe (two layers)

  1. Client, per tab — a sessionStorage flag records that this tab already reported a view of this product. The flag is written only after the send actually succeeds, so a blocked or failed send stays retryable.
  2. Server, per shop + session + product — a configurable window (default 30 minutes, merchant-adjustable 5–1440 in Settings) prevents duplicate view events. The server-side window is what keeps counts honest across reloads and repeated navigation.
Collection is gated by the Shopify Customer Privacy API and only runs when analytics processing is allowed:
  • At boot the tracker waits up to ~4 s for the consent API, polling and explicitly loading the consent feature if needed.
  • A visitorConsentCollected subscription restarts evaluation when the visitor answers the banner, with a slow fallback re-check for late flips.
  • Every outgoing batch re-checks consent before sending.
  • A first-party opt-out flag in localStorage (_lpi_opt_out = 1) blocks sending regardless of the API state.
  • Storefronts without the consent API (unmanaged) proceed; an API error also fails open rather than breaking the page.

Transport

  • Events are batched (POST, JSON body { "events": [...] }, up to 25 per batch) to the app-proxy endpoint with the shop domain as a query parameter.
  • Non-cart events are debounced ~900 ms; cart events flush immediately.
  • pagehide and tab-hide flush the queue via sendBeacon; the request is keepalive and same-origin.
  • Failed flushes retry up to 5 times with increasing backoff, then drop the queue rather than retry forever.
The app proxy strips cookies, so requests carry no server-side identity — the HMAC-signed shop parameter and the anonymous session UUID are all the identity that exists.

Live View Counter block

  • Polls the stats endpoint immediately, once more after ~3 s (so the visitor’s own first heartbeat is counted), then every 5 s; it also refreshes when the tab becomes visible or the section re-renders.
  • Renders only when the response is visible: true and count > 0; in every other case it hides itself. It never displays zero or a placeholder number.
  • Supports singular and plural wording automatically. The label text and the dot, text, and background colors are merchant-configurable in the theme editor; the label default is “people are viewing this right now”.
  • Per-product suppression and the minimum-views threshold are enforced server-side (see Settings), so the block cannot be tricked into showing a suppressed product.