Skip to content
Dashboard

Matching

Attribution answers one question: does this first app open belong to a click Detour recorded a few minutes ago? The answer is either an exact match or a score. This page describes how the server decides and which settings you can change.

On first app open the SDK calls the Detour match API. The server picks one of two matching modes based on what the payload contains.

flowchart TD
    A[First app open] --> B{clickId present?}
    B -->|Yes| C[Deterministic lookup]
    B -->|No| E[Probabilistic scoring]
    C --> D{Click exists<br/>and unmatched?}
    E --> F[Load unmatched clicks<br/>in the time window]
    F --> G[Score candidates,<br/>keep the highest]
    G --> H{Score above<br/>the threshold?}
    D -->|Yes| M1[Return link URL]
    D -->|No| N1[Return 404]
    H -->|Yes| M2[Return link URL]
    H -->|No| N2[Return 404]

A returned link URL marks the install as non-organic. A 404 stores it as organic. The default score threshold is 850.

  • Deterministic (preferred) — an exact clickId is present, from the Android install referrer. The server looks it up and returns the link if that click exists and is still unmatched. The lookup is exact.
  • Probabilistic (fallback) — no clickId, which is the norm on iOS. Detour scores the device fingerprint sent by the SDK against recent unmatched click beacons.

The SDK sends these fields when there is no clickId:

FieldTypeMeaning
platform, model, manufacturer, systemVersionstringDevice signature
screenWidth, screenHeight, scalenumberScreen size and pixel ratio
locale{ languageTag: string }[]Device languages. languageTag is scored.
timezonestring | nullDevice timezone
userAgentstringRaw user agent
timestampnumberCapture time in Unix milliseconds, checked against the match window
pastedLinkstring, optionaliOS only. Clipboard content, compared with the click URL and its token

The server scores each candidate click using separate scoring functions. The weights used by Detour today:

SignalWeightScored on
IP exact500iOS + Android
Model + system version exact450Android *
iOS system version match350iOS
User-agent device signature match350Android
Pasteboard URL/token match350iOS
Timezone200iOS + Android
Screen match200iOS + Android
Language match100iOS + Android

*The model and system version come from high-entropy client hints. The check itself is platform-agnostic, but WebKit does not implement those hints, so only Android browsers supply the values.

The highest weights go to the signals that are hardest to spoof or to match by coincidence: IP, device signature, and clipboard. Weaker signals such as language add only a small score.

Rules that change the totals at runtime:

  • Only one device signal is scored per candidate.

    Model + system version is used when the browser supplies both. On iOS it never does, so the system version parsed from the click’s user agent is scored instead. The user-agent device signature is the fallback everywhere else. The highest reachable total is therefore 1700 on iOS and 1450 on Android.

  • Screen comparison allows a tolerance of ±1 on width and height, ±0.01 on scale.

  • The iOS pasteboard score has two tiers:

    • 350 when the token matches and the pasted URL starts with the click URL.
    • 175 when only the URL prefix matches.

Both values are app-level settings from Link settings → Matching configuration:

  • Matching threshold — default 850, allowed range 700..1200
  • Matching time window — default 15 minutes, allowed range 5..180 minutes

Clicks outside the window are ignored. The window is measured between the click timestamp and the fingerprint timestamp. The default of 15 minutes covers a normal install and limits the number of false positives.

In probabilistic mode the server:

  1. Loads the app’s unmatched clicks inside the configured time window.
  2. Scores every candidate.
  3. Keeps the highest score.
  4. Breaks ties in favor of the newer click.
  5. Accepts the highest-scoring candidate only if its score is >= threshold.

A successful match does three things:

  • marks the click as matched,
  • stores the install as non-organic in the analytics install stream,
  • returns the resolved link URL.

With no match, the install is recorded as organic and the API returns 404.

  • iOS — IP + exact pasteboard token and URL500 + 350 = 850. Accepted at the default threshold.
  • Android — IP + user-agent device signature500 + 350 = 850. Accepted at the default threshold.
  • Timezone + language + screen200 + 100 + 200 = 500. Rejected.

Start with the defaults and change them only after you have seen real traffic in analytics. If too many matches are missed, lower the threshold or extend the time window. If you see cross-attribution in dense traffic, raise the threshold or shorten the window.

For practical edge cases that affect tuning decisions, see Limitations & Known Issues.