Server-Side GTM on Google Cloud Run: The Complete Engineering Guide
Chapter 1: The Core Architecture
To understand sGTM, you must understand the difference between the Web Container and the Server Container. They are not the same thing. They do different jobs in your data pipeline.
The Web Container (The Collector)
The Web Container lives in the user’s web browser. Its only job is to collect data from the screen. It looks at clicks, page views, and form inputs. In the past, the Web Container sent this data directly to many different places (Facebook, Google Analytics, TikTok). This was a problem. It made the website slow. Also, ad blockers stopped these connections.
Now, you change the rule. The Web Container must send data to only one place: your Server Container.
How to set this up:
- Open the Web Container.
- Go to the GA4 Configuration Tag.
- Find the setting called “Send to server container”.
- Enter your custom domain (for example,
[https://data.yourdomain.com](https://data.yourdomain.com)).
Why we do this: When you do this, the browser sends one data stream to your server. The browser does not talk to Facebook or Google directly. You control the data.
The Server Container (The Router)
The Server Container lives on Google Cloud Run. It is a secure server. Users cannot see what happens inside it. Ad blockers cannot block the work inside it, because the work happens on your server, not in the browser.
The Server Container receives the data from the Web Container. It cleans the data. It changes the data format. Then, it sends the data to the final destinations.
The First-Party Rule: Your Server Container must use a subdomain of your main website. If your website is store.com, your server must be data.store.com. Why? Browsers like Safari have strict rules (Apple ITP). They block cookies from third-party domains. But if the server is on the same domain as the website, the server can set a “first-party cookie”. This cookie is trusted by the browser. It lives longer (up to 2 years). This is critical for tracking users over a long time.
Chapter 2: Google Cloud Run Mechanics
Google Cloud Run is the engine. It is a serverless platform. This means you do not buy a physical server. You rent computing power by the millisecond. Google gives you an official Docker image for sGTM.
Hardware Specifications
When you create the Cloud Run service, you must choose the hardware.
- CPU: You usually select 1 vCPU. This is one virtual processor. It is enough for standard tracking.
- Memory (RAM): 512 MB is the minimum. If you process very large data objects (like big shopping carts with 50 items), you must increase this to 1 GB. If the container uses too much memory, Cloud Run will kill it. This causes data loss.
Performance and Throughput
How much data can one container handle?
- Requests Per Second (RPS): One standard container (1 vCPU, 512 MB) can process about 30 to 50 requests per second.
- Concurrency: This is a very important setting. The default concurrency in Cloud Run is 80. This means one single container can process 80 different HTTP requests at the exact same time.
What happens when traffic spikes? If 100 people click a button at the same millisecond, the first container takes 80 requests. It is full. Cloud Run instantly creates a second container to handle the remaining 20 requests. This is called “scaling up”. When the users leave, Cloud Run deletes the second container. This is called “scaling down”. You only pay for what you use.
The CPU Allocation Problem
You must make a critical choice in Cloud Run settings:
- CPU is only allocated during request processing: The server sleeps when there is no traffic. When a request arrives, the server must wake up. This takes 1 to 3 seconds. This is called a “Cold Start”. If the server takes 3 seconds to wake up, the user’s browser must wait 3 seconds for a response. This can break tracking.
- CPU is always allocated: The server never sleeps. It is always ready. It processes requests in milliseconds.
The Engineering Rule: For a production environment, you must choose “CPU is always allocated”. You must also set the “Minimum instances” to 1. This prevents cold starts. It costs a little more money, but your data pipeline will not lose events.
Chapter 3: The Event Journey Step-by-Step
What exactly happens inside the Server Container? We must look at the exact path of the data. The data goes through a strict pipeline.
Step 1: Ingestion (HTTP Request)
The user clicks “Buy”. The Web Container sends an HTTP POST request to [https://data.yourdomain.com/g/collect](https://data.yourdomain.com/g/collect). The body of this request contains the event data.
Step 2: The Client (The Listener)
Inside the Server Container, there is a component called a “Client”. The Client listens to incoming HTTP requests.
- The system has a GA4 Client.
- The GA4 Client looks at the URL path. It sees
/g/collect. - Because the path matches its rules, the GA4 Client “claims” the request.
Step 3: Parsing and the Event Data Object
The Client takes the raw HTTP request and breaks it apart. It looks at the headers (like the user’s IP address and User-Agent). It looks at the body. The Client translates this messy HTTP data into a clean JSON structure. This structure is called the Event Data Object. Why is this important? The Event Data Object is a universal language. It normalizes the data. It does not matter if the data came from a website or a mobile app. Once it becomes an Event Data Object, all Tags can read it.
Step 4: Triggers Evaluation
Now, the system looks at your Triggers. A Trigger is a true/false rule.
- Example rule:
If Event Name equals "purchase", then True.The Trigger looks inside the Event Data Object. If it finds the name “purchase”, the Trigger fires.
Step 5: Tag Execution
The Trigger tells the Tag to start working. A Tag is a piece of code that builds an outgoing request. If the Trigger fires a Facebook CAPI Tag:
- The Tag reads the Event Data Object.
- It takes the “transaction_id”.
- It takes the “user_email”.
- It formats this data exactly how Facebook wants it.
Step 6: HTTP Exit
The Tag sends a new HTTP POST request from the Cloud Run server to the Facebook server.
Step 7: The Response
Finally, the Client sends an HTTP 200 (OK) response back to the user’s browser. The journey is complete.
Chapter 4: Filtering and Data Protection
You do not want to send all data to the server. Bad data costs money and ruins analytics. You must use filters. There are two places to put filters.
1. Web Container Filters (First Line of Defense)
You must stop bad events before they leave the browser. Every HTTP request from the browser to Cloud Run costs money.
- Rule: If an event is not important (like a simple scroll event or a background video play), do not send it to the server. Do not trigger it in the Web Container. This saves network bandwidth and Cloud Run processing time.
2. Server Container Filters (Second Line of Defense)
Sometimes, bad data reaches the server. Bots and automated scripts scan the internet. They can find your custom domain (data.yourdomain.com) and send fake HTTP requests to it.
How to block bots: You can create a Custom Client or use an Exclusion Trigger.
- You look at the
User-Agentstring in the HTTP header. If theUser-Agentsays “HeadlessChrome” or “Googlebot”, you stop the process. - You create a Trigger rule:
Fire this tag ONLY IF User-Agent does not match "bot". - If the tag does not fire, the event dies in the server. It does not go to BigQuery or Facebook. Your database stays clean.
Chapter 5: Practical Setup Cases
Here is how you configure specific tasks. Each step requires exact logic.
Case 1: Hiding IP Addresses from Google Analytics
In standard tracking, Google Analytics sees the user’s IP address. For privacy (GDPR), you want to delete the IP address before Google sees it.
- Incoming Data: The Web Container sends the data to your Server Container.
- The Client IP: The Cloud Run server knows the user’s IP. The GA4 Client puts this IP into the Event Data Object.
- Tag Setup: In the Server Container, create a new Tag. Choose the type “Google Analytics: GA4”.
- The Fix: Open the “Advanced Settings” inside the Tag. Find the option called “Redact visitor IP address”. Check this box.
- What happens: When the Tag builds the outgoing HTTP request to Google servers, it simply deletes the IP address string from the payload. Google receives the event, but the IP field is empty (or zeroed). You are now protecting user privacy deterministically.
Case 2: Facebook Conversions API (CAPI) and Deduplication
Browsers block the Facebook Pixel. To fix this, you must send purchase events from your server directly to Facebook’s server. But you have a big problem: Deduplication.
If the browser’s Facebook Pixel works, it sends a purchase to Facebook. If your server also sends the same purchase to Facebook, Facebook counts two purchases. Your data is wrong. You must link them.
The Deduplication Rule: You must generate a unique Event ID (a random string of numbers and letters) for every event in the browser. You must send the exact same Event ID to both the Facebook Pixel (from the web) and the Server Container.
Step-by-Step Setup:
- Web Container: Create a Variable that generates a random
Event ID. - Web Tags: Add this
Event IDVariable to your GA4 Event Tag AND your Facebook Pixel Tag. Both tags fire at the same time with the same ID. - Server Tag: Go to your Server Container. Download the “Facebook Conversions API” template from the gallery.
- Mapping: In the Server Tag, map the
Event IDfield to read theEvent IDfrom the Event Data Object. - Hashing: Facebook requires you to encrypt user data (like email). This encryption is called SHA-256 hashing. The Facebook CAPI template does this automatically. You just map the
user_emailfield. The template hashes it before sending. - The Result: Facebook receives the browser event (ID: 123). Facebook receives the server event (ID: 123). Facebook compares the IDs. It sees they are identical. It deletes the browser event and keeps the rich server event.
Case 3: Sending Custom Tracking Logs to Google Cloud Pub/Sub
You want to build a custom data observability pipeline. You want to save every single event into a data warehouse (like BigQuery). The best way to do this is to send data from sGTM to Pub/Sub, and then from Pub/Sub to BigQuery.
sGTM does not have a built-in Pub/Sub tag. You must build an HTTP POST pipeline.
The Permissions Problem: Your Cloud Run container operates under a “Service Account”. This is a robot user in Google Cloud. By default, this robot user has no permissions.
- The Fix: You must go to Google Cloud IAM (Identity and Access Management). Find the Service Account for your Cloud Run instance. Give it the role
Pub/Sub Publisher. Now, the server is allowed to write messages to Pub/Sub topics.
The Encoding Problem: The Pub/Sub REST API does not accept normal JSON text. It strictly requires the data to be encoded in “Base64” format. sGTM does not do this automatically.
- The Fix: You must create a Custom Template in sGTM using Sandboxed JavaScript. You write a small script that takes the Event Data Object, converts it to a JSON string, and then encodes that string into Base64.
The Tag Setup:
- Create a Tag using the “HTTP Request” template.
- Method: POST.
- URL:
[https://pubsub.googleapis.com/v1/projects/](https://pubsub.googleapis.com/v1/projects/)[YOUR-PROJECT]/topics/[YOUR-TOPIC]:publish - Headers: Add
Content-Type: application/json. - Body: You must build the strict JSON structure that Pub/Sub needs. It looks like this:
{ "messages": [ { "data": "{{Your Base64 Variable}}" } ] } - The Result: The event arrives. The Custom Template encodes it to Base64. The HTTP Tag sends it to Pub/Sub. Pub/Sub accepts it because the Service Account has the correct role. The message is now safely stored in the messaging queue.
Chapter 6: Updates and System Health
Software gets old. Google updates the sGTM Docker image often to fix security bugs and add new features. You must update your server.
How to Update without Downtime: You do not need to turn off the server. Cloud Run has a smart deployment system.
- Go to the Cloud Run console.
- Click your service name.
- Click the button “Edit & Deploy New Revision”.
- Do not change any settings. Just scroll to the bottom and click “Deploy”.
- What happens: Cloud Run pulls the newest Docker image from Google. It creates a new container. It waits for the new container to become healthy. Then, it instantly moves all the website traffic to the new container. It deletes the old container. Your tracking does not stop for even one second.
Using Preview Mode (Debugging): When you build a new tag, you must test it. You must not guess if it works.
- Open Server GTM and click “Preview”. A debug window opens.
- Open Web GTM and click “Preview”.
- Go to your website and click a button.
- Look at the Server debug window. On the left side, you will see the incoming HTTP request.
- Click on the request. You can inspect the “Event Data” tab. Here you see the exact JSON object. If a variable is empty here, your Tag will fail. You can see exactly which Tags fired, and you can inspect the outgoing HTTP requests to see if they formatted correctly. You have complete visibility into the data flow.
Here is the next part of the complete engineering guide. We now move into advanced data architecture, security, and cost control inside Server-Side GTM on Google Cloud Run.
Chapter 7: Data Enrichment with Google Cloud Firestore
You must never send sensitive business data to the user’s browser. If you put “product profit margin” or “user CRM status” in the Web Data Layer, competitors and users can read it. You must do data enrichment on the server.
The Logic of Enrichment
- The Web Container sends only basic, non-sensitive identifiers (like
user_idorproduct_id). - The Server Container receives the ID.
- The Server Container connects to a database, finds the ID, and pulls the sensitive data (like “VIP Customer” or “Profit: $50”).
- The Server combines the original event with the new database data.
- The Tag sends the combined rich data to Facebook, BigQuery, or Google Analytics.
Why Firestore?
Google Cloud Firestore is a NoSQL document database. You must use it with sGTM because it is physically located in the same Google Cloud network as your Cloud Run server.
- Speed: A lookup request to Firestore takes 2 to 5 milliseconds. This is critical. If the database is slow, your Cloud Run container waits. If it waits, it uses CPU time, which costs you money.
- Integration: sGTM has a built-in “Firestore Lookup” Variable. You do not need to write complex authentication code. The Cloud Run Service Account automatically has access to Firestore if they are in the same GCP Project.
Step-by-Step Firestore Setup
- Prepare the Database: Go to GCP Firestore. Create a collection called
users. Create a document where the Document ID is theuser_id(e.g.,12345). Inside this document, add a field:customer_segment = high_value. - sGTM Variable: Go to Server GTM. Create a new Variable. Choose “Firestore Lookup”.
- Collection Path: Type
users/{{Event Data - user_id}}. This tells the variable exactly where to look. - Tag Configuration: In your outgoing Tag (like Facebook CAPI), add a new parameter. Use the Firestore Lookup Variable you just created.
- The Result: The server receives
user_id: 12345. It pauses the tag. It asks Firestore for the document. It getshigh_value. It adds this to the Facebook payload. The user’s browser never sees this process.
Chapter 8: Sandboxed JavaScript (Custom Templates)
If you need to change data in a way that standard sGTM variables cannot do (for example, complex mathematical calculations, string parsing, or base64 encoding), you must write custom code.
But you cannot write normal JavaScript in sGTM. You must write Sandboxed JavaScript.
What is the Sandbox?
The Sandbox is a strict security environment.
- Rule 1: No Global Objects. You cannot use
window,document, or standard ES6 functions likefetch()orPromise. - Rule 2: API Only. You must use specific Google APIs provided by the template editor.
- Rule 3: Strict Memory Limits. If your script uses too much memory or creates an infinite loop, the Sandbox will instantly kill the script to protect the Cloud Run container from crashing.
How to use Sandboxed APIs
If you want to read an HTTP header, you cannot just look at a global request object. You must import the exact tool for the job.
Example: Creating a custom Base64 Encoder Template
- Go to the “Templates” tab in sGTM. Click “New” under Variable Templates.
- You must explicitly request permissions in the template settings (e.g., “Allow this script to read Event Data”).
- In the code editor, you write the logic using required modules:JavaScript
// 1. Import the specific tools you need from the Sandbox API const require = require('require'); const encodeUriComponent = require('encodeUriComponent'); const makeBase64 = require('makeBase64'); // Fictional example of strict API const getEventData = require('getEventData'); // 2. Get the data const rawData = getEventData('ecommerce_payload'); // 3. Transform the data const stringData = JSON.stringify(rawData); const encodedData = makeBase64(stringData); // 4. Return the result return encodedData;
Why this is good: Because the code is sandboxed, even if you download a Template from a bad developer on the internet, the code cannot steal your server’s environment variables or crash the main Docker container. It is deterministic and isolated.
Chapter 9: Observability and Log Routing in GCP
A professional data engineer must know exactly what the server is doing. You cannot just hope it works. You must build an observability pipeline.
Cloud Logging (Standard Output)
Everything that happens in your sGTM Cloud Run container generates a log.
- If a container starts, it logs it.
- If a container runs out of memory (OOM), it logs a critical error.
- If you use the
logToConsoleAPI in a Sandboxed Custom Template, that text goes directly to Google Cloud Logging.
Building the BigQuery Log Pipeline
Cloud Logging is good for reading, but bad for analytics. You must route your Cloud Run logs into BigQuery to analyze performance, calculate exact costs, and find bottleneck patterns.
- The Log Router: Go to Google Cloud Logging > Log Router.
- Create a Sink: A “Sink” is a rule that catches specific logs and sends them to a destination.
- The Filter: You do not want all logs. You only want logs from your sGTM server. Write this strict query:
resource.type = "cloud_run_revision" AND resource.labels.service_name = "sgtm-server" - The Destination: Choose a BigQuery dataset. (Example dataset:
sgtm_telemetry). - The Result: Every time your Cloud Run container does something, a JSON row is automatically inserted into BigQuery.
What metrics to analyze in BigQuery:
Once the logs are in BigQuery, you can write SQL queries to track:
- Request Latency: How many milliseconds did it take the container to answer the browser? If the average is over 500ms, your Firestore lookups or external Tags are too slow.
- Cold Start Frequency: Count how many times the
textPayloadcontains “Container Sandbox activated”. If this number is high, you are losing data because users close the page before the container wakes up. You must switch CPU allocation to “always on”. - HTTP 500 Errors: Count how many times Facebook or Google Analytics rejected your server’s outgoing request.
Chapter 10: System Failures and Timeout Physics
In a server-side environment, you are dealing with external networks. External networks fail. Facebook’s API will go down. Pub/Sub will have latency. You must understand the physics of these failures to protect your system.
Synchronous vs. Asynchronous Execution
When a request hits sGTM, the Client creates the Event Data Object, and the Tags fire.
- The Danger: If a Tag makes an HTTP request to an external server (like a custom CRM API), the sGTM container waits for the response before it finishes the process.
- The Timeout Problem: Cloud Run has a strict timeout limit. If the external CRM API takes 30 seconds to answer, your Cloud Run container is stuck holding the connection for 30 seconds.
How this causes a system crash:
- Concurrency is set to 80.
- 80 users send events to sGTM at the same time.
- The sGTM container sends 80 requests to the slow CRM API.
- The CRM API is frozen. It does not answer.
- Your single Cloud Run container is now full (80/80 connections are waiting). It cannot accept new events.
- Cloud Run sees the container is full. It spins up a second container.
- 80 more users arrive. The second container sends 80 requests to the broken CRM API. It also freezes.
- Cloud Run spins up a 3rd, 4th, and 50th container.
- Result: You experience a massive spike in Cloud Run costs because a third-party API is slow.
The Solution: Deterministic Timeouts
You must never trust external endpoints. When you configure custom HTTP requests using Sandboxed JavaScript (sendHttpRequest), you must hardcode a strict timeout.
JavaScript
const sendHttpRequest = require('sendHttpRequest');
sendHttpRequest('https://slow-crm.com/api', (statusCode, headers, body) => {
// Handle response
}, {
method: 'POST',
timeout: 1500 // 1.5 seconds maximum. This is the hard limit.
});
Why this works: If the CRM takes longer than 1500 milliseconds, the script intentionally fails. The container frees up the memory and CPU. It closes the connection. It drops the CRM event, but the container survives to process the next user. In system engineering, it is better to drop one delayed event than to crash the entire server infrastructure.
Here is the final part of the engineering guide. This section covers cost mathematics, global architecture, safe deployment rules (CI/CD logic), and the strict diagnostic algorithm for finding errors.
Chapter 11: The Physics of Cost Optimization
Google Cloud Run is cheap, but it is not free. If you do not understand how Google calculates your bill, a high-traffic website will cost you thousands of dollars. You must optimize the system deterministically.
The Three Factors of Cost
Your bill depends on three exact metrics:
- CPU Time: You pay for the exact milliseconds the CPU is working.
- Memory (RAM): You pay for the amount of memory allocated to the container.
- Network Egress: You pay for the data that leaves the Google Cloud network.
Optimization Rule 1: Limit Network Egress
Data coming into Google Cloud is free (Ingress). Data going out of Google Cloud costs money (Egress).
- The Bottleneck: If your Server Container sends large JSON payloads (like 100 KB per event) to Facebook, Google Analytics, and TikTok, you generate massive Egress traffic.
- The Solution: Strip the data. Use Sandboxed JavaScript to delete unnecessary parameters before the Tag fires. If the browser sends 50 fields, but Facebook only needs 5 fields, delete the other 45 fields. You reduce Egress costs and make the HTTP request faster.
Optimization Rule 2: Control the Logs
As discussed in Chapter 9, logging is good. But Cloud Logging charges you for log storage.
- The Risk: If you log every single HTTP 200 (OK) response, a website with 10 million events a month will generate gigabytes of useless log data.
- The Solution: Set strict log levels. In your Cloud Run environment variables, set the logging level to only record “Errors” and “Warnings” in production. You only need full logs in your staging or testing environment.
Chapter 12: Global Architecture and Load Balancing
If your business is only in Ukraine, one Cloud Run server in Europe (e.g., Frankfurt) is perfect. But if you have users in the USA, Europe, and Asia, a single server creates physical latency. A signal from Tokyo takes 200 milliseconds to reach Frankfurt.
The Multi-Region Strategy
To make tracking instant for everyone, you must deploy multiple Cloud Run services.
- Deploy
sgtm-europein Frankfurt. - Deploy
sgtm-usain Iowa. - Deploy
sgtm-asiain Tokyo.
All three services use the exact same GTM Container Config string. They run the exact same logic.
The Global Load Balancer
You cannot give the user three different URLs. You need a Global HTTP(S) Load Balancer.
- Create a Load Balancer in Google Cloud.
- Connect your custom domain (
data.yourdomain.com) to the Load Balancer IP address. - Connect the Load Balancer to your three Cloud Run services (Serverless Network Endpoint Groups).
- The Result: When a user in Tokyo clicks a button, the Load Balancer sees their location. It routes the HTTP request to
sgtm-asia. The physical distance is short. The response takes 10 milliseconds.
The Compromise (Risks of this Architecture)
This architecture is fast, but it brings complexity.
- Cost Risk: A Global Load Balancer has a fixed monthly price (around $18/month minimum) plus data processing fees.
- Maintenance Risk: You must monitor three different services in Cloud Logging. If an error happens, you must check which region caused it.
Chapter 13: Safe Deployment (CI/CD Logic)
You must never make changes directly to the production server. A small mistake in a regex rule or a JavaScript variable will break the tracking for the whole company. You must follow strict deployment isolation.
The Two-Environment Rule
You must have two completely separate environments.
- Staging Environment:
staging-data.yourdomain.com. This runs on a separate Cloud Run service. - Production Environment:
data.yourdomain.com.
The GTM Environments Feature
Inside the Server GTM interface, you must use the “Environments” feature.
- Go to Admin > Environments.
- Create an environment called “Staging”.
- GTM will give you a specific configuration string for Staging (it ends with
&env=2). - Apply this specific string to the
CONTAINER_CONFIGenvironment variable in your Staging Cloud Run service.
The Deployment Pipeline
- Development: You create a new Tag in your GTM Workspace.
- Testing: You use GTM Preview Mode connected to the Staging server.
- Publishing to Staging: You publish the GTM Workspace ONLY to the “Staging” environment.
- Verification: You run automated or manual tests on
staging-data.yourdomain.com. You check the Network tab. You check BigQuery to ensure the schema is not broken. - Publishing to Production: Only after verification, you publish the version to the “Live” (Production) environment.
This strict pipeline guarantees zero critical failures in production.
Chapter 14: The Diagnostic Algorithm (Pipeline)
When data stops arriving in your final destination (like BigQuery or Facebook), you must not guess what is wrong. You must use a deterministic diagnostic algorithm. You check the system step by step, from the start to the end.
Follow this exact diagnostic tree:
Step 1: The Browser Egress Check
- Action: Open the website. Open Chrome DevTools (Network Tab). Click the button you want to track. Filter by your server URL (
data.yourdomain.com). - Question: Did the HTTP POST request fire?
- If NO: The problem is in the Web Container (frontend). Check Web GTM triggers. The Server is not the problem. Stop here and fix the frontend.
- If YES: Look at the HTTP status code of the request.
- If
CORS errororDNS error: Your domain SSL or A records are broken. - If
200 OK: The event successfully reached the server. Move to Step 2.
- If
Step 2: The Server Ingestion Check
- Action: Open Server GTM. Open Preview Mode. Do the action on the website again.
- Question: Do you see the request appear on the left side of the Server Preview window?
- If NO: The request reached the server, but no Client claimed it. Check the path (is it
/g/collect?). Check if your GA4 Client is active and configured to listen to that path. - If YES: Click on the request. Move to Step 3.
Step 3: The Event Data Object Check
- Action: In the Preview window, click the “Event Data” tab.
- Question: Is the data structured correctly? Are all the required variables (like
user_id,price,currency) present? - If NO: The Client did not parse the data correctly, or the Web Container did not send it. Fix the Client parsing logic.
- If YES: The server understands the data perfectly. Move to Step 4.
Step 4: The Trigger and Tag Check
- Action: Click the “Tags” tab in the Preview window.
- Question: Did your destination Tag (e.g., Facebook CAPI) fire?
- If NO: Your Trigger rules are wrong. Check the Trigger conditions. Maybe it expects
purchasebut the event name isPurchase(capital P). Fix the strict string matching. - If YES: The Tag fired. Move to Step 5.
Step 5: The Outgoing Request Check
- Action: Open the Tag details in the Preview window. Look at the outgoing HTTP request.
- Question: What is the HTTP response code from the external destination (Facebook/BigQuery)?
- If 400 (Bad Request): You formatted the JSON wrong. The destination rejected it. Read the error message in the payload and fix your Tag variables.
- If 401 (Unauthorized): Your API token or Service Account permissions are wrong. Update your authentication keys.
- If 200 (OK): The Server Container successfully did its job.
Final Conclusion: If Step 5 is 200 OK, but you still do not see data in Facebook or BigQuery, the problem is entirely inside the destination system (e.g., Facebook is taking 24 hours to process events, or your BigQuery SQL query is looking at the wrong date partition). The server architecture is proven to be healthy.
