Is Your Shopify Store Under-Reporting Sales? What We Learned Moving Stores to Server-Side GTM

Server-side tracking lessons for Shopify stores

If you run a Shopify or headless Shopify store and your GA4 reports fewer purchases than your back-end, or returning Safari customers keep showing up as new visitors, the cause is usually in your tracking setup.  We deal with tracking on Shopify stores almost every day, and so we have built up a database of issues we have come across. These issues are often related to how Shopify loads tags, sandboxes the checkout and resolves to its own IP range, and what that does to your cookies and click IDs. That’s apart from frequent updates from Shopify itslef.

After each project, we conduct a review of how it went, and what were the key learnings. For example, across the Shopify stores we have moved to server-side GTM on Stape, the server containers have captured 8% to 11% more conversions than the client-side setups they replaced. In Google Ads the rise has been 8% to 15%. At £500k a year of paid media, that means bids being set on a conversion count that is short by up to one in seven.

The checklist at the bottom of this post is a reference document for Shopify projects. It covers the migration itself, the Safari cookie cap that catches most stores, and consent and headless storefronts. The percentages come from multiple projects, so check them against your own store’s data before you quote them to anyone. Jump to the checklist.

What You Are Dealing With on Shopify

A Shopify implementation has several separate moving parts, and it can appear complex. Some of the problems we have seen came from not appreciating which part is responsible for loading GTM and which are creating the ecommerce events.

  • The Stape Shopify app (app embed) loads web GTM on storefront pages.
  • The Stape Custom Pixel listens to Shopify Customer Events and creates the checkout and purchase events, because normal theme GTM is not available inside checkout.
  • The theme may still contain old GTM loaders, data layer code and custom click tracking.
  • Other Shopify apps may be sending GA4 and Google Ads data of their own. A common one is the Google and YouTube app. 
  • The server container on Stape receives all of it, re-sets cookies and forwards to GA4, Google Ads and Meta.

Each one can double, drop or mislabel data on its own. Together they explain almost every “GA4 doesn’t match Shopify” ticket we have been asked to look at.

1. Migrating a Shopify Store to Stape Server-Side GTM

One rule shapes everything below. We never switch a store’s tracking over in one go. Always run server-side in parallel with the existing client-side setup, and send the server-side data to a new GA4 property created for that purpose. The store keeps reporting from the main GA4 property, and the server-side container sends data to your new test property. You can discover many issues simply by comparing the data from the two properties, and this allows you time to fix them before switching off your client-side tags. We have a template Looker Studio dashboard to compare the data and connect Claude AI to GA4 to automate this process. 

Know What Loads GTM and What Creates the Ecommerce Data

Once you have implemented the Stape Conversion Tracking App, you should be able to remove GTM script from the store front pages. Before doing this, check that the app’s GTM injection has not been turned off. In some instances, the Stape app GTM injection does not cover all store front theme pages, and so a GTM snippet will be required on those page.  Alternatively, if ‘Rely on Shopify Customer Privacy API consent states’ is enabled, or another part of the implementation is gating the app’s loader according to Shopify consent, this could prevent GTM from loading for new visitors. Investigate how consent has been implemented and check the Shopify set up in case you have conflicting consent status mechanisms. 

Only one mechanism should load web GTM. If the Stape conversion app is enabled, and there is also a loader in the theme,  both will run, and the same container loads twice. This presents a risk of duplicate events. Once the app is proven to load GTM, comment out or remove the theme code. Check the live page source and the network requests, because the app settings screen does not always reflect what is happening.

Use the Stape Shopify Conversion app rather than treating Shopify as an “Other” platform. Configure the web GTM ID, the custom tagging domain, the Custom Loader identifier and the ecommerce data layer in the app. It is the setup we recommend on every standard Shopify store. 

Set theme data layer values before the loader runs. Anything GTM needs from the theme should be in place before the Stape loader initialises.

Audit Shopify apps as well as GTM. Ask specifically whether any app sends GA4, Google Ads, Meta or TikTok data. On more than one store we discovered the Google & YouTube app was a second source of tracking. It did not need to be enabled as they already had GTM implemented. The app also creates its own Google Ads conversion actions, and they are often set as primary. Demote or remove them once the sGTM conversions are live, or Google Ads bids on both.

Check the ad platform apps for their own server events. Shopify’s Facebook & Instagram app on “Maximum” data sharing already sends Conversions API events from Shopify’s servers, and the TikTok and Pinterest apps can do the same. If you add Stape’s Meta CAPI tag as well, purchases may be counted twice unless both sources send the same event_id or one of them is switched off. Check the deduplicated event counts in Events Manager before and after the change.

Remove legacy and custom code rather than layering Stape on top. Custom click tracking published through Shopify.analytics and old checkout scripts can make debugging much harder than it needs to be.

Checkout and the Custom Pixel

The Custom Pixel does a different job from the app embed. It runs in a sandbox, so you have to test it differently.

Purchases can reach GA4 Realtime without appearing in the page data layer. Console messages such as “document is sandboxed and lacks allow-same-origin” are expected and do not mean tracking has failed. Web GTM Preview is not dependable inside checkout, so validate with sGTM Preview, the network requests, Shopify’s pixel console output and GA4 together. 

Do not edit Stape’s generated fetch() code in the Custom Pixel. Always use the Stape generated Ecommerce custom pixel code and avoid modifying it if possible. We have sometimes been advised to modify the script, such as replacing it with a script injection, but this can break the data layer. There is no need to have a separate GTM loader in the custom pixel because  the Stape-generated pixel already loads the container itself. 

Fix /web-pixels@... URLs in GA4. The sandbox reports its own URL as the page. Send event.context.window.location.href as page_location, especially for checkout events. sGTM has no browser of its own, so the server GA4 tag must pass the incoming page_location through.

Storefront cookies do not automatically reach checkout. A gclid persisted across the storefront is not guaranteed to be readable inside the sandboxed checkout. Read cookies in the pixel through the Web Pixels API (browser.cookie.get) and confirm in sGTM Preview that the client ID and click IDs arrive with the purchase.

Check the event schema after changing the data source. value, TotalValue, transaction_id, OrderId and email can all change name when you move to the Stape app. Build your Event Data variables from what actually reaches sGTM, not from the old container.

Know which legacy events still matter. Audit custom event tags to identify any legacy tags that are being used by the store for campaign tracking and optimisation.  These should not be removed until the Ads migration is  finished, and replacement server-side events have been tested and verified. 

Check what stopped on the thank-you page. Shopify retired Additional Scripts on the Thank you and Order status pages for Plus stores on 28 August 2025, and auto-upgraded all other stores on 26 August 2026. Any purchase tags that lived there no longer run. If purchases dropped around either date, check there before looking at the server container.

Post-purchase upsells change when the purchase fires. With an upsell app installed, checkout_completed fires on the first upsell page, before the customer accepts or declines the offer, so upsell revenue is not in the purchase value. If that page fails to load, the purchase may not be tracked at all.

Consider a server-side purchase backup. Some buyers never return from PayPal, Klarna or a 3D Secure check, and some block the pixel. The Stape Shopify app can send order webhooks to the server container, so the purchase reaches sGTM from Shopify itself rather than the browser. To attribute those orders, store the client ID and click IDs on the cart (as cart attributes) while the visitor is on the site, and deduplicate on transaction_id so an order from the pixel and the webhook is only counted once. Refund webhooks can send refund events to GA4 at the same time.

Express checkouts skip steps. Shop Pay, Apple Pay, Google Pay and PayPal buttons on product and cart pages can bypass begin_checkout, add_shipping_info and add_payment_info. Lower funnel step counts on those journeys are not a tracking fault if the purchase still arrives.

Attribution, Testing and Google Ads

Attribution needs more attention than event firing. The first big difference between the two properties in the parallel run was far more “Other” traffic on the server-side one. Persisting UTMs and click IDs (gclid, wbraid, gbraid, fbclid, msclkid) through to begin_checkout fixed it before switch-over.

Add payment providers to unwanted referrals in both GA4 properties: paypal.com, klarna.com, Clearpay, your 3D Secure domains and shop.app. Otherwise a buyer returning from a payment page can have the purchase credited to the provider as a referral.

Test as a brand-new visitor. Existing browsers keep GA and consent cookies, so a setup can look healthy while new sessions fail. Incognito and new-session tests can help expose loading failures.

Tag Assistant is not enough on its own. Use the page source, the network requests, the pixel console, sGTM Preview and GA4 Realtime together. To read the full Shopify checkout data layer, Chrome DevTools Local Overrides lets you swap one script for a local copy with extra logging, in your own browser only, without touching the live site. A local proxy such as Caddy does the same job outside Chrome, but needs a locally trusted certificate and fails if the store uses strict CSP hashes on that script. Either way, anything learned is a diagnostic, not a deployment path. The real implementation still has to go through Shopify’s Custom Pixel or the Stape app.

“Server-side must be higher” is not a safe acceptance test. sGTM usually showed more users and better paid-traffic recovery, but purchase counts were occasionally slightly lower. Investigate the difference rather than assuming the bigger number is right.

Some of the extra users can also be bots that client-side tags never saw. Check for unusual sources and very short sessions before accepting an uplift, and consider Stape’s bot detection power-up.

A tag firing is not a conversion validated. Check the transaction ID, value, currency, click IDs and the outgoing Google Ads request.

Match product IDs to your feeds. item_id must use the same format as the Merchant Center feed (the Google & YouTube app uses shopify_GB_productID_variantID), and Meta content_ids must match the catalogue. If they don’t, dynamic remarketing and conversions with cart data stop matching products, and nothing reports an error.

Send user-provided data with the purchase. checkout_completed includes the customer’s email and phone. Hash them and send them to Google Ads enhanced conversions and Meta advanced matching, gated on ad_user_data consent. This matters more as Safari strips click IDs in more places (see section 2).

Test revenue and zero-value orders separately. Use value > 0 for purchases and value = 0 for samples in sGTM rather than recreating legacy triggers.

Run client-side and server-side Google Ads in parallel first. Create new goals for sGTM tags, but configure them as a secondary data source so they don’t impact campaign optimisation. On average,  server-side purchase conversions in Google Ads are around 9% higher and revenue about 8% higher than client-side.  Analysis of the data indicates the gains mainly come from better click ID recovery.  This is an average, and so for one Shopify store we saw an 11% increase in Google Ads conversion rate and an 11% reduction in acquisition costs.

Part of that improvement is a reporting change rather than a saving: when the same clicks record more conversions, the reported cost per acquisition falls on day one. The lasting gain comes from bidding on the fuller conversion count, so judge it over several weeks against back-end sales, not on the first report.

Check attribution in the parallel run as well as event counts. On one store the server-side property showed Direct revenue rising sharply with no matching rise in Direct traffic. The comparison should cover source/medium, sessions, transactions, revenue and conversion rate by channel, and the switch waits until they match.

Compare like with like against your back-end. Use Online Store channel orders only, excluding POS, draft orders, subscription renewals (Recharge, Shopify Subscriptions) and B2B orders, which never pass through the storefront. Match the timezone, the revenue definition (tax, shipping and discounts) and the currency. On stores using Shopify Markets the pixel reports the currency the customer paid in, while Shopify reports in the shop’s currency.

Setting Up the Parallel Run in GTM

The parallel run is built in Tag Manager before anything touches the Shopify theme. The order matters.

  1. Create the server container in Google Tag Manager first. Stape asks for the container’s manual configuration key when you create the Stape container, so the Tag Manager container has to exist before the Stape one.
  2. Create the Stape container with that key, add the custom tagging domain and confirm the server container URL responds. Check the Stape plan at the same time: the duplicated tags roughly double server requests for the length of the parallel run, so confirm the request limit and what happens if you exceed it. For UK and EU stores, host the container in an EU zone.
  3. Duplicate every GA4 event tag in the web container. Export the GA4 event tags, then import them straight back into the same container with Merge and Rename conflicting. You now have a second copy of each GA4 event alongside the original.
  4. Create a new Google Tag, a new Google Tag Configuration Settings variable and a new Event Settings variable. Add server_container_url to both as a constant variable holding the Stape domain. Point the duplicated GA4 event tags at the new Google Tag and the new Event Settings variable. Leave the originals untouched, so client-side keeps sending exactly as before.
  5. Keep the GA4 client in the server container on JavaScript Managed during the parallel run. Server Managed sets a new FPID cookie and uses it as the client ID, so unless “Migrate from JavaScript Managed Client ID” is ticked, existing visitors get new IDs and the two properties stop being comparable. On a standard Shopify store, FPID is also set from Stape’s IP range, so Safari caps it at seven days like _ga (see section 2).
  6. Enable the GA4 client and create the server GA4 event tag with a trigger of Client Name equals GA4, sending to a new GA4 property created for the parallel run, not the reporting property. The store’s reports stay on the existing property, fed by the original client-side tags, until the two agree. Where several reporting properties are involved, route them with a constant or a RegEx table.
  7. Set up the new GA4 property like the reporting one: the same timezone and currency, unwanted referrals, cross-domain settings, 14-month data retention, key events, custom dimensions, internal traffic filters and the Google Ads link. Otherwise some of the differences between the two properties come from configuration, not tracking. Expect new-user counts in the new property to run high for the first few weeks.
  8. Check that checkout events reach the new property in the same session. Events from the Custom Pixel must fire the duplicated GA4 tags as well as the originals. If they arrive under a different stream or without the session cookie for the new measurement ID, checkout can start a new session and purchases show as Direct or Unassigned.
  9. Create the marketing pixels in the server container (Google Ads, Meta and the rest) and the Event Data variables they need: UTMs, click IDs, client_id and session_id. URL parser variables on page_location capture UTMs and other parameters.
  10. Send UTMs and click IDs with the GA4 event tags. If unassigned traffic rises in the server-side property, check which events are arriving without them.

The Migration Order We Use Now

Do not switch everything at once. The sequence for a Shopify store is:

  1. Inventory every mechanism that can load GA4, GTM or Google Ads: theme code, Customer Events pixels, Shopify apps and third-party scripts.
  2. Remove or disable competing implementations, so there is one GTM loader.
  3. Record a baseline of GA4 users, sessions, source/medium, purchases and revenue.
  4. Build the parallel run in GTM as above: server container, Stape container, duplicated GA4 events pointed at the new Google Tag. Then install the Stape app and Custom Pixel and map the event schema that reaches sGTM.
  5. Run client-side and server-side in parallel. The original tags keep sending to the reporting property; the server container sends to the new property. Server-side Ads conversions run as secondary actions.
  6. Test paid, organic, email and affiliate journeys through checkout as a new visitor, and compare Shopify orders with GA4 and Google Ads.
  7. Switch over. Once the two properties agree, point the server GA4 tag at the reporting property and pause the client-side GA4 tags. Check the reporting property stays healthy, then make the sGTM Ads conversions primary and pause the client-side Ads tags.
  8. Remove legacy pixel logic last.

2. Why Returning Safari Customers Look Like New Visitors

The Seven-Day Cap on Shopify

Since Safari 16.4, a cookie set by a server whose IP address differs from the website’s in the first half of the address is capped at seven days, even when it comes from a first-party subdomain. Cookies set by JavaScript are capped at seven days anyway, or 24 hours when the visitor arrives from a domain Safari classes as a tracker and the landing URL carries a query string or fragment, which covers most ad clicks.

Shopify storefronts resolve to Shopify’s IP range (23.227.38.x). A Stape container on a custom subdomain resolves to Stape’s Google Cloud range. So a tagging subdomain on Shopify does not get longer-lived cookies in Safari, however the server container is configured.

This is the usual cause when a store says returning Safari customers look like new visitors. It weakens Meta attribution, remarketing and CRM audiences, and any personalisation that needs to recognise a customer. On a store where most traffic is mobile, that is a large share of repeat buyers being bid on as if they were cold.

Safari also removes some click IDs before your tags see them. Link Tracking Protection strips gclid, fbclid, msclkid and other click IDs in Private Browsing and in links opened from Mail and Messages. Safari 26 added fingerprinting protection in all browsing modes, and reviews of the Safari 27 code report network-level blocking of some ad platform endpoints by IP address, with Microsoft’s UET and LinkedIn’s Insight Tag named so far. Server-side tags are not affected by that block, because the browser only talks to your own server, so send Microsoft Ads and LinkedIn conversions through their server-side APIs. Persist wbraid, gbraid and UTMs as well as gclid, and use enhanced conversions as the fallback when the click ID is missing.

Check the IP Addresses on Day One

Run nslookup on every storefront, the checkout subdomain, the server container domain and the Custom Loader domain. Compare the first half of each IP address with the storefront’s.

A different subdomain for checkout on the same primary domain is fixable. A different primary domain is a much harder problem.

If the store sets its own first-party cookies from its server, compare their expiry in Safari with _ga or FPID. If the store’s own cookie lasts longer, the IP rule is the cause.

Fix Options by Store Type

Whichever route you use, point the Custom Loader and the GA4 server_container_url at the same-origin path, and make the address match the page hostname exactly: www and the bare domain are different.

