§1 The problem
When I joined Elithair, the analytics setup was a standard client-side stack: Google Tag Manager, GA4, Meta Pixel, and a handful of conversion tags firing on page events. It worked — until it didn't. Cookie consent rates dropped below 40%. Ad platforms were operating with a fraction of the signal they needed. Reported ROAS looked fine; actual performance was not.
Client-side tracking depends on user consent, which is legally required under GDPR and practically declining year-on-year. Server-side tracking solves this by moving data collection off the browser entirely — events are sent directly from your server to the platforms, without requiring third-party cookies.
"Configure consent" is not a compliance strategy. Elithair operates across 40+ domains in multiple EU markets — any tracking solution had to be compliant by default, not by configuration.
§2 The architecture
The final setup has three layers: a consent management platform (CMP) at the edge, a server-side GTM container running on Stape.io, and direct API integrations to Google and Meta. No third-party cookies are set client-side after the transition.
| Layer | Tool | Role |
|---|---|---|
| Consent | CMP (Cookiebot) | Gate signal collection at the edge |
| Collection | GTM server container | Receive, validate, route events |
| Hosting | Stape.io | Managed sGTM infrastructure |
| Destination | GA4 / Meta CAPI | Direct API send |
§3 GTM server-side setup
The web container sends a single, unified data layer event to the server container. Routing logic lives server-side — one event can trigger GA4, Meta CAPI, and any other platform without duplicating tags in the browser.
// dataLayer push — web container
window.dataLayer.push({
event: 'lead_submitted',
lead_id: generateId(),
source: document.referrer,
consent: window.__cmp_status // 'granted' | 'denied'
});The server container then enriches the event with IP-derived geo data, applies consent checks, and routes to the appropriate destination. No PII leaves the server unencrypted.
§4 Stape.io hosting layer
Running a server-side GTM container yourself requires a Cloud Run or similar setup — manageable, but it adds ops overhead. Stape.io hosts the container on managed infrastructure and adds useful features: custom domains (so the tagging endpoint looks first-party), built-in GDPR tooling, and monitoring.
Custom domain routing must be validated in Search Console if you're using the same domain as your site. Don't skip this — you'll lose organic data if GTM's gtm.js requests start returning 404s.
Matching for Meta CAPI
Meta's Conversion API requires event matching to work well. The key parameters are em (hashed email), ph (hashed phone), and fbp/fbc (cookies). Server-side, we hash PII before send:
import hashlib
def hash_value(val):
# strip + lower normalisation is required by Meta CAPI spec
return hashlib.sha256(
val.strip().lower().encode()
).hexdigest()
payload = {
"em": hash_value(user_email),
"ph": hash_value(user_phone),
"event_name": "Lead",
"event_time": int(time.time()),
}§5 Results
After a 30-day migration period, with both stacks running in parallel to validate parity, we cut over. Key changes:
- Reported conversions in Meta increased by ~35% — not because more happened, but because more were measured.
- GA4 session data became consistent with server logs for the first time.
- ROAS calculations stabilised — no more volatility from consent opt-out spikes.
- Compliance audit passed with zero findings.
§6 What I'd do differently
The migration took longer than it needed to because we tried to maintain full parity with the legacy setup before cutting over. In retrospect, a cleaner break — accept a 2-week data gap, migrate everything at once — would have been faster and less confusing for the team reading the dashboards.
Also: document the routing logic in Stape.io before you need to debug it at 11pm. Trust me on this one.