Server-side GTM is the cleanest way to run Facebook CAPI, but it's also where events quietly go missing. The browser sends one thing, the server sends another, and Meta ends up with gaps or duplicates.
Here's the setup that keeps every event, and the handoffs where they usually leak.
How the pieces fit
The web container tags the browser and forwards a hit to your server container. The server container holds a Facebook CAPI tag that rebuilds the event and sends it to Meta over the API.
The pixel still fires in the browser for coverage and cookies. The server tag is the reliable copy. Deduplication keeps Meta from counting both.
Setup, step by step
- Stand up a server container on a first-party subdomain, so cookies like fbp aren't treated as third-party.
- In the web container, send events to the server via the GA4 client or a Meta-specific client.
- Add the Facebook Conversions API tag in the server container, with your pixel ID and CAPI access token.
- Pass a shared event_id from the browser event into the server tag, so it matches the pixel event.
- Forward user_data: fbp, fbc, hashed email, plus the real client IP and user agent from the request.
The server tag ends up sending something like this to Meta:
{
"event_name": "Purchase",
"event_id": "order_10432",
"action_source": "website",
"event_time": 1699999999,
"user_data": {
"em": "<sha256_email>",
"fbp": "fb.1...",
"client_ip_address": "<from request>",
"client_user_agent": "<from request>"
},
"custom_data": { "value": 49.00, "currency": "USD" }
}
Where events actually get lost
- The event_id isn't passed through, so the pixel and server events don't dedupe and Meta double counts.
- The server reads its own IP and user agent instead of the visitor's, tanking match quality.
- Consent Mode blocks the browser hit, and nothing forwards to the server, so the event never happens at all.
- The fbc value is dropped because the fbclid was never captured on the landing page.
Confirm before you trust it
Use the Test Events tab in Events Manager and run a real purchase. You want to see the event arrive from both the browser and the server, marked deduplicated, with a healthy Event Match Quality score.
Before you start
Two things save you hours. Get a CAPI access token from Events Manager and store it as a variable in the server container, not hardcoded in the tag. And set up the DNS and SSL for your first-party subdomain before you touch tags, because half of "lost events" turn out to be a server that isn't actually receiving traffic.
Keep the browser pixel live during the migration. Turn it off only once the server events are landing and deduplicating cleanly, so you never have a window with no data at all.
Consent and the server handoff
Consent Mode is where server-side setups lose the most events. If a visitor declines, the browser event is suppressed, and if nothing forwards to the server, that conversion never happens anywhere.
Decide deliberately what you send under denied consent, respect the choice, and document it. The goal is a defensible setup, not the maximum number of events at any cost.
What good looks like
A healthy setup shows, for each key event, a high deduplication rate and an Event Match Quality score in the green. Purchases reconcile with your store within a small margin, and refunds flow back so revenue doesn't drift up over time.
If those three hold a week after launch, the handoff is solid. If one slips, it's almost always the event_id or the forwarded user data, not the API itself.
Server-side GTM is the cleanest way to run Facebook CAPI, as long as you treat the browser-to-server handoff as the fragile part. Pass the shared event_id, forward the real user data, and the events you were quietly losing start showing up.
Want a stronger data analyst role or a raise? Grab the FREE Product Analyst Playbook and get the exact roadmap to your next offer.
