Four separate leaks, not one bug
The first leak is Safari and ITP. A cookie written from JavaScript by gtag or the Meta pixel lives seven days, and when the landing URL carries advertising parameters it can be cut to 24 hours. On a shop where the median path from first visit to purchase runs 9 to 14 days, that quietly removes attribution for a third of mobile traffic.
The second is content blocking. On stores with a strong desktop audience we routinely see 18 to 30 per cent of sessions where requests to google-analytics.com and connect.facebook.net never leave the browser at all. That is not statistical noise, it is a hole in the denominator, and it grows with how technical your audience is.
The third is consent. With a certified CMP in place, analytics acceptance typically lands between 55 and 80 per cent, driven mostly by whether the banner gives reject the same visual weight as accept. The fourth is far more mundane: a purchase event fired inside a component that does not re-render when the user returns to the thank-you page from the back-forward cache. That one costs 5 to 8 per cent of transactions and nobody investigates it, because it looks like a privacy problem.
Consent Mode v2 is not a toggle in your CMP
Consent Mode v2 added two signals next to the existing ones: ad_user_data and ad_personalization. Without them Google Ads cannot use the data for remarketing or modelling, even when the visitor accepted everything. Most implementations we audit have analytics_storage wired correctly and skip the newer fields entirely, because the Tag Manager template was configured in 2023 and nobody revisited it.
The default state has to be set before any Google tag loads, in denied mode, with wait_for_update at 500 ms. If the banner loads asynchronously after the GTM container, requests go out for a fraction of a second with no consent signals attached and Google discards them outright. Checking this takes two minutes: open the network panel, filter on collect, and read the gcs parameter on the request. G100 means denied, G111 means granted, and a missing parameter means Consent Mode is not running at all.
Conversion modelling, the entire reason to run Consent Mode rather than simply switching tags off, has an entry threshold. Google cites roughly 700 ad clicks per day per country and domain over seven days. An account spending PLN 8,000 a month in a single market will not reach it and will not receive modelled conversions, however clean the configuration is.
- Set default consent in a dedicated tag with higher priority than the GA4 container
- Enable url_passthrough so gclid survives redirects that happen before consent is given
- Read the gcs parameter on collect requests for both banner states
- Check Consent Mode status per conversion action under conversion diagnostics in Google Ads
- Confirm your CMP is on Google's certified partner list, because TCF 2.2 alone does not forward the signals
What a server container fixes, and what it will not
A server container on your own subdomain lets you set cookies from a Set-Cookie response header. That cookie is not subject to the seven-day ITP cap and can live 400 days, which rebuilds attribution for longer purchase paths. There is one condition and it gets broken often: the subdomain must resolve to your infrastructure through an A or CNAME record in your own DNS zone, not to a default hostname with gtm-server in it.
What it does not do is bypass consent. If the visitor declined marketing purposes, the server container still has to honour that, otherwise you are processing data with no legal basis. It also does not defeat blockers permanently, because filter lists match on path patterns and parameter names as well as hostnames. The gain is real, but I would describe it as recovering half of the blocked traffic rather than all of it.
Cost is worth modelling before you commit. On Cloud Run with three minimum instances in a European region, a shop generating 2 million events a month lands between PLN 180 and 320. Managed hosting costs about the same and saves a dozen hours of setup at the price of log access. Then add maintenance: every change to the shop's data layer now means updating two containers instead of one.
The architecture we default to
In the browser we keep one GA4 tag pointing at our own endpoint, plus the CMP. Everything else, Meta, Google Ads, Microsoft Ads and any affiliate systems, moves into the server container and is fed from the GA4 client. The purchase event leaves the browser once instead of five times, which also strips a few dozen kilobytes of third-party script off the main thread.
Deduplication is where most implementations fall over. Meta CAPI needs the same event_name and event_id pair from both pixel and server within a 48-hour window. We generate event_id on the backend alongside the order number and inject it into the data layer, rather than rolling a random value in the browser. Skip that and Meta reports a 60 per cent jump in purchases that is pure double counting, and a month later it has degraded campaign optimisation.
For Google Ads we add user-provided data: email address and phone number normalised and hashed with SHA-256, sent only when ad_user_data is granted. On lead accounts we also import offline conversions from the CRM keyed on gclid, usually on a daily schedule. These are two separate mechanisms and they do not substitute for each other, even though they get used interchangeably in conversation.
- One source of truth: a data layer emitted by the backend, not assembled in the theme
- event_id derived from the order number, identical for pixel and CAPI
- Hashing on the server, so normalisation logic never ships in page code
- A dedicated first-party subdomain endpoint with its own certificate
- A log of events dropped for missing consent, so the scale of the loss is reportable
Proving it worked
The benchmark is not GA4, it is the order database. Before the work starts we compute the ratio of GA4 transactions to paid orders in the shop system, day by day, across four full weeks. Shops that come to us typically start between 0.72 and 0.85. After a clean Consent Mode and server container rollout, 0.90 to 0.96 is a realistic target rather than parity, because some visitors declined and modelling does not fully cover that gap.
The second measure is match quality on the platform side. In Meta we look at Event Match Quality for purchase, where anything below 6.0 means you are sending too few parameters and 7.5 to 8.5 is achievable with a complete customer payload. In Google Ads the equivalent is the enhanced conversions coverage report, where we care about the share of conversions with matched data.
Conversion uplift is measured by comparing four-week windows, adjusted for seasonality and budget changes. Across projects from the last two years the median recovery was around 14 per cent of conversions in Google Ads and 19 per cent in Meta. Anyone promising a doubling from server-side tracking alone is selling you double counting.
This is not a GDPR workaround
Moving tags to a server changes nothing about your legal basis. You still need consent for marketing purposes, you still need to be able to reproduce it on request, and you remain responsible for whatever reaches Meta or Google. A hashed email address is still personal data, because it identifies a person on the receiving side, and supervisory authorities treat it that way.
In practice that means three things to settle with legal alongside the build: add the server container to your record of processing activities, sign a processing agreement with the hosting provider if it is not your own cloud, and rewrite the privacy policy so it matches what the code actually does. Choosing a European processing region is not strictly required, but it makes the transfer documentation considerably shorter.
Key takeaways
- The gap between orders and reported conversions has four causes and only two are fixable in code.
- Consent Mode without ad_user_data and ad_personalization will not unlock conversion modelling.
- Deduplication by an order-derived event_id matters more than the fact that CAPI is live.
- Realistic recovery after a clean rollout is in the mid teens to low twenties per cent, not a doubling.
- A server container does not create a legal basis you did not already have.
Common questions
Partly. Sending events to your own subdomain means some filter lists no longer recognise them, but the popular lists also match on path patterns and parameter names. In practice you recover roughly half of the previously blocked traffic, and effectiveness decays over time if you keep the default endpoint paths.
It is not a legal requirement, it is a Google requirement for advertisers targeting users in the European Economic Area. Without it, remarketing lists and conversion modelling stop working and the account starts showing warnings. The legal obligation concerns collecting consent in the first place and comes from GDPR and the ePrivacy rules.
For a shop with around 2 million events a month on Cloud Run in a European region, expect PLN 180 to 320 a month with three minimum instances. Managed hosting costs about the same and saves a dozen hours of setup. Add ongoing maintenance, because every data layer change now has to be applied in two containers.