↳ marketingengineer / teardown
Server-side tagging: what actually worked
We moved tracking off the browser and into a first-party server container. Here is the setup, the numbers before and after, and the two mistakes that cost us a week.
If it isn’t instrumented, it isn’t real. That was the whole reason for this project: our conversion data was leaking somewhere between the browser and the ad platforms, and every optimisation decision downstream was built on that leak.
Here’s the teardown — the setup we landed on, the before/after numbers, and the two mistakes that cost us a week.
The problem, measured
Before touching anything, we measured the gap. Three sources of truth, thirty days:
- Backend orders: 1,412
- GA4 purchases: 1,239 (−12.3%)
- Google Ads conversions: 1,184 (−16.1%)
A sixth of our conversions never reached the bidding algorithm. Smart Bidding was optimising against a sample, and not a random one — Safari and Firefox users, consent-decliners, and slow connections were systematically underrepresented.
The setup
First-party server container, dual-tagged through a transition month:
- Tagging server on a subdomain (
data.instead of a CNAME to a third party — keep the cookies first-party). - GA4 + Ads tags moved server-side. The browser fires one lightweight event to our endpoint; the server fans out.
- Enhanced conversions fed from the backend with hashed emails, not scraped from the DOM.
- Consent state forwarded, not assumed. Declined means declined — modelled conversions can fill the gap, fabricated ones may not.
The numbers after
Same three sources, thirty days post-cutover:
- Backend orders: 1,389
- GA4 purchases: 1,331 (−4.2%)
- Google Ads conversions: 1,346 (−3.1%)
The remaining gap is mostly consent declines, which is the gap that should exist. Cost per acquisition dropped 9% over the following six weeks with no other changes — Smart Bidding simply had better food.
The two mistakes
Mistake one: we trusted the default event mapping. The server container’s automatic GA4-to-Ads mapping silently dropped our custom value parameter for a week. Diff your platform numbers daily during any migration — we caught it because the dashboard had a tile for exactly that diff.
Mistake two: we underestimated cold-start latency. The first request after a quiet period took seconds, long enough for users to navigate away before the event fired. Minimum instance count fixed it; the bill went up by less than one lost conversion per month.
Steal this
The checklist version: measure the gap first, keep cookies first-party, feed enhanced conversions from the backend, forward real consent state, and diff platform numbers against the backend daily during migration.
Ship it, then tell me what broke.