Store typeRoute to longer cookiesWhat to know
Standard Shopify (theme)Stape Cookie Keeper through the Stape Shopify appThere is no supported way to serve a path on a Shopify-hosted storefront from your own server, so same-origin is not available. Shopify app proxies do serve a path on the storefront, but Shopify strips Set-Cookie headers from their responses, so they cannot set long-lived cookies. Cookie Keeper restores Google, Meta, TikTok and other platform cookies from a Shopify master cookie. Confirm which cookie with Stape (see the note below the table). Pro plan; custom cookies such as _gclid and utm_* need Business.
Headless (Hydrogen on Oxygen)Same-origin: serve the server container from the storefront, for example www.store.com/metrics, through a proxy route in the Hydrogen serverOxygen already runs on Cloudflare Workers, so a Cloudflare proxy in front of the site does not work ("Cloudflare in front of Cloudflare"). The route is a code change the store's developers must deploy.
No code change possibleCookie Keeper on the current domainImproves cookie life but does not fully solve Safari. A server-managed FPID does not help here: it is set from Stape's IP range, so Safari caps it at seven days as well. Say so plainly to the client.

A note on the master cookie. Stape’s Cookie Keeper documentation names _shopify_y and _y as the master cookies, but Shopify’s developer changelog says it stopped setting _shopify_y and _shopify_s from 1 January 2026. Check which cookies your store sets before relying on Cookie Keeper.

Making the Cookie Extender Work

Our standard pattern is a Custom HTML tag in web GTM that saves UTMs and click IDs as first-party cookies and pushes them to the data layer, plus Cookie Monster in the server container to re-set them from the server. Reviewing our own containers showed several ways this goes wrong:

  • The server must write the same cookie names it reads. For example, if in the web container it is set to  _gclid and then written as gclid in the server container, the click ID will never be extended. 
  • Gate both sides on consent. The web tag should need ad_storage and fire on Initialization and again on the consent update event, never on Consent Initialization. In the server container, split Cookie Monster into an analytics tag (_ga, _ga_<stream>, UTMs) and a marketing tag (click IDs, _fbc, _fbp), triggered by the GA4 consent signal: gcs matching ^G1.1 for analytics_storage and ^G11 for ad_storage.
  • Keep expiries consistent across web and server: 90 days for click IDs, 30 days for UTMs and two years for _ga.
  • Set cookies on the root domain (detected by the script, for example .store.com) so the checkout subdomain can read them.
  • Overwrite UTMs as a set when a new campaign arrives, so values from two visits are never mixed, and refresh _fbc when a new fbclid arrives.

Testing Cookie Life in Safari

  1. Test on real Safari on a Mac and a real iPhone. Chrome and simulators do not show the cap.
  2. Land with ?gclid=, ?fbclid=, ?ttclid= and UTMs. Record which cookies are set, whether by a Set-Cookie header or JavaScript, and their expiry in Web Inspector.
  3. Repeat with consent accepted, rejected and not yet answered.
  4. Confirm in sGTM Preview that the cookies arrive and that Cookie Monster or Cookie Keeper re-sets them, then check the expiry again on the next page.

3. Consent, Headless Storefronts and Multi-Store Setups

Consent

  • Connect the consent banner to Shopify’s Customer Privacy API. Three things on a Shopify store take consent from the API, not from GTM. The apps installed from the Shopify App Store (such as the Facebook & Instagram or Google & YouTube apps), Shopify’s own analytics, and the checkout, where GTM does not run. If the banner only updates Consent Mode in GTM, none of these will follow the visitor’s choice. You can check in the browser console with window.Shopify.customerPrivacy.currentVisitorConsent().
  • Set consent for the checkout separately. Custom pixels under Customer events run in a sandbox that can’t see the storefront’s data layer. They follow the consent setting on the pixel, so a pixel set to “Not required” fires whatever the visitor chose. That is only acceptable if the pixel passes the visitor’s consent into Consent Mode.
  • Check app tracking outside GTM. Theme app embeds and app pixels under Customer events may fire outside your consent setup. Check both lists, not just GTM.
  • Map ad_user_data and ad_personalization to marketing consent. They control whether user-provided data and remarketing are allowed in Google Ads. If they are mapped to statistics consent, a visitor who declines marketing but accepts statistics still sends them as granted. Fire a consent update on decline and on withdrawal as well as on acceptance, so the server container sees every change.
  • On headless stores, nothing is connected for you. The developer has to push the consent default before any events and connect the banner to the Customer Privacy API (in Hydrogen, the useCustomerPrivacy hook). If the default arrives after the first page and product events, those early events fire before Consent Mode is set.

Headless (Hydrogen) Storefronts

  • Page views on route changes and all ecommerce events are usually custom code. Check each fires once per route change with the right values.
  • Watch for literal "undefined" strings in event parameters, such as review scores.
  • A Content Security Policy is a good safeguard for an Ecommerce site, but ensure it allows requests to your server-side subdomain. 
  • The GTM loader and data layer live in the Hydrogen code, so ask for the code or read access to the repository at the start.

Several Stores or Storefronts on One Container

  • Map which country storefronts belong to which Shopify store before testing.
  • Where Meta pixels are chosen by hostname in a GTM lookup, test switching between storefronts and check the right pixel and IDs are used.
  • Repeat the core cookie and checkout test on at least one storefront from each store.
  • Agree how test orders are flagged. A cancellation window helps, but test orders can still trigger emails, fulfilment or affiliate commission.

Troubleshooting: Symptom to Cause

What you seeWhere to look first
Server-side property shows fewer users than client-side across every channelNew visitors are not getting GTM in the new setup. Check the live page source and network requests, not the app settings screen.
Every event is doubledTwo GTM loaders: the app embed and theme code both running. Remove one.
Purchases in GA4 Realtime but not in the data layerExpected. The Custom Pixel runs in a sandbox. Validate with sGTM Preview and the pixel console.
Page location shows /web-pixels@...Send event.context.window.location.href as page_location from the pixel and pass it through in the server GA4 tag.
Google Ads conversions missing click IDsStorefront cookies not reaching checkout. Read them in the pixel with browser.cookie.get.
More "Other" traffic in the server-side propertyUTMs and click IDs not persisted to begin_checkout.
Direct revenue up in the server-side property, Direct traffic flatAttribution broke at checkout. Check source/medium by channel as well as event counts.
Returning Safari customers appear as newSeven-day cookie cap. Run nslookup and compare IP ranges.
Click IDs never extended but _ga isCookie Monster writes a different cookie name from the one it reads.
Visitor declined marketing but ad_user_data shows grantedad_user_data mapped to statistics consent instead of marketing.
Meta purchases roughly doubled after adding StapeThe Facebook & Instagram app and Stape's Meta tag both sending Conversions API events without a shared event_id. Switch one off or align the event IDs.
Purchases dropped around 28 August 2025 (Plus) or 26 August 2026Purchase tags in Additional Scripts on the Thank you page stopped running when Shopify upgraded the page. Move them to a Custom Pixel or the server container.
Upsell revenue missing from purchasescheckout_completed fires on the first post-purchase upsell page, before the upsell is accepted.
Dynamic remarketing or cart data empty in Google Adsitem_id does not match the Merchant Center feed format.
Revenue gap on a multi-currency storeThe pixel sends the currency the customer paid in. Shopify reports in the shop currency.
Purchases credited to paypal.com or klarna.comPayment providers missing from unwanted referrals in GA4.

Final Checks Before You Call It Done

Frequently Asked Questions

Usually because the checkout runs in a sandbox the theme’s GTM cannot reach, and because ad blockers, browser extensions and Safari’s tracking prevention stop client-side tags firing. The Stape Custom Pixel and a server container recover most of the gap. On the stores we have migrated, 8% to 11% more conversions were captured.

Use the app. It configures the web GTM ID, the custom tagging domain, the Custom Loader and the ecommerce data layer in one place, and the Custom Pixel handles checkout. A theme snippet alone misses checkout entirely and is easy to end up running twice.

Safari caps cookies at seven days when the server setting them resolves to a different IP range from the website. Shopify storefronts resolve to Shopify’s range and a Stape container to Google Cloud’s, so a custom subdomain does not help on its own. Stape Cookie Keeper is the route for standard stores (confirm which master cookie it uses, as Shopify stopped setting _shopify_y in January 2026); a same-origin proxy route is the route for Hydrogen.

Usually more users and better paid-traffic recovery, but purchase counts can occasionally be slightly lower. Compare transaction IDs, values and click IDs rather than totals, and use zero-value sample orders as a control group to rule out duplication.

With one storefront, a clean theme and no legacy pixels, the parallel-running period is the longest part, because you need enough orders in both setups to compare transaction by transaction before switching GA4 and then Google Ads. Multiple storefronts, a headless build or old custom tracking add time, mostly in the inventory and testing steps.

Download the Shopify Server-Side Tracking Checklist

Every check in this guide, in order, as a printable A4 checklist with space to record your values and who signed each step off.

Not Sure What Your Shopify Store Is Losing?

Send us the store URL. We’ll run a free read-only check of how GTM loads, what reaches checkout and how long your cookies last in Safari, and tell you what it’s worth to fix. No access needed, no pitch.

Neal Cole

Neal Cole is the founder of Conversion Uplift and wrote the official GA4 and GTM audit course for CXL. He has spent 20+ years fixing measurement for Hastings Direct, Casumo, Gardening Express and other businesses where a wrong number is expensive.

More reading

How to Set Up ChatGPT Ads Conversion Tracking with Web and Server-Side GTM

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

The reCAPTCHA verification period has expired. Please reload the page.

Neal Cole
Author

Neal Cole

Founder of Conversion Uplift

Neal is the founder of Conversion Uplift and the author of the official GA4/GTM audit course for CXL. It's the course much of the industry trains on. He's spent over 20 years in digital analytics, including senior roles in online gaming and financial services, working out where businesses' numbers go wrong and what it costs them. His work now focuses on server-side tracking, GA4 and BigQuery: getting the data right, and being the person who's accountable for it when a decision depends on it.

How to Set Up ChatGPT Ads Conversion Tracking with Web and Server-Side GTM

How to Set Up OpenAI Ads Conversion Tracking in ChatGPT with Server-Side GTM, by Neal Cole of Conversion Uplift

Are you spending on ChatGPT Ads without knowing which leads or orders they produced? OpenAI’s Ads Manager gives you two ways to send it conversions: a browser pixel and a Conversions API. Use only the pixel and you lose conversions to ad blockers,  browser extensions and Apples tracking protection. Use both without matching them up and OpenAI counts every conversion twice, which makes your cost per lead look half what it really is.

This guide shows the full setup we tested end to end on our own site: the OpenAI Ads Pixel in web Google Tag Manager, the OpenAI Conversions API in server-side GTM (sGTM) hosted on Stape, and the Event ID that ties the two together so each conversion is counted once. It also covers the two problems that stopped our first attempt working, because you will probably hit at least one of them.

The video walks through every screen. The written steps below match it, so you can follow either. There’s also a free PDF checklist at the end of this post that covers every setting in order, with space to record the values you used. Jump to the download.

What we're building

We’re going to track a website lead in ChatGPT Ads using both:

  • the OpenAI Ads Pixel in web GTM
  • the OpenAI Conversions API in server GTM

We’re using the Stape templates for both.

Our website already pushes a generate_lead event into the data layer when somebody successfully submits the contact form. We’re going to use that as an example of how to turn it into OpenAI’s standard lead_created event.

Using the Pixel and the Conversions API together gives OpenAI two ways of receiving the conversion. The important part is giving both versions the same Event ID, so OpenAI knows they’re the same conversion rather than two separate leads.

If you’re an ecommerce site, the process is the same. Swap generate_lead for your purchase event and lead_created for OpenAI’s order_created, and read the ecommerce notes in each step.

Before you start: check the event you already have

Before setting anything up in OpenAI, establish what you’re going to track.

In our example, a successful form submission creates:

event: generate_lead
value: 1136
currency: GBP
lead_id: 1748
lead_type: contact
form_id: 18784
user_data.email_address: [email protected]

You don’t need these exact parameters. What matters is a reliable event that only fires when the conversion has actually happened.

For an ecommerce website, you’d normally start with existing ecommerce events such as purchase, add_to_cart or their equivalents.

Step 1: Set up OpenAI Ads Manager

Create the OpenAI Data Source

Set up a OpenAI Ads Manager account here: https://ads.openai.com/ or login if you already have account. To set up tracking, you will first need to add a logo and add payment details to the account. 

Once you have set up your account, go to the left-hand menu and select Tools then  Conversions and start on the Data Source tab.

Create a new Web Data Source. Call it something like Website Name Pixel Data Source. We called ours  Conversion Uplift Data Source.

Once it’s created, OpenAI gives you a Pixel ID. Copy it, because the same Pixel ID goes into both web GTM and server GTM.

Don't confuse the two OpenAI objects

A Data Source identifies the website or integration fo sending the data.

A Conversion Event defines the action you’re interested in, such as a lead or a purchase. That’s next.

Create the Conversion Event

Go to Tools then Conversions then Conversion Events and create a new conversion event.

For our contact form we used:

  • Display name: Lead Created
  • Event type: Standard
  • Standard event: lead_created

Associate it with the Web Data Source you just created.

For an ecommerce implementation, choose the standard event that describes the action you’re measuring. These include order_created, subscription_created and registration_completed.

 

For e-commerce sites, OpenAI recommend implementing five standard events, so the campaigns can see the whole journey and not only the final sale:

  1. page_viewed: a visitor lands on a page
  2. contents_viewed: a visitor views a product
  3. items_added: a product is added to the basket
  4. checkout_started: the visitor begins checkout
  5. order_created: the purchase completes

 

 Custom conversion events aren’t supported for that optimisation objective at the moment.

Create the Conversions API key

This turned out to be one of the most tricky parts of the implementation.

You need an API key for the server-side Conversions API request.

In Ads Manager go to Tools then Conversions, open Manage conversion keys and choose Create new key.

Give it a recognisable name such as GTM CAPI API. Copy the key when it’s generated and store it securely.

The key belongs in your server GTM container. Don’t expose it in web GTM or in website code. The Pixel ID isn’t a secret in the same way; the Conversions API key is. 

For this implementation, creating the key through Manage conversion keys proved to be the reliable route. There’s a longer explanation of why in the troubleshooting section below.

Step 2: Set up the OpenAI Pixel in web GTM

Install the Stape web GTM template

Open your web GTM container and go to Templates the Search Gallery.

Search for OpenAI Ads Pixel by Stape, accept and add the template.

Create a new tag using this template. We called ours OpenAI Conversion Tag.

Configure the web OpenAI Pixel

Enter the OpenAI Pixel ID from your Data Source. I prefer putting the ID into a GTM Constant variable rather than typing it into every tag.

For our lead event, configure:

  • Event Name Setup Method: Override
  • Event Type: Standard
  • Event Name: lead_created

Why Override?

Our website event is called generate_lead, but the OpenAI standard event we want to send is lead_created. So we’re explicitly mapping one to the other.

Stape also supports inheriting and mapping event names, which is useful for larger ecommerce implementations where you have several events to map.

Configure user data

Under User Identifiers Parameters, add:

Email Address → {{DLV - user_data.email_address}}

Our website already provides the email address inside user_data.email_address, so we’re reading that value with a Data Layer Variable.

Keep Enable Advanced Matching → ON.

Don’t add an email address as an ordinary GA4 custom event parameter just to make this work. Handle user data through the user-data functionality built for it.

Add the conversion value

Under Event Parameters, we added:

  • Amount (in the currency’s regular unit) → {{DLV - lead_value}}
  • Currency → {{DLV - lead_currency}}

Our example therefore sends:

Amount: 1136
Currency: GBP

The Stape server template converts the amount into the format the OpenAI API requires. In our successful outgoing request, £1,136 appeared as:

amount: 113600
currency: GBP

That’s expected. The API request represents the amount in the currency’s lowest unit, so pence rather than pounds.

Create the Event ID

This is the part that matters if you’re using both the browser Pixel and the server-side Conversions API.

Install Stape’s Unique Event ID variable template and create a variable from it. We called ours OpenAI Event ID.

In the OpenAI web tag, go to Server Side Tracking Settings → Event ID and select {{OpenAI Event ID}}.

The actual ID doesn’t matter. What matters is that the same ID reaches both OpenAI requests.

Pass the Event ID to server GTM

This was the missing step in our first implementation.

Generating the Event ID for the OpenAI web Pixel isn’t enough. Server GTM also needs to receive it.

We’re already sending our generate_lead event from web GTM to server GTM through GA4. So open the existing GA4 Event – generate_lead tag and add this Event Parameter:

event_id

with the value:

{{OpenAI Event ID}}

The flow is now:

generate_lead
      ↓
OpenAI Event ID
      ↓
 ┌───────────────┬────────────────┐
 ↓               ↓
OpenAI Pixel     GA4 generate_lead
 ↓               ↓
lead_created     event_id
                 ↓
             Server GTM

In our final test, the GA4 event and the OpenAI web Pixel both contained exactly the same Event ID.

Configure consent

In the web OpenAI tag, set Enable Google Consent Mode support → ON.

We also use the appropriate ad_storage consent state.

Your consent implementation depends on your CMP and your legal requirements, so don’t copy another website’s consent configuration.

Step 3: Set up the OpenAI Conversions API in server GTM

Add the server GTM template

Move into your server GTM container and add the Stape template OpenAI Ads Conversions API by Stape.

Create a tag called something like OpenAI CAPI Tag.

Configure the server OpenAI tag

For our lead implementation we used:

  • Event Name Setup Method: Standard
  • Event Name: lead_created
  • Pixel ID: the same Pixel ID used by the web tag
  • API Key: your Conversions API key
  • Action Source: Web
  • Validate Only: False
  • Use Optimistic Scenario: False while testing

