A customer sees an ad on a phone during a commute, looks the product up on a laptop that evening, and buys two days later after a search for the brand name. Shopify records a branded search. The ad that created the demand is invisible.

Multiply that by every channel and you get the report most eCommerce teams are looking at: a large direct-traffic bucket, a large branded-search bucket, and no basis for moving budget.

Parameters are lost before your script runs

The first failure is the most easily fixed and the most often missed. Campaign parameters are frequently stripped by a redirect — a link shortener, a consent gate, an app-specific route — that fires before any client-side script executes.

So capture happens server-side, on the first request, and is persisted immediately against a session identifier. Whatever the client-side layer does afterwards, the originating campaign is already recorded.

GET /products/brass-faucet?utm_source=google&utm_campaign=spring
  → record { source, campaign, referrer, ts } server-side, now
  → then redirect / render as normal

Stitching identity without a login

Joining a phone session to a laptop session is a probabilistic exercise, and the discipline is in refusing weak joins. A shared network alone is not evidence — a coffee shop would merge strangers.

  • Strong: an email entered in both sessions, or a logged-in account
  • Moderate: the same device characteristics returning across sessions
  • Weak on its own: shared IP address, or timing proximity
  • Never join on weak signals alone, however tempting the coverage improvement

Every join carries a confidence level, and reports can be filtered by it. A join that cannot be justified is left unjoined.

Events fire twice, or not at all

Cart, quote and checkout events are unreliable in practice: page unloads mid-request, users double-submit, apps fire duplicates. Deduplication on a deterministic event key is cheap and prevents inflated numbers that quietly discredit the whole report.

Report unknown as unknown

The single most valuable decision in this work is refusing to attribute what cannot be attributed. Assigning an unresolved order to direct traffic makes coverage look complete and makes the report useless.

In one production build, 94% of orders resolved to a genuine originating source. The remaining 6% is labelled unknown. The marketing team can act on 94% they trust far better than on 100% they do not.

Reconcile, do not argue

GA4, the ad platform and Shopify will never agree exactly — they use different windows, different identity models and different definitions of a conversion. Building a reconciliation view that shows the difference and explains its cause ends the recurring meeting about which number is right.

  • Compare on the same date range and the same attribution window
  • Expect ad-platform conversions to exceed yours; they count view-through
  • Treat your own order records as the source of truth for revenue
  • Document each known cause of divergence once, then reference it