GA4 Server-Side Tracking: Why WooCommerce Purchases Show (not set)
Quick answer: When you move WooCommerce tracking server-side, GA4 purchase events show (not set) source/medium or create orphaned sessions because the Measurement Protocol requires specific identifiers to join server hits to existing web sessions. The culprit is almost always a missing or randomly-generated client_id, absent session_id, or unforwarded ip_override. GA4’s MP endpoint silently accepts malformed payloads with a 204 response — failures are invisible until you audit your reports. Fix it by forwarding the original client_id from the _ga browser cookie, the session_id from the gtag session cookie, and the visitor’s real IP for geo attribution.
Why (not set) Appears in Server-Side GA4
Server-side tracking breaks attribution when session context from the browser isn’t forwarded correctly to the Measurement Protocol endpoint.
Here’s the thing: moving to server-side tracking doesn’t automatically fix your attribution — it shifts responsibility. The browser used to carry all the session context automatically. Now your server has to do it manually, and if you miss a single identifier, GA4 has nothing to tie the purchase event to the session that generated it.
GA4’s Measurement Protocol joins server hits to web sessions using the client_id. This value is stored in the _ga browser cookie and must be read from the frontend and passed to your server. If your server generates a fresh random UUID instead of forwarding the browser’s client_id, every purchase event becomes a ghost — a detached hit with no campaign, no source, no medium.
The result is (not set) across source/medium, campaign, and often device/geo dimensions. And because GA4 doesn’t distinguish between “correctly received” and “correctly attributed”, you won’t see any errors in your event stream.
A server-generated random client_id disconnects the purchase event from the user’s web session, creating orphaned attribution paths that inflate direct traffic and bury your real acquisition channels.
This is the attribution equivalent of a receipt with no customer name. GA4 knows a purchase happened. It just doesn’t know who bought, how they found you, or which campaign drove it.
The Silent 204: Why GA4 Lies About Success
GA4’s production Measurement Protocol endpoint returns 204 for any syntactically valid request — including ones with completely wrong data.
This is the part that trips up developers. You send your server-side purchase event. You get a 204 back. You assume everything’s working. Three days later, your GA4 dashboard is full of (not set).
The GA4 production endpoint (/mp/collect) returns 204 as long as the request is structurally valid — it does not validate the content of your parameters. Wrong client_id? 204. Missing session_id? 204. Garbled event name? 204. The hit lands. The attribution doesn’t.
The only way to validate a GA4 MP payload before it does damage is to use the debug endpoint:
https://www.google-analytics.com/debug/mp/collect
This endpoint returns a JSON response with validation messages, event-level feedback, and any parameter issues. Run every new server-side event through it before pointing at production. Translation: treat 204 from /mp/collect as “received”, not as “correct”.
GA4’s Measurement Protocol production endpoint returns 204 for malformed payloads — meaning your server-side events appear to work while silently producing (not set) in every attribution report.
You may be interested in: Why a Third of Your WooCommerce Visitors Are Invisible to Client-Side Pixels
The Identifiers You Must Forward
Four parameters determine whether a server-side GA4 hit joins a real session or becomes an orphaned data point.
Getting server-side tracking right means forwarding exactly the right context from the browser to your server before the purchase event fires. Here’s what you need:
1. client_id (non-negotiable)
Read this from the _ga cookie. It looks like GA1.1.1234567890.1234567890 — the numeric portion after the version prefix is your client_id. This is the primary key GA4 uses to join your server event to any existing session data. Get this wrong and everything else is irrelevant.
2. session_id
Read this from the _ga_MEASUREMENT_ID cookie (where MEASUREMENT_ID is your GA4 property’s stream ID, e.g. _ga_ABC123DEF). Without a matching session_id, your purchase event creates a new disconnected session in GA4 rather than extending the real one. The result: inflated session counts and broken funnel reports.
3. engagement_time_msec
This parameter signals that the session had user engagement. Without it, server-side MP events don’t count as engaged sessions — distorting your bounce rate and session quality metrics. Set it to any reasonable value (e.g., 100 or the actual time-on-page if you’re tracking it).
4. ip_override
This is the visitor’s real IP address, passed so GA4 can derive geo attribution for the hit. Omit it and location data goes blank for server-side events. Your geo reports will either show nothing or silently inherit a cached location from a prior session hit — neither of which is reliable.
| Parameter | Source | What breaks without it |
|---|---|---|
client_id |
_ga cookie |
Source/medium attribution → (not set); orphaned sessions |
session_id |
_ga_MEASUREMENT_ID cookie |
Purchase joins wrong or new session; funnel breaks |
engagement_time_msec |
Set manually (e.g. 100) | Session not counted as engaged; bounce rate distorted |
ip_override |
Real visitor IP from request headers | Geo attribution blank; location reports unreliable |
Read all four values on the frontend, pass them to your server (via a dataLayer push, a custom cookie, or a server-readable header), and include them in every GA4 MP purchase event.
The Timestamp Trap
Events sent outside GA4’s ~72-hour timestamp window drop silently, and events with wrong timestamps land in the wrong BigQuery partition.
Server-side events sometimes have timestamp issues that are easy to miss. GA4’s Measurement Protocol accepts a timestamp_micros parameter — and if you use it incorrectly, you’ll lose data without any warning.
The ~72-hour rule: Events with timestamp_micros more than roughly 72 hours in the past are silently dropped. No error. No 4xx. Just gone. This matters for WooCommerce stores that batch-process orders or retry failed events.
The partition problem: Events with wrong timestamps that fall within the window still land in GA4 — but in the wrong BigQuery partition. If you’re doing any BigQuery-level analysis (daily event tables, funnel queries by date), these events become invisible to standard queries while still consuming your hit quota.
The safest approach: if you’re sending the event as it happens in real time, omit timestamp_micros entirely. GA4 will use server receipt time. Only use timestamp_micros if you’re backfilling events and you’re confident your timestamp generation is correct down to microsecond precision.
You may be interested in: Chrome IP Protection in 2026: What It Breaks for WooCommerce Geo Reports
Geo Attribution and ip_override
Server-side GA4 hits need ip_override set to the visitor’s real IP — without it, location reports go blank for server-processed events.
Client-side pixels handle geo automatically — the browser’s IP is visible to Google’s servers as the request origin. When you move events server-side, the origin IP is your server’s, not the visitor’s. GA4 doesn’t know where your customers are.
The fix is ip_override. Set it to the visitor’s real IP address (available in the X-Forwarded-For header or equivalent, depending on your server setup). GA4 will use this for geo derivation, giving you accurate location reports for server-side events.
If neither ip_override nor user_location is sent, GA4 attempts to derive geo from prior client_id tagging — which only works if the same client_id had a recent browser-based hit with a known location. For first-touch server events, returning customers on new devices, or any event where the prior hit chain is broken, the result is blank geo attribution.
Don’t skip ip_override. It’s two lines of server code and it’s the difference between actionable geo reports and a map full of unknowns.
Server-Side Hit Checklist: What Breaks vs What Fixes
A practical comparison of common server-side GA4 mistakes and their correct implementations.
| What you might do | What actually happens | What to do instead |
|---|---|---|
| Generate a random UUID for client_id on the server | Orphaned session; (not set) source/medium across all reports | Read client_id from _ga cookie and forward it |
| Omit session_id | Purchase creates a new disconnected session; funnel data breaks | Forward session_id from _ga_MEASUREMENT_ID cookie |
| Skip engagement_time_msec | Session not counted as engaged; bounce rate inflated | Set engagement_time_msec to 100 (or actual time-on-page) |
| Omit ip_override | Geo reports blank or unreliable for server-side events | Pass X-Forwarded-For IP as ip_override |
| Use wrong timestamp_micros | Event drops silently or lands in wrong BigQuery partition | Omit timestamp_micros for real-time events |
| Validate against /mp/collect only | 204 gives false confidence; malformed payloads pass | Validate against /debug/mp/collect before going live |
Key Takeaways
- client_id is the critical link: Forward the
_gacookie value — never generate a new random ID on the server. This single mistake causes (not set) across all attribution dimensions. - 204 ≠ correct: GA4’s production MP endpoint accepts malformed payloads. Always validate new event payloads against
/debug/mp/collectbefore pointing at production. - Four identifiers, all required:
client_id,session_id,engagement_time_msec, andip_override— missing any one of them breaks a different part of your attribution. - Timestamp_micros is optional and risky: Omit it for real-time events. If you use it, events outside the ~72-hour window drop silently; wrong values land in the wrong BigQuery partition.
- Server-side attribution is a forwarding problem, not a technical failure: The data exists on the frontend. Your job is to read it and pass it to your server before the purchase event fires.
_ga browser cookie, GA4 can’t join the event to the original session and falls back to (not set) for source/medium attribution.”},{“id”:”faq-2″,”jsonQuestion”:”My GA4 MP request returns 204 — why isn’t the data showing up correctly?”,”jsonAnswer”:”GA4’s Measurement Protocol production endpoint returns 204 for any syntactically valid request, including ones with wrong field values. The 204 only confirms the hit was received, not that the data is correct. Use the /debug/mp/collect endpoint to validate payloads before going live.”},{“id”:”faq-3″,”jsonQuestion”:”What identifiers do I need to forward for correct GA4 server-side attribution?”,”jsonAnswer”:”You need at minimum: client_id (from the _ga cookie), session_id (from the _ga_MEASUREMENT_ID cookie), engagement_time_msec (so the session counts as engaged), and ip_override (the visitor’s real IP for geo attribution). Missing any of these causes partial or broken attribution.”},{“id”:”faq-4″,”jsonQuestion”:”What happens if I send the wrong timestamp_micros in a GA4 MP event?”,”jsonAnswer”:”Events with timestamp_micros more than ~72 hours old are silently dropped by GA4. Events with wrong timestamps that fall within the window still land — but in the wrong BigQuery partition, making them invisible to standard reports while still consuming quota.”},{“id”:”faq-5″,”jsonQuestion”:”How does server-side tracking affect GA4 geo reports?”,”jsonAnswer”:”Server-side hits need ip_override set to the visitor’s real IP for GA4 to derive geo attribution. Without it, GA4 attempts to use prior client_id tagging — but for first-touch server events or anonymous traffic, location reports simply go blank.”}]} –>
The most common cause is a missing or incorrect client_id. If your server generates a fresh random client_id instead of forwarding the one stored in the _ga browser cookie, GA4 can’t join the event to the original session and falls back to (not set) for source/medium attribution.
GA4’s Measurement Protocol production endpoint returns 204 for any syntactically valid request, including ones with wrong field values. The 204 only confirms the hit was received, not that the data is correct. Use the /debug/mp/collect endpoint to validate payloads before going live.
You need at minimum: client_id (from the _ga cookie), session_id (from the _ga_MEASUREMENT_ID cookie), engagement_time_msec (so the session counts as engaged), and ip_override (the visitor’s real IP for geo attribution). Missing any of these causes partial or broken attribution.
Events with timestamp_micros more than ~72 hours old are silently dropped by GA4. Events with wrong timestamps that fall within the window still land — but in the wrong BigQuery partition, making them invisible to standard reports while still consuming quota.
Server-side hits need ip_override set to the visitor’s real IP for GA4 to derive geo attribution. Without it, GA4 attempts to use prior client_id tagging — but for first-touch server events or anonymous traffic, location reports simply go blank.
References
- Google Analytics Measurement Protocol Reference (2026) — Google Developers
- Google Analytics Measurement Protocol Overview (2026) — Google Developers
- GA4 Measurement Protocol Deep Dive — GA4 Audits
If your server-side WooCommerce tracking is producing (not set) or orphaned sessions, the fix starts with forwarding the right identifiers. Transmute Engine™ handles client_id forwarding, session stitching, and server-side event routing automatically — so your GA4 attribution stays intact regardless of browser-side blocking or cookie restrictions.