Leaving Optimistic Scenario off during implementation means you can see whether OpenAI actually accepted the request, rather than the tag reporting success before the API has responded.

Configure the server Event ID

Create a server GTM Event Data variable with its Key Path set to:

event_id

We called ours Event Data – Event ID.

In the OpenAI CAPI tag, under Server Event Data Parameters, set:

Event ID → {{Event Data - Event ID}}

Keep Automap Server Event Data Parameters → ON.

For this hybrid Pixel and CAPI implementation we set Autogenerate an Event ID if none is found → OFF.

That’s deliberate. If the Event ID doesn’t arrive from web GTM, I would rather see the server tag fail during testing than have it create a different ID and lose the ability to deduplicate the browser and server events.

Configure server-side user data

Keep Automap User Data Parameters → ON.

Our incoming server GTM event contained user_data.email_address. The successful OpenAI request contained a SHA-256 hashed email rather than the plain address, which gives OpenAI another signal for matching the website conversion back to an eligible ad interaction.

Configure the server conversion value

Under Event Parameters add:

  • Amount (regular unit) → Server Event Data value
  • Currency → Server Event Data currency

Our incoming event contained:

value: 1136
currency: GBP

and the successful outgoing OpenAI request contained:

currency: GBP
amount: 113600

So don’t convert £1,136 to 113600 yourself when you’re using Stape’s regular unit field. The template handles that.

Configure the OpenAI click reference

The Stape server template supports OpenAI’s click reference and can manage the oppref value server-side.

For testing we used a dummy oppref in the landing-page URL, which let us confirm the value made it all the way into the server request.

A dummy value only proves the implementation works. It doesn’t create genuine OpenAI attribution. For that you need an eligible interaction with a real ChatGPT ad.

Set the server GTM trigger

Fire the CAPI tag on the server-side event that represents the genuine conversion. For us that’s generate_lead, which the server tag then maps to lead_created for OpenAI.

Don’t fire the CAPI tag on every GA4 request. It should only fire for the event you’re sending to OpenAI.

Step 4: Test the whole flow

Test the web Pixel

Start web GTM Preview and complete the contact form.

Open the generate_lead event and check the OpenAI tag. We verified:

Firing Status: Succeeded
Event Name: lead_created
Event Type: standard
Email: populated
Amount: 1136
Currency: GBP
Event ID: populated

Copy or note the Event ID, because you’re going to compare it with the server event.

Test server GTM

Open server GTM Preview at the same time and complete another test conversion.

Find the incoming generate_lead and inspect Event Data first. You should see:

event_name: generate_lead
event_id: [your event ID]
value: 1136
currency: GBP
user_data: ...

If event_id is missing here, don’t start changing the OpenAI CAPI tag. The problem is upstream. Check that your web GTM GA4 event is sending:

event_id → {{OpenAI Event ID}}

That was one of the main problems we found during this implementation.

Check deduplication

Compare the Event ID in the OpenAI web Pixel with the Event ID sent by the OpenAI CAPI tag. They must be identical for the same conversion.

Our final test produced the same ID all the way through:

OpenAI Web Pixel
        ↓
GA4 generate_lead
        ↓
Server GTM
        ↓
OpenAI CAPI

This is what allows OpenAI to recognise the browser and server events as the same conversion rather than counting two separate leads.

Check the outgoing OpenAI CAPI request

In server GTM Preview, open the outgoing request to OpenAI. Our successful request contained:

type: lead_created
action_source: web
event_id: [matching Event ID]
user:
    email_sha256
    ip_address
    user_agent
    obref
data:
    type: customer_action
    currency: GBP
    amount: 113600

Most importantly, the response was:

Status Code: 200

That proves OpenAI accepted the server-side event.

Check Ads Manager

Return to Ads Manager → Tools → Conversions and check the Data Source and Event Stream.

There’s an important distinction here. Event received means the implementation is sending data successfully. Attributed conversion means OpenAI has also connected that event with an eligible advertising interaction and the campaign’s configured conversion event.

Don’t assume an accepted test event will immediately appear as an attributed campaign conversion.

Troubleshooting: the Conversions API key problem we hit

This deserves its own section because the implementation looked correct but initially failed.

Our first outgoing request reached OpenAI but returned:

HTTP 401

with an error saying the key was missing the scope:

ads.third_party_events.write

At that point the payload was already correct. Event ID, hashed email, value and the other parameters were all reaching the OpenAI endpoint. The problem was the API key.

What fixed it

We went back to Ads Manager → Tools → Conversions, opened Manage conversion keys and created a new Conversion key there.

We replaced the previous key in server GTM without changing anything else. The next request returned:

HTTP 200

What I'd recommend

Create the CAPI credential through Tools → Conversions → Manage conversion keys → Create new key. That route makes it clear you’re creating a key specifically for conversion measurement.

OpenAI’s current documentation requires a Pixel ID and a Conversions API key for conversion measurement integrations. There are other API-key areas within the wider OpenAI interface, so I wouldn’t claim another route can never work. What we can say from our test is that for our direct Stape server GTM implementation, creating a fresh key from Manage conversion keys resolved the 401 missing-scope error immediately.

So if you get:

401
Missing scopes: ads.third_party_events.write

and the rest of the request looks correct, create a fresh key through Manage conversion keys before you start changing the GTM implementation. It could save you a lot of unnecessary debugging.

Step 5: Link the conversion to your campaign

Ads Manager’s setup checklist includes Link event to a campaign.

You don’t need to create a campaign to prove the Pixel and CAPI implementation works. But you do need to associate the conversion with your advertising if you want OpenAI to use it for campaign conversion measurement and optimisation.

For a conversion-optimised campaign, create a campaign with Objective → Conversions and select the standard conversion event you want the campaign to optimise towards. For our example that’s Lead Created.

Decide which conversion you want to optimise towards before launching the campaign.

Final checks before you publish GTM

I’d want all of these to pass before publishing either container:

That’s the configuration we successfully tested end to end. The same checks, plus every setting that feeds them, are in the PDF checklist at the bottom of this post.

Frequently asked questions

You can run the Pixel alone, but you’ll lose conversions to ad blockers, browser tracking protection and consent choices. The Conversions API sends the conversion from your server, so it isn’t affected by those. Running both, with a shared Event ID, gives OpenAI the most complete picture without double counting.

The OpenAI API represents amounts in the currency’s lowest unit, so pounds become pence. Stape’s template does the conversion for you when you use the “regular unit” amount field. Enter 1136, not 113600.

In Ads Manager go to Tools, then Conversions, then Manage conversion keys, and create a new key. In our testing, a key created elsewhere in the OpenAI interface returned a 401 “missing scopes: ads.third_party_events.write” error, and a fresh key from Manage conversion keys fixed it.

Almost always the web side. Check that your GA4 event tag in web GTM is sending an event_id parameter with the same Unique Event ID variable you gave the OpenAI Pixel tag. If that parameter isn’t there, server GTM has nothing to read.

Not for conversion-optimised campaigns at the moment. OpenAI requires a supported standard event such as lead_created, order_created, subscription_created or registration_completed for that objective.

Download the checklist

Every setting in this guide, in the order you need to make it, on six pages you can print or keep open in a second window. Each step has a tick box and a space to write the value you used (Pixel ID, variable names, trigger, sign-off date), so the person who audits the setup later can see exactly what was configured. It includes the troubleshooting table for the 401 missing-scope error and the missing Event ID.

No email address required.

Need this set up on your site?

I set this up on our own site first so I could see where it breaks. If you’d rather not spend an afternoon in two GTM containers and the OpenAI API, book a free tracking audit and I’ll tell you what your ChatGPT Ads tracking is doing today and what it would take to make it hold up.

Neal Cole, founder of Conversion Uplift

Neal Cole

Neal Cole is the founder of Conversion Uplift, a UK digital analytics consultancy, and wrote the official GA4 and GTM audit course for CXL. He works with marketing teams who spend real money on acquisition and want numbers they can stand behind.

More reading

How to Connect Claude to Google Tag Manager With a Self-Hosted MCP Server

Is Your Shopify Store Under-Reporting Sales? What We Learned Moving Stores to Server-Side GTM

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

The reCAPTCHA verification period has expired. Please reload the page.

Neal Cole
Author

Neal Cole

Founder of Conversion Uplift

Neal is the founder of Conversion Uplift and the author of the official GA4/GTM audit course for CXL. It's the course much of the industry trains on. He's spent over 20 years in digital analytics, including senior roles in online gaming and financial services, working out where businesses' numbers go wrong and what it costs them. His work now focuses on server-side tracking, GA4 and BigQuery: getting the data right, and being the person who's accountable for it when a decision depends on it.

How to Connect Claude to Google Tag Manager With a Self-Hosted MCP Server

Connect Claude to GTM Self hosted MCP Server

How to Connect Claude to Google Tag Manager With a Self-Hosted MCP Server

If you look after tagging for more than one or two clients, you already know how long a Google Tag Manager container review takes. Open a tag. Check the trigger. Check the variable it reads. Repeat forty times, then do the same again in the server-side container to see whether the two agree. A morning gone, and all you have at the end is a note that says “looks fine”.

A Google Tag Manager MCP server changes that arithmetic. It gives Claude a read-only door into your GTM configuration, so the reconciliation work becomes a conversation instead of forty browser tabs. In this guide I walk through the exact setup we use at Conversion Uplift: a self-hosted Google Tag Manager MCP server running on Google Cloud Run, reading GTM as a service account with Read permission and nothing more.

The video below shows the whole thing end to end. Every command is written out underneath, along with the five places it went wrong for me on camera, so you can avoid them.

On this page

What a Google Tag Manager MCP server actually does

MCP stands for Model Context Protocol. It is a standard way of giving an AI assistant a set of tools it is allowed to use. Anthropic created it for Claude, and it has since been adopted across all other major AI platforms.

The GTM MCP server itself is a small piece of software. It speaks the Google Tag Manager API on one side and MCP on the other. Once Claude is connected to it, you can ask questions in plain English and Claude reads the live configuration to answer them:

  • “Document every tag in this container, with its firing triggers and parameters.”
  • “Compare the parameters being sent with GA events by the web container against the parameters being sent by the All GA4 Events tags in the server container and tell me where they differ.”
  • “Which tags have no trigger attached?”
  • “Show me every tag that fires on all pages.”

That last one could take twenty minutes or more, depending upon how large the container. It now takes about fifteen seconds whatever the container size.

The work this replaces is auditing and reconciliation. Opening dozens of tags, triggers and variables by hand to confirm that two containers agree is the kind of task that is important, slow, and easy to get wrong when you are on the fortieth tag of the afternoon. Volume reports will not catch a mismatch either. Two conversion tags can both fire 1:1 in Google Ads and still be sending different values.

Why I self-host instead of using a hosted connector

There are ready-made hosted GTM connectors, Stape’s being the best known, and they are much quicker to switch on than the setup in this guide. For a single account, or an occasional look inside one container, they are a reasonable choice. I would not talk anyone out of starting there.

We tested the hosted route and it did not hold up to the way we work. The sticking point is authentication.

Hosted connectors typically sign in with an interactive OAuth token tied to one person’s Google login. That token expires and needs re-consenting. If you dip into a single account now and then, that is a minor annoyance. Across the number of client containers we review, it became a real obstruction: the connection kept dropping out and demanding re-authorisation part way through an audit, which meant starting the reconciliation again.

A self-hosted Google Tag Manager MCP server signs in as a service account through Workload Identity. That is a non-interactive machine identity with no login session to expire, so it keeps working unattended no matter how many client accounts you point it at. We grant that one identity Read access in each client’s GTM, and we control the whole thing: uptime, permission level, rate limits. Nothing depends on a third party’s token lifecycle.

My rule of thumb: if you have one or two containers, use a hosted connector. If you have ten, host it yourself.

How the read-only boundary works

Three things keep this safe, and they are worth understanding before you build it.

The service account only has Read. The server reads GTM as the service account. Because that account is a Read user in GTM, Google itself rejects any attempt to write, publish or delete. This is the entire safety boundary, which is why it never gets raised.

A bearer key protects the server. Every request has to carry the correct Authorization header. The key is a long random string you generate yourself and store in Google Secret Manager.

Workload Identity means no key file on the server. On Cloud Run the server borrows the service account’s identity automatically, so there is no JSON credential sitting on disk waiting to leak.

You can add Edit, Approve or Publish to that service account later. I would not add Publish. Publishing a container should stay a human decision made after someone has tested the change, not something an assistant can do because a prompt was worded loosely.

What you need before you start

You needWhy
A Google Cloud projectWhere the MCP server is hosted, on Cloud Run
Owner rights on that projectTo enable APIs, deploy and manage secrets
Billing enabledCloud Run and Cloud Build need a billing account attached. The free tier covers this comfortably
gcloud CLI, Git and Node.jsgcloud talks to Google Cloud, Git downloads the server, Node runs Claude Code
A service account with GTM ReadThe machine identity the server reads GTM as
A bearer keyThe password protecting your server
Claude CodeThe client that connects to the server

It is a one-time setup. Allow an hour the first time, then about ten minutes for each additional GTM account you share with the service account.

All the commands below are PowerShell on Windows. Set these three variables first and the rest of the guide copies straight across:

				
					$PROJECT = 'your-project-id'
$SA      = "mcp-server@$PROJECT.iam.gserviceaccount.com"
$REGION  = 'europe-west2'
				
			

Step 1: Create the Google Cloud project

Do this part in a browser, because the command line tools are not installed yet.

  1. Sign in at console.cloud.google.com with the Google account that has access to your GTM containers.
  2. Open the project picker in the blue bar at the top and choose New project.
  3. Give it a name. Google derives a project ID from that name, and the ID has to be unique across the whole of Google Cloud, so you may be offered something with digits appended. Write the ID down. Every command from here uses the project ID, not the name.
  4. Leave Location as No organisation unless your Workspace admin says otherwise, and click Create.

Then check two things in the Console. Under IAM and Admin, confirm your own account shows roles/owner. Under Billing, confirm a billing account is linked. If either is missing you will hit a wall four steps later, and the error message will not tell you that permissions were the cause.

Step 2: Install the tools

The most reliable route on Windows is the official installers:

Run each one and accept the defaults. On the Google Cloud CLI installer, choose Single user and keep Bundled Python ticked. Bundled Python is what saves you from version conflicts with any other Python on the machine.

Then close PowerShell, open a new window, and check all three:

				
					gcloud version
git --version
node -v
npm -v
				
			

Windows also has a package manager called winget, which installs all three in three lines:

				
					winget install --id Google.CloudSDK -e
winget install --id Git.Git -e
winget install --id OpenJS.NodeJS.LTS -e
				
			

Winget is optional and it needs Windows 10 version 1809 or later. If winget --version returns “not recognized”, go with the installers rather than spending twenty minutes on it. I lost that twenty minutes so you do not have to. More on that below.

Step 3: Sign in with gcloud

				
					gcloud init
				
			

Four prompts follow. Log in when asked, pick your Google account in the browser and approve the consent screen. When it asks which project to use, give it your project ID. When it offers to set a default Compute Region and Zone, answer n: that setting only affects Compute Engine, and Cloud Run takes its region explicitly in the deploy command.

Then confirm you are pointed at the right place and hold the rights you need:

				
					gcloud config set project $PROJECT
gcloud auth list
gcloud billing projects describe $PROJECT
				
			

You want a * beside your email address and billingEnabled: true.

Step 4: Create the service account and grant GTM Read

				
					gcloud iam service-accounts create mcp-server --display-name="GTM MCP (read-only)"
gcloud iam service-accounts list
				
			

The address follows the pattern [email protected]. Copy it exactly.

Now grant it access inside Google Tag Manager, once per GTM account you want Claude to read:

  1. Go to tagmanager.google.com and open the account.
  2. Admin, then User Management under the Account column.
  3. Click +, then Add users.
  4. Paste the service account address and set Account Permissions to Read, with Read on the containers too.
  5. Click Invite.

It takes effect straight away. A service account has no inbox, so there is no invitation for it to accept.

Step 5: Generate the bearer key and store it

Generate a strong random key:

				
					$bytes = New-Object 'System.Byte[]' 32
