The endpoint and the EU variant
POST https://www.google-analytics.com/mp/collect?measurement_id=G-XXXX&api_secret=… is the documented collection endpoint. Google also documents https://region1.google-analytics.com/mp/collect for EU-based traffic; Track uses the region1 host by default so requests originate and terminate in the EU.
The measurement_id is public (it is in your gtag snippet). The api_secret is created under Admin → Data streams → Measurement Protocol API secrets and belongs in the vault.
The body
{
"client_id": "1234567890.1700000000",
"user_id": "u_42",
"timestamp_micros": 1767225600000000,
"non_personalized_ads": false,
"consent": { "ad_user_data": "GRANTED", "ad_personalization": "DENIED" },
"user_data": { "sha256_email_address": ["<hash>"] },
"events": [{ "name": "purchase", "params": { "transaction_id": "A1001", "value": 129.9, "currency": "EUR", "items": [{ "item_id": "SKU-1", "price": 99.9, "quantity": 1 }], "engagement_time_msec": 100 } }]
}Limits from the reference: at most 25 events per request, 25 parameters per event, event names up to 40 characters, and timestamp_micros no older than 72 hours. Names starting with google_, ga_ or firebase_ are reserved.
client_id is the thing people forget
GA4 stitches server events into sessions using client_id, the value stored in the _ga cookie (GA1.1.<random>.<timestamp> → client_id = "<random>.<timestamp>"). Without the real client id, server events form their own orphaned users and inflate user counts.
Track reads the _ga cookie in the browser when analytics consent is granted, sends it with every event as a vendor id and uses it as client_id for Measurement Protocol deliveries. When no _ga value exists (pure server sources), it derives a stable pseudo client id from the anonymous id so events at least group per visitor.
It always says yes — validate on the debug endpoint
The production endpoint returns 2xx for anything syntactically acceptable. The only way to learn about semantic problems (unknown parameter types, reserved names, missing client id) is the debug endpoint /debug/mp/collect, which answers with validationMessages. In test mode Track sends every event to the debug endpoint first and only forwards it when the list is empty, so a broken mapping fails loudly in the wizard rather than silently in production.
What the Measurement Protocol cannot do
- It does not create sessions on its own; page views should come from gtag in the browser.
- It has no geo, device or campaign data unless you send it; most teams send purchases, refunds and backend events only.
- Realtime reports need
engagement_time_msec(any positive value) to count the user as engaged. - Attribution to campaigns still relies on the browser session the
client_idbelongs to.
The pragmatic split: gtag for page views and interactions, Measurement Protocol for the authoritative purchase with transaction_id (GA4 deduplicates purchases by transaction id), refunds and backend events.
Consent fields
Send consent.ad_user_data and consent.ad_personalization derived from the visitor's purposes and set non_personalized_ads: true when marketing consent is missing. Analytics consent is required for the event to be sent at all — the GA4 destination needs the analytics purpose in Track, not marketing.
Checklist
- To do: Measurement ID in the destination, API secret in the vault
- To do:
_gaclient id captured with analytics consent - To do: Purchases carry
transaction_id; browser and server purchases share it - To do: Debug validation passes in test mode
- To do: Events confirmed in DebugView with the expected parameters