GA4 Measurement Protocol payload builder
Build a GA4 Measurement Protocol request for a web stream and check it against Google's documented rules before you send it. Rules come only from Google's docs (fetched Oct 19, 2026): GA4 MP: Reference · GA4 MP: Send events (limitations) · GA4 MP: Validate events · GA4 MP: Recommended events.
/mp/collect or even /debug/mp/collect, it has no field for your API secret (the curl uses $GA4_API_SECRET), and no analytics run here. Honest status: the server-track-lab API forwards to the Meta Conversions API (live with your own pixel and token), Google via the Data Manager API (validate-only preview) and LinkedIn (dry-run). It does not forward to the GA4 Measurement Protocol.Request
api_secret: not asked for and never sent from this page. The curl reads it from $GA4_API_SECRET; create it under Admin → Data Streams → your stream → Measurement Protocol → Create, and keep it on your server.
Events
Checks
JSON body
1. Test first: /debug/mp/collect
Same body, different URL. The validation server returns validationMessages (fieldPath, description, validationCode such as NAME_INVALID or VALUE_OUT_OF_BOUNDS); an empty array means no problems found. Events sent here never show up in reports. It does not check your api_secret. Set validation_behavior to ENFORCE_RECOMMENDATIONS while testing for the most feedback.
2. Then send: /mp/collect
Run this from your server, not from browser code (that would expose the api_secret). A 2xx response only means the request was received, not that the event was valid or processed.
GA4 Measurement Protocol vs gtag.js
gtag.js (or Google Tag Manager) runs in the visitor's browser. It sets the _ga cookie, starts sessions, records page views and engagement, and sends events as they happen. The Measurement Protocol is the opposite end: a plain HTTPS POST from your server to https://www.google-analytics.com/mp/collect with a measurement_id and api_secret in the query string and a JSON body. Nothing runs in the browser, so ad blockers, closed tabs and consent-banner timing don't affect delivery. The trade-off is that nothing is automatic either. There's no cookie, no session and no page context unless you send them.
That's why Google positions the Measurement Protocol as a way to augment tag-collected data, not replace it. Web events sent this way should belong to a user the tag has already seen. The request is keyed by client_id, the same ID the tag stores in the browser, and ideally carries the browser's session_id too.
When to use it
- Events that happen off the page. A trial converts to paid in Stripe, a sales rep marks a deal won in the CRM, a renewal goes through, or a refund is issued. None of these has a browser tab open when it happens.
- Events the browser can't be trusted to send. Purchase confirmations where the thank-you page often isn't reached, or form submissions processed by your backend.
- Offline and delayed steps in a B2B funnel:
generate_leadfrom a form handler, thenqualify_leadorclose_convert_leadfrom your CRM, attributed to the original visit.
Don't use it to rebuild full browser tracking from the server. Page views, engagement time and session starts are what the tag is for. Note too that Google's Measurement Protocol docs now say it has reached "a mature, finalized product state" with no plans for deprecation, and recommend the Data Manager API for future-proof server-to-server integrations.
Reuse the client_id, or lose the attribution
The most common Measurement Protocol mistake is inventing a client_id on the server. GA4 then sees a brand-new user with no source, medium or campaign, and your conversion lands under "(not set)" or "Direct". Instead, read the ID in the browser while the user is on your site: gtag('get', 'G-XXXXXXXXXX', 'client_id', cb) (or the session with 'session_id'), or the Analytics Client ID variable in Tag Manager. Send it to your backend with the form or checkout, store it next to the lead or order, and reuse it when the later event fires. Google recommends the two-number form, like 1234567890.1712345678. That's the last two parts of a _ga cookie value like GA1.1.1234567890.1712345678, and the converter above extracts it. Add user_id if the person is logged in, so GA4 can join their devices. Use your internal account ID, never an email address.
Common pitfalls
- Trusting the 2xx.
/mp/collectreturns 2xx even for malformed events. Validate against/debug/mp/collectfirst. - Milliseconds instead of microseconds.
timestamp_microsis microseconds, soDate.now() * 1000. Events can be backdated up to 72 hours. Older ones are clamped to 72 hours ago, or rejected with ENFORCE_RECOMMENDATIONS. To join with tag data reliably, deliver within 48 hours. - No session_id or engagement_time_msec. Google notes that events without them may not count correctly toward engagement metrics or Realtime user counts. Pass the browser's session_id; a small engagement_time_msec (Google's examples use 100) is common.
- Reserved names.
session_start,first_visit,user_engagementand others are reserved. So are parameters starting with_,firebase_,ga_,google_orgtag.. - value without currency, or value sent as a string. Revenue won't compute. For
purchase, set value to the sum of price × quantity, excluding shipping and tax, and always send a uniquetransaction_idto avoid duplicates. - Limits silently dropping data. Up to 25 events per request, 25 parameters per event, names up to 40 characters, values up to 100 characters (500 on 360). With default validation, parameters over a limit are ignored rather than rejected.
- Exposing the api_secret. Never call the Measurement Protocol from browser JavaScript. Keep the secret on your server and rotate it regularly, as Google suggests.
- Consent. If you omit
consent, GA4 uses the consent settings from the client's online interactions. Sendad_user_dataandad_personalizationexplicitly when you know them.non_personalized_adsis deprecated.
Related: Google Enhanced Conversions builder · Meta CAPI payload builder · LinkedIn Conversions payload builder · event_id generator · Form & CRM conversions guide · Stripe webhooks guide
POST /api/v1/track call normalizes, hashes and dedupes your conversions, then forwards them to the Meta Conversions API (live with your own pixel and token). Google gets a Data Manager API validate-only preview and LinkedIn a dry-run preview. It does not send to GA4. Free for 1,000 events a month. Get a free API key · API docsFAQ
Does this page send my event to Google Analytics?
No. Everything runs in your browser. The page never calls /mp/collect or /debug/mp/collect, has no field for your API secret, and loads no analytics. Copy the curl and run it yourself; it reads the secret from a $GA4_API_SECRET environment variable.
Does server-track-lab forward events to GA4?
No. server-track-lab forwards to the Meta Conversions API (live with your own pixel and token), builds a Google Data Manager API validate-only preview, and builds a LinkedIn dry-run preview. It does not send to the GA4 Measurement Protocol. This builder is a standalone tool.
How far back can timestamp_micros go?
Google's docs say events and user properties can be backdated up to 72 hours. With the default RELAXED validation, older timestamps are overridden to 72 hours ago; with ENFORCE_RECOMMENDATIONS the event is rejected. Events meant to join gtag.js events should arrive within 48 hours of the original client-side event.
Why does /mp/collect return 2xx for a broken event?
The Measurement Protocol returns a 2xx status if the HTTP request is received, even when the payload is malformed or not processed. Send the same body to /debug/mp/collect first: it returns validationMessages, and its events never appear in reports. Note that the validation server doesn't check your api_secret.
What client_id should I use?
The one the user's browser already has. Google recommends two positive numbers joined by a period, which you get with gtag('get', 'G-XXXXXXXXXX', 'client_id', callback), or the Analytics Client ID variable in Google Tag Manager. A new random ID creates a new, unattributed user.
What are the main limits?
Per Google's docs: up to 25 events per request, 25 parameters per event, 25 user properties per request, event and parameter names up to 40 characters (starting with a letter; letters, numbers and underscores only), parameter values up to 100 characters (500 on GA4 360), user property names up to 24 and values up to 36 characters, 10 custom parameters per item, and a POST body under 130kB.