How to track user flows from web to iOS
Jun 20, 2025
If you use PostHog on both web and iOS apps, you can track user activity across them. When a user is authenticated on both web and mobile, their events are automatically associated by identifying them through the same account. We cover identifying users in-depth in the identifying users documentation.
If a user is not authenticated on web or mobile, you can't identify the user by their account. Instead, you can track web to mobile flows using deeplinks.
This is useful for use cases like:
Tracking unauthenticated users across web and mobile.
Identifying users on pre-authentication screens like onboarding and landing pages.
Marketing campaign attribution from an unauthenticated website to an authenticated mobile app.
Understanding cross-platform tracking
When a user is authenticated on both web and mobile, their events are automatically linked because they are identified by a shared distinct_id
associated with their account. This is why we recommend calling posthog.identify
on both web and mobile as soon as the user signs up or logs in.
When a user is not authenticated, you can't call posthog.identify
. Instead, PostHog tracks anonymous events using a randomly generated distinct_id
that contains no identifiable information.
To associate anonymous events from web to mobile, you can use deeplinks:
- Get a distinct ID from the web app using
posthog.get_distinct_id
- Attach the distinct ID and other properties like UTM parameters to the deeplink
- When the user opens the mobile app, call
posthog.identify
orposthog.alias
with the distinct ID and other properties
Let's go through an example with Next.js and SwiftUI. These principles apply to any other web and mobile app.
1. Get a distinct ID from the web app
On your Next.js app, you can get the distinct ID using posthog.get_distinct_id
. This will work for both authenticated and anonymous users.
For Next.js you should call posthog.get_distinct_id
in useEffect
to ensure it's called after PostHog is initialized and right after the page is loaded.
useEffect(() => {setDistinctId(posthog.get_distinct_id())}, [])
2. Create a deeplink with the distinct ID
Using the distinct ID, you can create a deeplink that will take the user to the mobile app when the user clicks on it. You should attach other properties to the deeplink like UTM parameters to track attribution.
<div style={{ minHeight: "100vh", display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center" }}><h1>Hello world</h1>{/* You can add other properties to the deeplink like UTM parameters */}<a href={`your-super-cool-mobile-app://launch?distinct_id=${distinctId}`} target="_blank" rel="noopener noreferrer">Go to Next.js</a><p>Distinct ID: {distinctId}</p></div>
3. Handle the deeplink in the mobile app
You need to configure your mobile app to handle the deeplink, you can follow the support universal links documentation from Apple. Then, in your mobile app, you have two situations to handle:
- The user is not authenticated on the mobile app
- The user is already authenticated on the mobile app
Below is a SwiftUI example of how to handle the deeplink in the mobile app. The DeeplinkHandler
is called from onOpenURL
in the App
struct.
3.1. The user is not authenticated on the mobile app
If the user is not authenticated on the mobile app, you can call posthog.identify
directly with the distinct ID and other properties like UTM parameters.
- If the web app is also unidentified, this will create a person in PostHog that links the web and mobile events.
- If the web app is identified, this will create a person in PostHog that links the web and mobile events.
if (authenticated()) {PostHogSDK.shared.alias(distinctId)} else {PostHogSDK.shared.identify(distinctId, userProperties: [] )}
3.2 The user is authenticated on the mobile app
If the user is already authenticated on the mobile app, you can call posthog.alias
with the distinct ID passed in the deeplink. Assuming you've already identified the authenticated user on the mobile app, this will attach the distinct ID to the existing person in PostHog.
if (authenticated()) {PostHogSDK.shared.alias(distinctId)} else {PostHogSDK.shared.identify(distinctId, userProperties: [] )}
Further reading
Questions? Ask Max AI.
It's easier than reading through 669 pages of documentation