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:
- Persist a first-party ID: “Stop creating a
client_idfor every event. Persist one first-party ID inlocalStorageor a cookie and reuse it,” recommends @clickfromai. This allows blocked-browser humans to maintain the same ID across pageviews. - Manage Session IDs: “Keep one
session_idfor the visit, rolling it after 30 minutes of inactivity,” advises @clickfromai. Sendpage_viewfirst withpage_locationandpage_referrer, then other browse events. - 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:
- Pass the Customer's User Agent: Send the customer’s actual
User-Agentheader with your Measurement Protocol requests. This helps GA4 identify real browsers versus known bots. - 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:
google_tag_loadedParameter: “Add a parameter such asgoogle_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.- Storage Persistence Parameter: @lumine proposed a “storage persistence” parameter with three states:
fresh(ID minted on this hit),carried(ID read from storage), orunresolved(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!