Webclat / GA4 Practice
Home / QA / Measurement Protocol and server-side

How do I send GA4 events server-side without gtag.js/GTM, and without leaking my Measurement Protocol API secret?

Quick answer

Send server-side GA4 events by having your own backend (not the browser) POST to https://www.google-analytics.com/mp/collect?measurement_id=G-XXXXXXX&api_secret=YOUR_SECRET with the event payload - the API secret must live only in server-side environment variables or a secrets manager, never in any file the browser downloads, because anyone who can read it can send arbitrary events to your property.

Why This Happens

01 / CAUSE

The Measurement Protocol's API secret is a write credential for your GA4 property's event stream, not a read-only public identifier like the Measurement ID (G-XXXXXXX) is - the Measurement ID is meant to be public and appears in your page source by necessity, but the API secret is meant to prove that whoever is calling /mp/collect is authorized to do so on your behalf.

Some official Google documentation and third-party guides have shown client-side usage patterns for quick testing, which has led to a real misconception that exposing the API secret in browser JavaScript is an accepted practice - it is not treated as safe by GA4's own security model, since anyone who views the page source (or intercepts the request) can extract the secret and send forged or spam events indefinitely, and GA4 has no built-in secret-rotation prompt to catch this.

Fix It

02 / STEPS

Keep the API secret exclusively server-side

Store it as an environment variable in your backend (Node/Python/PHP server, a serverless function, a Cloud Function) - never in a client bundle, a public repo, or a browser-executed script tag, even one loaded from your own domain.

Route the event through your own backend endpoint

Have the browser call your own API route (e.g. /api/track) with just the event data; your server then attaches the api_secret and forwards the request to /mp/collect. The browser never sees the secret at any point in this flow.

Generate a valid client_id server-side by reading it from the request, not inventing one

Pass the browser's real _ga cookie value (parsed to the client_id format) along with the event data to your backend endpoint, so the server-side event still attaches to the correct existing user session rather than creating a disconnected one - see the dedicated client_id format page.

Rotate the secret immediately if it has ever been exposed

If you find the API secret in client-side code, browser dev tools, or a public repository, generate a new one in GA4 Admin > Data Streams > your stream > Measurement Protocol API secrets, and revoke the old one - old secrets keep working until explicitly revoked.

How To Verify It Worked

03 / VERIFY

Open your site's rendered page source and any downloaded JS bundles and search for the literal string of your API secret - it should return zero matches. If it appears anywhere fetched by the browser, treat that as a live exposure regardless of whether an event has been misused yet.

Send a test event through your server-side endpoint to the Measurement Protocol debug endpoint (/debug/mp/collect) and confirm validationMessages is empty, then check GA4 DebugView (with debug_mode: true on the payload) for the event appearing correctly attributed - confirming the server-side path works end to end without ever exposing the secret to the browser.

Still Not Fixed?

We audit and fix GA4 implementations for a living - if this doesn't resolve it, the next step is usually a full event-by-event audit.

Talk to a GA4 Consultant