[System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($bytes)
$KEY = 'gtmk_' + ([Convert]::ToBase64String($bytes) -replace '\+','-' -replace '/','_' -replace '=','')
$KEY | Set-Clipboard
				
			

That copies the key to your clipboard without printing it on screen, which matters if you are recording or sharing a screen. Paste it into your password manager now. The gtmk_ prefix is a readable label, nothing more.

Enable the five APIs the deployment needs:

				
					gcloud services enable run.googleapis.com cloudbuild.googleapis.com artifactregistry.googleapis.com secretmanager.googleapis.com tagmanager.googleapis.com
				
			

Store the key as a secret. The file-writing step is not fussiness: piping a string to gcloud on Windows can add an invisible byte order mark or newline, and the server compares the key character for character. One stray character and every request comes back 401.

				
					$tmp = Join-Path $env:TEMP 'gtm-mcp-api-key.txt'
[System.IO.File]::WriteAllText($tmp, $KEY, (New-Object System.Text.UTF8Encoding($false)))
gcloud secrets create gtm-mcp-api-key --replication-policy="automatic" --data-file="$tmp"
				
			

Verify the stored copy matches, then tidy up and let the service account read it:

				
					$stored = gcloud secrets versions access latest --secret=gtm-mcp-api-key
$stored -eq $KEY
Remove-Item $tmp -Force
gcloud secrets add-iam-policy-binding gtm-mcp-api-key --member="serviceAccount:$SA" --role="roles/secretmanager.secretAccessor"
				
			

$stored -eq $KEY must print True. That comparison is the reliable check. Do not try to eyeball the two strings.

Step 6: Deploy the Google Tag Manager MCP server to Cloud Run

Download the GTM MCP server code and confirm you are in the right folder:

				
					cd $env:USERPROFILE
git clone https://github.com/paolobietolini/gtm-mcp-server.git
cd gtm-mcp-server
Test-Path main.go, Dockerfile
				
			

Both checks should return True. Then deploy:

				
					gcloud run deploy gtm-mcp --source . --region $REGION --service-account $SA --set-secrets SERVICE_ACCOUNT_API_KEY=gtm-mcp-api-key:latest --allow-unauthenticated
				
			

Answer Y to the prompt about creating an Artifact Registry repository. The build takes a few minutes. On success it prints a Service URL like https://gtm-mcp-000000000000.europe-west2.run.app. Copy it.

--allow-unauthenticated sounds alarming and is correct here. The server does its own bearer key check, and switching on Cloud Run’s separate identity layer would clash with it. Security comes from the bearer key plus the Read-only service account.

Test the GTM MCP server before involving Claude:

				
					$URL = 'https://your-service-url.run.app'
curl.exe "$URL/health"
				
			

You want {"service":"gtm-mcp-server","status":"healthy","version":"1.8.2"}. One response proves three things: the container started, it picked up the service account, and it could read the secret.

Step 7: Connect Claude Code

Update Claude Code first. Older Windows builds drop the Authorization header on tool calls, which produces the maddening failure where your curl tests pass but Claude gets 401s.

				
					npm install -g @anthropic-ai/claude-code@latest
claude mcp add --transport http gtm-mcp $URL --header "Authorization: Bearer $KEY" --scope user
claude mcp list
				
			

You want gtm-mcp: https://... (HTTP) - ✓ Connected. The --scope user flag makes the connection available from any folder rather than only the one you registered it in, which is what you want if you run audits from client folders.

Then start a session and test it:

				
					claude
				
			

Type these as messages, approving each tool call:

				
					Use the gtm-mcp server's ping tool and show me the raw result.
				
			
				
					Use the gtm-mcp server to list the GTM accounts I have access to.
				
			

A pong response proves the chain from Claude Code through the bearer key to the Google Tag Manager MCP server on Cloud Run. Your GTM accounts appearing proves the service account permission works. At that point you are done.

Step 8: Use it from Claude Desktop as well

Claude Code is the right client for audit work, because it holds a long reconciliation in one go and can save what it finds to a file. Claude Desktop suits quick questions, and anyone on the team who would rather not open a terminal. Both talk to the same Google Tag Manager MCP server, and the read-only boundary is identical, because it comes from the service account rather than the client.

Desktop needs the config file rather than the connector dialog. Its Add custom connector screen offers an OAuth client ID and secret, with no field for an Authorization header, so it cannot authenticate against a bearer-key server on its own. The way round it is mcp-remote, a small bridge that runs locally and adds the header for you.

Open Claude Desktop, then Settings, Developer, Edit Config. On Windows the file sits at %APPDATA%\Claude\claude_desktop_config.json. Add this inside the mcpServers object, alongside anything already there, and mind the comma after the previous entry:

				
					"gtm-mcp": {
  "command": "npx",
  "args": [
    "-y",
    "mcp-remote",
    "https://your-service-url.run.app",
    "--header",
    "Authorization:${AUTH_HEADER}"
  ],
  "env": {
    "AUTH_HEADER": "Bearer your-key-here"
  }
}
				
			

Two details decide whether this works. The header has no space after the colon, because a space inside a single argument gets split on some platforms and the header then arrives malformed. The space belongs inside the environment variable, in front of the key. And if Claude Desktop reports that it cannot find npx, change "command" to "cmd" and put "/c", "npx" at the front of the args array, which is the usual fix on Windows.

Then quit Claude Desktop completely, including from the system tray, and reopen it. Closing the window does not reload the config. Test it with the same two prompts from Step 7: ask for the ping tool, then ask it to list your GTM accounts.

Worth knowing that your bearer key now sits in plain text in two files, Claude Desktop’s config and Claude Code’s .claude.json. That is normal for MCP clients. It matters when you rotate the key, because you change it in Secret Manager, redeploy, and then update both files.

Five things that tripped me up on camera

I recorded this setup live rather than editing out the friction, because the friction is where people give up.

Winget is not where you expect it. winget --version returned “not recognized” on a machine that had App Installer 1.29 installed and healthy. The package was there. The app execution alias was switched off, so PowerShell could not see it. Get-AppxPackage Microsoft.DesktopAppInstaller tells you whether it exists; Settings, Apps, Advanced app settings, App execution aliases turns the alias back on. Given that winget is only a shortcut for installing three tools, the installers are the faster answer.

Check before you install. The Google Cloud CLI installer stopped with “there already is a version installed”, because an earlier winget command had succeeded. The right response is No, then Cancel, then gcloud version. Installing over the top can leave two component manifests that disagree, which surfaces later as odd gcloud components errors.

PowerShell does not submit the last line of a pasted block. Paste the next command straight away and it gets appended to the unsubmitted line. My screen filled with A parameter cannot be found that matches parameter name 'Forcegcloud', which is two commands welded together. Press Enter once after each paste.

Windows PowerShell mangles inline JSON. This one is a genuine error in the first version of my own written guide. Sending the MCP handshake with curl.exe -d '{"jsonrpc":"2.0",...}' fails, because PowerShell 5.1 strips the double quotes before handing the string to curl. The server replies malformed payload: unmarshaling jsonrpc message, which reads like a server fault and is nothing of the kind. Write the body to a file instead:

				
					$body = '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
[System.IO.File]::WriteAllText("$env:TEMP\mcp-test.json", $body, (New-Object System.Text.UTF8Encoding($false)))
curl.exe -s -X POST "$URL/" -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" -d "@$env:TEMP\mcp-test.json"
				
			

The error you get back is the pass. A reply saying "tools/list" is invalid during session initialization means your bearer key cleared the security layer and the server is speaking MCP, waiting for the handshake that Claude Code performs automatically. A flat 401 Unauthorized is the failure. I have watched people rebuild a working server because they read the first message as a fault.

Deploying outside the UK

The commands above deploy to europe-west2, which is London. If you are somewhere else, the region is the only thing that changes. Everything up to this point is global.

				
					$REGION = 'australia-southeast1'   # Sydney
				
			

Then deploy as written. Common choices are us-central1 (Iowa), us-east1 (South Carolina), europe-west1 (Belgium), asia-southeast1 (Singapore), asia-northeast1 (Tokyo), australia-southeast1 (Sydney) and southamerica-east1 (São Paulo). For the full list, run gcloud run regions list.

Pick the region nearest you, not nearest your client. The Tag Manager API is a single global Google endpoint, so matching a client’s country buys nothing. What you are shortening is the round trip between your own machine and Cloud Run, and that is what you feel while Claude works through a container.

Two things follow from changing region. The service URL contains the region, so a server deployed elsewhere gets a new hostname: if you have already registered it, run claude mcp remove gtm-mcp and add it again with the new URL. And the cloud-run-source-deploy Artifact Registry repository is created per region, so the first-run prompt about creating it appears again.

If data residency is the reason for the move, pin the bearer key to one region rather than Google-managed replication, and set a regional log bucket in Cloud Logging, because request logs default to global:

				
					gcloud secrets create gtm-mcp-api-key --replication-policy="user-managed" --locations="australia-southeast1" --data-file="$tmp"
				
			

Be straight with the client about what that covers. A regional deployment controls where your server and its logs live, and the server holds no GTM data at rest. It does not change where Google stores the container, because the API call goes to Google’s global endpoint either way. If a client needs that second guarantee, that is a question for Google rather than a deployment flag.

What it changes day to day

The honest answer is that the Google Tag Manager MCP server does not find things a careful analyst would miss. It finds them in ninety seconds instead of ninety minutes, and it does not get bored on the fortieth tag.

Where it earns its keep for us is web-to-server reconciliation. Comparing a client-side container against a server-side container to confirm that conversion IDs, labels, trigger conditions and consent settings line up is exactly where mismatches hide. Two containers can look correct in isolation and still disagree with each other.

What it does not do is verify that any of it is right. Claude reads the configuration you have. It cannot tell you that the purchase event is firing on a page it should not, that consent is being enforced in the wrong order, or that your revenue figure is 12% light because a tag fires before the dataLayer is ready. That still needs someone loading the site, walking the checkout and watching the network traffic. The MCP server tells you what the container says. A person tells you whether that matches reality.

That is the division of labour I would hold onto as more of this work gets automated. The machine reads faster than you can. Someone still has to be accountable for the answer.

Should you build this?

Build your own Google Tag Manager MCP server if you manage tagging across several GTM accounts, you are comfortable in PowerShell, and you want the connection to keep working without anyone re-authorising it. The hour it takes pays back on the second audit.

Use a hosted connector if you have one or two containers and want to try the idea this afternoon.

If what you actually want is confidence that your tracking is telling you the truth, the tooling is not the answer on its own. We run a free website analytics and SEO audit that shows what your setup is currently reporting and where it disagrees with itself. Neal Cole, who wrote the official GA4 and GTM audit course for CXL, reviews the findings. If you would rather have this built for you, we do that too.

Book Your Free Audit

Related reading: how to connect Claude to Google Analytics 4, common Google Tag Manager mistakes and the Google Analytics audit checklist.

Frequently asked questions

Is the Google Tag Manager MCP server read-only?

A Google Tag Manager MCP server is read-only if you configure it the way described here. The server reads GTM as a service account, and because that account holds Read permission in GTM, Google rejects any write, publish or delete. The permission level is the boundary, not the prompt wording.

What does it cost to run on Google Cloud?

Cloud Run bills per request and scales to zero when idle, so a container used for audits sits inside the free tier in normal use. You still need a billing account attached to the project, because Cloud Run and Cloud Build require one.

Do I need one MCP server for each client?

No. One self-hosted Google Tag Manager MCP server can read every GTM account that has shared Read access with your service account. Add the service account as a Read user in each client’s GTM and the same server covers them all.

Can I use this with Claude Desktop instead of the terminal?

Yes. Claude Desktop connects to the same server, though it needs the mcp-remote entry in its config file rather than the built-in connector dialog, because that dialog has no field for a bearer token. The section above has the exact JSON.

Can I run the Google Tag Manager MCP server outside the UK?

Yes. Change the --region value in the Cloud Run deploy command to any Cloud Run region and everything else stays the same. The service URL changes with the region, so re-register the server in Claude Code afterwards.

How long does the setup take?

Building the Google Tag Manager MCP server takes around an hour the first time, most of which is Google Cloud project admin and the Cloud Run build. Adding another GTM account afterwards takes a couple of minutes.

Full video transcript available below. Commands in this guide are PowerShell on Windows and were tested on 18/08/2026 against gtm-mcp-server v1.8.2.

More reading

How to connect Claude to Google Ads

How to Set Up ChatGPT Ads Conversion Tracking with Web and Server-Side GTM

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

The reCAPTCHA verification period has expired. Please reload the page.

Neal Cole
Author

Neal Cole

Founder of Conversion Uplift

Neal is the founder of Conversion Uplift and the author of the official GA4/GTM audit course for CXL. It's the course much of the industry trains on. He's spent over 20 years in digital analytics, including senior roles in online gaming and financial services, working out where businesses' numbers go wrong and what it costs them. His work now focuses on server-side tracking, GA4 and BigQuery: getting the data right, and being the person who's accountable for it when a decision depends on it.

How to connect Claude to Google Ads

How to connect Claude to Google Ads

How to connect Claude to Google Ads

If you spend time moving between Google Ads reports, account screens and spreadsheets just to answer a straightforward question, connecting Claude to Google Ads can give you a quicker way to investigate the numbers. This guide shows you how to connect Claude Desktop to the Google Ads API through Google’s Google Ads MCP server. The setup is read-only which means Claude can query and analyse account data, but this configuration does not give it permission to create, edit or pause campaigns.

I’ve written this for Windows and for people who are comfortable following a technical setup but do not want to spend an afternoon working out which Google account ID goes where. Allow roughly 25 to 30 minutes for the hands-on work. Google’s API approval can take longer if your developer token is not given production access automatically.

Prefer to watch a video?

The video below walks through the complete setup, from creating the Google Cloud project through to running your first Google Ads query in Claude.

On this page

What this setup connects:

  • A Google Cloud project, which holds the Google Ads API access and OAuth credentials.
  • A Google Ads Manager account (MCC), where your developer token lives and where client accounts can be linked.
  • Google Cloud CLI (gcloud), which handles the local Google sign-in and Application Default Credentials.
  • Claude Desktop, which runs the Google Ads MCP server and lets you ask questions in plain English.

Before you start, you will need:

  • A Google account with access to the Google Ads accounts you want to analyse.
  • A Google Ads Manager (MCC) account, or permission to create one.
  • A Google Cloud project.
  • Claude Desktop installed.
  • Python and pipx installed on your Windows computer.
  • Git installed before the MCP server runs for the first time.

Security point

Treat your OAuth client secret, developer token and local credential files like passwords. Do not paste live credentials into a blog post, shared document, public repository or chat transcript.

Step 1: Set up the Google Cloud project

You must have a Google account of some kind to use Google Cloud. Sign in to the Google Cloud Console with your existing Google account (personal Gmail or Google Workspace), accept the terms if prompted, and you’ll then be able to create your first Google Cloud project.

For this read-only Google Ads MCP setup, you do not need to enable Google Cloud Billing. Billing is only required if you later decide to use paid Google Cloud services such as Cloud Run, BigQuery or Vertex AI. So, go to the Google Cloud Console. You can use an existing project or create a new one. Make a note of the Project ID. This is not the same as the project name or project number, and you will need the Project ID in the Claude configuration later.

Google Cloud Console: console.cloud.google.com

Welcome Google Cloud

Welcome Google Cloud

With the same project selected, use the “Quick access”tiles at the bottom of the welcome page, and select “API APIs & Services”. Then  select “Library” in the menu on the left and enter the folliwing into the  search box  “Google Ads API”. This will take you to the API page, and click Enable. This is separate from any other APIs already switched on in the project, so it’s easy to miss.

Google Ads API

Google Ads API

Make absolutely sure you enable the Google Ads API in the same project whose ID you’ll use later. Enabling it on a different project is one of the most common reasons the tool returns a “not enabled” error at the end.

Step 2: Configure the Google Auth Platform

In the same Google Cloud project, select the “OAuth Consent Screen” from the lef-hand menu, and open the page. Click “Get started” to complete the app information. Enter a clear name such as “Google Ads MCP” and enter your own monitored email address for the support and contact details. Click “Next”.

App information entered

App information entered

For the Audience, choose External (Not internal – don’t make my mistake) as the app needs to be authorised by a Google account outside your own Workspace organisation.

Click Continue, enter your contact information (your email address), and Continue.

App information email

App information email

Click Finish to complete the initial setup, then return to Google Auth Platform and select Audience from the left-hand menu. Here, you need to Publish the app because otherwise tokens normally expire after seven days. Publishing avoids having to repeat the sign-in every week. Click to Confirm.

Confirm Publish New UI

Confirm Publish New UI

What if Google shows an unverified-app warning?

For a private setup that you are using yourself, an unverified warning can still appear. Read the warning and only continue when you recognise the app, project and account involved. Workspace administrators can also control whether an app is trusted or blocked.

Step 3: Create the OAuth desktop client

We now need to go to Clients in the left-hand menu and create a desktop app. Click on “Create client” at the top of the page, and choose Desktop app as the application type and give it a recognisable name, for example “Google Ads MCP”.

Create Client Desktop

Create Client Desktop

Copy the Client ID and the secret key in a secure and private location as you will need these later. Then download the client JSON and save it on your C: drive in a folder called “Keys”. This means Claude knows where to locate the secret key. In the video example I use the name of:

C:\keys\google-ads-mcp.json

That JSON file contains your OAuth client details. Do not publish it or commit it to a public repository.

Step 4: Create or confirm your Google Ads Manager account

The developer token is issued from a Google Ads Manager account, often called an MCC. If you do not already have one, create a manager account and link the Google Ads accounts you need to analyse. To create a Google Ads Manager accounts, go to ads.google.com/home/tools/manager-accounts

Give it your business name (no web addresses in the name), choose “Manage other people’s accounts”, and set your country, time zone and currency.

Write down the 10-digit Manager Customer ID shown in Google Ads. In the MCP configuration it is used as GOOGLE_ADS_LOGIN_CUSTOMER_ID, with the dashes removed.

Example

If your MCC ID is 123-456-7890, use 1234567890 in GOOGLE_ADS_LOGIN_CUSTOMER_ID.

Step 5: Get the Google Ads developer token

While signed in to the Manager account, open Admin and select API Center from the menu. Complete the API access form below as the developer token is required for Google Ads API calls.

Google Ads API Centre

Google Ads API Centre

Google now has four access levels: Test Account, Explorer, Basic and Standard. Explorer Access can query production accounts and is often enough for this read-only reporting setup. If Google gives you Test Account Access only, production accounts will not work until your access is upgraded. Basic Access raises the daily production quota and removes some Explorer restrictions.

Do not wait for Basic if you already have Explorer Access. For a read-only diagnostics setup, Explorer Access may be enough to start. Apply for Basic if you need the higher quota or functionality that Explorer restricts.

In the Manager account, go to Accounts, and select Sub-account settings. Use the + button and choose Link existing account. Enter the 10-digit customer IDs for the accounts you want to analyse.

The owner of each account must accept the manager-link request. Claude will only be able to query accounts available to the Google user you authenticate with.

Step 7: Install Google Cloud CLI and sign in with the Ads scope

Next, we need to download Google Cloud CLI for Windows from Google. You can go to PowerShell and run the command:

(New-Object Net.WebClient).DownloadFile("https://dl.google.com/dl/cloudsdk/channels/rapid/GoogleCloudSDKInstaller.exe", "$env:Temp\GoogleCloudSDKInstaller.exe")
& $env:Temp\GoogleCloudSDKInstaller.exe

This will open up G Cloud SLI or you can download it using the link below

cloud.google.com/sdk/docs/install

 

Run the installer and keep Bundled Python selected. After installation, close any open PowerShell windows and open a new one so the PATH update is picked up.

 

CLI Bundled Python

CLI Bundled Python

Check the installation by pasting this into a new PowerShell window:

gcloud --version

Then authenticate the Application Default Credentials with the Google Ads and Cloud Platform scopes. If your OAuth client JSON is stored at C:\keys\google-ads-mcp.json, run this as one line. Modify this if you are using a different location.

gcloud auth application-default login --scopes="https://www.googleapis.com/auth/adwords,https://www.googleapis.com/auth/cloud-platform" --client-id-file="C:\keys\google-ads-mcp.json"

A browser window should open. Sign in with the Google account that has access to the Ads accounts you want to query. When gcloud finishes, make a note of the Application Default Credentials file path it prints. This is a different JSON file from the OAuth client JSON you downloaded in Step 3.

 

Keep the scopes together. Run the command as one line and keep the scopes inside quotes. This avoids PowerShell splitting the command at the comma.

Step 8: Add the Google Ads MCP server to Claude Desktop

Before editing Claude, make sure Git is installed. The server is pulled from Google’s GitHub repository the first time it runs. You can install Git with Windows Package Manager:

winget install --id Git.Git

In Claude Desktop, open Settings, then Developer and Edit Config. This takes you to the folder where the Claude Config file is saved. Open the file and keep any existing MCP servers in the file. We will need to add the Google Ads server alongside them.

But before we do that, confirm the location of pipx by pasting this into a PowerShell window:

where.exe pipx

Use the path returned on your machine. The current Google Ads MCP documentation also recommends setting GOOGLE_APPLICATION_CREDENTIALS to the Application Default Credentials path printed by gcloud and GOOGLE_CLOUD_PROJECT to your Cloud Project ID. To keep it simple, I paste the existing Claude Config script into a Claude chat, alongside the new Google Ads MCP scipt, and ask it to modify the Config code. This helps prevent simple errors, such as too many or too few braces.

"google-ads": {
  "command": "C:\\path\\to\\pipx.exe",
  "args": [
    "run",
    "--spec",
    "git+https://github.com/googleads/google-ads-mcp.git",
    "google-ads-mcp"
  ],
  "env": {
    "GOOGLE_APPLICATION_CREDENTIALS": "C:\\path\\printed\\by\\gcloud\\application_default_credentials.json",
    "GOOGLE_PROJECT_ID": "YOUR_GOOGLE_CLOUD_PROJECT_ID",
    "GOOGLE_CLOUD_PROJECT": "YOUR_GOOGLE_CLOUD_PROJECT_ID",
    "GOOGLE_ADS_DEVELOPER_TOKEN": "YOUR_DEVELOPER_TOKEN",
    "GOOGLE_ADS_LOGIN_CUSTOMER_ID": "YOUR_MCC_ID_WITHOUT_DASHES"
  }
}

What the three Google Ads values mean

SettingUse this value
GOOGLE_PROJECT_IDYour Google Cloud Project ID, not the display name and not the project number.
GOOGLE_ADS_DEVELOPER_TOKENThe token shown in the Google Ads Manager account under Admin > API Center.
GOOGLE_ADS_LOGIN_CUSTOMER_IDYour Google Ads Manager (MCC) Customer ID with the hyphens removed.

Step 9: Restart Claude and test the connection

Save the configuration and fully restart Claude Desktop. Closing the window may leave Claude running in the background, so use Task Manager if necessary and end the Claude processes before reopening it.

In Claude Desktop, check Settings, then Developer and  you will see all the Local MCP servers. Google Ads MCP should be displayed.

Claude Local MCP Servers

Claude Local MCP Servers

If it is present, open a new chat and ask:

“Using the google-ads server, call list_accessible_customers and show the customer IDs.”

A list of customer IDs confirms that Claude can reach the Google Ads API with your authenticated account. If you see “developer token only approved for test accounts”,

your MCP configuration may be fine. Check the Access level in Google Ads, Admin, API Center. You need Explorer, Basic or Standard Access to query production accounts.

Troubleshooting the common problems

The Google Ads server does not appear

Fully quit Claude, reopen it and confirm you edited the config file opened by Settings > Developer > Edit Config.

The server starts with an error

Check that Git is installed and that the command points to the real pipx executable returned by where.exe pipx.

Google says the Ads API is not enabled

Confirm the Google Ads API is enabled on the exact Google Cloud Project ID used in the configuration.

The OAuth sign-in is blocked

Confirm you are using your own OAuth client JSON and that your Workspace administrator has not blocked the app or requested scopes.

Authentication worked, then stopped after a week

Check Google Auth Platform > Audience. An External app left in Testing is subject to the seven-day refresh-token limit.

One account is missing

Check that the account is linked to the MCC where required and that the Google user you authenticated with can access it.

A few ways to use the connection

Once the server is working, start with questions that help you investigate an account rather than asking for a broad summary. For example:

  • Which campaigns spent the most in the last 30 days, and how did their conversion rate change?
  • Show me campaigns where spend increased but conversions fell compared with the previous period.
  • Which customer IDs can I access through this login?
  • For customer ID 1234567890, show campaign performance this week and flag the largest changes.

For most account-level questions, include the customer ID in your prompt. That is especially useful when one Google login can access several client accounts.

Frequently asked questions

Is the Google Ads MCP connection read-only?

The Google Ads MCP server described here exposes search, metadata and customer-listing tools for analysis. This setup is intended for reading and investigating Google Ads data, not changing campaigns.

Is GOOGLE_ADS_LOGIN_CUSTOMER_ID my MCC ID?

Yes, when your access to the client account goes through a manager account. Use the 10-digit Manager Customer ID without hyphens.

Do I need Basic Access for the developer token?

Not always. Explorer Access can query production accounts and is often enough for read-only diagnostics. Test Account Access cannot query live production accounts.

Why should I publish the OAuth app?

If an External OAuth app stays in Testing, Google normally expires its refresh tokens after seven days. Publishing it as In production removes that testing limitation.

Is the OAuth client JSON the same as the ADC credentials file?

No. The OAuth client JSON identifies your OAuth application. The Application Default Credentials file is created by gcloud after you sign in. Keep both private and use the path that applies to each step.

Keep the connection secure

  • Store OAuth and ADC JSON files in a private folder with suitable Windows permissions.
  • Treat the developer token and client secret as credentials.
  • Do not commit credential files to Git or paste them into public support threads.
  • Reset a token or secret if you believe it has been exposed, then update the local configuration.

What to do next

Once you can list accessible customers, test the connection on one account you know well. Ask Claude for a simple campaign or conversion report, compare the result with the Google Ads interface, then build up to more useful diagnostic questions.

The value is not in producing another generic account summary. It is in shortening the time between spotting a performance question and getting to the rows of data that can answer it.

Need help with the analysis?

Claude will answer whatever your Google Ads account tells it. If the conversion tracking behind those numbers is wrong, you still get a confident answer, just built on the wrong data.

That is the part we handle. Neal wrote the official GA4 and GTM audit course for CXL, and we check the tracking behind the accounts before anyone acts on what the numbers say. We also audit analytics setups and investigate conversion performance.

Book Your Free Audit

Prefer to talk it through first? Get in touch or email [email protected].

Related reading: connect Claude to Google Analytics 4, connect Claude to Google Tag Manager with a self-hosted MCP server, and the Google Analytics audit checklist.

Official resources:

More reading

How to Connect Claude AI to Google Analytics 4

How to Connect Claude to Google Tag Manager With a Self-Hosted MCP Server

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

The reCAPTCHA verification period has expired. Please reload the page.

Neal Cole
Author

Neal Cole

Founder of Conversion Uplift

Neal is the founder of Conversion Uplift and the author of the official GA4/GTM audit course for CXL. It's the course much of the industry trains on. He's spent over 20 years in digital analytics, including senior roles in online gaming and financial services, working out where businesses' numbers go wrong and what it costs them. His work now focuses on server-side tracking, GA4 and BigQuery: getting the data right, and being the person who's accountable for it when a decision depends on it.

How to Connect Claude AI to Google Analytics 4

Connect Claude AI to Google Analytics 4 — MCP server setup guide

How to Connect Claude AI to Google Analytics 4

Published 29/07/2026. Last updated 30/08/2026. Windows setup, about an hour start to finish.

In short: Claude talks to Google Analytics 4 through an MCP server, a small program that runs on your own computer and passes read-only questions to the GA4 API. You need Python, a Google Cloud service account with Viewer access to your property, and one entry in Claude Desktop’s config file. Allow an hour.

In this post, learn how to connect to Claude AI and free up time for more added value tasks. You will be able to stop spending time checking if a GA4 property has been correctly configured and can check multiple properties using an automated approach. Connecting AI to GA4 opens up lots of opportunities to get easier access to data. Imagine, if you could open a chat window and type “Which of my traffic sources bring in lots of visitors but almost no engagement?” A minute later you have a real answer, pulled straight from your Google Analytics 4 data. No export to build, dashboard to check and no waiting on someone else to run the numbers.

That’s what happens once you connect Claude, Anthropic’s AI assistant, to your GA4 property. If you spend much of your week in GA4 or you have multiple GA4 properties to monitor, this changes how you work with it day to day.

Watch the Walkthrough (or Read On)

If you’d rather watch someone do this than read about it, our founder, Neal Cole, who created the CXL.com GA4 Audit course, has recorded a full walkthrough. Both routes end up in the same place, so pick whichever suits how you learn.

What You’ll Learn

By the end of this article you should understand:

  • What MCP actually is, without needing a technical background
  • Why connecting Claude to GA4 is worth the setup time
  • How to set the connection up yourself, step by step
  • The problems people usually hit, and how to fix them
  • What you actually gain once it’s running

What Is MCP?

You’ll see the term MCP a fair bit in this guide. It stands for Model Context Protocol. That sounds complicated, but the idea is simple. Think of it as a go-between that sits between Claude and another piece of software, in this case Google Analytics. Claude can’t reach into your GA4 account on its own. It needs a controlled way to ask for data and get something back. That’s the job of an MCP server. It takes your question, fetches the data from Google using credentials you set up yourself, and passes the answer back to Claude.
mcp ga4 architecture diagram

How Claude, the MCP server and GA4 fit together

 

You don’t need to understand how this works to get value from it, in the same way you don’t need to understand WiFi to use it. What matters is this: the connection only ever has read access. It can look at your data. It can’t change anything in your GA4 property.

 

Why Connect Claude to GA4?

Once connected, GA4 stops being something you navigate and starts being something you can just ask. You can type a question in plain English rather than hunting for the right report. “How much of my traffic came from AI tools like ChatGPT and Perplexity last week?” is a perfectly good question to type in and get answered.
Spotting trends gets quicker too. Ask Claude which pay-per-click campaigns are driving the best conversion rate this month, and skip building that report from scratch. It’s also decent at flagging things worth a second look. It can find landing pages with an odd bounce rate, or a traffic source bringing in volume, but barely any engagement. None of this replaces someone who actually knows GA4 properly, and it won’t catch every configuration issue in your setup. What it does is cut out a lot of the manual digging between having a question and getting an answer, which is most of what these tools are good for.
Claude responding to a test question using connected GA4 data

Example response using Conversion Uplift’s own GA4 data

 

Step-by-Step Setup Guide

This guide is for Windows only. If you’re on a Mac, the steps below won’t be exactly the same, but it will be the same process, just a few differences to do with Macs.
A few terms come up a lot in this guide, so here’s what they mean before you start:
PowerShell is a program built into Windows where you type text commands instead of clicking buttons. Think of it as a very plain, text-only way of telling your computer what to do.
An “environment variable” is a setting stored by Windows that tells programs where to find things on your computer. You’ll edit one called PATH later on.
An API is simply a door that one piece of software opens so another piece of software (in this case, the GA4 connector) is allowed to ask it for information.
A JSON file is just a plain text file, written in a specific format, that stores settings or credentials. You won’t need to understand the format, only where to find the file and where to put it.
There are three parts to this guide. First, getting your computer ready, then setting things up inside Google Cloud and GA4, and finally, connecting it all to Claude Desktop. Set aside a good hour rather than the “30 minutes” as some guides promise. I almost always find something doesn’t work first time. Between typing everything carefully and the odd hiccup, that’s realistically how long it takes.  However, there is no coding experience needed. Every command below is written out for you to copy and paste exactly as shown.

Before you start, you’ll need:

  • A Windows PC and about an hour
  • Claude Desktop app (claude.ai/download)
  • Admin access to the GA4 properties you wish to connect (you will need to add an email address)
  • A Google Cloud account with billing set up at console.cloud.google.com (the APIs themselves are free, Google just requires a card on file to open a project)
Setup roadmap for connecting Claude to GA4 in three stages

Setup roadmap for connecting Claude to GA4

Part 1: Prepare Your Computer

Most of this part happens in PowerShell. If you’ve never used it. Go to Start in Windows and type “PowerShell”, and press Enter. A black or blue window opens where you type commands and press Enter to run them. That’s it, that’s the whole tool.
Windows PowerShell Window

Windows PowerShell Window

Step 1: Install Python

The MCP connector is a small program written in Python, so Python needs to be on your machine first. You don’t need to have any coding experience to use Python because Claude will do all the coding for you, you just need to give AI the prompts, and it will do the rest.

  • Go to the website python.org/downloads and download the latest standalone installer (not the “Python install manager” option, make sure you pick the right one, as both appear on the page).

 

Python.org downloads page

Python Standalone Installer

  • Run the installer. On the very first screen, tick “Add python.exe to PATH”. This is the single most commonly missed step, and it’s what lets you type “python” in PowerShell later. Then click “Customise installation” rather than the quick install option.
  • On the Optional Features screen, leave everything ticked except “for all users”, and click Next.
  • On Advanced Options, tick “Associate files with Python” and “Create shortcuts for installed applications”. Then click Browse and set the install location to C:\Python (create the folder if it doesn’t already exist). The path must contain no spaces, which is why we’re not using the default location.

 

Python Path

C:\Python path – create this folder if it doesn’t exist

  • Click Install. If the final screen offers “Disable path length limit”, click it.
  • Open a new PowerShell window (closing and reopening matters here) and check it worked by typing:

python --version

You should see something like Python 3.14.6 printed back.

If Windows says: *“Python was not found; run without arguments to install from the Microsoft Store”* – this means Windows is intercepting the command and trying to send you to its own Store version instead of the one you just installed.
To fix it; go to Settings, then Apps, then Advanced app settings, then App execution aliases. Switch OFF both python.exe and python3.exe. Try again in a new PowerShell window.
Turn off Python aliases

Turning off Python.exe toggles

 

If PowerShell says: “the term ‘python’ is not recognised” , Python is installed, but Windows doesn’t know where to look for it. You need to add two folders to your PATH:

  • Go to Start again, type “environment variables”, and open “Edit the system environment variables”, then click the Environment Variables button.

 

Clicking into environment variables

Selecting Environment Variables

  • Under “User variables”, click the row named Path, then click Edit. Don’t click New, which creates a separate variable that Windows ignores.
  • Click New and add C:\Python, then New again and add C:\Python\Scripts. Click OK on every window.
  • Open a brand new PowerShell window and run python --version again.

 

Adding Python's install folders to the Windows PATH environment variable

Adding the Python folders to the PATH environment variable

Type these entries carefully, a single wrong character (like C:\pythonzscripts instead of C:\Python\Scripts) means Windows fails to find your programs, with no error to warn you. PATH changes only apply to new PowerShell windows, so always close and reopen after editing as otherwise PowerShell won’t have updated and it will give you the wrong feedback.

Step 2: Install pipx

This is the second thing you need to install on your machine. Pipx is a small installer tool that keeps Python programs, including the GA4 connector, tidy and self-contained on your machine. This step is three short blocks of commands, each run in its own fresh PowerShell window. Again, make sure you open a new PowerShell window.
  • Open PowerShell and run this, which tells pipx to use folders without spaces in their paths:
setx PIPX_HOME "C:\pipx-new"
setx PIPX_BIN_DIR "C:\pipx-new\bin"
  • Close that PowerShell window, open a new one, and run:
python -m pip install --upgrade pip
python -m pip install pipx
python -m pipx ensurepath
  • Close PowerShell again. Yellow warning text during install is normal. As long as you see “Successfully installed … pipx” somewhere in there, you’re fine.

Step 3: Install the GA4 MCP server

You are making good progress, next we need to confirm a few things.

  • Before installing, confirm exactly where Python lives on this machine, since it isn’t always C:\Python. In a new PowerShell window, run:
   python -c "import sys; print(sys.executable)"
  • PowerShell prints the exact location, for example C:\Python\python.exe. Copy this. You’ll use it in the install command below in place of C:\Python\python.exe if yours is different.

 

Install GA4 MCP server

Install GA4 MCP server

The one line that might look alarming, “WARNING: Skipping setuptools as it is not installed”, is harmless and so it is safe to ignore it. It’s a routine pipx notice and doesn’t affect the install. If this prints a path inside \WindowsApps\, or Python still isn’t recognised here, stop and finish the PATH and App execution alias fixes from Step 1 first. If the installer points at the Microsoft Store stub instead of your real Python install, this step will fail without any warning. In this case, follow the next steps:

  • Open a new PowerShell window and run:
pipx install analytics-mcp --python "C:\Python\python.exe" --force
  • Wait a minute or two. Success looks like: “installed package analytics-mcp … These apps are now available: analytics-mcp.exe”.

We now need to verify the install and find out exactly where the program landed. Paste this into PowerShell

pipx list
where.exe analytics-mcp
Verify the GA4 MCP server install location

Verify the GA4 MCP server install location

6. Write down the full path it reports. You’ll need to paste it into Claude’s settings in Step 9. It’s usually C:\pipx-new\bin\analytics-mcp.exe or C:\Users\YOURNAME\.local\bin\analytics-mcp.exe.
If where.exe says it “could not find files”, don’t worry, the program is still installed. Find it by running dir C:\Users\YOURNAME\.local\bin\analytics-mcp.exe (swap in your own username) or dir C:\pipx-new\bin\analytics-mcp.exe. Whichever command lists a file is your path.

Part 2: Set up Google Cloud and GA4

Step 4: Create a Google Cloud project

The Google Cloud project is just a container that holds your permissions. You will only need to do this once. If you don’t already have a Google Cloud account, you will need to create a new one or check if your organisation already has GCP set up.

  • Go to console.cloud.google.com and sign in. First time visitors are asked to accept terms and may need to set up a billing account under Billing.
  • Click the project dropdown at the top of the page, then “New Project”.
  • Give it a clear name, such as “MCP Analytics Connection”, and click Create.
  • When the “Create Project” notification appears, click Select Project, this step is easy to miss, and if you skip it, everything you do next happens in the wrong project.
  • Note your Project ID, shown on the Welcome page (e.g. mcp-analytics-connection). It’s usually your project name in lowercase with hyphens. You’ll need it in Step 9.

 

Google Cloud Project

Google Cloud Project

Step 5: Enable the two Analytics APIs

Next you will need to enable a couple of APIs in Google Cloud. APIs are the doors which opens doors for the connector, and it is a fairly simple process.
  • From the top left menu in Google Cloud, choose “APIs & Services”, then  select “Library” from the sub-menu on the left-hand side of the screen.
  • Next, use the Search box at the top of the  API Library page to search for “Google Analytics Admin API”, select it and it should open for you.

 

Google Analytics Admin API

Google Analytics Admin API

  • Now click to Enable.
  • Go back to the “Library”, and this time search for “Google Analytics Data API”, open it, and click Enable.

 

API Library

API Library

 

The Library has several similarly named APIs, make sure you enable exactly these two, not the older “Google Analytics API” or “Analytics Hub API”.

Step 6: Create the service account (your “robot user”)

You will need an email address to give access to Claude to your individual GA4 properties. We do this through what is called a service account. It is essentially a robot user. It’s an account that isn’t a person, but that Google and GA4 will treat like any other user you might add to your team.

 

  • In the top left menu,  select “IAM & Admin”, and then Service Accounts. When you land, check that  the top bar still shows your new project and not another unrelated project as this is a common mistake.
  • Now click “+” and then “Create service account”.
  • Give it a name that makes it clear what the account is for, for example “mcp-server”.
  • Next, add a description like “Google Analytics MCP Server”, and click “Create and continue”.
  • The next two screens, “Permissions” and “Principals with access”, are both optional. Skip them by clicking “Continue” on the first, and “Done” on the second. This is because GA4 access is granted separately in Step 8.

 

Create Service Accounts in MCP

Creating the service account in Google Cloud

  • Back on the Service Accounts list, copy the account’s email address, it looks like [email protected]. You’ll need it in Step 8 to give Claude access to your GA4 properties.

Step 7: Create and store the JSON key

Here we will need to store the JSON key and you only get one opportunity to do this, and so take your time.  However, if you don’t manage to get it the first time, it is easy to create a new key.

  • Click on the service account’s email in the list to open it, then go to the Keys tab.
  • Click “Add key”, then “Create new key”, and choose JSON, then click Create. The file should download automatically.
Create Key Dialog Box

Create Key Dialog Box

 

  • You then need to rename the downloaded file to ga4-mcp.json (no spaces).
  • Next, create a new folder on your c: drive  with the path  C:\keys. Move the file into this new folder and so the full path should be C:\keys\ga4-mcp.json.
This file works like a keycard to your analytics data. Don’t email it, don’t put it in a shared drive or public folder, and don’t add it to any code repository. A repository is just an online storage place for code, often shared with others, but this needs to be kept secret. If it ever leaks, go back to the Keys tab, delete that key, and create a new one. This includes if you put it into AI for some reason.
If the Keys tab still says “No rows to display” right after downloading, refresh the page. The key is there.
To check the key is in the right location, paste:
Test-Path C:\keys\ga4-mcp.json

If it says True, the key is in the right location, and you’re set to go. If False, search for it using this script. Google names downloaded keys after the project, so search for JSON files starting with the project ID (probably something like analytics-connection-…). Check your Downloads and user folder:

Get-ChildItem $HOME\Downloads, $HOME\Desktop, $HOME\Documents, C:\keys -Filter *.json -Recurse -ErrorAction SilentlyContinue | Select-Object FullName, LastWriteTime

Step 8: Give the robot user access to your GA4 property

Well done, you are now very close to completing the process of connecting  Claude to GA4. Next, we have to invite the service account to join GA4, exactly as you would a human colleague. Repeat this step for every property you want Claude to be able to query. Only give it Viewer access as we don’t want AI making any changes in GA4.

  • Go to analytics.google.com and select the property you want to connect.
  • Click Admin (the gear icon, bottom left).
  • 3. In the Property column, click Property access management.
  • 4. Click the “+” button (top right), then Add users.
  • 5. Paste the service account email from Step 6 into the Email addresses field. Untick “Notify new users by email”, since a robot account has no inbox to check.
  • 6. Under Direct roles, choose Viewer. Read-only is all it needs. Click Add.

 

Adding a service account with Viewer access in GA4 property settings

Giving the service account Viewer access in GA4

You will need to have Administrator access on the GA4 property to add users. If the Add button is greyed out, ask whoever manages the property to either upgrade your access or if they can add the service account email on your behalf.

Part 3: Connect Claude and Test It

Finally, you will need to modify the Claude Config file to connect your desktop Claude app to be able to communicate with your GA4 properties.

Step 9: Tell Claude Desktop about the server

Claude Desktop keeps its settings in a file called claude_desktop_config.json. You need to add a section that tells it where the GA4 server lives and which credentials it uses. The server you installed in Step 3 will not appear in Claude until this file is saved and the app restarted.

    • To open the config file:
      1. Open Claude Desktop. Click the Menu (three lines, top left), then File > Settings > Developer > Edit Config.
      2. A File Explorer window opens on the folder that holds claude_desktop_config.json. The button opens the folder, not the file.
      3. Right-click claude_desktop_config.json and open it in Notepad. If you have VS Code or Notepad++, use one of those instead: they’ll flag a missing comma before Claude has to.

To add the server:

What you paste depends on what’s already in the file. If the file is empty, or contains only {}, replace everything with this:

{
  "mcpServers": {
    "analytics-mcp": {
      "command": "C:\\pipx-new\\bin\\analytics-mcp.exe",
      "args": [],
      "env": {
        "GOOGLE_APPLICATION_CREDENTIALS": "C:\\keys\\ga4-mcp.json",
        "GOOGLE_PROJECT_ID": "your-project-id"
      }
    }
  }
}

If the file already has an "mcpServers" section, don’t paste a second one. A JSON file can’t hold two keys with the same name, and one of them gets dropped without any error. Add your server inside the section that’s already there, separated by a comma.

 

{
  "mcpServers": {
    "a-server-you-already-had": {
      "command": "..."
    },
    "analytics-mcp": {
      "command": "C:\\pipx-new\\bin\\analytics-mcp.exe",
      "args": [],
      "env": {
        "GOOGLE_APPLICATION_CREDENTIALS": "C:\\keys\\ga4-mcp.json",
        "GOOGLE_PROJECT_ID": "your-project-id"
      }
    }
  }
}

Everything else in the file stays where it is. You can run as many MCP servers as you want: mcpServers is a list of named entries, and adding one doesn’t remove the others.

Windows paths need double backslashes inside JSON. C:\keys\ga4-mcp.json becomes C:\\keys\\ga4-mcp.json. A single backslash breaks the file.

  • Personalise the three values: the command path (from Step 3 – double every backslash), the key file path (Step 7), and your Project ID (Step 4). I have bolded them in the above content.
  • Count your curly brackets before saving. A JSON file only works if every opening bracket has a matching closing one. If you’re unsure, paste the whole file into a free checker like jsonlint.com, which will point at the exact line of any problem.
  • Save the file properly using File, then Save (or Ctrl+S), not Save As, and don’t rely on closing the window to save it for you. Then close Claude properly: menu, then File, then Exit. Clicking the X only hides the window. The app keeps running with the old settings, which is the single most common reason this step “doesn’t work”.

Watch out for this: Notepad often adds a hidden .txt to the file name, saving it as claude_desktop_config.json.txt, which Claude will never see. In the Save dialog, set “Save as type” to “All Files” and “Encoding” to UTF-8, and check the name ends in .json with nothing after it. If you’ve already saved it, turn on file name extensions in File Explorer (View menu) to check, and rename it back if needed.

  • Make sure you now close the Claude desktop app completely. This is best done by holding down the Ctrl, Alt and Delete keys and select “Task manager”. Here you should select “Claude” and press Delete to close it down. Relaunch Claude from the Start menu and check > Settings > Developer:  analytics-mcp should now be listed under Local MCP servers.

 

Claude Desktop settings showing the GA4 analytics connector running

analytics-mcp listed in Claude Developer settings

Step 10: Test it

Congratulations, you can now test using your new MCP connection in a new Claude chat, type:

“Can you list all the Google Analytics 4 accounts you have access to using the Google Analytics tool?”

Claude should now reply with the full list of GA4 accounts and properties you granted access to in Step 8. If it does, you’re connected, from here on, just ask questions in plain English.

 

Claude responding to a test question using connected GA4 data

Example response using Conversion Uplift’s own GA4 data

Prompts to Try First

  • “Show me my top 20 traffic sources by source/medium for the last 30 days, with sessions, active users and engagement rate. Which sources bring lots of traffic but low engagement?”
  • “How much traffic came from AI tools (chatgpt.com, perplexity.ai, gemini.google.com, copilot.microsoft.com, claude.ai) in the last 7 days, and how does its engagement compare to the site average?”
  • “Compare this month’s sessions and key events to the same month last year, broken down by device category.”
  • “What are my top landing pages by entrances this week, and which have the highest bounce?”
  • “How much of my traffic came from affiliate sites in the last 30 days, and what’s their conversion rate compared to other channels?”

One habit worth keeping: always sanity check totals by asking Claude to run a separate report with no breakdowns for the same date range, rather than trusting a sum of rows from a breakdown table.

Common Issues and Tips

A setup that touches PowerShell and Windows settings will occasionally trip people up. Here’s what usually goes wrong.

“Python was not found; run without arguments to install from the Microsoft Store.”

Windows is intercepting the command. Go to Settings, then Apps, then Advanced app settings, then App execution aliases, and turn OFF python.exe and python3.exe. Then open a new PowerShell window.

“The term ‘python’ is not recognized.”

C:\Python isn’t on your PATH. Follow the PATH steps in Step 1. The entries need to go inside the existing Path variable, not as separate new variables. Then open a new PowerShell window.

“The term ‘pipx’ is not recognized.”

The Scripts folder is missing from PATH, or there’s a typo in it. Check the Path entries character by character (C:\Python\Scripts). As a workaround, you can replace pipx with python -m pipx in any command.

where.exe analytics-mcp returns nothing.

The pipx app folder isn’t on PATH, which is fine, since the config file uses the full path anyway. Confirm the file exists with dir C:\Users\YOURNAME\.local\bin\analytics-mcp.exe or dir C:\pipx-new\bin\analytics-mcp.exe.

The connector doesn’t show up in Claude’s Developer settings.

Usually one of two things: Claude wasn’t properly restarted (close it with File, then Exit, not the X button), or there’s a JSON error in the config file, often a missing comma or bracket. Paste the config into a checker like jsonlint.com if it still won’t show.

Claude answers without touching your GA4 data.

Start a new chat and ask explicitly: “Use the analytics-mcp tools to…”. Also check the connector is switched on for that chat in the tools menu near the message box.

“API Error: 529 Overloaded.”

Nothing wrong with your setup. Anthropic’s servers are temporarily busy. Click Try again, or check status.claude.com.

If something’s not working after a reasonable amount of poking around, it’s usually faster to get a second pair of eyes on it than to keep troubleshooting alone.

The Benefits, Summed Up

Once it’s running, here’s what it actually changes:

  • Answers come faster, without needing a report built first
  • People who aren’t GA4 specialists have an easier way in
  • Trends and anomalies get spotted sooner
  • More of your time goes on acting on what you find, less on finding it

Conclusion

Connecting Claude to GA4 won’t replace solid analytics knowledge, but it can cut down a lot of the friction between asking a question and getting an answer. If you or your team spend real time in GA4 but don’t want to live in the interface, this is a practical way to speed things up. To help your team, get AI to document all your events and parameters so that people know what to ask for, as otherwise, if you ask the wrong question, you will get the wrong answer or no answer at all.

This setup takes about an hour, done properly. After that, your analytics data is just a conversation away. It is also a great way of conducting regular audits to check for settings that may not be optimal for you, but remember to give it some context, such as the nature of your website and business goals, so that it can focus on what is most relevant to your website.

Want Help Getting This Right?

Claude will answer whatever your GA4 property tells it. If the property is mis-configured, you still get a confident answer, just built on the wrong numbers.

That is the part we handle. Neal wrote the official GA4 and GTM audit course for CXL, and we check GA4 properties, tag manager containers and server-side tracking setups so the numbers behind the answers hold up.

Book Your Free Audit

Prefer to talk it through first? Get in touch or email [email protected].

Related reading: the Google Analytics audit checklist, the Google Tag Manager audit checklist, and how to connect GA4 to BigQuery for analysis heavier than a chat window handles well.

More reading

Why Conversion Tracking Is Broken (and How to Fix It)

How to connect Claude to Google Ads

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

The reCAPTCHA verification period has expired. Please reload the page.

Neal Cole
Author

Neal Cole

Founder of Conversion Uplift

Neal is the founder of Conversion Uplift and the author of the official GA4/GTM audit course for CXL. It's the course much of the industry trains on. He's spent over 20 years in digital analytics, including senior roles in online gaming and financial services, working out where businesses' numbers go wrong and what it costs them. His work now focuses on server-side tracking, GA4 and BigQuery: getting the data right, and being the person who's accountable for it when a decision depends on it.

Why Conversion Tracking Is Broken (and How to Fix It)

Why Conversion Tracking Breaks

Why Conversion Tracking is Broken?

So, the number of conversions and revenue are a lot lower in GA4 and Google Ads than what your back-end data shows you. Well, conversion tracking should tell you which marketing campaigns generate leads and sales. Too often, it does not. These are common problems we come across every day and we see the same causes across different sectors and website platforms.

Quick plugins and native integrations make conversion tracking easy to install, but easy does not always mean accurate. Here are five common weaknesses, and how web and server-side Google Tag Manager can create a more reliable measurement setup.

Conversions may be missing, duplicated or attributed to the wrong channel. The numbers reported in Google Analytics 4 (GA4), Google Ads, Meta and an ecommerce platform may all disagree. Worse still, inaccurate conversion data can influence automated bidding, causing advertising platforms to optimise campaigns using unreliable signals.

The problem often begins with a tracking setup chosen because it was quick to install rather than because it was designed around the business’s measurement requirements.

In the accompanying video, I explain five common weaknesses in conversion tracking and how to build a more robust setup using Google Tag Manager (GTM) and server-side GTM.

 

1. Do not assume a plugin or native integration gives you reliable tracking

WordPress plugins such as Site Kit and ecommerce integrations available through app marketplaces can make GA4 quick to install. That is useful for a basic setup, especially when a business has limited technical resources.

However, convenience comes with trade-offs. You may have limited control over:

  • which events are sent;
  • when tags fire;
  • the data attached to each event;
  • consent behaviour;
  • duplicate-event prevention;
  • debugging and quality assurance; and
  • how tracking changes when the plugin or platform is updated.

Most plugins rely on third-party cookies, and even those that do incorporate server-side tracking, set limitaions on which events and parameters which are sent server-side. Bowser-based implementations depend heavily on client-side scripts and requests. These can be restricted by browser privacy controls, content blockers, consent choices, network failures and implementation conflicts.

This creates gaps between what actually happened on the website and what appears in your analytics and advertising platforms.

Why Google Tag Manager is usually a better foundation

A well-managed web GTM container gives you much more control. You can create a documented data layer, use consistent event names, apply validation rules and decide exactly which platforms receive each event.

GTM is not automatically accurate simply because it has been installed. It still needs a proper measurement plan, consent configuration, testing and ongoing governance. But it provides a far more flexible foundation than a collection of disconnected plugins.

2. Remove duplicate GA4 and Google tag loaders

After implementing GTM, check whether another plugin, theme, app or hard-coded script is also loading GA4 or the Google tag.

For example, a YouTube or GA4 app may continue to load its own tracking code after a new GTM implementation goes live. Two loaders can cause:

  • duplicate page views or ecommerce events;
  • the same transaction being reported more than once;
  • conflicting configuration settings;
  • inconsistent consent signals;
  • inflated conversion rates; and
  • more difficult debugging.

This does not mean every website with two Google-related scripts will definitely duplicate every event. The outcome depends on how each implementation is configured. Nevertheless, multiple uncontrolled loaders are a major warning sign. It is generally best practice to limit to a single loader.

Choose one governed implementation and remove or disable redundant tracking. Then test it in GTM Preview, GA4 DebugView and the browser’s network requests before publishing.

3. Move from web GTM to server-side GTM where the business case supports it

Web GTM is a major improvement over a fragmented plugin-based setup, but it still runs primarily in the visitor’s browser. Some measurement requests will be lost because of privacy controls, blockers, poor connectivity, page closures and technical errors.

Server-side Google Tag Manager adds a controlled tagging server between the website and analytics or advertising platforms.

Instead of the browser sending every request directly to several vendors, data can be routed through the business’s tagging endpoint. The server container can then validate, enrich, redact and distribute approved data to its destinations.

Google identifies improved performance, security and data control as key benefits of server-side tagging. A first-party context can also improve cookie durability and reduce the website’s direct communication with third-party domains.

Why consider Stape?

You can host server-side GTM through Google Cloud or another suitable platform. Stape offers managed hosting that can reduce the technical work and cost of deployment, particularly for small and medium-sized organisations.

Its custom-loader options can serve tagging resources from a first-party route and alter recognisable loading patterns. This may improve resilience in some environments, but it should not be described as invisible tracking or as a guaranteed way to defeat blockers. Tracking must still respect consent choices, privacy law and the user’s browser settings.

Server-side tagging is also not a magic fix. A badly designed data layer will send bad data more efficiently. The underlying event logic must be correct before it is routed through a server container.

4. Configure a first-party custom domain

Creating a server container is only part of the job. If it continues to use the cloud provider’s default domain, you will not receive all the benefits associated with first-party serving.

Configure the tagging server on a domain connected to your website—for example, data.example.co.uk—or use a same-origin path where the infrastructure supports it.

Google describes same-origin serving as best practice because it provides the security and durability benefits of server-set cookies. A first-party subdomain is also a common and practical option.

After configuring the domain, verify:

  • DNS and SSL are working correctly;
  • the web container sends requests to the correct server URL;
  • the server client claims the expected requests;
  • cookies are set with the intended domain and attributes;
  • consent signals pass correctly;
  • GA4 and advertising tags receive the expected parameters; and
  • the endpoint continues to work after publishing.

A custom domain that exists but is not used by the live tags achieves very little. Test the actual network traffic rather than relying on configuration screens.

5. Send Google Ads conversions directly rather than relying only on GA4 imports

GA4 conversions—now called key events in the GA4 interface—can be imported into Google Ads. This is convenient and can be useful for reporting or as a secondary comparison.

However, for the conversion actions used for bidding, I generally recommend implementing dedicated Google Ads conversion tags through web and server-side GTM. This gives you greater control over the data sent to Google Ads and avoids making the GA4 import the only source used for campaign optimisation.

The dedicated setup should include, where relevant:

  • a unique transaction or lead identifier;
  • conversion value and currency;
  • consent signals;
  • enhanced conversions using appropriately collected first-party data; and
  • deduplication between browser and server events.

In Google Ads, mark the trusted conversion action as Primary so it is included in the Conversions column and can be used for bidding. Keep the GA4-imported action as Secondary if you want it for observation and comparison without double-counting it in bidding goals.

Do not make this switch blindly. Run both sources in parallel, compare volumes and values, investigate discrepancies and confirm the direct Google Ads action is stable first.

A practical conversion-tracking architecture

A robust setup normally follows this route:

  1. The website pushes a clearly defined interaction into a data layer.
  2. Web GTM validates the event and applies the user’s consent state.
  3. The browser sends approved measurement data to a first-party tagging endpoint.
  4. Server-side GTM processes and controls the event.
  5. Dedicated tags send the required data to GA4, Google Ads and other approved platforms.

This architecture will not produce a perfect one-to-one record of every conversion. No modern web analytics system can promise that. It does, however, give you more control, better governance and a stronger opportunity to recover valid measurement signals than a collection of unmanaged plugins.

How to check whether your conversion tracking is broken

Look for these warning signs:

  • GA4 purchases do not match backend orders;
  • the same transaction ID appears more than once;
  • Google Ads reports substantially more conversions than your CRM or ecommerce platform;
  • key events fire when no genuine conversion occurred;
  • several plugins or scripts load the same GA4 measurement ID;
  • campaign attribution frequently appears as Direct, Unassigned or Referral;
  • consent is denied but marketing tags still set cookies or send identifiable data;
  • the server container exists, but browser requests still go directly to vendor domains; or
  • GA4 imports and direct Google Ads conversions are both Primary and double-counting the same outcome.

These symptoms do not all have the same cause. A proper audit should examine the website, data layer, GTM containers, consent platform, browser requests, server logs, platform settings and backend totals.

Final thoughts

Fast tracking installations are attractive, but a tracking tag appearing on a website is not proof that conversions are measured correctly.

Start by replacing fragmented plugins with a governed web GTM implementation. Remove duplicate loaders. Where the value and volume of conversions justify it, add server-side GTM, use a genuine first-party endpoint and send dedicated conversion data to advertising platforms.

Most importantly, test the complete journey—from the user’s action to the final platform report. Reliable conversion tracking is an engineered system, not a box to tick.

Call to action: If your analytics and advertising platforms disagree—or you are unsure whether your conversion data is trustworthy—Conversion Uplift can audit your GA4, Google Tag Manager, consent and server-side tracking setup. [Contact us to discuss a conversion-tracking audit.]

Suggested FAQs

Why is my conversion tracking not working?

Common causes include incorrect triggers, duplicate tags, missing consent configuration, browser restrictions, cross-domain problems, broken data-layer values and platform settings that count the wrong actions as conversions.

Is Google Tag Manager more accurate than a WordPress plugin?

Not automatically. GTM provides greater control, debugging and flexibility, which makes a robust implementation easier. Its accuracy still depends on the quality of the event logic, consent setup and testing.

Does server-side GTM prevent all tracking loss?

No. It can improve measurement resilience and control, but it cannot recover every blocked or uncollected interaction. It must also continue to respect valid consent choices and applicable privacy law.

Should I import GA4 conversions into Google Ads?

GA4 imports can be useful for reporting and comparison. For bidding-critical actions, a dedicated Google Ads conversion implementation often gives greater control. Avoid treating both versions of the same conversion as Primary unless intentional deduplication is in place.

Do I need a custom domain for server-side GTM?

You can technically send data to a provider’s default server URL, but a correctly configured first-party domain—or preferably a same-origin path where practical—is needed to gain the main cookie durability and first-party serving benefits.

More reading

Use AI to Audit GA4 Attribution & UTM Parameters (Faster, Smarter)

How to Connect Claude AI to Google Analytics 4

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

The reCAPTCHA verification period has expired. Please reload the page.

Neal Cole
Author

Neal Cole

Founder of Conversion Uplift

Neal is the founder of Conversion Uplift and the author of the official GA4/GTM audit course for CXL. It's the course much of the industry trains on. He's spent over 20 years in digital analytics, including senior roles in online gaming and financial services, working out where businesses' numbers go wrong and what it costs them. His work now focuses on server-side tracking, GA4 and BigQuery: getting the data right, and being the person who's accountable for it when a decision depends on it.

Use AI to Audit GA4 Attribution & UTM Parameters (Faster, Smarter)

AI GA4 Attribution and UTM Audit

AI GA4 Attribution Audit

GA4 attribution is often inaccurate because most websites misuse UTM parameters. Inconsistent UTM naming conventions, incorrect medium values, mixed casing, and poorly structured campaign tags all lead to broken attribution in Google Analytics 4. When UTMs are not implemented correctly, GA4 cannot reliably assign traffic to the right source, medium, or channel group, resulting in inflated Direct traffic, high Unassigned sessions, and misleading performance data. Correct use of UTM parameters is essential for accurate GA4 attribution, reliable channel reporting, and informed marketing decisions — without it, campaign analysis and ROI measurement are fundamentally unreliable.

You can either watch my video here or read the blog below — both cover the same process.
If you deal with messy GA4 attribution, inconsistent UTMs, or bloated channel groups, this will save you hours.

 

Why GA4 Attribution Audits Are So Painful

In a typical GA4 audit, you’ll head to Traffic acquisition report, and then start breaking things down by adding an extra dimension such as:

  • Session source / medium

  • Campaign name

  • Campaign ID (if used)

That’s where the fun stops.

What starts as ~20 rows quickly becomes hundreds once you factor in:

  • Google Ads

  • Meta / Facebook

  • TikTok

  • Email campaigns

  • Affiliates

  • Display

  • Random “one-off” campaigns

Go to the Explore section and create a custom report if you need to add multiple dimensions to your report. However, manually reviewing that is slow, boring, and error-prone.

The Real Problem: UTMs Are Usually a Mess

Across most accounts we audit, we see the same issues:

  • Uppercase vs lowercase UTMs

  • Non-standard medium names (paid, social_paid, email_campaign, etc.)

  • Campaign names that mean nothing

  • Different teams using different “rules”

  • Marketers copying broken UTMs because “that’s how it’s always been done”

GA4 doesn’t forgive this. Attribution breaks. Channel groups inflate. “Unassigned” grows.

The AI-Assisted GA4 Attribution Audit Workflow

Here’s the exact process shown in the video.

1️⃣ Export the GA4 data

  • Use Traffic acquisition or an Explore report

  • Include:

    • Session Source / Medium

    • Campaign (and campaign ID if relevant)

  • Then use the ‘Share this report’ option in the GA4 UI, and choose your preferred Download option. We use the CSV option.

 

2️⃣ Clean the spreadsheet (lightly)

Before uploading to AI:

  • Save the CSV file as an Excel Spreadsheet
  • Remove columns that add no value (company name, notes, etc.)

  • Keep it focused on attribution fields

This helps AI process the data properly and avoids it generating garbage.

3️⃣ Upload to AI (and use a proper prompt)

Once uploaded, use a clear, structured prompt. Ours asks AI to:

  • Evaluate GA4 traffic acquisition data

  • Identify inconsistent or sub-optimal UTM usage

  • Flag:

    • Case sensitivity issues

    • Non-standard mediums

    • Broken or ignored naming conventions

  • Recommend:

The output is blunt — and that’s exactly what you want.

Here is the full prompt:

“Evaluate this traffic acquisition data from Google Analytics 4  and list inconsistent or sub-optimal use of UTM parameters, such as upper case and not using standard naming conventions that Google does not recognise. identify how changes in the use of  UTM parameters could be deployed to provide more consistent channel groups to help marketing people set up UTM parameters for campaigns using best practice naming conventions. Also suggest how to break up generic default channel groups into custom channel groups in the GA4 console.”

What AI Is Brilliant At (and Humans Are Bad At)

AI excels at spotting patterns across hundreds of rows, including:

🚨 Common issues flagged

  • Paid instead of cpc

  • social_paid instead of paid_social

  • email_campaign instead of email

  • display_ads instead of display

  • Overloaded or meaningless campaign names

✅ What you get back

  • Bad examples vs good examples

  • Clear UTM naming frameworks

  • Practical guidance marketers can actually follow

  • Channel grouping recommendations that work with GA4 logic.

Why This Matters (Tell It Like It Is)

If your UTMs are wrong:

  • GA4 attribution is wrong

  • Channel performance is wrong

  • Budget decisions are wrong

AI doesn’t fix bad data automatically — but it exposes problems fast. That’s the real win.

Bonus: Turn the Output into Client Deliverables

Once AI has reviewed the data, you can:

  • Ask follow-up questions.

  • Simplify the language.

  • Generate a PDF audit summary to share with your client.

  • Share clear UTM rules with marketing teams.

This turns a messy export into something clients actually understand.

Final Thought

AI won’t replace proper GA4 implementation, but for attribution audits and UTM reviews, it’s a serious time-saver.

If you’re still eyeballing hundreds of GA4 rows manually, you’re doing it the hard way.

👉 Watch the video above if you want to see it live
👉 Read this blog if you want the repeatable process

Either way, clean UTMs = better attribution = better decisions.

More reading

Track AI-Sourced Traffic with Google Tag Manager (Step-by-Step)

Why Conversion Tracking Is Broken (and How to Fix It)

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

The reCAPTCHA verification period has expired. Please reload the page.

Neal Cole
Author

Neal Cole

Founder of Conversion Uplift

Neal is the founder of Conversion Uplift and the author of the official GA4/GTM audit course for CXL. It's the course much of the industry trains on. He's spent over 20 years in digital analytics, including senior roles in online gaming and financial services, working out where businesses' numbers go wrong and what it costs them. His work now focuses on server-side tracking, GA4 and BigQuery: getting the data right, and being the person who's accountable for it when a decision depends on it.

Track AI-Sourced Traffic with Google Tag Manager (Step-by-Step)

Tracking AI Visits and Backlinks using GTM

AI assistants and AI search engines (ChatGPT, Gemini, Perplexity, Copilot, etc.) increasingly surface and share your content. This guide shows how to track AI referrals in GA4 using Google Tag Manager (GTM) and GA4 with a clean dataLayer push, so multiple tags (GA4/Ads/Meta) can consume the same signal.

What you can use this for?

  • Measure AI visibility: See how often AI platforms send you traffic and which platforms matter most.

  • Content strategy: Identify pages most cited by AI tools and double down on those topics for SEO purposes.

  • Channel comparison: Compare AI-sourced sessions with Organic/Social/Direct for engagement and conversion.

  • Campaign targeting: Build audiences (e.g., “Came from ChatGPT”) for remarketing or personalisation.

 

How this differs from “AI snippet” GTM implementations?

  • AI snippet tracking (e.g., text-fragment #:~:text= / PAA/Featured Snippets) measures clicks from enhanced Google SERP features.

  • This method measures referrals from external AI platforms (ChatGPT/Gemini/Perplexity/Copilot/You.com/etc.), outside traditional search.

  • Use both to see the full picture: inside-Google AI features and off-Google AI platforms.

 

Overview of the approach:

  1. A CustomJavaScriptVariable detects AI platforms via document.referrer and UTM hints.

  2. A Custom HTML tag runs on DOM Ready and, if a match exists, pushes an ai_referral_detected custom event with parameters into the dataLayer.

  3. You create Data Layer Variables (DLVs) that read those parameters.

  4. A GA4 Event tag fires on the Custom Event (ai_referral_detected) and sends parameters to GA4.

  5. Make sure you register custom dimensions in GA4 and analyse. To minimise the number of new event scoped custom dimensions that you need to register, try and re-use some generic custom dimensions that you may use elsewhere, such as type and sub_type.

Step 1 — Create the detector (Custom JS Variable)

GTM → Variables → New → Custom JavaScript
Name: CJ AI Source Detector

function () {
try {
// — Helpers as function expressions (ES5-safe in blocks) —
var trim = function(s){ return (s || ”).replace(/^\s+|\s+$/g,”); };
var lc = function(s){ return (s || ”).toLowerCase(); };
var getQueryParams = function(href){
var params = {};
var q = href.split(‘?’)[1] || ”;
if (!q) return params;
var hashSplit = q.split(‘#’)[0] || q; // strip fragment
var parts = hashSplit.split(‘&’);
for (var i=0; i<parts.length; i++){
var kv = parts[i].split(‘=’);
if (!kv[0]) continue;
var key = lc(decodeURIComponent(kv[0]));
var val = ”;
try { val = lc(decodeURIComponent(kv.slice(1).join(‘=’))); } catch(e) { val = lc(kv.slice(1).join(‘=’)); }
params[key] = val;
}
return params;
};
var build = function(platform, method, pattern){
return {
platform: platform.name,
category: platform.category,
detection_method: method, // ‘referrer’ | ‘utm’
match_pattern: String(pattern),
referrer: String(document.referrer || ”),
utm_source: qp.utm_source || ”,
utm_medium: qp.utm_medium || ”,
utm_campaign: qp.utm_campaign || ”
};
};

// — Inputs —
var href = String(window.location && window.location.href || ”);
var ref = lc(String(document.referrer || ”));
var qp = getQueryParams(href);
var utmConcat = trim([qp.utm_source, qp.utm_medium, qp.utm_campaign].join(‘ ‘));

// — Catalog of AI platforms (extend anytime) —
var catalog = [
{ name:’ChatGPT’, category:’AI Chat’,
domains:[/chat\.openai\.com/], utm:[/chatgpt/,/openai/] },
{ name:’Claude’, category:’AI Chat’,
domains:[/claude\.ai/], utm:[/claude/] },
{ name:’Poe’, category:’AI Chat’,
domains:[/poe\.com/], utm:[/poe/] },

{ name:’Perplexity’, category:’AI Search’,
domains:[/perplexity\.ai/], utm:[/perplexity/] },
{ name:’Google Gemini’, category:’AI Search’,
domains:[/gemini\.google\.com/,/bard\.google\.com/], utm:[/gemini|bard/] },
{ name:’Microsoft Copilot’, category:’AI Search’,
domains:[/copilot\.microsoft\.com/,/bing\.com\/chat/], utm:[/copilot|bingchat/] },
{ name:’You.com’, category:’AI Search’,
domains:[/you\.com/], utm:[/youai|you\.com/] },
{ name:’Phind’, category:’AI Search’,
domains:[/phind\.com/], utm:[/phind/] },
{ name:’Kagi’, category:’AI Search’,
domains:[/kagi\.com/], utm:[/kagi/] },
{ name:’Brave (AI Summary)’, category:’AI Search’,
domains:[/search\.brave\.com/], utm:[/brave/] },
{ name:’DuckDuckGo (DuckAssist)’, category:’AI Search’,
domains:[/duckduckgo\.com/], utm:[/duckassist|ddg-ai/] },

{ name:’Meta AI’, category:’Assistant’,
domains:[/meta\.ai/], utm:[/metaai/] },
{ name:’Arc Search’, category:’Assistant’,
domains:[/arc\.net/], utm:[/arc/] }
];

// — 1) Referrer match —
for (var i=0; i<catalog.length; i++){
var p = catalog[i];
for (var d=0; d<p.domains.length; d++){
if (ref && p.domains[d].test(ref)) {
return build(p, ‘referrer’, p.domains[d]);
}
}
}

// — 2) UTM match —
if (utmConcat) {
for (var j=0; j<catalog.length; j++){
var q = catalog[j];
for (var r=0; r<q.utm.length; r++){
if (q.utm[r].test(utmConcat)) {
return build(q, ‘utm’, q.utm[r]);
}
}
}
}

// No detection
return null;
} catch (e) {
return null;
}
}

 

Step 2 — Push the event into the dataLayer (Custom HTML tag)

Create a new custom HTML tag in GA4 to push the parameters to the data layer. Use the DOM Ready trigger.

GTM → Tags → New → Custom HTML
Name: AI Referral – dataLayer push
Trigger: DOM Ready (once per page)

<script>
(function () {
try {
// prevent double-push on the same page
if (window.__aiReferralPushed) return;

var det = {{CJ AI Source Detector}}; // returns object or null
if (!det || !det.platform) return;

window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: ‘ai_referral_detected’,
ai_source_platform: det.platform,
ai_source_category: det.category,
ai_detection_method: det.detection_method,
ai_match_pattern: det.match_pattern,
page_referrer: det.referrer || document.referrer || ”,
ai_utm_source: det.utm_source || ”,
ai_utm_medium: det.utm_medium || ”,
ai_utm_campaign: det.utm_campaign || ”
});

window.__aiReferralPushed = true;
} catch (e) {}
})();
</script>

If you enforce Consent Mode, add a consent check or fire this after consent is set.

Step 3 — Create Data Layer Variables (DLVs)

Create the following Data Layer Variables (DLV) by going to the User Defined Variables and use enter the following keys to capture the values:

  • ai_source_platform

  • ai_source_category

  • ai_detection_method

  • ai_match_pattern

  • page_referrer

  • ai_utm_source

  • ai_utm_medium

  • ai_utm_campaign

Step 4 — GA4 Event Tag

Here we create a new GA4 Event tag for ai_referral_detected and use this send new parameters to GA4.

Tag type: GA4 Event
Event name: ai_referral_detected
Trigger: Custom Event → Event name = ai_referral_detected


Event parameters:

ParameterValue
type{{ai_source_platform}}
sub_type{{ai_source_category}}
method{{ai_detection_method}}
pattern{{ai_match_pattern}}
page_referrer{{page_referrer}}
source{{ai_utm_source}}
medium{{ai_utm_medium}}
campaign{{ai_utm_campaign}}

Step 5 — Register GA4 Custom Dimensions

GA4 → Admin → Custom definitions → Create custom dimension (Scope: Event):

To help prevent GA4 running out of event scoped custom dimensions, we suggest you use some generic labels, such type, and subtype to minimise the need for registering new custom dimensions. If you are automatically exporting your GA4 data to BigQuery, you don’t need to register the parameter names if you don’t plan to analyse them in the GA4 console.

  • ai_source_platform → use the generic name of “type”

  • ai_source_category → use the generic name of “sub_type”

  • AI Detection Method →  use method

  • AI Match Pattern → use pattern

(UTMs don’t need registering unless you want them in standard reports.)

Step 6 — QA & Debug

Test your set up in GTM Preview Mode and GA4DebugView before you publish.

  • GTM Preview: Load your site from an AI platform (or use a UTM test URL like
    https://yourdomain.com/?utm_source=chatgpt&utm_medium=aiassistant) and confirm the ai_referral_detected event appears with parameters.

  • GA4 DebugView: Check the event and parameters arrive.

  • Duplicates: The window.__aiReferralPushed guard helps avoid double pushes.

 

Step 7 — Analysing in GA4

Use the Explore reports section of GA4 to create custom reports. First add all the necessary custom dimensions to the Free Form report. This will allow you to add them to the report. Create segments based upon the source of traffic, including the event ai_referral_detected.

Explorations (Free Form):

  • Dimensions: AI Source Platform (type), Landing page + query string

  • Metrics: Sessions, Engagement rate, Conversions

  • Compare AI vs Organic/Direct segments.

Funnel/Pathing:

  • Entry by AI Source Platform (type) → subsequent pages → conversion.

Audiences:

  • Build an audience where event_name = ai_referral_detected to compare lifetime value or remarket.

Looker Studio (recommended visuals):

  • Time series by AI Source Platform (type) with a filter set for event name equals ai_referral_detected.

  • Top landing pages for AI referrals

  • Conversion rate by platform and landing page

Benefits of the dataLayer push approach

  • Multi-destination ready: The same event can feed GA4, Google Ads, Meta, or custom endpoints.

  • Debuggable: Easy to see in GTM’s Data Layer panel for QA.

  • Separation of concerns: Detection logic is isolated (variable), distribution is centralised (event).

  • Future-proofing: As new AI platforms emerge, update the catalog once; all tags keep working.

Notes & gotchas

  • SPAs: On single-page apps, also fire on “History Change” if navigation happens without reload.

  • Strict CSP: If inline scripts are blocked, convert the push into a template or use a GA4 tag with DLV mapping.

  • Consent Mode: Align firing with your consent policy.

  • Evolving ecosystem: Keep the platform catalog up-to-date.

Tracking visits and backlinks from AI tools is becoming an essential part of understanding how your content is discovered and shared in an AI-driven web. By setting up this GTM and GA4 framework, you’ll gain visibility into how platforms like ChatGPT, Gemini and Perplexity surface your site — giving you insights traditional analytics can’t.

If you’d like support implementing this or improving your GA4 and GTM setup, get in touch with the team at Conversion Uplift  by email [email protected]. We help businesses enhance data quality, streamline analytics, and uncover opportunities that drive measurable growth.

More reading

How to Audit Your SERP Snippets Using Google Tag Manager

Use AI to Audit GA4 Attribution & UTM Parameters (Faster, Smarter)

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

The reCAPTCHA verification period has expired. Please reload the page.

Neal Cole
Author

Neal Cole

Founder of Conversion Uplift

Neal is the founder of Conversion Uplift and the author of the official GA4/GTM audit course for CXL. It's the course much of the industry trains on. He's spent over 20 years in digital analytics, including senior roles in online gaming and financial services, working out where businesses' numbers go wrong and what it costs them. His work now focuses on server-side tracking, GA4 and BigQuery: getting the data right, and being the person who's accountable for it when a decision depends on it.

How to Audit Your SERP Snippets Using Google Tag Manager

GTM SERP Snippet Match Audit

Audit Your SERP Snippets Using GTM

When it comes to SEO, alignment between your title tags, meta descriptions, and on-page headings plays a huge role in click-through rates and content performance. Our Google Tag Manager SEO audit allows you to compare what users see on your site with what search engines display in the SERPs — especially on dynamic or JavaScript-rendered pages?

That’s where a SERP Snippet Match Audit comes in. You can download a copy of the GTM receipe at the bottom of this post.

🔍 Why It Matters

Many websites unknowingly have mismatched or duplicate metadata. For example:

  • The <title> tag doesn’t match the on-page H1

  • The meta description is missing or generic

  • The first paragraph doesn’t reflect the page intent

These inconsistencies can harm your click-through rates, confuse users, and weaken your content’s SEO relevance.

Traditional SEO crawlers show what’s in the static HTML — but modern websites often inject metadata dynamically through JavaScript frameworks like React, Next.js or Elementor. That’s why we can use Google Tag Manager (GTM) to collect these values directly from the rendered page and send them into Google Analytics 4 (GA4) for analysis.

⚙️ How to Set It Up

You can use this simple Custom HTML tag in GTM to capture your metadata and push it into the data layer.

<script>
(function() {
try {
var titleTag = document.title || ”;

var metaEl = document.querySelector(‘meta[name=”description”]’);
var metaDesc = metaEl ? (metaEl.getAttribute(‘content’) || ”) : ”;

var h1El = document.querySelector(‘h1’);
var h1Text = h1El ? ((h1El.innerText || h1El.textContent || ”)).trim() : ”;

var pEl = document.querySelector(‘p’);
var firstPara = pEl ? ((pEl.innerText || pEl.textContent || ”)).replace(/\s+/g,’ ‘).trim() : ”;
firstPara = firstPara.substring(0, 200); // keep it short

window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: ‘serp_snippet_audit’,
serp_title: titleTag,
serp_meta_description: metaDesc,
serp_h1: h1Text,
serp_first_paragraph: firstPara
});
} catch (e) {
// Avoid breaking the page
}
})();
</script>

