Hotjar Cross-Domain Tracking | OpsBlu Docs

Hotjar Cross-Domain Tracking

Configure cross-domain and subdomain tracking to follow users across multiple domains.

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.com and checkout.example.io)
  • Marketing sites separate from app domains (marketing.example.comapp.example.com)
  • E-commerce flows that span domains (store.example.compayment.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.com
  • blog.example.com
  • app.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

  1. Visit www.example.com
  2. Navigate to blog.example.com
  3. Check Hotjar recordings
  4. Verify the session continues uninterrupted

Common Subdomain Issues

Issue: Sessions Split Across Subdomains

Symptoms:

  • User journey from www to app shows 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:

  1. 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
  2. Track user attributes

    • See which domain the user started on
    • Understand the full journey context
    • Analyze behavior differences per domain
  3. 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

GDPR/CCPA:

  • 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

  1. Start on Domain 1

    • Hotjar loads correctly
    • User ID generated and stored
    • hj('identify') called with user ID
  2. 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
  3. Verify in Hotjar

    • Go to Recordings
    • Filter by user ID
    • See sessions from both domains for the same user
  4. Cross-Browser Testing

    • Chrome
    • Firefox
    • Safari
    • Edge
  5. 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:

  1. Generate session token on Domain 1
  2. Store mapping: token → user_id in database
  3. Pass token to Domain 2
  4. Domain 2 looks up user_id via API call
  5. 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: