Cross-Domain Tracking
Resolving session breaks and attribution issues when users navigate between domains.
When You Need Cross-Domain
Common Scenarios
| Scenario | Example | |----------|---------| | Separate checkout | shop.com → checkout.thirdparty.com | | Payment processor | site.com → stripe.com/checkout | | Multi-brand | brand1.com → brand2.com | | Subdomains (sometimes) | www.site.com → app.site.com | | Regional domains | site.com → site.co.uk |
What Happens Without It
User Journey Without Cross-Domain:
─────────────────────────────────────────────────────────
site.com checkout.external.com
Session 1 Session 2 (NEW)
─────────────────────────────────────────────────────────
Source: google/cpc Source: site.com/referral
User: abc123 User: xyz789 (NEW)
─────────────────────────────────────────────────────────
Result:
• Attribution broken (conversion credited to site.com referral)
• User appears as new visitor
• Session splits
• Self-referrals in reports
GA4 Configuration
Method 1: GA4 Admin Settings
- Admin → Data Streams → Select your stream
- Configure tag settings → Configure your domains
- Add all domains that should share sessions:
example.com
checkout.example.com
shop.example.com
example.co.uk
Method 2: GTM Configuration
In your Google Tag (GA4 Configuration):
- Configuration Settings → Domains to link
- Add comma-separated list:
example.com,checkout.example.com,shop.example.com
What This Does
When enabled, GA4 appends the _gl parameter to cross-domain links:
https://checkout.example.com/cart?_gl=1*1abc2de*_ga*MTIzNDU...
This parameter contains:
- Client ID
- Session ID
- Timestamp
- Linker version
Subdomain Tracking
Same Domain, Different Subdomains
By default, cookies are set on the full hostname:
www.example.com sets cookie on: www.example.com
app.example.com CANNOT read cookie from www.example.com
Solution: Root Domain Cookies
In GA4 Configuration tag, set:
- Fields to Set →
cookie_domain=.example.com
// Or via gtag:
gtag('config', 'G-XXXXXXXXXX', {
'cookie_domain': '.example.com'
});
The leading dot makes the cookie accessible to all subdomains.
Troubleshooting
Issue: Sessions Still Splitting
Diagnosis:
- Navigate from domain A to domain B
- Check URL for
_glparameter - Check cookie domain on both sites
// Check cookies
document.cookie.split(';').filter(c => c.includes('_ga'))
// Should see same _ga cookie value on both domains
Common Causes:
| Cause | Solution |
|-------|----------|
| _gl parameter missing | Verify link decoration enabled |
| _gl stripped by redirect | Preserve query parameters |
| Cookie domain mismatch | Set explicit cookie domain |
| JavaScript redirect | Use proper link decoration |
Issue: Self-Referrals
Symptom: Your own domain appears as a traffic source
Fix in GA4:
- Admin → Data Streams → Your stream
- More tagging settings → List unwanted referrals
- Add your domains:
example.com
checkout.example.com
Issue: _gl Parameter Not Appearing
Check Link Decoration:
// In console, check if gtag is decorating links
gtag('get', 'G-XXXXXXXXXX', 'linker', function(linker) {
console.log('Linker settings:', linker);
});
Manual Decoration (if needed):
// Force decorate specific links
document.querySelectorAll('a[href*="checkout.example.com"]').forEach(link => {
link.addEventListener('click', function(e) {
e.preventDefault();
gtag('get', 'G-XXXXXXXXXX', 'client_id', function(clientId) {
const url = new URL(link.href);
url.searchParams.set('_gl', generateLinkerParam(clientId));
window.location.href = url.toString();
});
});
});
Issue: Payment Processor Redirect
Many payment processors strip query parameters:
Solution 1: Embed in Path
// Some processors allow path-based data
// https://checkout.processor.com/session/YOUR_GA_DATA_HERE
Solution 2: Server-Side Tracking
Send purchase event from your server after payment callback:
// Server endpoint receiving payment callback
app.post('/payment-callback', async (req, res) => {
const { orderId, amount, ga_client_id } = req.body;
// Send to GA4 server-side
await sendGA4Event(ga_client_id, 'purchase', {
transaction_id: orderId,
value: amount
});
});
Solution 3: Post-Purchase Page
Redirect back to your domain for tracking:
Processor: payment.complete → your-site.com/thank-you?order=123
Your site: Tracks purchase on thank-you page
Testing Cross-Domain
Manual Testing
- Clear cookies on both domains
- Navigate from source domain to destination
- Check URL for
_glparameter - Compare GA4 client_id on both domains
// Get client_id on each domain
gtag('get', 'G-XXXXXXXXXX', 'client_id', function(clientId) {
console.log('Client ID:', clientId);
});
// Should be identical on both domains
GTM Preview Mode
- Start Preview on both containers (if separate)
- Complete cross-domain journey
- Check that same client_id appears on both
GA4 DebugView
- Enable debug mode
- Navigate across domains
- Verify same user_pseudo_id in both sessions
Complex Scenarios
Multiple Third-Party Domains
site.com → payment.processor1.com → shipping.processor2.com → site.com
Solution:
- Use server-side tracking for third-party steps
- Pass order ID through the flow
- Reconstruct journey server-side
iFrames
Cross-domain iframes can't share cookies:
// Parent page: send client_id to iframe via postMessage
const clientId = getGA4ClientId();
iframe.contentWindow.postMessage({ clientId: clientId }, 'https://iframe-domain.com');
// iframe: receive and use
window.addEventListener('message', function(event) {
if (event.data.clientId) {
gtag('config', 'G-XXXXXXXXXX', {
'client_id': event.data.clientId
});
}
});
Single Page Apps with Route Changes
// For SPAs, track virtual pageviews
window.addEventListener('routeChange', function(event) {
gtag('event', 'page_view', {
page_location: event.detail.url,
page_title: event.detail.title
});
});
Verification Checklist
## Cross-Domain Verification
### Configuration
- [ ] All domains listed in GA4 Admin
- [ ] Same Measurement ID on all domains
- [ ] Cookie domain set to root (if subdomains)
- [ ] Unwanted referrals configured
### Testing
- [ ] _gl parameter appears on cross-domain links
- [ ] _gl parameter survives redirects
- [ ] Client ID matches across domains
- [ ] Session ID matches across domains
- [ ] No self-referrals in reports
### Data Validation
- [ ] User counts accurate (no inflation)
- [ ] Session counts accurate (no splitting)
- [ ] Attribution correct (source preserved)
- [ ] Conversions track properly
BigQuery Validation
-- Check for session continuity across domains
WITH sessions AS (
SELECT
user_pseudo_id,
(SELECT value.int_value FROM UNNEST(event_params)
WHERE key = 'ga_session_id') as session_id,
REGEXP_EXTRACT(
(SELECT value.string_value FROM UNNEST(event_params)
WHERE key = 'page_location'),
r'^https?://([^/]+)'
) as domain
FROM `project.analytics_XXXXXX.events_*`
WHERE _TABLE_SUFFIX = FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE(), INTERVAL 1 DAY))
)
SELECT
user_pseudo_id,
session_id,
ARRAY_AGG(DISTINCT domain) as domains_in_session
FROM sessions
GROUP BY user_pseudo_id, session_id
HAVING COUNT(DISTINCT domain) > 1
LIMIT 100
Previous: GA4 Data Discrepancies Related: Debug & Validation