Trigger: DOM Ready
Event name: serp_snippet_audit

Once the data is pushed into the data layer, you can create a GA4 event tag with the same parameters (title_tag, meta_description, h1_text, first_paragraph) to store and analyse in GA4 or BigQuery.

⚙️ Register the Custom Dimensions in GA4

GA4 won’t automatically recognise these new parameters. You must register them as Custom Dimensions before they can appear in your reports.

Follow these steps:

  1. Go to your GA4 Property → Admin

  2. Under Data display, select Custom definitions

  3. Click Create custom dimension

  4. Add each of the following with Scope: Event

  • Title Tag — Event parameter: title_tag — Description: The page’s HTML title

  • Meta Description — Event parameter: meta_description — Description: The page’s meta description

  • H1 Text — Event parameter: h1_text — Description: The first H1 text visible to users

  • First Paragraph — Event parameter: first_paragraph — Description: The first paragraph of on-page text

  1. Save each one.

  2. Wait up to 24 hours for data to appear in GA4 Explorations.

📊 Analysing the Data in GA4 or BigQuery

In GA4 Explorations, you can:

  • Compare Title Tag and H1 Text to find inconsistencies

  • Identify missing or duplicate Meta Descriptions

  • Review which pages have weak or empty first paragraphs

If your property exports data to BigQuery, you can go further — calculate text similarity scores or flag mismatched metadata programmatically.
For example, identify pages where the title and H1 don’t share any common words, or where the meta description repeats across multiple URLs.

