Mixpanel Cross-Domain Tracking | OpsBlu Docs

Mixpanel Cross-Domain Tracking

How to track users across multiple domains and subdomains with Mixpanel. Covers cross-domain configuration, cookie handling, session stitching, and.

Overview

Cross-domain tracking in Mixpanel ensures user identity and session continuity when visitors navigate between multiple domains, subdomains, or applications you own. Unlike Google Analytics' linker parameter approach, Mixpanel relies on preserving the distinct_id and maintaining consistent device identification across domain boundaries through cookies, local storage, or explicit identifier passing.

Proper cross-domain tracking prevents user fragmentation, maintains accurate funnel analysis, and ensures attribution integrity when conversion paths span multiple properties.

When Cross-Domain Tracking Is Required

Multi-Domain Scenarios

  • Separate checkout or payment domains: Main site on www.example.com, checkout on secure.example.com or checkout.example.com
  • Third-party payment processors: Redirects to Stripe, PayPal, or other gateways that return users to confirmation pages
  • Regional or brand domains: example.com, example.co.uk, brand.example.com
  • Marketing microsites: Campaign-specific landing pages on different domains that funnel to main conversion flows
  • Partner integrations: Co-branded experiences spanning partner domains
  • Mobile app to web transitions: Deep links from native apps opening web views on different domains

When Cross-Domain Tracking May Not Be Needed

  • Subdomains with shared root domain: If properly configured, cookies can span www.example.com and blog.example.com
  • Same-domain SPAs: Single-page applications that don't cross domain boundaries
  • Isolated properties: Completely separate products with independent user bases and analytics needs

Mixpanel Identity Management

distinct_id Fundamentals

Mixpanel tracks users through the distinct_id property:

  1. Anonymous tracking: On first visit, Mixpanel generates a random distinct_id (device_id)
  2. Identified tracking: After login/signup, you set distinct_id to a known user identifier
  3. Identity merge: The alias or identify method links anonymous and identified IDs

Identity Persistence Mechanisms

Mixpanel stores distinct_id in multiple locations:

Storage Method Scope Persistence Cross-Domain
Cookie (mp_<project_token>_mixpanel) Subdomain or root domain Until expiration Only if same root domain
localStorage Specific origin Persistent No
Session storage Specific origin Session only No

Cross-Domain Configuration

Subdomain Tracking Setup

For tracking across subdomains of the same root domain:

// Initialize Mixpanel with cross-subdomain tracking
mixpanel.init('YOUR_PROJECT_TOKEN', {
  cross_subdomain_cookie: true,
  cookie_domain: '.example.com'  // Note the leading dot
});

This configuration:

  • Sets cookies at the root domain level (.example.com)
  • Makes cookies accessible from www.example.com, blog.example.com, checkout.example.com
  • Maintains distinct_id automatically across subdomain navigation

For HTTPS-only environments:

mixpanel.init('YOUR_PROJECT_TOKEN', {
  cross_subdomain_cookie: true,
  cookie_domain: '.example.com',
  secure_cookie: true,  // Requires HTTPS
  cookie_name: 'mp_example_user'  // Custom cookie name
});
Option Type Description
cross_subdomain_cookie boolean Enable cross-subdomain tracking (default: false)
cookie_domain string Root domain for cookie (e.g., '.example.com')
secure_cookie boolean Require HTTPS for cookie (default: false)
cookie_name string Custom cookie name (default: 'mp_<token>_mixpanel')
cookie_expiration number Days until cookie expires (default: 365)

True Cross-Domain Tracking (Different Root Domains)

Manual distinct_id Propagation

When navigating between completely different domains:

// Source domain (www.example.com)
function navigateWithIdentity(targetUrl) {
  const distinctId = mixpanel.get_distinct_id();
  const urlWithId = `${targetUrl}?mp_distinct_id=${encodeURIComponent(distinctId)}`;
  window.location.href = urlWithId;
}

// Usage
document.getElementById('checkout-button').addEventListener('click', function(e) {
  e.preventDefault();
  navigateWithIdentity('https://checkout.otherdomain.com/cart');
});
// Destination domain (checkout.otherdomain.com)
mixpanel.init('YOUR_PROJECT_TOKEN');

// Read distinct_id from URL and set it
const urlParams = new URLSearchParams(window.location.search);
const crossDomainId = urlParams.get('mp_distinct_id');

if (crossDomainId) {
  mixpanel.identify(crossDomainId);

  // Clean up URL to remove parameter
  const cleanUrl = window.location.pathname + window.location.hash;
  window.history.replaceState({}, document.title, cleanUrl);
}

