How to set up cross-domain tracking in PostHog

Nov 19, 2024

Using multiple domains or subdomains is a common way to split up a product. For example, a company might have a marketing website, a web app, and documentation, each with their own domain or subdomain.

We recommend using the same PostHog project across your product to ensure accuracy and consistency, but this can require tracking across domains. To help with this, PostHog has both automatic and manual options for setting up cross-domain tracking, and this tutorial shows how to set them up.

Cross subdomain tracking with automatic first-party cookies

If you are using the JavaScript snippet or posthog-js, PostHog automatically sets a first-party cookie that works between subdomains. For example, the same cookie would work for posthog.com, us.posthog.com, and merch.posthog.com. If you use initialize posthog-js or the snippet with either localStorage+cookie (the default) or just cookie, you don’t need to do more work to make this happen.

First-party cookies ensure you get the most data possible, as third-party cookies often get blocked or removed. Similarly, you can set up a reverse proxy to send events from your domain so they aren’t intercepted by tracking blockers.

Passing IDs across domains

Tracking users across different domains, like posthog.com and hogflix.com, requires some extra work. You need to pass users' distinct_id and session_id between PostHog initializations to ensure they are connected. This not only ensures tracking is accurate and consistent, but session replays and feature flag evaluations work across domains too.

Getting IDs on the first domain

To do this, first, we need to get the distinct_id and session_id from the first domain. To do this, we can call posthog.get_distinct_id() and posthog.get_session_id() and pass them in the URL hash to the second domain.

A barebones example in React looks like this:

JavaScript
import React from 'react'
import { Link } from 'react-router-dom'
import { usePostHog } from 'posthog-js/react'
function FirstDomain() {
const posthog = usePostHog()
const sessionId = posthog.get_session_id()
const distinctId = posthog.get_distinct_id()
return (
<div className="card">
<h1>First Domain</h1>
<p>This is the first domain.</p>
<Link to={`domain2.com#session_id=${sessionId}&distinct_id=${distinctId}`}>Go to Second Domain</Link>
</div>
)
}
export default FirstDomain

Bootstrapping the IDs on the second domain

On the second domain, we can check for the session_id and distinct_id in the URL hash and then bootstrap those values in our PostHog initialization.

In our barebones React example, our second domain's main.jsx file would look like this:

JavaScript
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'
import posthog from 'posthog-js'
import { PostHogProvider} from 'posthog-js/react'
// Parse hash parameters
const hashParams = new URLSearchParams(window.location.hash.substring(1))
const distinct_id = hashParams.get('distinct_id')
const session_id = hashParams.get('session_id')
posthog.init("<ph_project_api_key>", {
api_host: "https://us.i.posthog.com",
bootstrap: {
sessionID: session_id,
distinctID: distinct_id
}
})
createRoot(document.getElementById('root')).render(
<StrictMode>
<PostHogProvider client={posthog}>
<App />
</PostHogProvider>
</StrictMode>,
)

This ensures that the distinct_id and session_id are the same on both domains.

Identifying and merging users on the second domain

We recommend bootstrapping the IDs if you need to track users across domains. If you can't bootstrap, for instance if you can't access the window object, you can use a combination of identify and alias to merge the users instead. This requires a stable distinct ID like a username, app user ID, or email and won't connect the session replays and feature flag evaluations between the two domains.

The setup on the first domain is the same: a link with the distinct_id in the URL hash (we don't use the session_id).

On the second domain, we need to use the stable distinct ID to:

  1. identify the anonymous distinct ID from the second domain
  2. alias the distinct ID from the first domain

In our barebones React example, this looks like this:

JavaScript
import React, { useState } from 'react'
import { usePostHog } from 'posthog-js/react'
function SignUpPage() {
const [username, setUsername] = useState('')
const posthog = usePostHog()
const hashParams = new URLSearchParams(window.location.hash.substring(1))
const old_distinct_id = hashParams.get('distinct_id')
const handleSubmit = (e) => {
e.preventDefault()
posthog.identify(username)
if (old_distinct_id) {
posthog.alias(old_distinct_id, username)
}
}
return (
<div className="card">
<h1>Sign Up</h1>
<form onSubmit={handleSubmit}>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder="Enter username"
/>
<button type="submit">Submit</button>
</form>
</div>
)
}
export default SignUpPage

This connects the events from both domains to the same user. See our docs on identifying users and using alias for more details.

Using third-party cookies (or their equivalent)

Another way is using third-party cookies (or an equivalent method) to get the data from one domain to another. This isn’t recommended.

For example, you can add an iframe from one domain and pull tracking data into another. To apply cookies when viewing an iframe on another domain, your domain needs to set cookies with the following header:

Set-Cookie: session=your_session;
SameSite=None; Secure

This effectively makes it a third-party cookie, which many browsers, sites, and extensions block and remove. You can try to use it, but we recommend you use other methods.

Further reading

Subscribe to our newsletter

Product for Engineers

Read by 25,000+ founders and builders.

We'll share your email with Substack

Comments