🧠 What About Google Rewriting Title Tags?

It’s worth noting that Google often rewrites title tags and meta descriptions in the search results — usually to better match the user’s query or to shorten long titles. This behaviour can’t be tracked directly in GA4 or GTM, as it changes dynamically per search.


However, the SERP Snippet Audit is still valuable because it captures the on-page data you control — the title, meta description, and H1 that Google uses as inputs when deciding whether to rewrite. By analysing these elements together, you can identify pages with poor alignment or overly long titles that are more likely to be rewritten.


The goal isn’t to track Google’s edits, but to strengthen your own metadata so that when rewrites happen, they’re based on accurate, well-optimised content.

💡 Why Use GTM Instead of SEO Crawlers?

  • Works with JavaScript-rendered content (React, Next.js, Elementor, etc.)

  • Captures the rendered content seen by users and search engines

  • Automatically updates when content changes

  • Integrates seamlessly with GA4 and Looker Studio dashboards

For analytics and SEO teams, it bridges the gap between technical SEO and real-world user experience.

🧭 Next Steps

If you’re already using GTM for analytics or server-side tagging, adding a SERP Snippet Match Audit is a quick, low-overhead upgrade.
It reveals how well your metadata aligns with what’s visible on-page — helping you optimise for both CTR and relevance. Rather than creating your own tags, triggers and variables, you can download the receipe here.

