
Author Name
Emtiaz Hossain
A UK Shopify jewellery brand spending £18,000+ a month on Google Ads asked for a routine GA4 and GTM audit ahead of a Shopify deprecation deadline. The audit found two active problems that had been quietly corrupting campaign performance the entire time: conversions counted twice, and a chunk of purchases with no ad click attached to them at all.
Two GTM tags were firing to the same Primary Google Ads conversion action on every purchase. The legacy tag sent no order ID, so Google couldn't deduplicate it against the current one. Over a 28-day window, the account reported 625+ conversions worth £66,000+. The real number, once the duplication was accounted for, was approximately 300 to 310 purchases. Every Performance Max campaign and the brand Shopping campaign were running Target ROAS bidding on that inflated data, meaning Smart Bidding had been optimizing toward a conversion volume that didn't exist.
The Conversion Linker tag's domain list covered the storefront domain but not checkout.shopify.com. A GCLID cookie set when someone clicked a Google ad and landed on the storefront couldn't be read once the browser moved to the separate checkout domain, and there were no tracking templates configured anywhere in the account as a UTM fallback. The purchase conversion still fired, but with no GCLID attached, so Google Ads had no way to connect it back to the ad click that earned it. Google's own Tag Diagnostics flagged this directly, it had been sitting there unaddressed.
A related but distinct problem: shopify.com wasn't on the GA4 referral exclusion list, so when checkout sessions redirected back to the store, GA4 attributed the purchase to shopify.com as a referral instead of the original marketing channel. Over 90 days this misattributed 1,015 sessions, 48 purchases, and £5,856 in revenue away from the channels that actually earned it. This is a separate setting from cross-domain tracking, both need configuring independently, and only one had been done.
A GTM variable feeding Enhanced Conversions was reading the wrong dataLayer field, sending a two-letter region code ("ENG", "SCT") as the postal code on every hit instead of an actual postcode, degrading Meta and Google's ability to match the hashed customer data. Separately, a currency variable's fallback default was set to EUR on a store that transacts entirely in GBP, a silent bug that would only surface if the real currency value was ever missing from the dataLayer. Neither had thrown an error. Both had been quietly shipping bad data.
Shopify was removing the legacy "Order status page additional scripts" mechanism for non-Plus merchants on a fixed date. The store's entire GTM setup depended on that mechanism plus a custom pixel using a postMessage bridge to talk to a GTM container loaded via theme.liquid. Once Shopify's change landed, both pieces would stop working together. Google's own documentation is explicit that running Google tags inside a Shopify custom pixel isn't a supported implementation in the first place, an unrelated finding that made the deadline more of a forcing function than a surprise.
Phase 1 fixed what was actively broken, before touching the migration:
shopify.com and shop.app to the GA4 referral exclusion listbegin_checkout tag set to fire unlimited times per session instead of once, which had been inflating the GA4 checkout funnelPhase 2 migrated the store off GTM entirely, onto Shopify's native Google & Youtube App, which fires directly from Shopify's Customer Events system with no dependency on theme.liquid:
theme.liquid and disconnected the GTM custom pixel from Customer EventsA real test purchase was run through the new setup and checked via Tag Assistant and the GA4 debugger:
ad_storage and analytics_storage consent grantedThe old Google Ads conversion action was kept active as a Secondary, reporting-only action through a 10 to 14 day monitoring window, so Smart Bidding had stable data to recalibrate against and there was a clean before/after comparison. It was removed once the monitoring period confirmed zero conversions were still routing through the old path.
Check whether more than one tag fires to the same conversion action on the same trigger, and whether each one includes an order ID or transaction ID for deduplication. If a legacy tag lacks that field, every hit it sends counts as a brand new conversion no matter how many times the same order already fired elsewhere. Cross-referencing the platform's reported conversion count against actual order volume in the backend is the fastest sanity check.
Smart Bidding algorithms need a stable run of consistent data to recalibrate after any tracking change. Running the new setup as Secondary and the old one as Primary (or vice versa, whichever was live longer) for a monitoring window means there's a real before/after comparison and a safe rollback if the new setup has an issue, instead of finding out two weeks later with no baseline to compare against.
For standard ecommerce events, no, native apps like Shopify's Google & YouTube App cover the core conversion events out of the box. The tradeoff shows up with custom event structures or sending data to multiple third-party platforms simultaneously (Meta, TikTok, Pinterest, etc.), which is where a server-side GTM setup becomes the stronger long-term option since it can fan one clean data source out to every platform at once.
Discussed this on LinkedIn
Found this helpful?
Drop a comment on the LinkedIn post – I read every reply and love connecting with tracking folks.
Book a free 30-minute call. I will review your current tracking setup, show you where conversions are leaking, and tell you exactly what I would fix.
How a UK marketing agency's LinkedIn Insight Tag upgrade turned into a full conversion audit, catching duplicate tags and stale triggers across eight lead-gen conversions.
Duplicate form conversions in GA4 are usually a reload firing the same tag twice, not a real second submission. Here's the Custom JavaScript variable that catches it, plus other common causes to rule out.
WPForms has two submission modes, AJAX and non-AJAX, that need completely different GTM tracking patterns. Here's the real code for both, plus the validation logic non-AJAX forms need.