Cross-domain tracking in GA4: why it breaks and how to fix it
Every time a user crosses between your domains, there's a 50/50 chance GA4 loses them. Here's how to set it up so it actually works.
Cross-domain tracking is the feature I get called in to fix more than any other. Not because it’s conceptually difficult. The idea is simple: keep the same user session when someone moves between your domains. But the implementation sits at the intersection of cookies, URL parameters, redirects, consent tools, and browser privacy features. Any one of those can silently break it, and you won’t notice until someone asks why 40% of your checkout conversions show up as “direct” traffic.
I’ve fixed cross-domain tracking on setups involving two domains, and on setups involving eleven. The problems are usually the same.
When you actually need cross-domain tracking
First, let’s make sure you actually need this. I see people configuring cross-domain tracking when they don’t need it, which creates its own problems.
You need cross-domain tracking when:
- Your main site is on example.com but checkout happens on shop.example-checkout.com
- You run landing pages on a separate domain (landingpages.io) that link to your main site
- You have multiple brand sites that share a shopping cart or user account system
- Your payment gateway redirects users to a third-party domain and back
You do NOT need cross-domain tracking when:
- You’re moving between subdomains (blog.example.com to shop.example.com). GA4 handles subdomains automatically with proper cookie configuration.
- You’re linking to an external site you don’t own. You can’t (and shouldn’t) track users on someone else’s domain.
- You’re using iframes from the same domain.
The subdomain vs. cross-domain confusion is surprisingly common. If all your properties are subdomains of the same root domain, you just need to make sure your cookie_domain is set to the root domain (e.g., .example.com). No cross-domain tracking needed.
How cross-domain tracking works under the hood
When a user clicks a link from domain-a.com to domain-b.com, GA4 appends a _gl parameter to the URL. This parameter contains the user’s client_id and session data, encoded and timestamped.
The URL looks something like this:
https://domain-b.com/page?_gl=1*1abc2de*_ga*MTIzNDU2Nzg5MC4xNjk4NzY1NDMy
When domain-b.com loads, the GA4 tag reads the _gl parameter, extracts the client_id, and uses it instead of generating a new one. Both domains now share the same user identity.
The _gl parameter is generated by what Google calls the “linker.” In GTM, this is configured through the cross-domain tracking settings in your Google tag. The linker does two things:
- Decorates outbound links with the
_glparameter when the user clicks them - Reads incoming
_glparameters and applies the client_id from them
Both sides need to be configured. If domain-a.com decorates links but domain-b.com doesn’t read them, nothing happens.
The five ways cross-domain tracking breaks
Here’s where my troubleshooting experience comes in. These are the failure modes I see repeatedly, ordered roughly by frequency.
1. Redirect chains stripping the _gl parameter
This is the single most common cause of broken cross-domain tracking. User clicks a link to domain-b.com, the _gl parameter is appended. But domain-b.com has a redirect in place. Maybe it’s an HTTP to HTTPS redirect. Maybe it’s a www to non-www redirect. Maybe it’s a marketing redirect through a URL shortener.
Each redirect is an opportunity for the _gl parameter to get stripped. Some server configurations preserve query parameters through redirects, others don’t. Apache and Nginx handle this differently. CDN edge redirects almost always strip parameters.
How to diagnose: Open Chrome DevTools, go to the Network tab, and watch the redirect chain. I cover the full debugging toolkit and process in a separate guide. Click a cross-domain link and see if the _gl parameter survives every hop.
How to fix: Configure your redirects to preserve query parameters. In Nginx:
# Bad: strips query parameters
rewrite ^/(.*)$ https://www.domain-b.com/$1 permanent;
# Good: preserves query parameters
rewrite ^/(.*)$ https://www.domain-b.com/$1$is_args$args permanent;
Or better yet, eliminate unnecessary redirects in the cross-domain path entirely.
2. Payment gateways eating your session
This is the ecommerce-specific version of the redirect problem. User goes from your site to Stripe Checkout, PayPal, or a bank’s 3D Secure page. Then they come back.
You cannot (and should not) configure cross-domain tracking with payment gateways. You don’t own those domains, and they won’t preserve your _gl parameter. So when the user returns to your site after payment, GA4 sees them as a new session.
Cross-domain tracking not working?I'll diagnose your setup and fix the session breaks you're not seeing.
Book a Free Audit →How to fix: Add the payment gateway domain to your referral exclusion list in GA4. Go to Admin > Data Streams > your stream > Configure tag settings > List unwanted referrals. Add domains like checkout.stripe.com, paypal.com, and any 3D Secure domains your payment processor uses.
This won’t maintain the session (the user will still get a new session), but it will prevent the payment gateway from overwriting the original traffic source. Without referral exclusion, a user who arrived via a Google Ads click will show up as “referral from stripe.com” after completing payment.
3. Consent tools blocking the linker
Cookie consent tools can interfere with cross-domain tracking in two ways.
First, if your consent tool blocks GA4 from loading until consent is granted, the linker can’t decorate outbound links. The user clicks a cross-domain link before granting consent, no _gl parameter gets added.
Second, some consent tools run their own redirect on the destination domain to handle consent preferences, and that redirect can strip the _gl parameter.
How to fix: Configure your consent tool to allow GA4 to load in “consent denied” mode (using Google Consent Mode v2). In this mode, GA4 loads, the linker works, and links get decorated. But no cookies are set and no data is sent to Google until consent is granted. The session continuity is preserved.
4. JavaScript link decoration failures
The linker works by intercepting click events on anchor tags. This means it can fail when:
- Links are generated dynamically after the page loads (the linker may not catch them)
- Navigation happens through JavaScript (window.location.href = ”…”) instead of regular link clicks
- Links open in new tabs via right-click (the linker only handles left-click events)
- Buttons use form submissions instead of anchor tags
How to diagnose: Right-click an outbound cross-domain link and copy the URL. If it doesn’t contain the _gl parameter, the linker isn’t decorating it. Then left-click the same link and check the URL in the address bar on the destination page. If it has _gl now, the linker is working on click but not on copy, which is expected behavior. If it still doesn’t have _gl on click, the linker isn’t finding the link.
How to fix: For dynamically generated links, make sure the linker script loads before the links are created. For JavaScript navigation, you need to manually call the linker API:
// Get the decorated URL manually
gtag('get', 'G-XXXXXXX', 'client_id', function(clientId) {
// Use the Measurement Protocol or manually append the _gl parameter
});
In practice, the cleanest solution is to use regular anchor tags wherever possible.
5. Subdomain cookie configuration conflicts
This one’s subtle. You have main.com and sub.main.com, and you’ve correctly identified that you don’t need cross-domain tracking. But you also have other.com in the mix, and you do need cross-domain tracking between main.com and other.com.
The problem: your GA4 cookie_domain is set to .main.com (with the leading dot, which includes subdomains). When a user moves from sub.main.com to other.com, the linker decorates the link. But the client_id being passed comes from the .main.com cookie. If other.com then sets its own cookie, you now have two different cookie scopes in play.
How to fix: Be explicit about your cookie domain configuration across all domains. Make sure every domain in your cross-domain group is listed in the linker configuration, and test the full matrix of domain transitions.
Step-by-step setup with GTM
Here’s the actual configuration process. I’m assuming you’re using Google Tag Manager.
Step 1: Identify all domains in the group.
List every domain where you need session continuity. Include subdomains only if they’re on a different root domain.
Example: example.com, shop.example-store.com, app.example-platform.io
Step 2: Configure the Google tag.
In GTM, open your Google tag (the GA4 configuration tag). Go to Configuration Settings.
Under “Domains to configure for cross-domain measurement,” add each domain. Use the format:
| Match type | Domain |
|---|---|
| Contains | example.com |
| Contains | example-store.com |
| Contains | example-platform.io |
Using “Contains” is safer than “Equals” because it catches subdomains automatically.
Step 3: Set referral exclusions in GA4.
In GA4 Admin > Data Streams > your stream > Configure tag settings > List unwanted referrals, add all domains in your cross-domain group. This prevents domain-a.com from showing up as a referral source when a user moves to domain-b.com.
This is the step people forget. Without it, every cross-domain transition creates a new “referral” attribution, destroying your actual traffic source data.
Step 4: Deploy and test on all domains.
Push your GTM container live on every domain in the group. The linker needs to be active on every domain, not just the “starting” domain.
How to verify it’s working
Testing cross-domain tracking requires checking multiple layers. Here’s my process.
Real-time check
- Open domain-a.com in Chrome with the GA Debugger extension active
- Click a link to domain-b.com
- Verify the URL contains the
_glparameter - On domain-b.com, check the GA4 Real-time report
- You should see the same client_id on both domains
DebugView check
This gives you more detail than Real-time:
- Enable debug mode on both domains
- Walk through a cross-domain journey
- In DebugView, verify that page_view events from both domains appear under the same debug device
- Check that session_start only fires once (on the first domain)
If you see session_start firing on the second domain, the linker isn’t working. The second domain is creating a new session instead of continuing the existing one.
BigQuery validation
This is the definitive test. After collecting some data (wait at least 24 hours for BigQuery export), run this query:
SELECT
user_pseudo_id,
event_name,
event_timestamp,
(SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'page_location') as page_url,
(SELECT value.int_value FROM UNNEST(event_params) WHERE key = 'ga_session_id') as session_id
FROM `project.analytics_XXXXXXX.events_*`
WHERE user_pseudo_id = 'YOUR_TEST_CLIENT_ID'
AND _TABLE_SUFFIX = FORMAT_DATE('%Y%m%d', CURRENT_DATE() - 1)
ORDER BY event_timestamp
Replace YOUR_TEST_CLIENT_ID with the client_id you noted during testing. You should see events from both domains under the same user_pseudo_id, with the same ga_session_id.
If the session_id changes when the domain changes, cross-domain tracking failed for that transition.
The redirect chain test
This is the one most people skip. Don’t just test the happy path. Test every variation:
- Click a link from domain-a to domain-b with HTTP-to-HTTPS redirect
- Click a link through a URL shortener
- Click a link that goes through a marketing redirect
- Click a link from an email client (some email clients strip query parameters)
- Right-click and “Open in new tab”
- Use the back button after crossing domains and then navigate again
Each of these can behave differently.
Common debugging patterns
When cross-domain tracking isn’t working, here’s my diagnostic sequence:
-
Check the outbound link. Does it have
_gl? If not, the linker isn’t decorating. Check your GTM configuration. -
Check the landing URL. Does it still have
_glafter all redirects? Open DevTools Network tab and trace the full redirect chain. -
Check consent. Is GA4 loading before the user clicks the cross-domain link? If consent blocks GA4, the linker can’t run.
-
Check the cookie. On the destination domain, look at the
_gacookie value. Does the client_id portion match what was sent in the_glparameter? -
Check referral exclusions. Even if client_id carries over, missing referral exclusions will create a new campaign attribution, which can look like tracking is broken in reports even when the user identity is correct.
I’ve seen setups where the cross-domain tracking itself was working perfectly (same client_id on both domains), but the team thought it was broken because they hadn’t set referral exclusions. Every cross-domain transition was showing up as a “referral” from the other domain, making it look like sessions were splitting. These kinds of attribution errors can silently destroy your conversion data and make profitable campaigns look like failures.
Frequently asked questions
Q: Do I need cross-domain tracking for subdomains in GA4?
No. GA4 handles subdomains automatically if your cookie domain is set to the root domain (e.g., .example.com). You only need cross-domain tracking when users move between completely different root domains, like example.com and example-checkout.com.
Q: Why does my GA4 show the payment gateway as a traffic source?
When a user goes to a third-party payment domain (Stripe, PayPal) and returns, GA4 treats the return as a new session with the payment gateway as the referral source. Fix this by adding the payment domain to your referral exclusion list in GA4 Admin under Data Streams, Configure tag settings, List unwanted referrals.
Q: How do I test if cross-domain tracking is working in GA4?
Click a cross-domain link and check that the URL contains a _gl parameter. Then verify in GA4 DebugView that events from both domains appear under the same debug device and that session_start only fires on the first domain. For definitive validation, query BigQuery to confirm the same user_pseudo_id and ga_session_id appear across both domains.
Q: What causes the _gl parameter to get stripped from URLs?
Redirect chains are the most common cause. HTTP-to-HTTPS redirects, www-to-non-www redirects, CDN edge redirects, and URL shorteners can all strip query parameters. Check each hop in the redirect chain using Chrome DevTools Network tab, and configure your server to preserve query parameters through all redirects.
When cross-domain tracking isn’t the answer
Sometimes the right move is to accept that you can’t track across domains and design around it.
If your checkout goes through a third-party platform that you can’t add your GA4 tag to (some SaaS checkout systems, marketplace platforms), no amount of linker configuration will help. In those cases, pass transaction data back via server-side events using the Measurement Protocol, matching on a shared identifier like an order ID or email hash.
Cross-domain tracking is powerful when it works. But it’s also GA4’s most maintenance-heavy feature. Every time you add a redirect rule, change a CDN configuration, update your consent tool, or modify your link structure, you should re-test it. Set up a monthly check. It will break again. It always does.
Artem Reiter
Web Analytics Consultant