GA4 Measurement Protocol: Sending Server-Side Conversions to GA4
A marketer's guide to the GA4 Measurement Protocol: api_secret setup, client_id and session_id stitching, validation, limits, transaction_id deduplication, key events vs Google Ads conversions, and consent.
In short
The GA4 Measurement Protocol is an HTTP API that lets your server send events to Google Analytics 4 by posting JSON to Google's collect endpoint with a measurement ID and an API secret. It is built to supplement the browser tag, not replace it, and it is not a Google Ads conversion upload. Used correctly, it recovers conversions the browser never reported.
Most tutorials stop at the JSON payload. This guide covers the parts that decide whether the data is usable: where client_id and session_id come from, why standard reports lag Realtime by a day or two, how to stop double-counting purchases, and what the key-events rename means when you link GA4 to Google Ads. If you are new to the wider topic, start with our server-side conversion tracking guide and come back here for the GA4 specifics.
What is the GA4 Measurement Protocol, and what is it not?
The Measurement Protocol is a server-to-server channel for GA4 events. Google's documentation is explicit that it augments automatic collection through gtag, Tag Manager, and Firebase rather than replacing it, and that a property fed only by the Measurement Protocol gets partial reporting. In practice that means the tag stays on the page and the server fills in what the tag misses: offline steps, back-end confirmations, and purchases blocked by ad blockers or Safari restrictions.
It is equally important to know what it is not. The Measurement Protocol does not upload conversions to Google Ads, it is not the mechanism behind Google Ads Enhanced Conversions, and it does not trigger GA4's event-modification or event-creation rules. Events with reserved names are rejected. Here is the split.
| Question | Measurement Protocol answer |
|---|---|
| Sends events to a GA4 property from a server? | Yes. POST JSON to /mp/collect with measurement_id and api_secret. |
| Replaces gtag or GTM on the site? | No. Google designs it to augment tag-based collection; a server-only property gets partial reporting. |
| Creates new users or sessions with attribution? | No. Events attach to an existing client_id and session; without a browser-derived client_id the event lands in a new, source-less session. |
| Uploads conversions to Google Ads? | No. Google Ads conversions come from the Google tag, Tag Manager, the Google Ads API, or importing GA4 key events into a linked Ads account. |
| Counts as Google Ads Enhanced Conversions? | No. Google lists the Google tag, Tag Manager, and the Google Ads API as the enhanced-conversions methods, and states that conversions imported from Analytics are not supported for enhanced conversions. |
| Runs GA4 event-modification rules on the incoming event? | No. Rules for generating or renaming events are not triggered by Measurement Protocol events. |
How do you send conversions to GA4 from a server?
You need four things: a measurement ID, an API secret, the browser's client_id and session_id for the person who converted, and a JSON body that names the event. The steps below are for a web data stream (gtag or GTM). App streams use a Firebase app ID and app_instance_id instead, but the logic is the same.
- 1In GA4, open Admin, then Data streams under Data collection and modification, and select your web stream. Copy the Measurement ID (it starts with G-).
- 2On the same stream, open Measurement Protocol API secrets and click Create. Store the secret server-side only. Google warns that exposing it in client code lets anyone send spam data into your property.
- 3On the page, capture the visitor's identifiers with gtag('get', 'G-XXXXXXXXXX', 'client_id', cb) and gtag('get', 'G-XXXXXXXXXX', 'session_id', cb). Persist both against the order, lead, or user record so the server can find them later.
- 4When the conversion happens on the back end, build the JSON body: client_id at the top level, and an events array where each event has a name and a params object containing session_id and engagement_time_msec plus the event's own parameters.
- 5POST the body to https://www.google-analytics.com/mp/collect?measurement_id=G-XXXXXXXXXX&api_secret=YOUR_SECRET. Use region1.google-analytics.com if you need EU-based collection.
- 6Before shipping, send the same body to the validation endpoint (the same path with /debug inserted) and fix every validationMessage it returns. Then confirm the event in Realtime and DebugView.
- 7Mark the event as a key event in GA4 Admin if it should count as a conversion, and only then import it into Google Ads if you plan to bid on it.
Anatomy of a Measurement Protocol payload
Every field in the payload does a job in reporting, and leaving one out produces a specific symptom. The table maps each field to what breaks without it.
| Field | Where it goes | What it does | Symptom if missing or wrong |
|---|---|---|---|
| measurement_id | Query string | Routes the request to your web data stream | Event silently goes to the wrong property or nowhere |
| api_secret | Query string | Authenticates the request; case-sensitive | Silently dropped. The validation server does not check api_secret, so a typo passes validation and still fails in production |
| client_id | Body, top level | Ties the event to the browser instance GA4 already knows | Event creates a brand-new user with no acquisition source |
| session_id | events[].params | Ties the event to the live or recent session; must be all digits | Event reports as (not set) / (not set) or a new direct session |
| engagement_time_msec | events[].params | Marks the user as engaged for the session | User does not count toward active users or engaged sessions |
| timestamp_micros | Body or event level | Backdates the event, up to 72 hours | Backdating is limited to 72 hours; ENFORCE_RECOMMENDATIONS rejects older events, so do not rely on RELAXED to process them |
| user_id | Body, top level | Cross-device User-ID; optional | No cross-device stitching; also recommended whenever user_data is sent |
| consent | Body, top level | ad_user_data and ad_personalization, each GRANTED or DENIED | Advertising features may not be able to use the event |
client_id and session_id explained: why server events show as direct or (not set)
Measurement Protocol events inherit attribution from the client_id and session_id they carry. GA4 does not look at the request's referrer or UTMs; it joins the event to the session the browser tag already opened, and that session holds the source, medium, and campaign. Send an invented client_id and GA4 creates a fresh user with no acquisition data. Send a real client_id but no session_id and Google's own help page says to expect (not set) / (not set) for session-scoped dimensions until you add a valid session_id from the client-side event.
Two practical rules follow. First, the client_id must be read from the browser: gtag('get') returns it, and it is the same value stored in the _ga cookie. Capture it at the moment of the form submit or checkout and store it with the record. Second, the session_id must be the current numeric session identifier, not a string you made up, and Google's reference requires it to match ^\d+$. Creating a new session_id creates a new session, which is exactly how sessions get inflated when a server sends its own value.
There is a subtler trap if you already run a server-side Google Tag Manager container. As of September 2026, Stape's troubleshooting guidance notes that server-side GA4 tagging derives client_id from the FPID cookie while the web tag uses _ga, so Measurement Protocol events built from the wrong cookie fail to join the session and surface as unassigned or direct. If your reports show a wall of direct purchases, check which cookie the server read before anything else. Our UTM parameters article covers the browser side of the same problem: attribution is only as good as the identifiers that carried it.
Does Measurement Protocol data show in Realtime? How do you validate it?
Yes. Valid Measurement Protocol events appear in the Realtime report and, with debug_mode set, in DebugView within seconds. Standard reports are a different story: Google's data-freshness documentation says processing can take 24 to 48 hours, and reports can change during that window. A server event that is visible in Realtime and absent from the Events report an hour later is normal, not broken.
- 1Send the exact production body to the validation endpoint, https://www.google-analytics.com/debug/mp/collect, with the same query parameters. An empty validationMessages array means the structure is valid. Events sent here never reach reports.
- 2Re-check api_secret and measurement_id by hand. The validation server does not verify either, so they are the most common cause of a request that validates but never lands.
- 3Add debug_mode: true to the event params during testing and watch DebugView for the event, its parameters, and the user properties attached to it.
- 4Open Realtime and confirm the event name, event count, and that the user appears under active users. If the user does not appear, engagement_time_msec is probably missing.
- 5Wait 24 to 48 hours, then confirm the event in the standard Events report and in Explorations with session source and medium. Only now can you judge attribution.
What are the GA4 Measurement Protocol limits?
The limits are small enough to hit in ordinary e-commerce payloads. Batch orders at 25 events per request at most, keep bodies under 130kB, and trim parameter values to 100 characters on a standard property. All of the figures below come from Google's Measurement Protocol reference and event reference.
| Limit | Value | Practical note |
|---|---|---|
| Events per request | 25 | Batch offline conversions in chunks of 25 |
| POST body size | Under 130kB | Large items arrays on multi-line orders can exceed this |
| Event name length | 40 characters, alphanumeric and underscores | Reserved names are rejected |
| Parameters per event | 25 | Excludes items array contents |
| Parameter name length | 40 characters | Same rule as event names |
| Parameter value length | 100 characters (500 on GA 360) | Long product names get truncated or rejected |
| User properties per request | 25 | Names 24 characters, values 36 characters |
| Custom parameters per item | 27 | Per Google's purchase event reference |
| Backdating window | 72 hours via timestamp_micros | ENFORCE_RECOMMENDATIONS rejects older events; do not rely on RELAXED to process them |
| Session ID format | Digits only | Must match ^\d+$ |
How do you avoid duplicate purchases between gtag and the Measurement Protocol?
Send the same transaction_id from both the browser and the server, and pick one of them as the source of truth for revenue. Google's event reference states that transaction_id exists to help you avoid duplicate purchase events, so a browser purchase and a server purchase with matching IDs are treated as one. The Measurement Protocol does not have Meta-style event_id deduplication, so transaction_id is the only handle you get, and it only applies to purchase.
For non-purchase conversions such as generate_lead or sign_up, there is no built-in deduplication. The practical rule is one source per event: either the browser sends it or the server sends it, never both. The table lays out the three sane configurations.
| Strategy | Browser sends | Server sends | When to use it |
|---|---|---|---|
| Browser primary, server backstop | purchase with transaction_id | purchase with the same transaction_id | Most sites. GA4 collapses the duplicate; the server copy fills gaps left by blockers |
| Server primary | page_view, add_to_cart, begin_checkout | purchase only, with client_id and session_id from checkout | Sites where the thank-you page is unreliable or the order completes off-site |
| Split by event | All engagement events | Back-office events only (refund, qualified_lead) | Events the browser never sees; no dedup needed because there is no browser copy |
Whichever you choose, do not mix strategies per campaign or per region. Half-duplicated data is harder to diagnose than fully duplicated data because the totals look plausible. If you are unsure which pixels and tags currently fire on your checkout, run the free Tracking Pixel Inspector on the confirmation page before deciding who owns the purchase event.
Key events vs Google Ads conversions: what the rename means for server-side data
In GA4, a conversion is now called a key event, and the word conversion is reserved for Google Ads. Google explains the rename as a fix for a long-standing discrepancy: events marked as conversions in Analytics were measured differently from conversions in Google Ads, so the two never matched. Marking a Measurement Protocol event as a key event in GA4 changes nothing in Google Ads by itself. You create a Google Ads conversion from that key event in a linked Ads account, and only then can you bid on it, report on it in Ads, or build remarketing audiences from it.
Two implications matter for a server-side setup. First, a key event created from Measurement Protocol data can be imported into Google Ads, but its ad-click attribution depends entirely on the session it joined, which is why client_id and session_id matter so much. Second, this path is not Enhanced Conversions. Google's Enhanced Conversions for web documentation lists the Google tag, Google Tag Manager, and the Google Ads API as the supported methods and notes that conversions imported from Analytics do not support enhanced conversions. If your goal is hashed-identifier matching inside Google Ads, the Measurement Protocol is the wrong tool, and EndFrame does not offer Google Ads Enhanced Conversions or offline conversion import either.
Consent and user-provided data on server-sent events
Server-sent events carry no consent signal unless you add one. The Measurement Protocol reference exposes a consent object with two fields, ad_user_data and ad_personalization, each set to GRANTED or DENIED. The older non_personalized_ads flag is deprecated in favor of ad_personalization. Your server should read the visitor's stored consent state and mirror it on every request; a consent management platform is your responsibility here, because neither the Measurement Protocol nor EndFrame provides one.
User-provided data (email, phone, name, address) can be sent in a user_data object, but the rules are strict. Unlike gtag, which hashes automatically, the Measurement Protocol requires you to normalize and SHA-256 hash the values yourself and hex-encode the result. Google's normalization rules include trimming whitespace, lowercasing, formatting phone numbers to E.164, and removing dots before the domain in gmail.com and googlemail.com addresses. Google allows up to three email or phone values and two address values per request to improve match likelihood, and recommends sending user_id alongside user_data. Send only what your privacy notice and consent state permit.
How EndFrame's GA4 destination uses the Measurement Protocol
EndFrame forwards conversions to GA4 as Measurement Protocol events without you writing the integration. Its lightweight snippet collects the first-party visit and action stream on your site, you define conversion goals by event match or URL match, and the GA4 destination is a toggle in the dashboard. When a goal fires, EndFrame sends the conversion to GA4 as a Measurement Protocol event, reusing the visitor's GA client ID when the snippet captured it so the event joins the right session, alongside its Meta Conversions API, TikTok Events API, and custom webhook destinations. There is no separate server-side GTM container to host.
Two honest caveats. EndFrame's browser-to-server event-ID deduplication is its own mechanism across destinations; inside GA4, purchase deduplication still runs on transaction_id, so keep your order ID consistent on both sides. And everything in this article about client_id, session_id, and the 24-to-48-hour reporting delay applies to any Measurement Protocol sender, EndFrame included. The advantage is that the raw visit-to-action-to-conversion chain is stored in EndFrame, so when GA4's numbers lag or land as (not set), you can audit what was actually sent and when. Shopify-specific tools such as Analyzify also send purchase events server-side to GA4; as of September 2026, its documentation describes that capability as purchase-only and tied to its yearly plan, which is a reasonable fit for stores that need nothing beyond the purchase event. If you want to see what your site loads before choosing a path, the free GA4 tracking audit reports which tags fire and estimates what iOS and ad blockers are hiding.
The bottom line
The GA4 Measurement Protocol is a reliable way to get server-side conversions into GA4, provided you treat it as a supplement to the browser tag rather than a replacement. Read client_id and session_id from the browser, send engagement_time_msec, validate against the debug endpoint, expect standard reports to lag by up to 48 hours, and deduplicate purchases with transaction_id. Keep in mind that a GA4 key event is not a Google Ads conversion until you import it, and that none of this is Enhanced Conversions. Get those fundamentals right and the server becomes the backstop your reporting has been missing.
Frequently asked questions
Can the Measurement Protocol send purchases from a CRM or order system?
Yes, as long as the record carries the browser's client_id and session_id captured at checkout. Without them GA4 creates a new user with no acquisition source. You also need to send within 72 hours of the event using timestamp_micros, include transaction_id so a browser purchase for the same order is not double-counted, and hash any user-provided data with SHA-256.
Will Measurement Protocol events show up in GA4 Realtime?
Yes. Valid events appear in the Realtime report within seconds and in DebugView if you include debug_mode in the event parameters. Standard reports and Explorations are different: Google states processing can take 24 to 48 hours, so an event visible in Realtime but missing from the Events report the same day is expected behavior, not a failure.
Why are my sessions inflated after adding Measurement Protocol events?
Because your server is inventing session IDs. Google's reference notes that creating a new session_id creates a new session, so every server event with a made-up value opens a fresh session with no source. Read the numeric session_id from the browser with gtag('get') at the time of the action, store it with the record, and reuse it on the server request.
Is the GA4 Measurement Protocol the same as Google Ads Enhanced Conversions?
No. Enhanced Conversions is a Google Ads feature implemented through the Google tag, Google Tag Manager, or the Google Ads API, and Google states that conversions imported from Analytics are not supported for it. The Measurement Protocol feeds GA4 only. A GA4 key event can be imported into Google Ads as a conversion, but that import does not become an enhanced conversion.
Do I still need gtag or GTM if I send everything through the Measurement Protocol?
Yes. Google designs the Measurement Protocol to augment tag-based collection, and warns that a property fed only by server events gets partial reporting. The browser tag creates the user, opens the session, and records acquisition source. Server events then join that session. Remove the tag and your server-side conversions lose their attribution context.
Sources
- 1.Measurement Protocol (Google Analytics 4) overview - Google for Developers
- 2.Send Measurement Protocol events to Google Analytics - Google for Developers
- 3.Measurement Protocol reference (limits, consent, user_id, validation_behavior) - Google for Developers
- 4.Measurement Protocol events reference (purchase, transaction_id) - Google for Developers
- 5.Validating Measurement Protocol events - Google for Developers
- 6.Send user-provided data using Measurement Protocol - Google for Developers
- 7.gtag.js API reference (get command: client_id, session_id) - Google for Developers
- 8.[GA4] Measurement Protocol - Analytics Help
- 9.[GA4] Data freshness - Analytics Help
- 10.[GA4] Key events - Analytics Help
- 11.Conversion tracking is now renamed to key events - Google Merchant Center Help
- 12.About enhanced conversions for web - Google Ads Help
- 13.Fix GA4 Unassigned and Not Set Traffic Sources - Stape
- 14.Server-Side Conversion Tracking for GA4 Purchase Event - Analyzify Docs
Try EndFrame
Recover the conversions iOS and ad blockers hide.
EndFrame collects events first-party and forwards conversions server-side to Meta, GA4, TikTok, and webhooks with event-ID deduplication — then shows the result in real time, with the visit-to-conversion path you can audit.
No credit card required