marketingengineer.

↳ 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:

  1. Tagging server on a subdomain (data. instead of a CNAME to a third party — keep the cookies first-party).
  2. GA4 + Ads tags moved server-side. The browser fires one lightweight event to our endpoint; the server fans out.
  3. Enhanced conversions fed from the backend with hashed emails, not scraped from the DOM.
  4. 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.