Sending some GA4 events client-side and the rest server-side
A hybrid split is real, but not where people think. The transport switch is all-or-nothing — the useful split happens inside the container, not in the browser.
On this page
- The transport switch is all-or-nothing, so the split happens after the request lands
- Some events stop meaning anything the moment they leave the browser
- Four identifiers have to make the crossing, and one of them carries consent
- Where each event should go
- Moving half your events doesn't halve the bill
- How to sequence it
Short answer: split by what each event needs, not by which ones feel important. Events that depend on the browser — the page, the referrer, the scroll, the session itself — stay client-side, because the server never sees any of that unless the browser sends it. Events that need to survive the browser — refunds, lead qualification, subscription renewals, anything that happens after the tab closed — go server-side. But the switch Google gives you doesn't work per event: server_container_url is set on the Google tag and routes everything. The split you actually build happens inside the server container, deciding which destinations each event reaches, plus a second entry path for things your backend raises on its own.
The transport switch is all-or-nothing, so the split happens after the request lands
Most hybrid plans die here. The mental model is a dial per event: purchase goes server-side, scroll stays in the page. That dial doesn't exist in the documented setup.
Routing to a tagging server is a single configuration parameter. Google's Tag Manager help gives one instruction for it: "To send all events to a Tag Manager server container instead of Google Analytics", add server_container_url to the Google tag's configuration settings (Google Tag Manager Help, checked 11 September 2026). Its scope is fixed by the same rule that governs every config parameter — they "influence how your Google tag behaves and can only be set on the Google tag level" (Google, Send data to server-side Tag Manager, updated 30 July 2026). Event-level settings are for parameters, not for transport.
So one Google tag means one destination for its hits. What varies is what happens next. A server container receives requests, and a client — the adapter that claims a request and turns it into events — decides how to read them; the pre-installed Google Analytics client handles gtag traffic, and a Measurement Protocol client handles payloads from anywhere else (Google, An introduction to server-side tagging, updated 30 July 2026). Once an event exists inside the container, triggers fire tags per event, exactly as they do in a web container.
That gives you three splits that are real, in descending order of how often they're the right answer:
- Split by destination, inside the container. Every browser hit goes to the server; the container decides, per event, which vendors receive it.
add_to_cartgoes to GA4 only,purchasegoes to GA4 and Ads and the CRM. This is per-event, it's configuration rather than code, and it's the one people are usually describing when they say hybrid. - Split by origin. Browser events enter through the Google Analytics client. Backend events — a refund raised by your order system, a lead your sales team marked qualified — enter through the Measurement Protocol client on the same container. Two doors, one set of rules.
- Split by tag. A second Google tag with its own configuration and its own destination. Structurally possible because the setting is per-tag, but you now maintain two consent wirings and two sets of identifiers, and the failure mode is silent divergence between them.
Some events stop meaning anything the moment they leave the browser
Three GA4 event names cannot be sent from your server at all. first_visit, session_start and user_engagement are on the Measurement Protocol's reserved list, along with first_open and the app-only names (Google, Measurement Protocol reference, updated 10 September 2026). A journey that exists only on your server never starts a session. It attaches to whatever session the identifier already had, or to nothing.
Geography and device are the quieter problem. When a Measurement Protocol event carries no user_location, ip_override, device or user_agent, Google Analytics derives both by joining to your tagging events on client_id — so a server event inherits the location and hardware of the last browser hit, not of the request you actually made. If that join finds nothing, device category "defaults to desktop for Web streams". A refund for a customer who bought on a phone can land in your reports as a desktop event, and nothing in the interface will tell you.
The server container's own Measurement Protocol client is narrower still. Google states it plainly: the client "doesn't support all the features of the Measurement Protocol endpoint, like deriving geographic and device information from tagging events" (Google, updated 30 July 2026).
Two more, both easy to discover late. Event rename and generate rules configured in the Google Analytics interface "aren't triggered by events sent with the Measurement Protocol" — your application has to implement that logic itself. And Google's own framing of the protocol is that it exists "to augment existing events collected using gtag, GTM, or Firebase"; sent on its own, "only partial reporting may be available" (Google, Measurement Protocol, updated 8 June 2026). A hybrid split is supported. A migration of your whole stream to the protocol is not.
Everything that depends on the page belongs on the client side for the same structural reason: the URL, the referrer, the title, how far someone scrolled, what was on screen when they clicked. Your server sees only what the browser chose to put in the request.
Four identifiers have to make the crossing, and one of them carries consent
A split holds together on identifiers or it doesn't hold together at all.
client_id is required in every Measurement Protocol payload. It lives in the _ga cookie, which Google describes as the cookie "used to distinguish users", and Tag Manager exposes it as the Analytics Client ID built-in variable. Read it in the browser and store it against the record your backend will later act on — you cannot reconstruct it afterwards.
session_id is the one people skip. It lives in _ga_<container-id>, the cookie that persists session state, and it's what binds a server event to the session it belongs to rather than to the user's latest activity. It also has a deadline: to make an event reflect the geography and device of a specific session, Google requires session_id on the event and delivery "within 24 hours of the start of the session". Send the refund three days later and it can only inherit the most recent context.
user_id is the only identifier that joins across devices — Google's remarketing guidance is explicit that cross-device requires it. And engagement_time_msec is what makes an event count as engagement; without it your server events exist but contribute nothing to engaged sessions.
Then the part that matters more than the reporting. If a Measurement Protocol payload omits the consent object, "Google Analytics uses the consent settings from corresponding online interactions for the client". That inheritance runs on client_id. Carry the wrong one, or lose it, and you have not merely mis-attributed a session — you have applied somebody else's consent state to a real person's data. Changing how data travels never changes whether you were allowed to collect it, which is the same rule that applies to what server-side tagging actually recovers and to Google Tag Gateway.
Build in a check, because the protocol won't complain. It "returns a 2xx status code if the HTTP request is received" and "doesn't return an error code if the payload is malformed, or if the data is incorrect or not processed". Every mistake in this section fails quietly.
Where each event should go
| Event | Where it belongs | Why |
|---|---|---|
page_view, scroll, click, search and video events | Client | The context exists only in the page |
session_start, first_visit, user_engagement | Client, by force | Reserved names — the protocol rejects them |
add_to_cart, begin_checkout | Client, forwarded by the container | Needs browser context; destinations chosen server-side |
purchase | Either, forwarded by the container | Duplicate it and you'll double-count; pick one origin |
| Refunds, renewals, lead qualification, offline conversions | Server | The browser is gone by the time they happen |
The one row worth arguing about is purchase. Sending it from both the confirmation page and the order system is the most common way people break a hybrid setup, because the two copies carry different identifiers and neither system deduplicates them for you.
Moving half your events doesn't halve the bill
The cost question has a counter-intuitive answer: what you pay for a tagging server is mostly unrelated to how many event types you route through it.
Google's Cloud Run configuration for server-side tagging uses the CPU always allocated model, and under instance-based billing you are charged "for the entire lifetime any Cloud Run container instances" rather than per request (Google Cloud, Cloud Run pricing, checked 11 September 2026). Your floor is minimum instances multiplied by wall-clock time. Add a second event type to a server that is already running and the marginal cost is egress and a log line — and Google documents how to switch request logging off, which is the charge most likely to surprise you at volume.
Two constraints cap the other direction. The provisioned servers "should have at most 1 vCPU", because "additional vCPUs are not utilized and affect autoscaling negatively", and you "must deploy exactly 1 preview server" with no autoscaling beyond one instance (Google, manual setup guide, updated 8 May 2026). You cannot buy your way out with a bigger box.
So the decision isn't how many events to move. It's whether you want a tagging server at all — which is a question about durability, first-party context and routing, and which we costed against managed hosting in our comparison of the three ways to run one.
Before you plan a split, find out what your current setup actually dispatches and from where. Our Google Tag Gateway checker reports which endpoint your Google tag is talking to and whether it's first-party — that tells you which of the three splits above you're already halfway into.
How to sequence it
Start with the destination split, because it's the one that costs nothing to reverse. Route everything to the container, keep every existing tag firing exactly as it did, and confirm your reports don't move. Only then add vendor tags on the server side and retire their browser equivalents one at a time.
Add the origin split second, and only for events the browser genuinely cannot raise. Capture client_id and session_id at the point of purchase, store them with the order, and pass them back when the refund or renewal happens. Send one event type through the protocol first and check it in reports before you add the next.
Leave the tag split alone unless you have a reason you can write down in a sentence. Two Google tags means two consent configurations, and the day they drift is the day your numbers stop reconciling with no error anywhere to explain it.
If you'd rather have that built and verified than debugged later — the container wiring, the identifiers, the consent state carried across the boundary rather than dropped at it — that's what we do.
Sources
- 1.Google — Set up your Google tag in Google Tag Manager: 'To send all events to a Tag Manager server container instead of Google Analytics… server_container_url' · Checked 2026-09-11
- 2.Google — Send data to server-side Tag Manager: 'Configuration parameters influence how your Google tag behaves and can only be set on the Google tag level'; the server container's Measurement Protocol client 'doesn't support all the features of the Measurement Protocol endpoint, like deriving geographic and device information from tagging events' · Updated 30 July 2026, checked 2026-09-11
- 3.Google — An introduction to server-side tagging: clients claim requests and turn them into events; server_container_url config example · Updated 30 July 2026, checked 2026-09-11
- 4.Google — Measurement Protocol: 'only partial reporting may be available'; geographic and device information joined from tagging using client_id; rename/generate rules aren't triggered by Measurement Protocol events · Updated 8 June 2026, checked 2026-09-11
- 5.Google — Measurement Protocol reference: client_id required; session_id and engagement_time_msec; reserved event names include first_visit, session_start and user_engagement; device defaults; 2xx returned even for malformed payloads · Updated 10 September 2026, checked 2026-09-11
- 6.Google — [GA4] Cookie usage on websites: _ga distinguishes users, _ga_<container-id> persists session state; browsers cap first-party cookie lifespan at 400 days in Chrome and 7 days in Safari · Checked 2026-09-11
- 7.Google — Set up server-side tagging with Cloud Run: each server approximately $45/month at 1 vCPU and 0.5GB with CPU always allocated; 'We recommend running a minimum of 2 instances' · Updated 12 May 2026, checked 2026-09-11
- 8.Google — Server-side tagging overview: 'We recommend a minimum of 3 instances per container for redundancy'; '$30-$50 per server per month' · Updated 30 July 2026, checked 2026-09-11
- 9.Google — Manual setup guide: 'You must deploy exactly 1 preview server'; 'The provisioned servers should have at most 1 vCPU' · Updated 8 May 2026, checked 2026-09-11
- 10.Google Cloud — Cloud Run pricing: with instance-based billing you are billed for the entire lifetime of the container instance, not per request · Checked 2026-09-11