Decoding GA4's '(not set)' Mystery After Shopify Server-Side Tracking

Hey fellow store owners and data sleuths!

Lately, there's been a buzzing discussion in the community that hits close to home for anyone who's made the smart move to server-side tracking on Shopify. The scenario? You flip the switch for server-side GA4, feeling all modern and data-secure, then BAM! Your GA4 reports are suddenly flooded with users marked as "(not set)" for Country, Landing Page, and Session Source. It looks like a bot invasion, but as our resident expert @koncz.szabi pointed out, it’s often “mostly not one.”

This isn't just a cosmetic issue; it throws your analytics off, making it hard to understand who your actual customers are and where they're coming from. So, let’s dive into what’s really happening and, more importantly, how we can fix it, pulling insights directly from a fantastic thread where folks like @lumine and @clickfromai really dug into the nitty-gritty.

Why Your GA4 Reports Are Saying "(not set)"

The core of the problem lies in how server-side tracking (often via Measurement Protocol) interacts with GA4, especially when client-side tags are blocked or behave differently. Here's the breakdown:

1. The Ad-Blocker Effect: Real Users, Invisible IDs

This is probably the biggest chunk of your "(not set)" bucket. Many of your customers use ad-blockers (uBlock, AdGuard), privacy browsers (Brave, Firefox with strict protection), or Safari's content blockers. These tools prevent Google's client-side tags (like GTM or the global site tag) from running and setting the crucial _ga cookie that gives GA4 a persistent user ID.

However, Shopify's own first-party pixel often bypasses these blockers because it’s not on their blocklists. When you activate server-side tracking, this pixel sends data directly from your server to GA4. The problem? Without that initial _ga cookie from the client-side, GA4 receives events with no recognized ID. Each event looks like a “new user” with no history, leading to "(not set)" for landing page and source. The traffic was always there; your ability to attribute it just changed.

2. Server IP & User Agent Override: Messing with Geography and Bots

When events arrive via the Measurement Protocol from your server, GA4 sees your server's IP address and user agent, not your customer's. As @koncz.szabi explained, GA4 determines country and city from the IP of the request it receives. If you don't explicitly pass the customer's IP via the ip_override field, GA4 just sees your server's location, leading to "Country = (not set)".

Similarly, GA4's bot filtering relies on the user agent and IP of the incoming request. If it sees your server's details, even crawlers that run JavaScript can “walk straight through,” as @lumine put it, appearing as legitimate traffic.

3. Client ID & Session ID Persistence: The "New User" Illusion

A common issue, highlighted by @clickfromai, is that server-side tracking might be “creating a client_id for every event.” If your setup isn't persisting a unique ID for the user across their visit (and ideally across multiple visits), every pageview or interaction becomes a “new user” in GA4. This inflates your user count and fragments their journey, making analysis impossible.

4. Storage Latency & Event Buffering: Missing the First Beat

@lumine dove deep into this one. Inside the Shopify pixel sandbox, reading from browser storage (like localStorage) is an asynchronous operation, meaning it returns a promise. If events fire before that promise resolves and the stored ID is retrieved, those events get sent without an earlier ID and are marked as “new.” This particularly affects the first event of a page load – which is often all a crawler emits – skewing your “new vs. returning” ratios.

Your Action Plan: Cleaning Up Your GA4 Data

Don't panic! The community has laid out some solid strategies to tackle this. Here’s how to get your GA4 data back on track:

Step 1: Validate Your Revenue Data First

Before diving into complex fixes, ensure your core revenue tracking is intact. As @TrackAlive suggested, “pull yesterday’s Shopify order count and compare it to ecommercePurchases in GA4 Explore for the same day.” If they're within roughly 15% (accounting for consent), your server-side tracking is likely capturing revenue, and the "(not set)" is more an attribution puzzle than a lost-order emergency.

Step 2: Fix Client ID & Session ID Persistence

This is crucial for understanding user journeys. Instead of minting a new ID for every event:

  1. Persist a first-party ID: “Stop creating a client_id for every event. Persist one first-party ID in localStorage or a cookie and reuse it,” recommends @clickfromai. This allows blocked-browser humans to maintain the same ID across pageviews.
  2. Manage Session IDs: “Keep one session_id for the visit, rolling it after 30 minutes of inactivity,” advises @clickfromai. Send page_view first with page_location and page_referrer, then other browse events.
  3. Buffer Events: To handle storage latency, “Buffering those events and flushing after the read resolves is the only handling that does not” skew the data, as @lumine noted. This ensures events wait for the ID to be retrieved before dispatching.

Step 3: Improve Attribution with IP Override & User Agent

To ensure GA4's bot filtering and geo-location work correctly:

  1. Pass the Customer's User Agent: Send the customer’s actual User-Agent header with your Measurement Protocol requests. This helps GA4 identify real browsers versus known bots.
  2. Use ip_override: “Pass the visitor user agent and, where consent permits, ip_override. Otherwise GA4 sees your server and geography or bot filtering will be wrong,” explains @clickfromai. This tells GA4 the real IP of the user, not just your server.

Step 4: Smart Segmentation with Custom Dimensions

To distinguish between ad-blocker users and crawlers, add custom parameters to your events:

  1. google_tag_loaded Parameter: “Add a parameter such as google_tag_loaded=true/false. Register it as a custom dimension so blocked-browser traffic can be compared separately,” suggests @clickfromai. This tells you if the client-side Google tag actually fired.
  2. Storage Persistence Parameter: @lumine proposed a “storage persistence” parameter with three states: fresh (ID minted on this hit), carried (ID read from storage), or unresolved (storage read failed). “A crawler running JavaScript in a fresh context per fetch reads fresh every single time and never once reads carried,” offering a powerful way to segment.

Step 5: Review Consent Declarations

Check your pixel extension’s customer_privacy block. If analytics is declared true, unconsented visitors might never emit events client-side. But if your server-side setup captures them regardless of client-side consent, it will further skew your "(not set)" bucket towards crawlers, as @lumine highlighted.

Step 6: Update Deprecated APIs

If you’re relying on browser.sendBeacon for flush at unload, be aware it’s deprecated. “The browser API reference marks browser.sendBeacon deprecated and says to use the standard fetch with keepalive: true instead,” advises @lumine. This ensures short visits are still captured.

This whole "(not set)" issue, while frustrating, is a fantastic opportunity to refine your tracking setup and gain a much clearer picture of your actual customer base. By implementing these strategies, you'll be able to confidently separate real customers from bots and get the accurate GA4 data you need to make informed decisions. Remember to annotate your GA4 reports with the date you implement these changes, as existing data won't be retroactively fixed, but your future insights will be golden!

Share:

Use cases

Explore use cases

Agencies, store owners, enterprise — find the migration path that fits.

Explore use cases