GA4 Measurement Protocol in n8n: sending server-side conversions
Published 11 August 2026 · 5 min read
A visitor blocking third-party scripts, a private browsing window, or simply a _ga cookie cleared before conversion — these are all purchases, sign-ups, or leads that gtag.js will never see reach Google Analytics 4. Garimella, Kostakis, and Mathioudakis measured this in a study presented at ACM Web Science (2017): across a large sample of news sites, popular ad blockers also prevent analytics scripts from loading, not just ads themselves. GA4's Measurement Protocol solves the same problem as Meta's Conversions API covered in our Meta server-side tracking guide: send the event directly from your server, without depending on what the browser lets through. This guide builds that pipeline in n8n, from the conversion webhook to the API call.
The Measurement Protocol in one minute
Unlike the Data API (which reads already-collected reports — see our automated GA4 report guide), the Measurement Protocol writes new events directly into a GA4 property. It's a plain HTTP endpoint: a POST to https://www.google-analytics.com/mp/collect?measurement_id=G-XXXXXXX&api_secret=YOUR_SECRET, with a JSON body describing one or more events.
The measurement_id is found in GA4 under Admin → Data Streams → your web stream (format G-XXXXXXX). The api_secret is generated on the same screen, under Measurement Protocol API secrets → Create. These two values are enough to authenticate the call — no complex Google Cloud credential, unlike the read-side Data API.
Step 1 — Store the secret as an n8n credential
The secret should never be hardcoded into an HTTP Request node: create a generic credential (Header Auth or an encrypted environment variable) to hold it, with the same discipline covered in our securing credentials and API keys guide. The measurement_id itself isn't secret and can travel as a plain URL parameter on the node.
Step 2 — Grab the client_id client-side
The Measurement Protocol requires a client_id identifying the visitor — without it, GA4 rejects the event. This isn't an identifier to invent server-side: it already exists in the _ga cookie set by gtag.js, formatted as GA1.2.123456789.1699999999. The client_id is the second-to-last segment (123456789.1699999999): a small front-end script reads it and passes it along with the form or call that triggers the conversion, exactly like Meta's fbc/fbp must be captured client-side before reaching n8n.
function getGa4ClientId() {
const match = document.cookie.match(/_ga=GA\d\.\d\.(\d+\.\d+)/);
return match ? match[1] : null;
}
Step 3 — Trigger the workflow on the actual conversion
As with any server-side tracking call, the trigger should be the confirmed business action, not its mere creation: a validated order webhook (see our Shopify connection guide and our e-commerce order automation article), or a confirmed Stripe payment webhook as detailed in our Stripe webhooks and payment follow-up article. A webhook that fires twice for the same transaction needs to be filtered upstream, following the same principle as webhook idempotence.
Step 4 — Build the event payload
A Set node assembles the JSON body expected by the endpoint. For an e-commerce purchase:
{
"client_id": "{{ $json.ga_client_id }}",
"events": [{
"name": "purchase",
"params": {
"transaction_id": "{{ $json.order_id }}",
"value": {{ $json.order_total }},
"currency": "EUR",
"items": [
{
"item_id": "{{ $json.product_sku }}",
"item_name": "{{ $json.product_name }}",
"price": {{ $json.product_price }},
"quantity": {{ $json.quantity }}
}
]
}
}]
}
The transaction_id field plays the same role as the event_id in Meta's Conversions API: it's what lets GA4 match this server call with the purchase event gtag.js may have already sent client-side, and count the conversion only once. If available, a user_id (an internal customer account identifier, not raw personal data) can be added at the payload's root level to sharpen cross-device matching.
Step 5 — Call the API and validate with the debug endpoint
An HTTP Request node does a POST of this payload to https://www.google-analytics.com/mp/collect?measurement_id=...&api_secret=.... Before going live, temporarily point the same node at https://www.google-analytics.com/mp/debug/mp/collect: this endpoint accepts the identical payload but returns a detailed validation report (missing field, wrong type, unrecognized event) instead of recording it — unlike the production endpoint, which silently accepts a malformed payload without ever surfacing the event in the GA4 interface, making after-the-fact debugging very difficult.
Respecting consent, not just bypassing blockers
The Measurement Protocol bypasses technical browser blockers, not GDPR obligations: if a visitor declined audience-measurement cookies, the server call simply must not fire. The most reliable approach is to store the consent status at the moment it's captured (in the same application table as the order or lead) and gate the HTTP Request node on it with an upstream IF — the same reflex covered in our GDPR data processing register with n8n guide, applied here to ad tracking rather than to the register itself.
For teams that need to justify what was sent to Google and when, logging every call to a dedicated Supabase table (event, client_id, transaction_id, consent status, API response) reuses exactly the audit-trail pattern from the "Audit logging in Supabase" workflow in the Compliance & Audit Pack — a sub-workflow called via Execute Sub-workflow after each send, rather than a Supabase node duplicated across every tracking automation.
Best practices and common pitfalls
- Always read the
client_idfrom the existing_gacookie, never generate a new one server-side: a made-upclient_idcreates a phantom visitor that doesn't join any session GA4 already knows. - Reuse the same
transaction_idbetween gtag.js and the Measurement Protocol to avoid double-counting every purchase in reports. - Always validate via
/debug/mp/collectbefore publishing: the production endpoint shows no explicit error, it simply ignores malformed events. - Gate sending on actual consent, stored at the moment of conversion — never assumed.
- Never send identifiable personal data (email, name) in
params: the Measurement Protocol has no dedicated hashing mechanism like Meta's Conversions API, and Google explicitly forbids sending PII in event parameters.
Summary
A Measurement Protocol pipeline in n8n rests on five building blocks: a client_id read from the existing _ga cookie and passed to the server, a trigger on the actually confirmed conversion, a structured JSON payload with a transaction_id shared with gtag.js, a POST call to the GA4 endpoint pre-validated via /debug/mp/collect, and a consent check before any send. If your n8n instance already handles order or payment webhooks for your e-commerce automations, this server-side tracking bolts on as one more node in an existing workflow, rather than a new integration built from scratch.
FAQ
Frequently asked questions
Should you disable gtag.js once the Measurement Protocol is in place?
No. Just like the Meta Pixel and its Conversions API, gtag.js and the Measurement Protocol are designed to coexist: gtag.js captures whatever the browser lets through, the Measurement Protocol captures the same conversion server-side. Reusing the same client_id (and the same transaction_id for a purchase) on both sides lets GA4 match the two events instead of counting them twice.
Where does the client_id sent to the Measurement Protocol come from?
It already exists in the visitor's browser, inside the _ga cookie set by gtag.js, in the form GA1.2.123456789.1699999999. The client_id is the second-to-last segment (123456789.1699999999): read it client-side and pass it to n8n along with the conversion event — don't regenerate one server-side.
Does the Measurement Protocol bypass GDPR consent?
No, and it shouldn't be used for that purpose. The server call bypasses technical blockers (extensions, ITP), not legal obligations: if the visitor declined audience-measurement cookies, the call must simply not fire, exactly as the gtag.js tag wouldn't fire client-side. The reliable approach is to store the consent status at the moment it's captured and gate the n8n node on it.
Can you test an event before sending it in production?
Yes, using the /debug/mp/collect endpoint: it accepts the exact same payload as the production endpoint but returns a detailed validation report (missing fields, wrong types) instead of recording the event in GA4 reports. Switch the URL to /mp/collect once validated.
Bundle FlowKit Complet
€269