---
title: Cross-device attribution
description: What Retidal's identity resolution actually guarantees — and what it doesn't — across sessions, visitors, and devices.
---

Retidal attributes an event to an ad click by resolving through three IDs —
`visitorId`, `sessionId`, `userId` — in a fixed priority order. This page is
about being precise on what that resolution does and does not promise. Getting
this wrong in either direction creates real support load: under-promising means
customers don't trust the numbers; over-promising means they escalate when a
conversion doesn't get credited the way marketing expected.

## Prerequisites

- Events sent with a consistent `visitorId`/`sessionId` per [Sending events](/docs/sending-data/events),
  and (once a visitor logs in) [Identifying users](/docs/sending-data/identify) called
  with the resulting `userId`.

## The three IDs

| ID | Scope | Lifetime | Who generates it |
| --- | --- | --- | --- |
| `visitorId` | Device / browser | Permanent for that device, until storage is cleared | You — persist it in `localStorage` or a long-lived cookie |
| `sessionId` | A single visit | Session-scoped — gone when the session ends | You — a fresh session cookie with no max-age |
| `userId` | Your logged-in user | Exists once the user has logged in | Your own auth system |

## Attribution priority

When an event arrives, Retidal looks for an attribution source in this order:

| Priority | Tier | Condition | What happens |
| --- | --- | --- | --- |
| Highest | Tier 0 — Explicit | The event itself carries a Click ID, `shortLinkId`, or UTM params | Used directly, no lookup needed. |
| High | Tier 1 — Session | The event's `sessionId` matches an existing attribution record | The event inherits whatever ad click first attributed that session. |
| Medium | Tier 2 — Visitor | The event's `visitorId` has an attribution record from the last 24 hours | Fuzzy cross-session match on the same device. |
| Low | Tier 3 — UTM only | Only UTM parameters are present, no Click ID | Recorded, but there's nothing to callback to an ad platform with. |

`sessionId` is what makes Tier 1 possible: every event in the same session
automatically inherits the ad attribution from that session's first hit. Omit
`sessionId` and you fall back to Tier 2 — a 24-hour fuzzy `visitorId` match,
which is both lower priority and less accurate.

## What identify does

Calling `POST /api/v1/identify` links a `visitorId` to a `userId` and, as a side
effect, backfills that `userId` onto every prior event and visit **for that same
`visitorId`**. This is how a user who clicked an ad before logging in still gets
correctly attributed once they log in — their pre-login activity on that device
gets stamped with their `userId` retroactively.

## What it does not guarantee

<Danger title="No cross-device stitching beyond a shared userId">
  Identity resolution joins activity by `userId`, `sessionId`, and `visitorId` —
  nothing in the sources for this integration describes probabilistic or
  fingerprint-based device linking (matching by IP, device model, or similar
  signals shared across two *different* `visitorId`s). If the same person clicks
  an ad on their phone and converts on their desktop, Retidal only connects those
  two events if **both devices eventually call `identify` with the same
  `userId`**. Until that happens, they are two independent visitors with two
  independent attribution histories — there is no automatic bridge.
</Danger>

Concretely:

- A visitor who clicks an ad on Device A, never logs in there, then converts on
  Device B after logging in: the conversion on Device B is attributed to
  whatever ad clicked on Device B (or nothing, if there wasn't one) — the Device
  A click is not connected, because Device A was never identified.
- A visitor who clicks an ad on Device A, logs in on Device A, then converts on
  Device B while logged in as the same user: both devices now share the same
  `userId`, so both histories exist under that `userId`, but the *attribution
  record itself* (the click → session/visitor link) still lives per-device —
  Retidal doesn't retroactively promote Device A's ad click to be "the" cause of
  a Device B conversion unless your reporting explicitly reasons over the shared
  `userId`'s full event history.
- `visitorId` fuzzy matching (Tier 2) is scoped to 24 hours and to a single
  `visitorId` — it is not a cross-visitor or cross-device mechanism at all.

Set expectations accordingly: cross-device *reporting* (seeing all of one user's
activity once they're identified everywhere) works well because everything is
keyed on `userId`. Cross-device *ad-click attribution* (crediting a specific
platform click for a conversion that happened on a different device) only works
to the extent your users identify themselves promptly on every device they use.

## What this unlocks

Understanding attribution priority unlocks correctly explaining to stakeholders why a
conversion was (or wasn't) credited to a specific ad click, and correctly setting
expectations for [Ad platforms](/docs/attribution/ad-platforms) reporting, which depends
entirely on a click having won attribution priority in the first place.

## Verify it worked

<Steps>
  <Step title="Enable Debug view">
    Console → Projects → Debug, or, from a browser session already logged into the
    console (so the `trackly_session` cookie is sent automatically):
    ```bash
    curl -s -X POST https://retidal.com/api/projects/{projectId}/debug-mode \
      -H "Cookie: trackly_session=$SESSION_COOKIE" -H "Content-Type: application/json" \
      -d '{"action":"enable"}'
    # expect: 200 — debug mode now captures incoming events for 30 minutes
    ```
    This is a **Management API** call — host `https://retidal.com`, session-cookie
    authenticated (see [Debug view](/docs/sending-data/debug-view)).
  </Step>
  <Step title="Send an event with predictable attribution inputs">
    Send a test event via [Sending events](/docs/sending-data/events) carrying the
    `sessionId`/`visitorId` (or Click ID) combination you expect to win a specific
    tier, tagged `_debug: 1` for instant capture.
  </Step>
  <Step title="Check the attribution tier in Debug view">
    ```bash
    curl -s "https://retidal.com/api/projects/{projectId}/debug-events?limit=1" \
      -H "Cookie: trackly_session=$SESSION_COOKIE"
    # expect: 200 with the captured event's attributionTier matching what you'd
    # predict from the priority order above (Tier 0 explicit > Tier 1 session >
    # Tier 2 visitor > Tier 3 UTM-only)
    ```
    Console → Projects → Debug shows the same `attributionTier` for the event —
    the per-visitor conversion detail page (Console → Projects → Visitors) shows
    postback status and platform, but not the attribution tier itself.
  </Step>
</Steps>

## If it doesn't work

If the wrong tier (or the wrong click within a tier) appears to have won, work through
[Attribution looks wrong](/docs/troubleshooting/attribution-looks-wrong).

## Next steps

<CardGroup cols={2}>
  <Card title="Identifying users" href="/docs/sending-data/identify">
    The identify call and its trait rules.
  </Card>
  <Card title="Sending events" href="/docs/sending-data/events">
    Make sure sessionId is on every event to get Tier 1 attribution.
  </Card>
</CardGroup>
