Skip to content

Add more details about cross-domain tracking #1210

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions docs/events/cross-navigation/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
title: "Cross-navigation tracking"
description: "Configure cross-domain tracking to maintain user identity between different domains in your ecosystem"
date: "2025-07-26"
sidebar_position: 5
---

When users navigate between different domains in your ecosystem—such as from your main website to a subdomain, partner site, or mobile app—their user identity is typically lost. This creates gaps in your user journey data and makes it difficult to understand the complete customer experience across your digital properties.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Subdomains should be covered by discoverRootDomain (now the default), so I'm hesitant to put it here unqualified as it's a bit misleading. It's usually a special case to want per-subdomain identities and need these settings to apply there.


Cross-navigation (also called cross-domain) tracking solves this problem by passing user identification data in URL parameters when users click links to other domains. This enables you to maintain user continuity across your applications.

To use cross-navigation tracking, configure the [web](/docs/sources/trackers/web-trackers/cross-domain-tracking/index.md) or [native mobile](/docs/sources/trackers/mobile-trackers/tracking-events/session-tracking/index.md#decorating-outgoing-links-using-cross-navigation-tracking) trackers to add an additional parameter, named `_sp`, to the querystring of outbound links. This process is called "link decoration".

Link decoration makes the added values visible in the `url` field of events on the destination page or URI. The querystring is added only to the events at the destination: it doesn't persist throughout the user's session.

## Querystring properties

The `_sp` querystring parameter has two different formats: extended or short. You can also configure exactly which properties you want to include.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can only configure them when using extended; domainUserId & timestamp are both required for both formats.


Available properties:

| Property | Description | Extended | Short |
| ---------------- | ---------------------------------------------- | -------- | ----- |
| `domainUserId` | Current tracker-generated UUID user identifier | ✅ | ✅ |
| `timestamp` | Current epoch timestamp, millisecond precision | ✅ | ✅ |
| `sessionId` | Current session UUID identifier | ✅ | |
| `subjectUserId` | Custom business user identifier | ✅ | |
| `sourceId` | Application identifier | ✅ | |
| `sourcePlatform` | Platform of the current device | ✅ | |
| `reason` | Custom information or identifier | ✅ | |

For example, the link `appSchema://path/to/page` would look like this after decoration:

```
appSchema://path/to/page?_sp=domainUserId.timestamp.sessionId.subjectUserId.sourceId.sourcePlatform.reason
```

## How are the querystring parameters processed?

By default, your pipeline will process only the short format querystring `_sp={domainUserId}.{timestamp}`, even if you've decorated the links with the extended format. To process the extra properties, you'll need to configure the [cross-navigation enrichment](/docs/pipeline/enrichments/available-enrichments/cross-navigation-enrichment/index.md).

Both default and extended formats will populate the [atomic](/docs/fundamentals/canonical-event/index.md) `refr_domain_userid` and `refr_dvce_tstamp` fields. The enrichment also adds a `cross_navigation` entity to the event.

| Feature | Default behavior | With cross-navigation enrichment |
| ------------------------------------------------------- | ---------------- | -------------------------------- |
| Processes short format `_sp={domainUserId}.{timestamp}` | ✅ | ✅ |
| Processes extended format properties | ❌ | ✅ |
| Populates `refr_domain_userid` field | ✅ | ✅ |
| Populates `refr_dvce_tstamp` field | ✅ | ✅ |
| Adds `cross_navigation` entity | ❌ | ✅ |
2 changes: 1 addition & 1 deletion docs/events/timestamps/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: "Timestamps"
description: ""
date: "2025-05-15"
sidebar_position: 4
sidebar_position: 400
---

Snowplow events have multiple timestamps that are added as the payload moves through the pipeline. The set of timestamps is designed to account for devices with incorrectly set clocks, or delays in event sending due to network outages.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
---
title: "Cross Navigation Enrichment"
title: "Cross-navigation enrichment"
sidebar_position: 5
sidebar_label: Cross Navigation
sidebar_label: Cross-navigation
---

This enrichment parses the extended cross navigation format in `_sp` querystring parameter and attaches the `cross_navigation` context to an event.
This enrichment parses the extended cross-navigation format in the `_sp` querystring parameter, and attaches a `cross_navigation` entity to an event.

The `_sp` parameter can be attached by our Web ([see cross-domain tracking](/docs/sources/trackers/web-trackers/cross-domain-tracking/index.md)) and [mobile trackers](/docs/sources/trackers/mobile-trackers/tracking-events/session-tracking/index.md#decorating-outgoing-links-using-cross-navigation-tracking) and contains user, session and app identifiers (e.g., domain user and session IDs, business user ID, source app ID). The information to include in the parameters is configurable in the trackers. This is useful for tracking the movement of users across different apps and platforms.
Check out the [cross-navigation](/docs/events/cross-navigation/index.md) page to learn why this can be useful.

The extended cross navigation format can be described by `_sp={domainUserId}.{timestamp}.{sessionId}.{subjectUserId}.{sourceId}.{platform}.{reason}`
The extended cross-navigation format is `_sp={domainUserId}.{timestamp}.{sessionId}.{subjectUserId}.{sourceId}.{sourcePlatform}.{reason}`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe note some of these fields may be empty/null because the fields to attach is configured in the tracker.


If this enrichment isn't enabled, Enrich parses `_sp` querystring parameter according to the old format, `_sp={domainUserId}.{timestamp}`
If this enrichment isn't enabled, Enrich parses the `_sp` querystring parameter according to the old format, `_sp={domainUserId}.{timestamp}`

## Configuration

- [Schema](https://github.com/snowplow/iglu-central/blob/master/schemas/com.snowplowanalytics.snowplow.enrichments/cross_navigation_config/jsonschema/1-0-0)
- [Example](https://github.com/snowplow/enrich/blob/master/config/enrichments/cross_navigation_config.json)

```json reference
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL, cool!

This kind of makes the Schema link above redundant. Maybe add title="Schema" and swap them?

I'd say embed the example as well, but this is about as boring as enrichment configs get so I'm not sure it matters. 😅 I guess it makes it easy to copy/paste?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, it's not documented but apparently we can change the "See full example on GitHub" text too if that doesn't make sense in this context.

```json reference title="Schema" referenceLinkText="See schema on Github"

https://github.com/snowplow/enrich/blob/master/config/enrichments/cross_navigation_config.json
```

```mdx-code-block
import TestingWithMicro from "@site/docs/reusable/test-enrichment-with-micro/_index.md"

Expand All @@ -28,10 +32,10 @@ import TestingWithMicro from "@site/docs/reusable/test-enrichment-with-micro/_in
This enrichment extracts `_sp` querystring parameter from the following inputs:

- The `page_url` field from the Snowplow event
- The referer uri extracted from corresponding HTTP header in the raw event
- The `referer` URI extracted from corresponding HTTP header in the raw event
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would drop this list and just say page_url; it's an implementation detail that enrich will use the referer if an event doesn't explicitly specify a page_url; that referrer just becomes the page_url.
Calling this out kind of implies the query strings will be merged, which isn't the case AFAIK.


## Output

This enrichment adds a new derived context to the enriched event with [this schema](https://github.com/snowplow/iglu-central/blob/master/schemas/com.snowplowanalytics.snowplow/cross_navigation/jsonschema/1-0-0).
This enrichment adds a new derived entity to the enriched event based on [this schema](https://github.com/snowplow/iglu-central/blob/master/schemas/com.snowplowanalytics.snowplow/cross_navigation/jsonschema/1-0-0).

Also, it continues to populate `refr_domain_userid` and `refr_dvce_tstamp` enriched event fields as before.
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ For instance, with this configuration:
SessionConfiguration(
foregroundTimeout: Measurement(value: 360, unit: .seconds),
backgroundTimeout: Measurement(value: 15, unit: .seconds)
)
)
```

</TabItem>
Expand Down Expand Up @@ -212,30 +212,23 @@ Snowplow.createTracker(getApplicationContext(), namespace, networkConfig, sessio

## Decorating outgoing links using cross-navigation tracking

:::note Not available before v6
:::note
This feature was introduced in version 6.0.0 of the iOS and Android trackers.
:::

The tracker provides a `decorateLink` API to decorate outgoing links from the mobile app to another mobile app or to a website.
This API adds an `_sp` parameter to the links containing information about the user, app, and current session.
This is useful for tracking the movement of users across different apps and platforms.
It is part of our cross-navigation solution and is equivalent to [cross-domain tracking on the JavaScript tracker](/docs/sources/trackers/web-trackers/cross-domain-tracking/index.md).

For example, calling `decorateLink` on `appSchema://path/to/page` will produce the following result:
The `decorateLink` function is part of Snowplow's [cross-navigation solution](/docs/events/cross-navigation/index.md) for tracking the movement of users across different apps and platforms.

```
appSchema://path/to/page?_sp=domainUserId.timestamp.sessionId.subjectUserId.sourceId.platform.reason
```
Choose which parameters to include using a `CrossDeviceParameterConfiguration` object. The `domainUserId` and `timestamp` are always included automatically.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this link to the API docs or something? The table below implies to me that we need to pass the values from the Controller/Configurations, but instead we actually just need to pass booleans (or a string for the reason) for each field and the tracker collects the values. This is only apparent from the examples further down ATM.


The `decorateLink` function adds the following information to the link (configurable using the `CrossDeviceParameterConfiguration` object passed to the method):
This table shows the options:

- `domainUserId` – The current tracker generated user identifier (value of `SessionController.userId`) – required.
- `timestamp` – The current ms precision epoch timestamp – required.
- `sessionId` - The current session identifier (value of `SessionController.sessionId`) – optional.
- `subjectUserId` - The custom business user identifier (value of `SubjectController.userId`) – optional.
- `sourceId` – The `appId` (value of `TrackerConfiguration.appId`) – optional.
- `platform` - The platform of the current device (value of `TrackerController.devicePlatform` – optional.
- `reason` – Identifier/information for cross-navigation – optional.
| Property | Description | Value used |
| ---------------- | -------------------------------- | ---------------------------------- |
| `sessionId` | Current session UUID identifier | `SessionController.sessionId` |
| `subjectUserId` | Custom business user identifier | `SubjectController.userId` |
| `sourceId` | Application identifier | `TrackerConfiguration.appId` |
| `sourcePlatform` | Platform of the current device | `TrackerController.devicePlatform` |
| `reason` | Custom information or identifier | Custom string |
Comment on lines +227 to +231
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this note what is/isn't enabled by default, and perhaps the reasoning for that? E.g. user_id isn't enabled by default to prevent accidentally leaking personal data to third party URIs, so requires enabling explicitly, and platform can usually be inferred from the source App ID.


<Tabs groupId="platform" queryString>
<TabItem value="ios" label="iOS" default>
Expand Down
Loading