Does your content say what your search results promise?
Now you can measure it.

More reading

How to Create an Added-Value GA4/GTM Audit

Track AI-Sourced Traffic with Google Tag Manager (Step-by-Step)

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

The reCAPTCHA verification period has expired. Please reload the page.

Neal Cole
Author

Neal Cole

Founder of Conversion Uplift

Neal is the founder of Conversion Uplift and the author of the official GA4/GTM audit course for CXL. It's the course much of the industry trains on. He's spent over 20 years in digital analytics, including senior roles in online gaming and financial services, working out where businesses' numbers go wrong and what it costs them. His work now focuses on server-side tracking, GA4 and BigQuery: getting the data right, and being the person who's accountable for it when a decision depends on it.

How to Create an Added-Value GA4/GTM Audit

MeasureCamp London 2025

How To Add Value to a GA and GTM Audit

Most GA4 and GTM audits focus on technical accuracy — checking tags, validating triggers, and fixing broken event tracking. While that’s important, it’s not enough.

If your audit doesn’t inspire action, build trust, and connect analytics to business outcomes, it’s just another report that gets ignored.

At MeasureCamp London 2025, I shared a session on how to make GA4 and GTM audits genuinely valuable — not just technically correct, but strategically useful. Here’s a summary of the key takeaways from my presentation.

