← Back to Blog

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 _ga cookie 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/collect before pointing at production.
  • Four identifiers, all required: client_id, session_id, engagement_time_msec, and ip_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.
<!– wp:yoast/faq-block {"questions":[{"id":"faq-1","jsonQuestion":"Why does my server-side GA4 purchase event show (not set) for source/medium?","jsonAnswer":"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.”},{“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.”}]} –>
Why does my server-side GA4 purchase event show (not set) for source/medium?

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.

My GA4 MP request returns 204 — why isn’t the data showing up correctly?

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.

What identifiers do I need to forward for correct GA4 server-side attribution?

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.

What happens if I send the wrong timestamp_micros in a GA4 MP event?

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.

How does server-side tracking affect GA4 geo reports?

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

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.