If your primary booking engine, checkout portal, or sub-application lives on a secondary domain, your marketing attribution is likely lying to you. Modern enterprise marketing stacks routinely send high-intent paid traffic to a primary domain (e.g., brand.com) before redirecting users to a transactional secondary domain (e.g., checkout-brand.com). When set up correctly, Google Analytics 4 (GA4) passes session identifiers using the _gl query parameter across domain boundaries to maintain a single continuous user journey.
However, modern frontend architectures—specifically Single Page Applications (SPAs) built on React, Next.js, or Vue—frequently strip these linker parameters before Google Tag Manager (GTM) can parse them. Compounded by Safari’s Intelligent Tracking Prevention (ITP), your attribution data collapses: original campaign parameters disappear, session continuity breaks, and your secondary domain becomes your top referral source. This guide breaks down the root technical causes of _gl parameter stripping and provides actionable scripts and configurations to fix your GA4 cross-domain tracking.
The Cross-Domain Parameter Leak
When SPA routers clean query parameters or Safari ITP caps client-side storage, your primary marketing site becomes your top referral channel while paid channels are starved of conversion credit.
Section 1: The Hidden Business & Revenue Risk of Broken Cross-Domain Attribution
When cross-domain tracking fails, GA4 cannot stitch the visitor's pre-purchase engagement with their transaction. This creates three critical failures in your marketing analytics:
[ Paid Ads Traffic ] ──> [ brand.com ] ──( Broken _gl Pass )──> [ checkout-brand.com ]
│
▼
GA4 Attribution Outcome:
• Source: brand.com (Referral)
• Campaign Data: LOST
• Safari ITP: New User Created
1. Referral Self-Exclusion Failure & Channel Pollution
When a user navigates from brand.com to checkout-brand.com without a valid _gl parameter, GA4 treats the arrival at checkout-brand.com as a brand-new session. Because the traffic originated from brand.com, GA4 registers brand.com as the session_referrer.
Consequently, your acquisition reports show your main marketing site as your top-performing referral channel, while Paid Search, Paid Social, and Organic traffic streams are starved of conversion credit.
2. Double-Counting Users and Sessions
A single prospective client exploring your offerings on brand.com and subsequently booking on checkout-brand.com is logged as two separate users and two distinct sessions. This artificially inflates your Total Users metric, depresses your true conversion rate by 50%, and invalidates downstream funnel analysis.
3. Campaign Attribution Erasure via Safari ITP
Apple’s Safari Intelligent Tracking Prevention (ITP) aggressively targets link decoration—the exact mechanism GA4 relies on (_gl=...). When ITP detects link decoration across top-level domains, it caps client-side storage (document.cookie) lifespans to 24 hours or 7 days.
If a prospective customer clicks a Google Ad, lands on brand.com, and converts on checkout-brand.com eight days later, Safari wipes the client-side _ga cookie. GA4 re-identifies the returning user as a brand-new direct visitor, completely severing your ROAS metrics.
Section 2: Technical Breakdown: Why _gl Query Parameters Get Stripped
Understanding why your _gl parameter fails requires analyzing the client-side execution lifecycle across modern web applications.
Timeline of a Broken Cross-Domain Linker Request:
1. User clicks link to secondary domain
│
2. Client-side JS fires e.preventDefault() ──> [ Race Condition! ]
│ GTM Native Linker fails to execute
3. Router pushes URL to browser address bar
│
4. SPA Framework (Next.js/React) mounts ───> [ Parameter Truncation! ]
│ Router strips '_gl' query string
5. GTM Container Initializes ──────────────> [ GA4 Reads URL ]
'_gl' is gone. New Client ID generated.
Root Cause 1: SPA Client-Side Router Truncation
Frameworks like Next.js (next/router or next/navigation), React Router, and Vue Router intercept incoming URLs during client-side hydration. If your app cleans query strings on initial mount—a common practice to enforce canonical URLs or remove tracking tags—the SPA router rewrites window.location.href before GTM's container script initializes. By the time the GA4 Configuration tag fires, the _gl query parameter has already been deleted from the DOM.
Root Cause 2: Asynchronous Redirect & Click-Handler Race Conditions
Custom JavaScript click listeners on buttons or links often execute e.preventDefault(), process an asynchronous API request, and then programmatically trigger window.location.href = targetUrl. GTM’s native Cross-Domain Linker relies on binding directly to the click event to decorate the link dynamically before navigation occurs. Asynchronous custom redirects bypass GTM's event listeners entirely, navigating the user to an undecorated URL.
Root Cause 3: Client-Side Storage Partitioning in Safari ITP 2.3+
Safari ITP tracks link decoration by detecting URL parameters that match known analytics patterns (_gl, gclid, fbclid). If Safari determines that a cross-site navigation occurred with a decorated query string, it partitions client-side storage, caps document.cookie lifespan for _ga to 24 hours, and completely isolates local storage between top-level domains.
Section 3: Diagnostic JavaScript Code Snippet
Before deploying fixes, run this diagnostic script to verify whether your application is receiving, parsing, or stripping the _gl parameter on landing.
Inject this script into the <head> of your secondary domain above your Google Tag Manager snippet, or execute it directly within your browser’s Developer Console (F12 -> Console):
<script>
(function () {
'use strict';
console.group('Choozr GA4 Cross-Domain Diagnostic');
const currentUrl = new URL(window.location.href);
const glParam = currentUrl.searchParams.get('_gl');
const gaCookie = document.cookie.split('; ').find(row => row.startsWith('_ga='));
console.log('1. Current URL:', currentUrl.href);
console.log('2. Incoming _gl Parameter:', glParam ? '%cFOUND: ' + glParam, 'color: #00ff00; font-weight: bold;' : '%cMISSING / STRIPPED', 'color: #ff0000; font-weight: bold;');
console.log('3. Active _ga Cookie:', gaCookie ? gaCookie : 'No _ga cookie set');
// Monitor SPA Router Changes for Parameter Truncation
let lastUrl = location.href;
new MutationObserver(() => {
const newUrl = location.href;
if (newUrl !== lastUrl) {
if (lastUrl.includes('_gl=') && !newUrl.includes('_gl=')) {
console.warn('CRITICAL WARNING: SPA Router stripped the _gl parameter during client-side transition!');
console.warn('Previous URL:', lastUrl);
console.warn('Stripped URL:', newUrl);
}
lastUrl = newUrl;
}
}).observe(document, { subtree: true, childList: true });
console.groupEnd();
})();
</script>
Section 4: Step-by-Step Technical Fix & Audit Protocol
To build a resilient cross-domain tracking setup, implement this four-step engineering workflow.
┌────────────────────────────────────────────────────────┐
│ STEP 1: GA4 Native Domain Configuration │
│ Add target domains to Configure Your Domains in GA4 │
└───────────────────────────┬────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────┐
│ STEP 2: GTM Custom Click Listener │
│ Manually decorate async programmatic redirects │
└───────────────────────────┬────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────┐
│ STEP 3: SPA Hydration Preservative Script │
│ Preserve _gl parameter across client-side router hooks │
└───────────────────────────┬────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────┐
│ STEP 4: Server-Side HTTP Cookie Overrides (sGTM) │
│ Issue HttpOnly cookies to bypass Safari ITP 7-day cap │
└───────────────────────────┘
Step 1: Configure GA4 Admin Domain Settings Correctly
- In GA4, navigate to Admin > Data Streams > [Your Web Stream] > Configure tag settings.
- Click Configure your domains.
- Add all primary and secondary top-level domains using match type
Contains:brand.comandcheckout-brand.com. - Under List unwanted referrals, add both
brand.comandcheckout-brand.comto prevent self-referrals from overwriting existing source attribution.
Step 2: Implement a GTM Custom Click Listener for Async Buttons
For buttons using e.preventDefault() or framework redirects, create a Custom HTML Tag in GTM that fires on Initialization - All Pages:
<script>
(function() {
const targetDomains = ['checkout-brand.com'];
document.addEventListener('click', function(e) {
const anchor = e.target.closest('a') || e.target.closest('button');
if (!anchor) return;
let targetUrl = anchor.href || anchor.getAttribute('data-href');
if (!targetUrl) return;
const isTargetDomain = targetDomains.some(domain => targetUrl.includes(domain));
if (isTargetDomain && typeof google_tag_data !== 'undefined' && google_tag_data.glBridge) {
e.preventDefault();
const decoratedUrl = google_tag_data.glBridge.generateUrl(targetUrl);
window.location.href = decoratedUrl;
}
}, true);
})();
</script>
Step 3: Hardening SPA Routers to Retain _gl Parameters
Ensure your application router retains query string state upon mount. For Next.js applications (_app.tsx / layout.tsx), implement this preservation hook before page-view tags fire:
import { useEffect } from 'react';
import { useRouter } from 'next/router';
export const usePreserveGA4Linker = () => {
const router = useRouter();
useEffect(() => {
if (typeof window === 'undefined') return;
const urlParams = new URLSearchParams(window.location.search);
const gl = urlParams.get('_gl');
if (gl && !router.query._gl) {
router.replace(
{
pathname: router.pathname,
query: { ...router.query, _gl: gl },
},
undefined,
{ shallow: true }
);
}
}, [router]);
};
Step 4: Bypass Safari ITP Restrictions with Server-Side GTM (sGTM)
To solve Safari ITP's 7-day storage cap on client-side decorated cookies, shift cookie issuance from the client browser to your server infrastructure via Server-Side GTM (sGTM):
- Route your analytics calls through a first-party custom domain (e.g.,
metrics.brand.com). - Configure your sGTM container to write the
_gacookie using the HTTP Response HeaderSet-Cookieinstead of client-side JavaScript.
HTTP/1.1 200 OK
Set-Cookie: _ga=GA1.1.182930491.1710000000; Path=/; Domain=.brand.com; Max-Age=63072000; Secure; HttpOnly; SameSite=Lax
Cross-Link Reference: For step-by-step sGTM setup and first-party domain configuration, read our detailed guide on Server-Side Tagging via GTM.
Reclaim Your Marketing Attribution Accuracy
Broken cross-domain tracking inflates customer acquisition costs and hides true campaign performance. Our analytics team will audit your GTM container, inspect your client-side data layer, and eliminate data leaks across your entire conversion funnel.
Book A Free Strategy Audit →