1. Change the Perception: From Technical to Strategic

Too often, audits are viewed as compliance tasks. A great GA4/GTM audit reframes that.
It shows how accurate data supports decisions that improve marketing performance, customer experience, and business growth.

Audits should empower teams, not just highlight problems.

2. Be Honest

Be transparent about what an audit can and can’t achieve. GA4 isn’t an accounting system — it’s designed for insight, not perfect accuracy.

Don’t over-promise. Some tracking fixes will need input from developers or backend teams. The goal is to build trust, not false expectations.

3. Ask Lots of Questions

The best insights come from curiosity. Understand the client’s business model, key user journeys, and what success looks like for them.

An audit without context is just a list of fixes.

4. Use Their Language

Avoid jargon. Talk about conversions, revenue, and customer experience — not tags, triggers, and data layers.
When stakeholders understand what you’re saying, they act on your recommendations.

5. Add Value with Measurement and Insight

Here are some practical ways to go beyond the basics:

  • Measurement Plan – Align business objectives with user goals and KPIs.

  • Regular Updates – Provide transparent weekly progress summaries: what was done, what’s next, key risks, and hours used.

  • Core Web Vitals – Use Google’s performance metrics to link analytics with site speed and user experience.

  • Automatic Alerts – Set triggers for sudden drops in conversions or traffic.

  • AI UTM Analysis – Use AI tools to identify poor UTM structures and suggest consistent naming conventions.

  • Event Values – Assign a value to key GA4 events based on conversion rate and lifetime value.

  • Device Mining Dashboard – Analyse conversion performance by device type, OS, and browser. Credit to Craig Sullivan and Charles Meaden for this concept. Get the dashboard here.

  • Session Replay – Tools like Microsoft Clarity add qualitative context to GA4 metrics.

  • BigQuery Export – Unlock unsampled GA4 data for advanced analysis and cost control.

  • Knowledge Transfer – Train teams via Microsoft Teams or Google Meet to interpret their own data. Empowerment is a form of added value.

6. The Outcome: Trust, Insight, and Collaboration

A truly valuable GA4/GTM audit builds stronger partnerships. It delivers insights that clients can act on, transparency that drives trust, and measurable improvements that reinforce your credibility.

Adding value isn’t about doing more work — it’s about changing how your work is perceived.

You can access our comprehensive GA4 and GTM Audit checklist here.

📥 Download the Full Presentation here:

More reading

How Do You Set Up Server-Side Tracking for GA4 With Stape?

How to Audit Your SERP Snippets Using Google Tag Manager

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

The reCAPTCHA verification period has expired. Please reload the page.

Neal Cole
Author

Neal Cole

Founder of Conversion Uplift

Neal is the founder of Conversion Uplift and the author of the official GA4/GTM audit course for CXL. It's the course much of the industry trains on. He's spent over 20 years in digital analytics, including senior roles in online gaming and financial services, working out where businesses' numbers go wrong and what it costs them. His work now focuses on server-side tracking, GA4 and BigQuery: getting the data right, and being the person who's accountable for it when a decision depends on it.