Server-Side Session Bridging

For sensitive flows where URL parameters are undesirable:

// Source domain - store distinct_id server-side
app.post('/checkout/initiate', async (req, res) => {
  const distinctId = req.body.distinct_id;
  const sessionToken = generateSecureToken();

  // Store in Redis/database with expiration
  await redis.setex(`mp_session:${sessionToken}`, 300, distinctId);

  res.json({
    checkoutUrl: `https://checkout.otherdomain.com?session=${sessionToken}`
  });
});

// Destination domain - retrieve distinct_id
app.get('/checkout', async (req, res) => {
  const sessionToken = req.query.session;
  const distinctId = await redis.get(`mp_session:${sessionToken}`);

  res.render('checkout', {
    distinctId: distinctId
  });
});
<!-- Destination page template -->
<script>
  mixpanel.init('YOUR_PROJECT_TOKEN');
  const distinctId = '<%= distinctId %>';
  if (distinctId) {
    mixpanel.identify(distinctId);
  }
</script>

Authentication Flow Identity Management

Signup Flow

// Before signup - user is anonymous
mixpanel.track('Signup Form Viewed', {
  source: 'homepage',
  plan: 'free'
});

// After signup - create alias link
function handleSignup(userId, email) {
  const previousId = mixpanel.get_distinct_id();

  // Create alias (only call once per user)
  mixpanel.alias(userId, previousId);

  // Identify with new user ID
  mixpanel.identify(userId);

  // Set user properties
  mixpanel.people.set({
    '$email': email,
    '$created': new Date().toISOString(),
    'signup_source': 'web'
  });

  mixpanel.track('Signup Completed', {
    method: 'email'
  });
}

Login Flow

// Login on different domain
function handleLogin(userId) {
  // Identify user (no alias needed for returning users)
  mixpanel.identify(userId);

  // Update last login
  mixpanel.people.set({
    '$last_login': new Date().toISOString()
  });

  mixpanel.track('Login', {
    domain: window.location.hostname
  });
}

Cross-Domain Login Decision Matrix

Scenario Action Method
First-time signup Link anonymous to user ID mixpanel.alias(userId, anonymousId) then identify(userId)
Returning user login (same domain) Identify user mixpanel.identify(userId)
Returning user login (different domain) Identify + pass userId mixpanel.identify(userId) on both domains
Cross-domain after login Propagate userId in URL/session Pass userId parameter
Logout Reset to new anonymous ID mixpanel.reset()

Third-Party Checkout Integration

Payment Provider Flow

When redirecting to Stripe, PayPal, or other payment processors:

// Before redirect to Stripe
function initiateCheckout() {
  const distinctId = mixpanel.get_distinct_id();

  // Track checkout initiation
  mixpanel.track('Checkout Initiated', {
    amount: 99.99,
    currency: 'USD'
  });

  // Create Stripe session with return URL containing distinct_id
  fetch('/api/create-checkout-session', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      distinct_id: distinctId,
      success_url: `https://example.com/success?mp_id=${distinctId}&session_id={CHECKOUT_SESSION_ID}`,
      cancel_url: `https://example.com/cart?mp_id=${distinctId}`
    })
  })
  .then(res => res.json())
  .then(data => {
    window.location.href = data.url;
  });
}

// On success page
window.addEventListener('load', function() {
  const urlParams = new URLSearchParams(window.location.search);
  const distinctId = urlParams.get('mp_id');
  const sessionId = urlParams.get('session_id');

  if (distinctId) {
    mixpanel.identify(distinctId);

    // Track purchase
    fetch(`/api/get-order/${sessionId}`)
      .then(res => res.json())
      .then(order => {
        mixpanel.track('Purchase Completed', {
          order_id: order.id,
          amount: order.amount,
          currency: order.currency,
          payment_provider: 'stripe'
        });
      });
  }
});

First-Party Proxy Configuration

To maintain consistent tracking across domains and bypass ad blockers:

Nginx Proxy Setup

# Proxy Mixpanel tracking endpoint
location /mp/ {
    proxy_pass https://api.mixpanel.com/;
    proxy_set_header Host api.mixpanel.com;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_ssl_server_name on;
}

# Proxy Mixpanel decision endpoint
location /mp-decide/ {
    proxy_pass https://decide.mixpanel.com/;
    proxy_set_header Host decide.mixpanel.com;
    proxy_set_header X-Real-IP $remote_addr;
}

JavaScript Configuration with Proxy

