Server-Side GTM + Meta Ads Setup Guide

I audit about 30 server-side GTM setups per year, and I’d say 70% of them are sending garbage data to Meta. The most common issue? People set up the server container but never properly configure the Conversions API tag, so they’re just proxying browser events through their server without any of the actual benefits of server-side tracking.

The other big one is data mapping. Meta’s Conversions API is picky about data formats — phone numbers need to be E.164, emails need to be lowercase and trimmed, and if you’re not hashing PII correctly, your events just disappear into the void.

What You’ll Have Working By The End

Prerequisites

Step 1: Server Container Setup and Deployment

Create your server-side container in GTM and get it deployed. I always deploy to Google Cloud Run because it scales automatically and you only pay for requests.

In GTM, go to Admin → Create Container → Server. Copy the container config and the CONTAINER_CONFIG value — you’ll need this.

For Cloud Run deployment, use this app.yaml if you want the simple approach:

runtime: nodejs18
env_variables:
  CONTAINER_CONFIG: "YOUR_CONTAINER_CONFIG_STRING_HERE"
  PORT: "8080"

automatic_scaling:
  min_instances: 0
  max_instances: 10
  target_cpu_utilization: 0.6

The critical piece is setting up your transport URL. In your server container, go to Admin → Container Settings and set your Server Container URL. This should be your custom domain (like gtm.yourdomain.com) pointed to your Cloud Run service.

Most people screw this up by using the default Cloud Run URL instead of a custom domain, which causes CORS issues and makes debugging harder.

Step 2: Web Container Configuration

You need to modify your web container to send events to your server instead of directly to Meta.

First, create a new GA4 Configuration tag (yeah, it sounds weird, but this is how you send events to server-side GTM):

Tag Type: GA4 Configuration Measurement ID: Use G-XXXXXXXXXX format (you can use a fake one like G-1234567890) Server Container URL: https://gtm.yourdomain.com

Set this to fire on All Pages.

Then modify your existing Meta Pixel events to also send to the server. For each conversion event, create a GA4 Event tag:

Tag Type: GA4 Event Configuration Tag: Select your server container config tag above Event Name: Keep the same name as your Meta event (Purchase, Lead, etc.)

In Event Parameters, map all your existing Meta event data:

currency: {{dlv - currency}}
value: {{dlv - value}}
content_ids: {{dlv - content_ids}}
email: {{dlv - email}}
phone: {{dlv - phone}}

The key here is sending both browser and server events initially, so you can compare data and gradually shift traffic.

Step 3: Meta Conversions API Tag Configuration

In your server container, add the Meta Conversions API tag. This is where most implementations break.

Tag Type: Meta Conversions API Pixel ID: Your Meta Pixel ID API Access Token: Your Conversions API token from Events Manager

For the Event Data Configuration:

Event Name: Use {{Event Name}} (this pulls from your GA4 events) Event ID: This is critical for deduplication. Use something like:

{{Event Name}} + "_" + {{Client ID}} + "_" + {{Timestamp}}

User Data Object (this is where data quality matters):

// Email - must be lowercase, trimmed, and hashed
em: {{email_hashed}}

// Phone - must be E.164 format and hashed
ph: {{phone_hashed}}

// Client IP and User Agent for attribution
client_ip_address: {{Client IP Address}}
client_user_agent: {{User Agent}}

// Facebook Click ID if available
fbc: {{fbc}}
fbp: {{fbp}}

For the hashing, create Custom JavaScript variables:

Email Hashing Variable:

function() {
  var email = {{dlv - email}};
  if (email) {
    return CryptoJS.SHA256(email.toLowerCase().trim()).toString();
  }
  return undefined;
}

Custom Data Object for e-commerce events:

{
  currency: {{dlv - currency}},
  value: {{dlv - value}},
  content_ids: {{dlv - content_ids}},
  content_type: "product",
  content_name: {{dlv - content_name}}
}

Set this tag to fire on your conversion events (Purchase, Lead, etc.).

Step 4: Data Deduplication Setup

This is the step that separates working implementations from broken ones. You need event IDs that match between browser and server.

In your web container Meta Pixel tag, add this to Custom Data:

{
  eventID: {{Event ID Variable}}
}

Create an Event ID variable that generates the same ID format you’re using in the server container:

function() {
  return {{Event Name}} + "_" + {{GA Client ID}} + "_" + Date.now();
}

The event ID must be identical between browser and server events, or Meta will count them as separate conversions.

Step 5: Attribution Data Enhancement

Server-side tracking lets you send additional attribution data that browsers can’t access. In your server container, create variables for:

Server-side Client ID Variable:

function() {
  var clientId = getEventData('client_id') || getEventData('ga_session_id');
  return clientId;
}

Enhanced User Agent Variable:

function() {
  var ua = getEventData('user_agent');
  // Add any server-side user agent enrichment here
  return ua;
}

IP Address Variable (automatically populated by server container):

function() {
  return getEventData('ip_override') || getRemoteAddress();
}

This data improves Meta’s attribution models, especially for iOS traffic where client-side data is limited.

Testing & Verification

Use Meta’s Test Events feature in Events Manager. Send test events and verify:

  1. Events show up in real-time — if there’s a delay longer than 30 seconds, something’s wrong with your server setup
  2. Match Quality is “Good” or “Great” — if it’s “Poor”, your PII hashing is probably broken
  3. Deduplication is working — send the same event from browser and server with identical event IDs, should only see one event
  4. Custom parameters are populated — check that your e-commerce data (value, currency, content_ids) is showing up correctly

For server container debugging, use the GTM server container preview mode. You should see events flowing from web → server → Meta.

Check your Cloud Run logs for any 400/500 errors from Meta’s API. Common ones:

Troubleshooting

Problem: Events showing in server container preview but not reaching Meta Solution: Check your API access token permissions. Go to Events Manager → Settings → Conversions API and verify the token has “Manage” permissions, not just “View”.

Problem: Duplicate events in Meta despite using event IDs Solution: Your event IDs aren’t matching between browser and server. Add console.log to your web container to verify the ID format matches exactly what the server is generating.

Problem: Match quality is “Poor” for all events Solution: Your PII hashing is broken. Meta expects SHA256 of lowercase, trimmed email/phone. Test with a known email: SHA256 of “test@example.com” should be “973dfe463ec85785f5f95af5ba3906eedb2d931c24e69824a89ea65dba4e813b”.

Problem: Server container returning 500 errors Solution: Usually a Cloud Run memory/CPU limit issue. Check your container config — the default is 1CPU/512MB which isn’t enough for high-traffic sites. Bump to 2CPU/1GB.

Problem: Events working in test mode but not in live traffic Solution: Your triggers are probably too restrictive. Check that your GA4 Event tags in web container are firing on the same triggers as your original Meta Pixel events.

Problem: Attribution looking worse after server-side implementation Solution: You’re missing fbp/fbc parameters or IP address. These are critical for Meta’s attribution models. Make sure your server container is sending client IP (not server IP) and preserving Facebook’s browser identifiers.

What To Do Next

Now that you have server-side tracking working, you can start leveraging the additional capabilities. Check out Server-Side GTM Lead Form Tracking to capture leads that browser tracking misses, or explore Server-Side GTM Checkout Tracking for better e-commerce attribution.

Want me to audit your current Meta tracking setup? I’ll identify exactly what’s broken and how much revenue you’re missing. Get a free tracking audit — takes about 15 minutes and I’ll show you the specific issues in your account.

This guide is part of the Server-Side GTM Implementation Hub — complete guides for setting up server-side tracking across all major platforms and use cases.