
Server-Side Tracking Is Not Optional Anymore
Browser-side tracking has been losing events for years. Ad blockers, Safari’s Intelligent Tracking Prevention, Firefox’s default protections and declined cookie consent all remove conversion events before they reach an ad platform. Bidding then optimises on the conversions that survive, and those come mostly from people with looser privacy settings, so the sample is skewed as well as smaller.
What the server sends
Server-side tracking through Meta’s Conversions API and Google’s Data Manager / Enhanced Conversions sends the same events from a server the agency controls, matched and deduplicated against the browser pixel. Events are sent only for visitors who gave consent. For those visitors, events still arrive when an ad blocker or Safari’s ITP stops the browser pixel.
The server copy is a second message carrying the same event name and timestamp, an event ID shared with the browser copy, hashed identifiers such as email or phone, the click identifier that arrived in the landing URL, plus the order value and currency. Most of that is not a tagging problem. The click identifier arrives once, on the first page view, while the conversion may happen later, after a form, a phone call, or a manual approval. Something has to store it against the record that eventually becomes a conversion.
We save the click ID in a column on the lead record, so it is still there when the lead converts two weeks later. Each conversion becomes a row that can be replayed and checked against the ad platform’s reports, and every later debugging step depends on that. Match quality depends on the event: a quote request or a checkout comes with an email address and a phone number, while an anonymous product view brings almost nothing.
Where deduplication breaks
Both copies reach the platform, which is expected to collapse them into one using the shared event ID. When that ID is wrong, the two copies are counted as two conversions. Reported conversions inflate, cost-per-acquisition looks better than it is, and bidding optimizes toward a number that does not exist. That failure is worse than no server-side tracking at all, because it fails in the direction everyone wants to believe.
So the ID is generated once, on the server, and handed to the browser with the rendered page. Retries are the second trap: calls to an ad platform time out, and a retry must reuse the event ID, because a new ID duplicates the conversion. Sending is idempotent, and the record notes whether a successful send already happened.
Both sides should also send the same parameters. When they do not, the event count can stay right while match quality drops, which is easy to miss.
Consent, and what gets written down
Server-side tracking is sometimes sold as a way around consent. Sending events for visitors who declined is unlawful under GDPR, and we do not build it. A server has no cookie banner, so if the consent decision is kept only in the browser, the server will send events for visitors who declined, because it never learned that they did.
So the consent decision is stored as a field on each event record, and the server reads it before sending anything. Before any of this is built, we write down, per event:
the event name and the exact user action that triggers it
the parameters sent, and which of them are personal data
the identifiers used for matching and how they are hashed
how long the raw event row is kept before it is deleted
That document is the scope. A fixed price only works when the deliverable is written this precisely, and the same document lets anyone review the integration later.
After launch
For an agency, the effect shows up in numbers the client already watches. Reported conversions move closer to the real number, and cost per acquisition usually drops because bidding runs on a fuller, though never complete, data set.
What we cannot hand over is a clean measurement of the recovery. The events the browser lost were never recorded, so there is no baseline, and a before-and-after comparison is distorted by the learning phase restarting and by modelled conversions already standing in for the missing ones. Rising reported conversions show that the integration works. They do not measure how much demand was invisible before.
After launch, a broken integration is easy to miss, because the browser pixel keeps sending numbers. So we compare browser and server event counts and flag differences, and API version changes are planned in advance. Who does this after launch is agreed in the scope.
There is also a shift in responsibility: hashed or not, customer identifiers are stored in a database on the client’s side and sent from there, and that belongs in the contract.
How we scope and price this kind of work is on the services page.