mixpanel.init('YOUR_PROJECT_TOKEN', {
  api_host: 'https://yoursite.com',  // Your domain
  api_routes: {
    track: '/mp/track',
    engage: '/mp/engage',
    groups: '/mp/groups',
    record: '/mp/record'
  },
  cross_subdomain_cookie: true,
  cookie_domain: '.yoursite.com'
});

Benefits of First-Party Proxying

Benefit Impact
Bypass ad blockers Reduces tracking data loss by 20-40%
Maintain cookies across domains Cookies from your domain aren't blocked
Improved attribution Less fragmentation from blocking
GDPR compliance First-party data collection

iFrame and Widget Tracking

Embedded Widget Communication

For widgets embedded across different domains:

// Host page (example.com)
window.addEventListener('message', function(event) {
  if (event.data.type === 'widget_request_identity') {
    const distinctId = mixpanel.get_distinct_id();

    // Send distinct_id to widget
    const widgetFrame = document.getElementById('widget-iframe');
    widgetFrame.contentWindow.postMessage({
      type: 'identity_response',
      distinct_id: distinctId
    }, 'https://widget.otherdomain.com');
  }
});
// Widget page (widget.otherdomain.com)
mixpanel.init('YOUR_PROJECT_TOKEN');

// Request identity from parent
if (window.parent !== window) {
  window.parent.postMessage({
    type: 'widget_request_identity'
  }, '*');
}

// Receive identity
window.addEventListener('message', function(event) {
  if (event.data.type === 'identity_response') {
    const distinctId = event.data.distinct_id;
    mixpanel.identify(distinctId);

    // Now track widget events with parent's identity
    mixpanel.track('Widget Loaded');
  }
});

iFrame Security Considerations

  • Use specific origins instead of '*' in postMessage for security
  • Validate message origins before processing
  • Only share identity data, not sensitive information
  • Consider if iframe tracking genuinely adds analytical value

Edge Cases and Special Scenarios

SPA Navigation Across Subdomains

For single-page apps that navigate across subdomains:

// Initialize with SPA support
mixpanel.init('YOUR_PROJECT_TOKEN', {
  cross_subdomain_cookie: true,
  cookie_domain: '.example.com',
  persistence: 'localStorage'  // More reliable for SPAs
});

// Track virtual page views
function trackPageView(pageName) {
  mixpanel.track('Page View', {
    page: pageName,
    url: window.location.href,
    referrer: document.referrer
  });
}

// Use with SPA router
router.afterEach((to, from) => {
  trackPageView(to.name);
});

When users click links from mobile apps:

// Mobile app (React Native)
import mixpanel from 'mixpanel-react-native';

function openWebCheckout(userId, productId) {
  const distinctId = userId;
  const webUrl = `https://example.com/checkout/${productId}?mp_id=${distinctId}&source=mobile_app`;

  // Track the transition
  mixpanel.track('Web Checkout Opened', {
    product_id: productId,
    source: 'mobile_app'
  });

  Linking.openURL(webUrl);
}
// Web checkout page
mixpanel.init('YOUR_PROJECT_TOKEN');

const urlParams = new URLSearchParams(window.location.search);
const mobileUserId = urlParams.get('mp_id');
const source = urlParams.get('source');

if (mobileUserId && source === 'mobile_app') {
  mixpanel.identify(mobileUserId);
  mixpanel.track('Checkout Page Viewed', {
    transition_source: 'mobile_app'
  });
}

EU Data Residency

For EU data residency requirements with cross-domain tracking:

mixpanel.init('YOUR_PROJECT_TOKEN', {
  api_host: 'https://api-eu.mixpanel.com',
  cross_subdomain_cookie: true,
  cookie_domain: '.example.com',
  ignore_dnt: false  // Respect Do Not Track
});

Validation and Testing

Pre-Deployment Testing Checklist

Test Scenario Expected Result Validation Method
Navigate from domain A to B Same distinct_id on both Check Live View
Login on domain B after browsing A Anonymous events merge with user Profile timeline
Signup flow across domains Alias created, no duplicate profiles People page
Third-party checkout return Session continues, purchase attributed Funnel report
iFrame widget loads Widget inherits parent distinct_id Event properties
Ad blocker enabled Events still tracked via proxy Network tab

Live View Validation

  1. Open Mixpanel Live View
  2. Start a test session on source domain
  3. Note the distinct_id for your test events
  4. Navigate to destination domain
  5. Verify same distinct_id appears in Live View
  6. Check that session properties persist

Identity Merge Validation

// Add test helper to console
window.mpTest = {
  getDistinctId: () => mixpanel.get_distinct_id(),
  checkIdentity: () => {
    console.log('Current distinct_id:', mixpanel.get_distinct_id());
    console.log('Cookie value:', document.cookie.match(/mp_[^=]+_mixpanel=([^;]+)/)?.[1]);
  }
};

