Server side tracking: the technical guide to fixing your broken analytics
TL;DR
Difficulty: Advanced
Server side tracking puts a server on your own domain between the browser and the ad platforms, so you control what is sent and can report sales from the order itself rather than from a page that may not load. It is not a way around consent: the browser still sends data to your server, so the same cookie law applies, and the server must forward only what the visitor agreed to. Refused orders stay in your own reporting, renamed and without identifiers. Use the order number as the event id so each sale counts once. The most it can recover is your consented share of orders.
A tagging server on your own domain can give you cleaner conversion data, but it cannot give you a single sale from a visitor who said no. In this guide I build server side tracking with Google Tag Manager, the Meta Conversions API and the GA4 Measurement Protocol, show where consent sits in every step, and use an invented Bristol cycling shop's October to work out what the server recovers and the ceiling it cannot pass.
What server side tracking fixes, and what it does not
Server side tracking puts a server you control between the visitor's browser and the ad platforms. The browser sends its events to your server, and your server decides what goes to Google, Meta and the rest, through their official APIs. Done well, it gives you cleaner data, durable first party cookies where the visitor agreed to them, conversions sent from the order itself rather than from a page that may never load, and full control over which parameters leave your domain.
What it does not do is change whether you need consent. The browser still runs code that sends information to your server, and that step sits under the same cookie law as any pixel. So the honest order is: ask first, then let the server forward only what the visitor's choice allows.
The earlier version of this article opened with a claim that browser pixels miss 30% to 50% of conversions, and sold server side tracking as something ad blockers cannot stop. I could not find a source for the first figure, and the second is a framing I will not use. Both are gone. What replaces them is the mechanism, the vendor documentation, and a worked example with the arithmetic shown.
One invented business carries the examples. Severn Kit is a cycling clothing shop in Bristol, run by Priya, selling online at severnkit.example. Every number about it is made up. The law and the vendor documentation are not, and they are footnoted.
Why browser tags lose conversions
Three things stand between a sale and the ad platform's report, and only one of them is a technical fault.
The first is the banner. A visitor who refuses advertising cookies should not be measured for advertising, and the platform should not see the sale. That is not a leak to plug. It is the law working, and it is usually the biggest gap.
The second is the browser. Safari's Intelligent Tracking Prevention deletes cookies created in JavaScript, and all other storage a script can write, after seven days without the visitor interacting with the site. When the visitor arrives through a link decorated with a click id, cookies created in JavaScript on that landing page are capped at 24 hours.1 A Google or Meta click id stored by a tag is exactly that kind of cookie. Firefox isolates cookies per site with Total Cookie Protection, on by default for all users since June 2022.2
The third is plumbing. The payment provider sends the buyer back to a confirmation page that never fully loads, the buyer closes the tab before the tag has fired, or a content blocker stops the request. The order exists in the shop's database. The browser never told anyone.
The old version also blamed Apple's App Tracking Transparency. That framework governs apps, not websites, so I have taken it out.
What changes when a server sits in the middle
In the classic setup, the page loads each vendor's tag, and each tag sends data straight from the browser to its vendor. With server side tagging, the page sends one stream of events to a tagging server on your own domain, a client in that server turns each request into an event, and server tags forward what you allow to each vendor. Google recommends running the server on your first party domain, and says you control how the data is shaped and where it goes, and that only you have access to the data in the server until you send it on.3
Read the flow from the top. The banner comes before anything is sent. The decision box is the server applying the visitor's choice, and the two branches never meet again: a refused order never reaches an ad platform by a side door. The last box is the reminder that the shop's own database counts every order whatever happened above it.
Server side does not move the consent question
The European Data Protection Board's guidelines on the scope of the ePrivacy rule are blunt about this. When software on the device calls an API endpoint, or JavaScript instructs the browser to send asynchronous requests with the targeted information, that access clearly falls within article 5(3).4 Tracking pixels count too: the board treats the collection of identifiers through them as gaining access.4 An event that the browser sends to track.severnkit.example instead of to Meta is still an event the browser was told to send.
The British rule says the same in its own words. Regulation 6 of the Privacy and Electronic Communications Regulations, rewritten from 5 February 2026, covers storing information on a device, gaining access to it, and collecting information the device emits automatically.5 The Information Commission (formerly the ICO, renamed on 30 September 20266) says regulation 6 applies whenever scripts and tags access or store information on a user's device, and describes server side tags as reducing tag activity on the device, not removing it.7 On advertising it does not hedge: there is no exception for online advertising purposes, including ad measurement and performance, and consent is needed for connecting a visitor id to a purchase that is shared with advertising partners.8
For a British shop selling into the EU the same structure holds. Germany's TDDDG § 25 allows storing or reading information on the device only with consent, except where it is strictly necessary for a service the user explicitly asked for.9 Austria's TKG 2021 § 165 paragraph 3 says the same in Austrian wording.10 Ireland's S.I. 336/2011 regulation 5 does too.11 The differences between the four countries, and why Germany and Austria are stricter in practice, are in the article on cookies and consent for shops selling into Germany and Austria.
So server side tracking is not a way around consent. The client still asks before setting or reading anything on the device, and the server forwards only what the consent state allows.
Basic or advanced consent mode
Google's consent mode carries the banner's answer to Google's tags, and it comes in two shapes. In basic mode, Google's tags do not load until the visitor interacts with the banner, and if the visitor refuses, nothing is sent to Google. In advanced mode, the tags load straight away with consent denied by default and send cookieless pings while it stays denied, which Google uses for modelling.12 Google's server side guide confirms that in basic mode no analytics cookies are set, accessed or read from the device, either on the client or the server.13
My default follows the market. For Germany, Austria and Ireland I build basic mode, and I switch to advanced only when the shop's own lawyer has advised it in writing. For the UK, the rewritten regulation 6 covers information the device emits, and I found no statement from the Information Commission that either blesses or condemns cookieless pings, so advanced mode is a risk decision the business takes knowingly. The full setup, in the order that works, is in event tracking and consent mode.
Consent travels with every event
In a Google Tag Manager setup the consent state rides along with each request. The banner passes the choice to the Google tag, the Google tag adds consent parameters to the HTTP request it sends to your server, and Google's own product tags in the server container are consent aware and adjust what they send. Google's guide adds that because of this you only need to set up consent mode in the web container.13
That sentence covers Google's product tags, and Google says nothing similar about third party templates. So I treat a Meta tag in the server container as blind to consent until I give it a condition: it fires only when advertising consent is granted. In a custom build, the server does the check itself before every forward.
Worked example: Severn Kit's routing rule
When an order completes, Priya's backend knows three things: the order, the consent state the banner stored for this visitor, and nothing more. This is the routing rule, written so the next person to touch it can read it:
// Runs once per completed order, in the shop backend or on the tag server
function routeOrder(order) {
const ads = order.consent.ad_storage === 'granted' && order.consent.ad_user_data === 'granted'
const analytics = order.consent.analytics_storage === 'granted'
if (ads) {
sendToMeta({ event_name: 'Purchase', event_id: order.number, order })
sendToGoogleAds({ transaction_id: order.number, order })
}
if (analytics) {
sendToGA4({ name: 'purchase', transaction_id: order.number, order })
}
if (!ads) {
// Refused: renamed, no identifiers, stays in the shop's own reporting
ownReport.record({ name: 'purchase_nc', value: order.itemTotal, currency: 'GBP', day: order.day })
}
}
The refused branch is the rule I apply on every site I build, and I state it as my practice rather than as a reading of the law. An order from a visitor who refused advertising is renamed purchase_nc. It carries no click id, no cookie id, no hashed email, no IP address and no user agent. It is never mapped to a conversion action, never used for optimisation, and never eligible for an audience or for retargeting. It exists so the total in the shop's own report matches the database, and for nothing else. I keep it inside the shop's own systems: the ad platforms see nothing of it.
Why a new name rather than a flag: the people who run ad accounts change, and a different event name is much harder to misuse by accident than a setting someone forgot.
Building it with Google Tag Manager server side
Google Tag Manager's server container is the most common route, and Google documents each step.
The tagging server
You create a new container in Tag Manager with Server as the target, then provision the server. The simplest path is Google Cloud Run. Google prices each server at approximately 45 US dollars a month, as a Cloud Run instance with one vCPU and 0.5GB of memory, recommends a minimum of two instances to reduce the risk of data loss in an outage, and expects two to 10 servers to handle 35 to 350 requests per second, depending on the tags.14 It also warns that with more than about a million requests a month, request logging can cost real money, and suggests switching it off.14
If you run your own infrastructure, the tagging server is a Docker image, published at gcr.io/cloud-tagging-10302018/gtm-cloud-image:stable, and the same image serves as the preview server with different settings.15 Google updates the image periodically, so whoever runs it has to update it too.15
The domain, and what Safari makes of it
Google's custom domain guide gives three options for a site at www.example.com: the same origin, such as https://www.example.com/metrics, which it calls best practice; a subdomain, such as https://metrics.example.com; and the cloud provider's default domain, which runs in a third party context and can only set JavaScript cookies.16 Google's table gives the subdomain full access to server set cookies.
Safari reads it differently. WebKit caps any cookie set in an HTTP response at seven days when the request goes to a first party subdomain that resolves through a CNAME pointing elsewhere, or to a server on a third party IP address.1 A subdomain mapped to a cloud host is exactly that. So if durable cookies matter to you, the same origin route is the one that holds in Safari: a path on your main domain, served through the same load balancer or CDN as the site.
A durable cookie is only lawful once the visitor has agreed to what it is for. The domain decides how long a consented cookie lasts. It does not decide whether you may set one.
Pointing the web container at the server
In the web container you tell the Google tag to send to your server container URL, the server_container_url option in gtag.js.3 Google recommends sending to the server through the Google Analytics tag, which can use several transport methods, and lists the content security policy entries your site needs so those requests are not blocked by your own site.17
Inside the server, a client claims each incoming request and turns it into an event. The GA4 client handles requests in Google's format. Before any tag sees the event, transformations let you allow only named parameters, edit or add parameters, or exclude parameters from tags, for all tags or only some.18 That is where I drop the IP address and anything else a vendor has no business receiving.
Worked example: what Severn Kit's server costs
Priya's site gets about 1,500 sessions a day at around 15 events each, so events a day. Spread over seconds that is requests per second, far below the 35 that Google expects two servers to manage. Two instances is simply Google's floor. Cost: US dollars a month, billed in dollars, before traffic charges. At requests a month she stays under the million where Google suggests switching request logging off, so she can keep it for now.
Google Ads and GA4 from the server
Google Ads conversions and enhanced conversions
The Google Ads conversion tag runs in the server container, and it needs the Conversion Linker tag there too. With ad_storage denied, Google says its Ads cookies are not written or read.13
Enhanced conversions add a hashed email address or phone number to a conversion, so Google can match it to a signed in account. The data is hashed with SHA256 before it is sent.19 Hashing is not anonymisation, and Google treats it accordingly: in the server container, enhanced conversions work only when consent is granted,13 and when ad_user_data is denied, personal data for advertising is not sent.12
Server events for GA4
The Measurement Protocol sends events to GA4 directly, server to server, which suits refunds, offline sales and anything that happens without a page. Google is clear about its place: it augments collection through gtag, Tag Manager or Firebase and does not replace it, and sending events with the Measurement Protocol alone may give only partial reporting.20
Each request needs an API secret from the data stream settings. Events can be backdated up to 72 hours. A consent object carries ad_user_data and ad_personalization, and if you leave it out, GA4 uses the consent settings from the visitor's online interactions.21 I set it explicitly on every request, so the server never relies on a default it cannot see.
Sending purchases to Meta from the server
Meta's Conversions API takes events from your server. A purchase looks like this:
{
"data": [
{
"event_name": "Purchase",
"event_time": 1792852200,
"event_id": "SK10482",
"action_source": "website",
"event_source_url": "https://severnkit.example/checkout/complete",
"user_data": {
"em": ["<sha256 of the normalised email>"],
"ph": ["<sha256 of the normalised phone number>"],
"client_ip_address": "203.0.113.24",
"client_user_agent": "<the browser's user agent>",
"fbc": "fb.1.1792850400000.<fbclid>",
"fbp": "fb.1.1792850400000.1234567890"
},
"custom_data": { "currency": "GBP", "value": 89.00 }
}
]
}
Here is what each part must be, from Meta's parameter reference. action_source is required. event_time is a Unix timestamp in seconds and can be up to seven days before you send the event. If any event in the batch is older than that, Meta returns an error for the whole request and processes none of it.22
The customer fields come in two kinds. Email and phone must be hashed with SHA256. Before hashing, an email is trimmed and lowercased, and a phone number loses symbols, letters and any leading zeros and must include the country code. The IP address, user agent, fbc and fbp are sent unhashed. fbc has the format fb. plus a subdomain index, the creation time and the fbclid from the landing URL.23
In Node the hashing is a few lines:
const crypto = require('crypto')
const sha256 = (s) => crypto.createHash('sha256').update(s).digest('hex')
const normaliseEmail = (e) => e.trim().toLowerCase()
const normalisePhone = (p) => p.replace(/\D/g, '').replace(/^0+/, '')
// A UK number written as 0117 496 0123 becomes 441174960123 before hashing
const ukPhone = (p) => '44' + normalisePhone(p)
Nothing in the Conversions API filters by consent for you. Everything in that payload is personal data sent for advertising, so the server sends it only for visitors who granted advertising consent. That is the routing rule above, and it is not optional.
Deduplication: count each sale once
If the browser pixel and the server both send the same purchase, Meta receives it twice. Meta recommends running the pixel and the Conversions API together, which it calls a redundant setup, and says that when you do, you must set up deduplication.24
The method Meta recommends is the event id and event name. The pixel's eventID must match the server's event_id, and the pixel's event must match the server's event_name. Meta deduplicates matching events received within 48 hours of the first one with a given id.24 So the id has to be the same value in both places, which means generating it once. The order number is the obvious choice: unique per sale, known to the browser on the confirmation page and to the server.
The pixel side is a fourth argument:
fbq('track', 'Purchase', { value: 89.00, currency: 'GBP' }, { eventID: 'SK10482' })
Both sends happen only after advertising consent. Without it neither the pixel nor the server sends anything.
Worked example: Severn Kit's October, counted twice and once
In October Priya's consented buyers produce 527 purchase events from the browser pixel and 620 from the server. Every sale the pixel saw, the server saw too. What Meta reports depends on whether the ids match:
Here is the number of purchases Meta counts, is the number received from the pixel, the number received from the server, and the number Meta recognised as the same sale arriving twice. Adding two counts and subtracting the overlap is the counting rule behind Foundations: probability.
Without matching ids, Meta recognises no overlap, so and the report says purchases, almost twice the truth. With the order number as the id on both sides, and the report says . The return on ad spend in the first case would look superb and mean nothing.
What the server can recover, and the ceiling it cannot pass
The useful question is not how many conversions server side tracking adds, but how many it could add at most. The answer is set by the banner, not by the server:
is the largest number of purchases the ad platforms may lawfully receive, is the share of orders placed with advertising consent, and is the number of orders in the shop's database. A share applied to a count is covered in Foundations: ratios and rates.
Worked example: what the banner allows at Severn Kit
Priya's database holds 1,000 web orders for October. Each order carries the consent state stored at checkout: 620 with advertising consent, 380 without. So and .
Before the server, the pixel delivered 527 of those 620. The other 93 were consented sales the browser never reported: payment returns that did not reach the confirmation page, tabs closed early, and a few browsers that blocked the tag. After the server, the backend sends every consented order, so the platforms receive all 620.
The gain, as a share of what the pixel delivered:
is the relative increase, the purchases received with the server in place and the purchases received from the pixel alone. Relative change is explained in Foundations: percentages.
Step by step: , then , about 18%. Real, useful for bidding, and far from the 30% to 50% the old version promised. The 380 refused orders do not move at all. They appear in Priya's own report as purchase_nc and nowhere else.
The horizontal axis shows who is counting, the vertical axis is orders. The server closes the gap between the third and fourth bars. Nothing closes the gap between the second and the first, and nothing should.
The click id adds a second layer. A Google or Meta click id held in a cookie created by JavaScript lasts 24 hours in Safari when the visitor came through a decorated link.1 On a same origin setup the server can keep the consented click id in a cookie it sets itself, or store it with the session in the shop's own database, so a Safari buyer who returns after a week can still be matched. That improves attribution of the 620. It does not add a 621st.
Testing it before anyone trusts it
Server side tracking is harder to see than a pixel, so testing is part of the build, not an afterthought.
Tag Manager's server preview lists every incoming request, shows which client claimed it, the event it produced, which tags fired and the outgoing requests to each vendor with their parameters.25 Meta's Events Manager has a test events view, and GA4 has DebugView, both of which show events as they arrive.
I test in this order. First, in a private window with the banner untouched, nothing reaches the tag server except what the strictly necessary exceptions allow. Second, after a refusal, a test order produces purchase_nc in the shop's report and nothing at Meta or Google Ads. Third, after acceptance, the same order arrives once at each platform, with the order number as the id and the IP address removed where I said it would be. A setup that passes the first two and fails the third loses data. A setup that fails the first two breaks the law.
When it is worth building
Server side tracking pays for itself when advertising spend is large enough that a better consented signal changes bids, when a meaningful share of buyers use Safari, and when the shop wants control over what leaves its domain. It is not worth building to chase visitors who said no, because it cannot lawfully reach them.
For most shops the order of work is the same. First a correct banner and consent mode in the browser, with the default loading before any tag, as set out in event tracking and consent mode. Then the server, with the routing rule written into the code. Then first party data where people have agreed to it, the subject of the cookieless marketing and first party data strategy. Your raw data stays yours, and the terms for code and models are in the analytics and data science terms.
If you want to know what your tags send before anyone presses the banner, and what a server would add, I run a free workshop, and the first two weeks of putting it right are on me.