TL;DR
GA4 defines a session with two values, not one. The client_id says which browser, and the session_id says which session on that browser. Most Shopify tracking discussion stops at the client_id, but a server-side purchase with a matching client_id and a missing or stale session_id still opens a fresh session and files the revenue under direct/none [3]. The session_id lives inside the _ga_<id> cookie and rolls after 30 minutes of inactivity or when the campaign source changes [1][2]. WeltPixel Conversion Tracking now mirrors the browser's current session_id into the server-side purchase so the sale folds back into the originating session. When no session cookie is available, on admin orders, some Shop Pay cross-domain checkouts, subscription renewals, and declined-cookie visits, a fallback value is used and the order still lands a new session. This article covers what a GA4 session is, how the _ga_<id> cookie carries the session_id, and why a matched client_id is necessary but not sufficient.
Key Takeaways
- A GA4 session is a set of events grouped by one
session_id; the value lives in the_ga_<id>cookie, one cookie per GA4 property [1][2]. - The session rolls after 30 minutes of inactivity or when the campaign source or medium changes, and gtag writes a new
session_idat that moment [1][2]. - A matched
client_idis necessary but not sufficient. A server-side purchase needs the matchingsession_idtoo, or GA4 opens a new session at direct/none [3]. - The Measurement Protocol accepts
session_idand, for non-zero engagement,engagement_time_msecwith a minimum of 1 millisecond [3]. - A February 2026 Measurement Protocol change added cookie-based
client_idandsession_idrecognition, confirming the cookie-to-server pattern [3]. - When no
_ga_<id>cookie is readable (admin orders, some Shop Pay checkouts, renewals, declined cookies), a fallbacksession_idis used and the order lands a fresh session. - Verify in GA4 DebugView whether your purchase event carries the same
session_idas the browser's page_view, not just the sameclient_id.
What is a GA4 session, and what is the session_id?
A GA4 session is a collection of events that GA4 groups together under one identifier, the session_id [1]. When a visitor lands on your store, gtag opens a session and assigns it an integer session_id. Every page_view, add_to_cart, and the eventual purchase that shares that value belongs to the same session, which is how GA4 reports one source, one medium, and one campaign for the whole visit.
The session_id is not the same thing as the client_id. The client_id identifies the browser across visits; the session_id identifies one specific visit on that browser. One client_id produces many session_id values over time, one per session. That distinction is the whole point of this article, and it is separate from the identity question of client_id versus user_id, which is covered in Client ID vs User ID in GA4 on Shopify.
GA4 rolls the session, meaning it ends the current one and starts a new one with a fresh session_id, in two situations [2]. The first is 30 minutes of inactivity: if no event fires for 30 minutes, the next event starts a new session. The second is a campaign change: if the visitor returns through a different source or medium, GA4 treats it as a new session so the new campaign gets credit. Both rules matter on Shopify because a checkout can straddle them.
How does the ga cookie carry the session_id?
The session_id is stored client-side in a cookie named _ga_<id>, where the suffix is the GA4 measurement ID. Each GA4 property you run has its own cookie, so a store sending to two properties has two _ga_<id> cookies and two independent session_id values that roll on their own clocks.
The cookie value is structured. In the common format it reads like GS1.1.SESSION_ID.TIMESTAMP.EVENT_COUNT.ENGAGEMENT_TIME, where the third field is the session_id as a Unix-seconds integer. A newer keyed format carries the same data in labeled segments. The practical takeaway is that the live session_id is sitting in that cookie, and it changes the instant gtag rolls the session on a timeout or a campaign change.
This is where a Shopify-specific complication enters. The browser-side analytics that fire from Shopify's Customer events run inside a sandboxed context that cannot reliably read the main page's _ga_<id> cookie directly. Reading the cookie once at page load and caching the value is not enough, because gtag can roll the session later in the same page life, on a campaign change for example, and the cached value would then be stale. WeltPixel Conversion Tracking handles this by reading the current session_id from the _ga_<id> cookie on the main page on every page load and making that fresh value available to the purchase event, so the value sent is the session that is actually active, not a frozen one. Browser events respect Shopify's Customer Privacy API signals, so this only happens when consent allows analytics.
Why a matched client_id is not enough to keep a purchase in-session
Here is the failure that surprises operators. A Shopify order is confirmed server-side, and a good tracking app carries the browser's client_id into the server-side purchase event so GA4 knows which browser bought. People assume that is the whole job. It is not.
GA4 keys a session on both the client_id and the session_id [3]. If the server-side purchase carries the correct client_id but a session_id that is missing or does not match the browser's active session, GA4 cannot place the event in the existing session. It opens a new session instead, with no campaign context, and files the revenue under direct/none. The order shows the right device in DebugView and still reports as Direct, which is the exact symptom that sends people debugging the client_id when the client_id was fine all along.
A stale session_id fails a different way. If the purchase carries the browser's old session_id, captured before a campaign change rolled the session, the sale folds into the stale session and inherits the old campaign rather than the current one. Either way the revenue ends up in the wrong place. The fix is to carry the browser's current session_id alongside the client_id, which is what the cookie bridge above exists to guarantee. The broader attribution version of the Direct symptom, for logged-in and cross-device orders, is covered in why Shopify marks logged-in and cross-device orders as Direct.
What does the Measurement Protocol require for session continuity?
Server-side GA4 events reach Google through the Measurement Protocol, the API that Shopify tracking apps use to send a confirmed purchase. For a web stream the payload must carry a client_id, and for the event to count as engaged it should carry engagement_time_msec, with a documented minimum of 1 millisecond [3]. To keep the event in the browser's session, the session_id must travel inside the event parameters too.
A February 2026 Measurement Protocol change added support for recognizing a client_id cookie and a session_id cookie in payloads [3]. That is Google formalizing the same cookie-to-server passthrough that keeps a purchase in its original session: read the live identifiers from the GA4 cookies and send them with the server event. WeltPixel Conversion Tracking already operates on this principle, carrying both the client_id and the current session_id from the browser's GA4 cookies into the purchase event.
The transport itself, how the server-side purchase is structured and delivered to Google, is its own topic. If you want the mechanics of the Measurement Protocol send, see GA4 Measurement Protocol on Shopify: what it is. This article is only about one parameter inside that payload, the session_id, and why it decides whether the purchase joins the browser's session or starts a new one.
When the session cookie is absent: the honest limit
The bridge cannot invent a session_id that never existed. Several order types never carry a readable _ga_<id> cookie, and for those the app falls back to a generated session value derived from the order, which by definition does not match any browser session. The result is a fresh GA4 session at direct/none. This is structural, not a bug.
The cases are consistent. Admin-created orders have no browser at all. Some Shop Pay cross-domain checkouts load on a different domain where the store's _ga_<id> cookie is not available to the browser analytics. Subscription renewals fire server-side with no live session. And visitors who declined the analytics cookie never had a _ga_<id> cookie to read. In every case the purchase is still sent, so revenue is recorded, but it lands a new session rather than folding into a prior one. The unattributed-revenue version of this outcome is covered in GA4 (not set) unattributed orders on Shopify.
The operator value is the diagnosis. When you see a purchase show up as Direct with a client_id that matches the browser, do not assume the client_id bridge broke. Check whether the order type is one of these cookieless cases, because a matched client_id with a fallback session_id is the expected behavior there, not a defect.
Verify it on your own store
You can confirm the session_id behavior directly in GA4 rather than guessing.
- In GA4, open Admin → DebugView and enable debug mode for your store session.
- Load your storefront and watch the
page_viewevent arrive. Click it and note thesession_idparameter value. - Complete a test order through the normal browser checkout so a server-side purchase fires.
- Find the
purchaseevent in DebugView and compare itssession_idto thepage_viewvalue. A match means the sale folded into the browser's session. - Also compare the
client_idvalues. If theclient_idmatches but thesession_iddoes not, you have found the exact split this article describes, and the purchase will report as a separate session. - Repeat with an admin-created order or a renewal and expect a non-matching, fallback
session_id, which is the documented cookieless behavior.
If you only run one check, compare the purchase and page_view session_id values. That single comparison tells you whether your revenue is folding into the right session or fragmenting into a new one.
FAQ
Does carrying the client_id keep my purchase in the same GA4 session?
Not on its own. GA4 keys a session on both the client_id and the session_id, so a purchase with a matched client_id but a missing or stale session_id still opens a new session at direct/none [3]. You need both identifiers to fold the sale into the browser's session.
How long does a GA4 session last before it rolls?
A GA4 session ends after 30 minutes of inactivity, and the next event starts a new session with a new session_id [2]. It also rolls when the visitor returns through a different campaign source or medium, so the new campaign gets credit [2].
Where is the GA4 session_id stored?
In the _ga_<id> cookie, where the suffix is your GA4 measurement ID [1]. Each property has its own cookie, so multiple GA4 properties carry separate, independently rolling session_id values.
Why does my Shop Pay order show as Direct even though tracking is installed?
Some Shop Pay checkouts load on a domain where the store's _ga_<id> cookie is not readable by the browser analytics, so no browser session_id is available. The purchase sends with a fallback value and opens a new session at direct/none, which is structural for that checkout path.
Is the session_id personal data I need consent for?
The session_id is a GA4 identifier, not personal data, so it does not add a new consent gate. The browser analytics that read it respect Shopify's Customer Privacy API signals, so the session bridge only runs when consent allows analytics.
Keep every sale in the session that earned it
A matched client_id proves which browser bought, but only a matched session_id keeps the sale in the session that holds the campaign. WeltPixel Conversion Tracking reads the current session_id from the _ga_<id> cookie and carries it into the server-side purchase, so the order folds back into the originating GA4 session instead of fragmenting into direct/none, across GA4, Meta, TikTok, Google Ads, and Reddit.
Install WeltPixel Conversion Tracking on the Shopify App Store
Sources
- Google Analytics Help. "[GA4] Sessions." https://support.google.com/analytics/answer/11986666
- Google Analytics Help. "[GA4] Session timeout and campaign timeout." https://support.google.com/analytics/answer/12070602
- Google Analytics for Developers. "Measurement Protocol (Google Analytics 4)" and changelog. https://developers.google.com/analytics/devguides/collection/protocol/ga4