// Test cross-domain flow
// On domain A:
mpTest.checkIdentity();  // Note the ID

// On domain B:
mpTest.checkIdentity();  // Should match domain A if configured correctly

Profile Merge Settings Verification

  1. Go to Project Settings > Identity Merge
  2. Check merge rules configuration:
    • ID Merge: Merges profiles when alias is called
    • Original ID Merge: Legacy behavior
    • None: No automatic merging (manual only)
  3. Verify merge behavior matches expectations

Network Tab Inspection

Monitor requests during cross-domain navigation:

  1. Open DevTools > Network
  2. Filter for mixpanel.com or your proxy endpoint
  3. Navigate across domains
  4. Verify distinct_id in request payloads matches
  5. Check that /decide endpoint responses are consistent

Verify cookie settings in DevTools:

// Check cookie configuration
function inspectMixpanelCookie() {
  const cookies = document.cookie.split(';');
  const mpCookie = cookies.find(c => c.trim().startsWith('mp_'));

  if (mpCookie) {
    const [name, value] = mpCookie.split('=');
    const decoded = JSON.parse(decodeURIComponent(value));
    console.log('Mixpanel Cookie:', {
      name: name.trim(),
      distinct_id: decoded.distinct_id,
      device_id: decoded.device_id
    });
  }
}

Funnel Report Validation

After implementing cross-domain tracking:

  1. Create a funnel that spans both domains
  2. Run the funnel for test users
  3. Verify conversion rates make sense
  4. Check for drop-offs at domain boundaries
  5. Compare before/after implementation metrics

Troubleshooting

Symptom Likely Cause Solution
Different distinct_id on each domain Cookies not shared Set cross_subdomain_cookie: true and cookie_domain
distinct_id lost after redirect URL parameter not passed Add distinct_id to redirect URL
Duplicate profiles created alias called multiple times Call alias only once on signup, use identify for login
Cookie not persisting Cookie domain mismatch Verify cookie_domain matches actual domain structure
Events blocked on subdomain Ad blocker targeting Implement first-party proxy
Identity lost in iframe Cross-origin restrictions Use postMessage to share distinct_id
Checkout return loses session Return URL missing distinct_id Include distinct_id in success_url
Different distinct_id in mobile app vs web No identifier bridging Pass user ID from app to web URL
EU users not tracking Wrong API endpoint Use api_host: 'https://api-eu.mixpanel.com'
Session restarts after login Reset called incorrectly Only call reset() on logout
Properties inconsistent across domains Super properties not synced Re-register super properties after identify

Common Implementation Errors

Calling alias Multiple Times

// WRONG - creates duplicate profiles
function handleLogin(userId) {
  mixpanel.alias(userId);  // Don't alias on every login
  mixpanel.identify(userId);
}

// CORRECT - alias only on signup
function handleSignup(userId) {
  const anonId = mixpanel.get_distinct_id();
  mixpanel.alias(userId, anonId);  // Only once
  mixpanel.identify(userId);
}

function handleLogin(userId) {
  mixpanel.identify(userId);  // Just identify
}
// WRONG - will not work across subdomains
mixpanel.init('TOKEN', {
  cookie_domain: 'www.example.com'  // Missing leading dot
});

// CORRECT
mixpanel.init('TOKEN', {
  cookie_domain: '.example.com'  // Includes all subdomains
});

Missing distinct_id Propagation

// WRONG - doesn't pass identity
window.location.href = 'https://checkout.example.com/cart';

// CORRECT
const distinctId = mixpanel.get_distinct_id();
window.location.href = `https://checkout.example.com/cart?mp_id=${distinctId}`;

Best Practices

  1. Use subdomain architecture when possible: Easier to maintain shared cookies than cross-domain parameter passing
  2. Implement first-party proxy: Reduces tracking loss from ad blockers
  3. Test alias behavior thoroughly: Incorrect aliasing creates unfixable duplicate profiles
  4. Document identity flow: Maintain clear documentation of when alias vs identify is called
  5. Monitor profile merge activity: Watch for unexpected duplicate profiles
  6. Use server-side for sensitive flows: Don't expose user IDs in URLs for security-critical flows
  7. Validate in staging first: Test cross-domain flows in staging environment
  8. Set up alerts: Monitor for sudden drops in cross-domain conversion metrics
  9. Respect user privacy: Honor DNT flags and consent preferences across all domains
  10. Clean up URL parameters: Remove identity parameters from URL after reading to avoid sharing