Overview
Cross-domain tracking in Segment ensures that user identity remains consistent as visitors navigate between multiple domains you control. Unlike platform-specific solutions (GA4's _gl parameter or Adobe's Visitor API), Segment's cross-domain tracking revolves around persisting the anonymousId across domain boundaries and coordinating identity resolution across both client-side and server-side sources.
When properly configured, cross-domain tracking prevents session fragmentation, maintains attribution accuracy, and ensures that downstream destinations receive unified user profiles regardless of domain transitions.
When Cross-Domain Tracking Is Required
Common Multi-Domain Scenarios
- Separate checkout domains: Main site on
www.example.com, checkout onsecure-checkout.example.com - Third-party payment processors: Redirects to Stripe, PayPal, or other gateways that return users to confirmation pages
- Multi-brand properties:
brand-a.example.com,brand-b.example.comsharing analytics - Hosted authentication: OAuth flows or SSO providers on separate domains (e.g.,
auth.example.com) - Marketing microsites: Campaign landing pages on
promo.example.comdirecting to main site - Partner integrations: Co-branded experiences spanning your domain and partner domains
- Regional domains:
example.com,example.co.uk,example.derequiring unified tracking - App-to-web handoffs: Mobile app deep links opening web content on different domains
How Segment Handles Cross-Domain Identity
The anonymousId Lifecycle
Segment assigns each user an anonymousId (typically a UUID v4) stored in:
- First-party cookies:
ajs_anonymous_idin the browser - Local storage:
ajs_anonymous_idas a fallback - Session storage: Temporary persistence for session-scoped tracking
When a user navigates to a new domain:
- If the cookie domain doesn't match, the
anonymousIdis not accessible - A new
anonymousIdis generated, fragmenting the user's identity - Downstream destinations receive events from two "different" users
Cross-Domain Identity Strategies
Segment offers three primary approaches:
| Strategy | Use Case | Implementation Complexity |
|---|---|---|
| Query Parameter Propagation | Simple multi-domain flows | Low |
| Shared Cookie Domain | Subdomains only | Low |
| Server-Side Identity Resolution | Complex flows, high security | High |
Implementation Methods
Method 1: Query Parameter Propagation
Pass the anonymousId between domains via URL parameters.
Client-Side Implementation
// Domain A: Capture anonymousId before redirect
function redirectToDomainB(targetUrl) {
const anonymousId = analytics.user().anonymousId();
const separator = targetUrl.includes('?') ? '&' : '?';
const urlWithId = `${targetUrl}${separator}ajs_aid=${anonymousId}`;
window.location.href = urlWithId;
}
// Example usage
redirectToDomainB('https://checkout.example.com/cart');
// Results in: https://checkout.example.com/cart?ajs_aid=abc-123-def-456
Domain B: Restore anonymousId
// Domain B: Read and restore anonymousId from URL
(function() {
const urlParams = new URLSearchParams(window.location.search);
const crossDomainId = urlParams.get('ajs_aid');
if (crossDomainId) {
// Set anonymousId before analytics.js initializes
analytics.load('YOUR_WRITE_KEY', {
integrations: {
'Segment.io': {
apiHost: 'api.segment.io/v1',
protocol: 'https'
}
},
anonymousId: crossDomainId
});
} else {
// Normal initialization
analytics.load('YOUR_WRITE_KEY');
}
})();
Automatic Link Decoration
For static HTML links between domains:
// Automatically decorate all cross-domain links
analytics.ready(function() {
const anonymousId = analytics.user().anonymousId();
const crossDomains = ['checkout.example.com', 'auth.example.com'];
document.querySelectorAll('a').forEach(function(link) {
const hostname = new URL(link.href, window.location.origin).hostname;
if (crossDomains.some(domain => hostname.includes(domain))) {
const separator = link.href.includes('?') ? '&' : '?';
link.href = `${link.href}${separator}ajs_aid=${anonymousId}`;
}
});
});
Method 2: Shared Cookie Domain
For subdomains under the same parent domain, configure cookie sharing.
Analytics.js Configuration
analytics.load('YOUR_WRITE_KEY', {
integrations: {
'Segment.io': {
apiHost: 'api.segment.io/v1',
protocol: 'https'
}
},
cookie: {
domain: '.example.com', // Note the leading dot
path: '/',
maxage: 31536000, // 365 days
sameSite: 'Lax'
}
});
Cookie Scope
| Configuration | Accessible From |
|---|---|
domain: '.example.com' |
www.example.com, checkout.example.com, auth.example.com |
domain: 'example.com' |
Only example.com (not subdomains) |
domain: undefined |
Current domain only |
Method 3: Server-Side Identity Resolution
For high-security flows or when client-side storage is unreliable.
Flow Architecture
1. User starts on Domain A (client-side tracking active)
2. Server captures anonymousId before redirect
3. Redirect to Domain B includes server-generated token
4. Domain B server exchanges token for anonymousId
5. Server-side track call with correct anonymousId
6. Client-side analytics initializes with restored ID
Implementation Example
// Domain A: Server endpoint to capture anonymousId
app.post('/api/prepare-checkout', (req, res) => {
const anonymousId = req.body.anonymousId;
const sessionToken = crypto.randomBytes(32).toString('hex');
// Store mapping in Redis with TTL
redis.setex(`cross_domain:${sessionToken}`, 300, anonymousId);
res.json({
checkoutUrl: `https://checkout.example.com?token=${sessionToken}`
});
});
// Domain B: Server endpoint to restore identity
app.get('/checkout', async (req, res) => {
const token = req.query.token;
const anonymousId = await redis.get(`cross_domain:${token}`);
if (anonymousId) {
// Track server-side with correct identity
analytics.track({
anonymousId: anonymousId,
event: 'Checkout Started',
properties: {
source: 'cross_domain'
}
});
// Pass anonymousId to client
res.render('checkout', { anonymousId });
} else {
// Fallback: Generate new anonymousId
res.render('checkout', { anonymousId: null });
}
});
<!-- Domain B: Checkout page template -->
<script>
<% if (anonymousId) { %>
analytics.load('YOUR_WRITE_KEY', {
anonymousId: '<%= anonymousId %>'
});
<% } else { %>
analytics.load('YOUR_WRITE_KEY');
<% } %>
</script>
Third-Party Integration Flows
Payment Processor Return URLs
When redirecting to external payment providers:
// Before redirect to Stripe/PayPal
function initiatePayment(provider) {
const anonymousId = analytics.user().anonymousId();
const userId = analytics.user().id();
// Construct return URL with identity parameters
const returnUrl = new URL('https://example.com/confirmation');
returnUrl.searchParams.set('ajs_aid', anonymousId);
if (userId) {
returnUrl.searchParams.set('ajs_uid', userId);
}
// Pass to payment provider
if (provider === 'stripe') {
stripe.redirectToCheckout({
sessionId: 'sess_xxx',
successUrl: returnUrl.toString()
});
}
}
// On return from payment processor
function handlePaymentReturn() {
const params = new URLSearchParams(window.location.search);
const anonymousId = params.get('ajs_aid');
const userId = params.get('ajs_uid');
if (anonymousId) {
// Track purchase with preserved identity
analytics.track('Order Completed', {
order_id: params.get('order_id'),
total: parseFloat(params.get('total'))
}, {
anonymousId: anonymousId,
userId: userId || undefined
});
}
}
OAuth and SSO Flows
For authentication flows on separate domains:
// Pre-authentication: Store anonymousId
function initiateLogin() {
const anonymousId = analytics.user().anonymousId();
// Store in session storage (survives redirect)
sessionStorage.setItem('segment_anon_id', anonymousId);
// Redirect to auth provider
window.location.href = 'https://auth.example.com/login?return_to=' +
encodeURIComponent(window.location.href);
}
// Post-authentication: Restore and identify
function handleAuthCallback() {
const anonymousId = sessionStorage.getItem('segment_anon_id');
const userProfile = getUserProfileFromToken();
// Initialize with preserved anonymousId
analytics.load('YOUR_WRITE_KEY', {
anonymousId: anonymousId
});
// Identify user (links anonymousId to userId)
analytics.identify(userProfile.id, {
email: userProfile.email,
name: userProfile.name
});
// Clean up
sessionStorage.removeItem('segment_anon_id');
}
Device-Mode vs Cloud-Mode Considerations
Device-Mode Limitations
Device-mode destinations load third-party scripts directly in the browser:
- Subject to ad blockers and privacy extensions
- May not receive events if blocked before cross-domain transition
- Cookie access restricted by browser privacy features (ITP, ETP)
Cross-Domain Strategy Matrix
| Scenario | Recommended Approach |
|---|---|
| Device-mode only | Query parameter + early restoration |
| Cloud-mode only | Server-side identity resolution |
| Hybrid (both modes) | Dual tracking with fallback logic |
| High ad-block traffic | Prioritize cloud-mode destinations |
Fallback Configuration
// Enable cloud-mode fallback for critical destinations
analytics.ready(function() {
const integrations = analytics.Integrations;
// Check if Google Analytics device-mode loaded
if (!integrations['Google Analytics']) {
console.warn('GA device-mode blocked, cloud-mode active');
}
});
Edge Cases and Solutions
Safari ITP and Privacy Browsers
Problem: Safari Intelligent Tracking Prevention strips first-party cookies after 7 days for cross-site navigation.
Solution:
// Detect Safari ITP and use localStorage as primary
function getStoragePreference() {
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
const isITP = isSafari && !document.cookie.includes('ajs_anonymous_id');
return isITP ? 'localStorage' : 'cookie';
}
analytics.load('YOUR_WRITE_KEY', {
storage: {
preference: getStoragePreference()
}
});
Session Storage Limitations
Problem: Session storage doesn't persist across tabs or windows.
Solution: Use a hybrid approach with localStorage backup:
function persistAnonymousId() {
const anonymousId = analytics.user().anonymousId();
// Primary: Cookie
// Fallback: LocalStorage
// Session: SessionStorage for current tab
localStorage.setItem('ajs_anonymous_id_backup', anonymousId);
sessionStorage.setItem('ajs_anonymous_id_session', anonymousId);
}
analytics.ready(persistAnonymousId);
Mobile App to Web Transitions
Problem: Deep links from mobile apps to web don't share cookie space.
Solution: Pass anonymousId as deep link parameter:
// Mobile app: Construct deep link
const deepLink = `https://example.com/product?id=123&ajs_aid=${userAnonymousId}`;
openBrowser(deepLink);
// Web: Restore from deep link
const params = new URLSearchParams(window.location.search);
const appAnonymousId = params.get('ajs_aid');
if (appAnonymousId) {
// Use app's anonymousId for web session
analytics.load('YOUR_WRITE_KEY', {
anonymousId: appAnonymousId
});
// Track transition
analytics.track('App to Web Transition', {
product_id: params.get('id'),
source: 'mobile_app'
});
}
Validation and Testing
Pre-Implementation Checklist
| Check | Validation Method |
|---|---|
| Identify all domains in user journey | Flow mapping exercise |
| Determine if subdomains allow shared cookies | Test cookie with .domain.com |
| List third-party redirects (payment, auth) | Review checkout and login flows |
| Assess device-mode blocking rates | Check analytics.js load rate |
| Document identity requirements | Define acceptable identity loss rate |
Testing Cross-Domain Flows
1. Segment Debugger Validation
// Enable verbose debugging
analytics.debug(true);
// Monitor anonymousId transitions
analytics.ready(function() {
const originalAnonId = analytics.user().anonymousId();
console.log('Starting anonymousId:', originalAnonId);
// Store for comparison after navigation
sessionStorage.setItem('test_anon_id', originalAnonId);
});
// On destination domain
analytics.ready(function() {
const currentAnonId = analytics.user().anonymousId();
const originalAnonId = sessionStorage.getItem('test_anon_id');
if (currentAnonId === originalAnonId) {
console.log('✓ Cross-domain identity preserved');
} else {
console.error('✗ Identity fragmentation detected');
console.log('Original:', originalAnonId);
console.log('Current:', currentAnonId);
}
});
2. Live Debugger Inspection
- Open Segment source debugger:
app.segment.com/[workspace]/sources/[source]/debugger - Start user flow on Domain A
- Note the
anonymousIdin the event payload - Navigate to Domain B
- Verify subsequent events use the same
anonymousId
3. Network Tab Verification
1. Open DevTools > Network tab
2. Filter: segment.com/v1
3. Click a cross-domain link
4. Inspect request payload on destination domain:
{
"anonymousId": "abc-123-def-456", // Should match source domain
"event": "Page Viewed",
...
}
4. Destination Validation
For each connected destination, verify unified profiles:
1. GA4 > Reports > Real-time
2. Navigate cross-domain flow
3. Verify single user appears in real-time report
4. Check client_id consistency in DebugView
1. Mixpanel > Users > Live View
2. Filter to test user's anonymousId or email
3. Verify events from both domains appear in single profile
4. Check "User Activity" shows continuous journey
-- BigQuery/SQL export validation
SELECT
user_id,
amplitude_id,
event_type,
server_upload_time
FROM events
WHERE amplitude_id = 'test-anonymous-id'
ORDER BY server_upload_time;
Automated Testing
// Cypress test for cross-domain identity
describe('Cross-Domain Tracking', () => {
it('preserves anonymousId across domains', () => {
let originalAnonId;
// Domain A
cy.visit('https://www.example.com');
cy.window().then(win => {
originalAnonId = win.analytics.user().anonymousId();
});
// Navigate to Domain B
cy.get('a[href*="checkout.example.com"]').click();
// Domain B
cy.origin('https://checkout.example.com', { args: { originalAnonId } }, ({ originalAnonId }) => {
cy.window().should(win => {
const currentAnonId = win.analytics.user().anonymousId();
expect(currentAnonId).to.equal(originalAnonId);
});
});
});
});
Troubleshooting
| Symptom | Likely Cause | Solution |
|---|---|---|
Different anonymousId on destination domain |
Query parameter not passed or not restored | Verify URL decoration and restoration logic |
anonymousId present but events still fragmented |
Destinations receiving events before ID restoration | Initialize analytics.js with anonymousId before .load() |
| Cross-domain links not decorated | Link decoration runs before analytics.ready | Move decoration logic inside analytics.ready() callback |
| Shared cookie domain not working | Cookie domain doesn't match subdomain structure | Use .example.com with leading dot for subdomains |
| Safari users losing identity after 7 days | ITP cookie expiration | Implement server-side identity resolution or use localStorage |
| Payment processor return loses session | Return URL missing identity parameters | Include ajs_aid in success_url and cancel_url |
| OAuth callback creates new anonymousId | Pre-auth anonymousId not persisted | Store in sessionStorage or URL state parameter before redirect |
| Server-side events not attributed to web session | Server using different anonymousId | Ensure server receives and uses client's anonymousId |
| Destination shows duplicate users | Cloud-mode and device-mode sending different IDs | Disable device-mode or ensure consistent ID across both modes |
| Mobile app to web transition fragments | Deep link doesn't include anonymousId | Pass anonymousId in deep link URL parameters |
Monitoring and Alerting
Key Metrics to Track
// Track cross-domain transition rate
analytics.track('Cross-Domain Navigation', {
from_domain: document.referrer ? new URL(document.referrer).hostname : 'direct',
to_domain: window.location.hostname,
anonymousId_source: getAnonymousIdSource() // 'url', 'cookie', 'localStorage', 'generated'
});
function getAnonymousIdSource() {
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get('ajs_aid')) return 'url';
if (document.cookie.includes('ajs_anonymous_id')) return 'cookie';
if (localStorage.getItem('ajs_anonymous_id')) return 'localStorage';
return 'generated';
}
Alert Thresholds
| Metric | Threshold | Alert Action |
|---|---|---|
anonymousId_source: 'generated' rate |
>10% of cross-domain traffic | Investigate identity propagation failure |
| Destination profile duplication | >5% week-over-week increase | Check device-mode vs cloud-mode sync |
| Cross-domain conversion drop | >15% week-over-week decrease | Verify payment/checkout flows |
Best Practices
- Early Restoration: Initialize analytics with
anonymousIdbefore calling.load()to prevent event loss - URL Parameter Cleanup: Remove
ajs_aidfrom URL after restoration to avoid sharing or bookmarking - Consent Alignment: Ensure consent state propagates cross-domain to maintain compliance
- Fallback Strategy: Always have cloud-mode destinations as fallback for blocked device-mode tracking
- Testing Coverage: Include cross-domain flows in regression test suites
- Documentation: Maintain a domain map with identity strategy for each transition
- Privacy Compliance: Hash or tokenize identity parameters if passing through untrusted third parties
- Expiration Handling: Set appropriate TTLs for server-side identity tokens (5-10 minutes max)
- Monitoring: Track identity source distribution and alert on anomalies
- Destination Alignment: Verify each destination supports cross-domain identity resolution