Customer journeys
A journey is the set of sessions 3x attributes to one person: the first ad click three weeks ago, a direct visit last week, the quiz on the phone today. Sessions stay cookieless sessions; a journey only links them. Linking goes exactly as far as the legal basis allows.
How sessions are linked
Every link has a reason. Two sessions that share a key (the same visitor id, click id, hashed identity …) belong to the same journey; journeys merge when a later key connects them.
| Reason | Key | Needs consent | Confidence |
|---|---|---|---|
manual | a link you set in the dashboard, the API or MCP (for example a WhatsApp lead confirmed to a session) | – | 1.0 |
consent_vid | the first-party visitor id x3_vid, or the consented ad cookies _fbp / _ttp | yes | 0.95 |
identity | the same hashed e-mail, phone or external id from a lead (identify(), leads.create) – also across devices | no (your form's legal basis) | 0.95 |
click_id | the same ad click id (gclid, gbraid, wbraid, fbclid, li_fat_id, ttclid, msclkid) arriving in the URL of several sessions, or persisted after consent (x3_clk) | no (URL parameter) | 0.9 |
daily_hash | the same daily visitor hash – same day only, its salt is deleted every night | no | 0.6 |
consent_vid (key kind install) | the install id of an app (app tracking): every session of one app install, sent only with the app's analytics purpose granted | yes (the app's consent mode) | 0.95 |
Without consent nothing is stored on the device, so a visitor who returns next week without a click id, without consent and without filling in a form starts a new journey. 3x never uses fingerprinting and never matches IP address or user agent across days.
Ambiguous keys link nothing
A key shared by too many sessions is ignored, because it no longer identifies one person: a click id pasted into a shared link, an office NAT where many colleagues use the same browser build, a test e-mail address used by a whole team.
| Key | Ignored above |
|---|---|
click ids (gclid, fbclid, …) | 20 sessions |
li_fat_id | 50 sessions |
| daily visitor hash | 8 sessions |
| hashed e-mail, phone, external id | 500 sessions |
x3_vid, _fbp, _ttp, app install id | 2 000 sessions |
A journey stops growing at 500 sessions.
Retroactive merging
Links work in both directions: when a person fills in a form in their fifth session, the identity
key links that session to the lead's sessions on other devices, and every session already linked to
either side joins the same journey. Linking runs right after ingest, after leads.create, and as a
background job (journeys.link) that catches up on everything else; the
Imported history is backfilled into journeys from click ids and leads.
Manual link and unlink
- Link:
POST /v1/projects/{project}/journeys/linkwith{sessionId, targetSessionId}or{sessionId, journeyId}(scopeleads:write), the dashboard's journey page, or the MCP tooljourneys_link. - Unlink:
POST /v1/projects/{project}/journeys/unlinkwith{sessionId}detaches a session into its own journey; automatic keys no longer pull it back (only a new manual link does).
Retention and erasure
- Sessions deleted by the project's retention drop their links with them; a journey whose sessions are all gone is removed by the maintenance job.
- Erasing a lead (
DELETE /v1/projects/{project}/leads/{id}) removes the identity links it contributed; the journey is split again where nothing else connects its parts. - Keys are stored as keyed HMACs, never as raw values – except click ids (stored on sessions anyway)
and the consented
_fbp/_ttpvalues used for conversion uploads, which are cleared after 90 days.
Visits of a lead
The lead profile's Sitzungen, the API GET /v1/projects/{project}/leads/{id}/sessions and MCP
leads_sessions list every visit of a lead – before and after the inquiry – with the evidence that
links it. There is no second link table: the visits of a lead are its anchors plus every session of
their journeys.
| Evidence | Meaning | Without consent? |
|---|---|---|
form_submit | the lead's own visit (form, quiz, x3.lead(), identify() in that visit) | yes – the visitor sent the form |
wa_ref | the WhatsApp reference code of the visit led to the lead | yes – the visitor sent the code |
conversion | a conversion of the lead was recorded in this visit | yes |
sdk_identify | the website called x3.identify({ leadId }) in this visit with the lead's external id, X3 id or CRM record id | per the project's identify consent rule |
identity | the same hashed e-mail, phone or external id in another inquiry | yes |
manual | a person assigned the visit (dashboard „Einem Lead zuordnen“, REST, MCP) – user and time are stored, audit log lead.session.link | yes – your knowledge |
visitor_id | the same consented visitor id x3_vid (or _fbp/_ttp) as a linked visit – earlier and later visits of that browser | no: consent only |
click_id | the same ad click id as a linked visit | yes (URL parameter) |
same_day | the same daily visitor hash as a linked visit – same day only | yes, same day only |
What X3 does without consent: nothing is written to or read from the visitor's device beyond what the cookieless measurement reads (§ 25 TDDDG). A returning visitor without consent is recognized only through deterministic, user-initiated facts – a form or quiz they send, a WhatsApp code, a click id in the ad link, your website reporting a known lead, a manual assignment – or on the same day through the daily hash, whose salt is deleted every night. Never through fingerprinting, never by IP or browser across days. „Kam X-mal wieder“ and the leads list's „Besuche“ therefore count only those visits for visitors without consent; Pulse's „Wiederkehrende Besucher“ counts consenting visitors only.
- Merged leads (a duplicate merged into its original) add their visits to the original
(
viaLeadnames the duplicate). - Zuordnung lösen (
POST …/leads/{id}/sessions/unlink) deletes this lead's manual andidentify({ leadId })links of the visit and detaches it from the journey, so automatic links stop applying to it; the inquiry visit itself cannot be removed. A new manual assignment brings it back. - Erasure: erasing the lead removes its manual and identity links (and the journeys they held together split again); retention deletes visits after the project's retention days – the lead stays, its visit list gets shorter.
- Sessions list: the column Lead and the filter
f.lead=eq:true(„Nur mit Lead“) show visits that belong to a lead; the leads list's Besuche counts every visit of the lead.
Visitor id lifetime
x3_vid lives 13 months (395 days) by default and at most, renewed on every visit with consent –
the period European data-protection authorities commonly accept for consented analytics cookies. A
project may shorten it with tracking.journeys.visitorIdDays (1–395, PATCH
/v1/projects/{project}/tracking-config), and it is never longer than the project's retention: a cookie
that outlives the data would only re-identify visitors whose earlier visits are already deleted. The
SDK uses the lifetime for the JavaScript cookie and sends it to the first-party proxy (POST /v1/id
with days), which sets the HttpOnly cookie with it. Name the lifetime in your privacy notice (see
Privacy).
Attribution models
Session reports keep their meaning: model **session** (the default) credits a conversion to the
session it happened in. Journey models spread one conversion over the journey's sessions that
started before it (plus 5 minutes of clock skew) within a 90-day lookback:
| Model | Credit |
|---|---|
session | the converting session gets 100 % (today's reports) |
first_touch | the journey's first session in the window gets 100 % |
last_touch | the last session before the conversion gets 100 % |
linear | equal shares for every session |
position | 40 % first, 40 % last, 20 % split across the sessions in between (one session: 100 %, two: 50/50) |
time_decay | weight halves every 7 days before the conversion: 2^(−Δt / 7 d), normalised |
Pass model to GET /v1/projects/{project}/ads/report and
GET /v1/projects/{project}/analytics/breakdown (MCP: ads_performance, analytics_breakdown) or
use the model switcher in the dashboard's Campaigns and Ads views.
- In the ads report, conversions, leads and conversion value become fractional credits under a journey model, and CPL, cost per conversion and ROAS follow the chosen model. Sessions, relevant sessions, cost per session and cost per relevant session stay session-level – they describe traffic, not outcomes.
- In a breakdown, every row keeps its session KPIs and gains
attributed: {conversions, leads, value}for the chosen model. - Credit that lands on a session without an ad (direct, organic) is not shown in ad rows, so ad
totals under journey models can be lower than under
session.
Journey coverage
GET /v1/projects/{project}/analytics/journeys (MCP journeys_coverage) reports for the filter:
journeys, multi-session journeys, coverage = share of converting sessions whose journey has more
than one session (and the same for leads), average sessions to convert, median days to convert,
and linked sessions per reason. Low coverage on a site with long sales cycles usually means few
visitors consent and few click ids or forms connect their visits.
API
| Endpoint | Scope | Purpose |
|---|---|---|
GET /v1/projects/{project}/journeys | analytics:read | Journeys with a session in the range (min_sessions, converted, cursor pagination) |
GET /v1/projects/{project}/journeys/{id}?by=journey|session|lead | analytics:read (by=lead: leads:read) | One journey: sessions with source, ad, device, key events and link reason; conversions with the credit of every model and the match quality per destination; leads only with leads:read |
POST /v1/projects/{project}/journeys/link | leads:write | Manual link |
POST /v1/projects/{project}/journeys/unlink | leads:write | Detach a session |
GET /v1/projects/{project}/leads/{id}/sessions | analytics:read + leads:summary | Every visit of a lead with its evidence (visits of a lead) |
POST /v1/projects/{project}/leads/{id}/sessions · …/sessions/unlink | leads:write | Assign a visit to a lead / remove it |
GET /v1/projects/{project}/sessions/{sessionId}/lead-candidates | analytics:read + leads:read | Leads of a visit and suggestions to assign it |
GET /v1/projects/{project}/analytics/journeys | analytics:read | Journey coverage |
A journey that was merged into another one still resolves: the response carries the surviving
journey with requestedId set to the id you asked for.
What consent changes
What the SDK stores after consent (x3_vid, x3_clk, _fbp, _fbc, _ttp) and how it is
deleted again is described in Consent and CMPs; the
legal reasoning in Legal basis.