Overview
Cross-domain tracking allows you to follow user journeys across multiple domains or subdomains as a single continuous session. This is essential for businesses with:
- Multiple domains (e.g.,
example.comandcheckout.example.io) - Marketing sites separate from app domains (
marketing.example.com→app.example.com) - E-commerce flows that span domains (
store.example.com→payment.secure-gateway.com) - Subdomain-based architecture (
www.example.com,blog.example.com,shop.example.com)
How Hotjar Handles Domains
Default Behavior
By default, Hotjar treats each domain as a separate site:
- Different cookies set per domain
- Sessions don't carry across domains
- User journeys appear fragmented
Subdomain Tracking (Automatic)
Good news: Hotjar automatically tracks across subdomains when they share the same root domain.
Works by default:
www.example.com → blog.example.com → app.example.com
All three share cookies because they're on .example.com
Does NOT work automatically:
example.com → different-domain.com
example.com → checkout.io
These require manual cross-domain setup.
Subdomain Tracking Setup
Prerequisites
- Same Hotjar Site ID on all subdomains
- Cookies set at root domain level
Installation
Step 1: Install Same Site ID on All Subdomains
Use the same Hotjar tracking code (with identical Site ID) on:
www.example.comblog.example.comapp.example.com- Any other subdomain
Step 2: Verify Cookie Domain
Hotjar cookies are automatically set at the root domain level:
Cookie: _hjSessionUser_123456
Domain: .example.com ← Note the leading dot
This allows the cookie to be read by all subdomains.
Step 3: Test
- Visit
www.example.com - Navigate to
blog.example.com - Check Hotjar recordings
- Verify the session continues uninterrupted
Common Subdomain Issues
Issue: Sessions Split Across Subdomains
Symptoms:
- User journey from
wwwtoappshows as two separate sessions - User attributes not persisting across subdomains
Causes:
- Different Site IDs on different subdomains
- Cookie domain restrictions
- Third-party cookie blocking
Solutions:
Verify Identical Site ID:
// On www.example.com
console.log(window._hjSettings.hjid); // Should be: 123456
// On app.example.com
console.log(window._hjSettings.hjid); // Should be: 123456 (same)
Check Cookie Domain:
// Open DevTools > Application > Cookies
// Look for _hjSessionUser_*
// Domain should be: .example.com (with leading dot)
Manually Set Cookie Domain (if needed):
This is rarely needed, but if Hotjar doesn't auto-detect correctly:
// Custom cookie domain setting
(function(h,o,t,j,a,r){
h.hj=h.hj||function(){(h.hj.q=h.hj.q||[]).push(arguments)};
h._hjSettings={
hjid: YOUR_SITE_ID,
hjsv: 6,
hjdomain: '.example.com' // Force root domain
};
// ... rest of tracking code
})(window,document,'https://static.hotjar.com/c/hotjar-','.js?sv=');
Cross-Domain Tracking (Different Root Domains)
The Challenge
Hotjar doesn't natively support cross-domain tracking like Google Analytics. Cookies can't be shared across different root domains due to browser security policies.
Example scenario:
User flow:
1. Lands on marketing.com
2. Clicks "Buy Now"
3. Redirects to secure-checkout.io
4. Completes purchase
Without cross-domain setup:
- Two separate sessions in Hotjar
- No way to connect the user journey
Workaround: User Identification
While you can't share cookies, you can manually link users using the Identify API.
Implementation Strategy
Step 1: Generate Unique User ID
Create a consistent user identifier that can be passed across domains:
// On first domain (marketing.com)
function getUserId() {
// Check if user already has ID
let userId = localStorage.getItem('user_id');
if (!userId) {
// Generate new ID
userId = 'user_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
localStorage.setItem('user_id', userId);
}
return userId;
}
const userId = getUserId();
hj('identify', userId, {
source: 'marketing_site'
});
Step 2: Pass User ID Across Domains
Append the user ID to URLs when navigating to different domains:
// When user clicks link to different domain
document.getElementById('checkout-button').addEventListener('click', function(e) {
e.preventDefault();
const userId = getUserId();
const targetUrl = 'https://checkout.example.io/cart';
const urlWithUser = `${targetUrl}?user_id=${userId}`;
window.location.href = urlWithUser;
});
Step 3: Read User ID on Destination Domain
On the receiving domain (checkout.example.io), extract the user ID from the URL and identify the user:
// On second domain (checkout.example.io)
function getUserIdFromUrl() {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get('user_id');
}
const userId = getUserIdFromUrl();
if (userId) {
// Store locally for subsequent pages
localStorage.setItem('user_id', userId);
// Identify in Hotjar
hj('identify', userId, {
source: 'checkout_site',
referrer_domain: document.referrer
});
}
Step 4: Continue Identifying on All Pages
Ensure subsequent page navigations on the second domain maintain the user ID:
// On all pages of checkout.example.io
const userId = localStorage.getItem('user_id') || getUserIdFromUrl();
if (userId) {
hj('identify', userId);
}
Complete Cross-Domain Example
Domain 1: marketing.example.com
<script>
// Hotjar tracking code here (with Site ID 123456)
// Generate or retrieve user ID
function getUserId() {
let userId = localStorage.getItem('user_id');
if (!userId) {
userId = 'user_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
localStorage.setItem('user_id', userId);
}
return userId;
}
// Identify user
const userId = getUserId();
hj('identify', userId, {
domain: 'marketing',
timestamp: new Date().toISOString()
});
// Modify links to checkout
document.querySelectorAll('a[href*="checkout.example.io"]').forEach(link => {
link.addEventListener('click', function(e) {
const originalUrl = this.href;
const separator = originalUrl.includes('?') ? '&' : '?';
this.href = `${originalUrl}${separator}user_id=${userId}`;
});
});
</script>
Domain 2: checkout.example.io
<script>
// Hotjar tracking code here (with SAME Site ID 123456)
// Extract user ID from URL
function getUserIdFromUrl() {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get('user_id');
}
// Retrieve or extract user ID
let userId = localStorage.getItem('user_id') || getUserIdFromUrl();
if (userId) {
// Store for future pages
localStorage.setItem('user_id', userId);
// Identify in Hotjar
hj('identify', userId, {
domain: 'checkout',
referrer: document.referrer,
timestamp: new Date().toISOString()
});
// Clean URL (optional, for aesthetics)
if (window.history.replaceState && getUserIdFromUrl()) {
const cleanUrl = window.location.pathname + window.location.hash;
window.history.replaceState({}, document.title, cleanUrl);
}
}
</script>
Analyzing Cross-Domain Journeys
With this setup, you can:
Filter recordings by user ID
- In Hotjar, go to Recordings
- Filter by user attribute:
user_id - View all sessions for a specific user across both domains
Track user attributes
- See which domain the user started on
- Understand the full journey context
- Analyze behavior differences per domain
Create cross-domain funnels (manual)
- Track events on both domains
- Use user ID to connect the dots
- Analyze drop-off between domains
Important Considerations
Privacy & Compliance
- Passing user IDs across domains may require additional consent
- Update privacy policy to disclose cross-domain tracking
- Provide opt-out mechanisms
Best Practices:
- Use non-identifiable user IDs (not emails or names)
- Hash sensitive identifiers
- Respect DNT (Do Not Track) headers if applicable
Security
URL Parameter Exposure:
- User IDs in URLs are visible in browser history
- Could be logged in server access logs
- Consider using POST requests for sensitive flows
Alternative: POST-Based Handoff
For sensitive flows, use a POST form instead of URL parameters:
<!-- On marketing.example.com -->
<form method="POST" action="https://checkout.example.io/cart">
<input type="hidden" name="user_id" value="USER_ID_HERE">
<input type="hidden" name="session_data" value="ENCRYPTED_DATA">
<button type="submit">Proceed to Checkout</button>
</form>
On the receiving end, read from POST data instead of URL params.
Session Continuity Limitations
Even with user ID linking, Hotjar limitations remain:
- Recordings may still appear as separate sessions
- Heatmap data won't automatically merge
- Funnel analysis across domains requires manual effort
Why: Hotjar's architecture treats each page load as a potential new session. User ID helps you filter and connect, but doesn't merge sessions natively.
Testing Cross-Domain Tracking
Test Checklist
Start on Domain 1
- Hotjar loads correctly
- User ID generated and stored
-
hj('identify')called with user ID
Navigate to Domain 2
- User ID passed via URL or POST
- User ID extracted and stored on Domain 2
-
hj('identify')called with same user ID
Verify in Hotjar
- Go to Recordings
- Filter by user ID
- See sessions from both domains for the same user
Cross-Browser Testing
- Chrome
- Firefox
- Safari
- Edge
Privacy Testing
- User can opt out of tracking
- User IDs are not PII
- Consent is collected appropriately
Debugging
Check User ID Transfer:
// On Domain 1 (before navigation)
console.log('User ID:', getUserId());
// On Domain 2 (after navigation)
console.log('Received User ID:', getUserIdFromUrl());
console.log('Stored User ID:', localStorage.getItem('user_id'));
Verify Identify Calls:
// Enable Hotjar debug mode
localStorage.setItem('hjDebug', 'true');
// Trigger identify
hj('identify', userId, attributes);
// Check console for confirmation
Monitor Network Requests:
Open DevTools > Network, filter by "hotjar", and look for:
- Identify API calls
- User ID in request payload
Alternative Approaches
Iframe-Based Tracking
If the second domain is embedded via iframe, you can use postMessage to share user IDs:
Parent Page (Domain 1):
const iframe = document.getElementById('checkout-iframe');
const userId = getUserId();
iframe.contentWindow.postMessage({
type: 'user_id',
value: userId
}, 'https://checkout.example.io');
Iframe (Domain 2):
window.addEventListener('message', function(event) {
if (event.origin !== 'https://marketing.example.com') return;
if (event.data.type === 'user_id') {
const userId = event.data.value;
hj('identify', userId);
}
});
Server-Side User Mapping
For more robust tracking, maintain a server-side user mapping:
- Generate session token on Domain 1
- Store mapping:
token → user_idin database - Pass token to Domain 2
- Domain 2 looks up user_id via API call
- Identify in Hotjar with retrieved user_id
This avoids exposing user IDs in URLs and provides a single source of truth.
Best Practices Summary
Do:
- Use the same Hotjar Site ID across all domains/subdomains
- Pass non-PII user identifiers between domains
- Document your cross-domain tracking approach
- Test thoroughly across browsers and devices
- Disclose cross-domain tracking in privacy policy
Don't:
- Pass PII (emails, names) in URL parameters
- Assume Hotjar natively supports cross-domain like GA
- Forget to identify users on the receiving domain
- Ignore privacy regulations
- Use different Site IDs across domains you want to link
Troubleshooting
Sessions Still Splitting
Check:
- Both domains use same Site ID
- User ID passed correctly via URL
hj('identify')called on both domains with same ID- localStorage available on both domains
User Attributes Not Persisting
Check:
- Identify API called after Hotjar loads
- User ID is a string, not a number
- Attributes are valid (no nested objects)
Can't Filter by User ID in Hotjar
Check:
- User ID provided to
hj('identify')call - Recordings have captured since identify was implemented
- Sufficient time passed for data to process (few minutes)
Next Steps: