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.
How matching starts
Section titled “How matching starts”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.
Matching modes
Section titled “Matching modes”- Deterministic (preferred) — an exact
clickIdis 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.
Probabilistic payload
Section titled “Probabilistic payload”The SDK sends these fields when there is no clickId:
| Field | Type | Meaning |
|---|---|---|
platform, model, manufacturer, systemVersion | string | Device signature |
screenWidth, screenHeight, scale | number | Screen size and pixel ratio |
locale | { languageTag: string }[] | Device languages. languageTag is scored. |
timezone | string | null | Device timezone |
userAgent | string | Raw user agent |
timestamp | number | Capture time in Unix milliseconds, checked against the match window |
pastedLink | string, optional | iOS only. Clipboard content, compared with the click URL and its token |
Scoring weights
Section titled “Scoring weights”The server scores each candidate click using separate scoring functions. The weights used by Detour today:
| Signal | Weight | Scored on |
|---|---|---|
| IP exact | 500 | iOS + Android |
| Model + system version exact | 450 | Android * |
| iOS system version match | 350 | iOS |
| User-agent device signature match | 350 | Android |
| Pasteboard URL/token match | 350 | iOS |
| Timezone | 200 | iOS + Android |
| Screen match | 200 | iOS + Android |
| Language match | 100 | iOS + 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 versionis 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
±1on width and height,±0.01on scale. -
The iOS pasteboard score has two tiers:
350when the token matches and the pasted URL starts with the click URL.175when only the URL prefix matches.
Threshold and time window
Section titled “Threshold and time window”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..180minutes
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.
Candidate selection
Section titled “Candidate selection”In probabilistic mode the server:
- Loads the app’s unmatched clicks inside the configured time window.
- Scores every candidate.
- Keeps the highest score.
- Breaks ties in favor of the newer click.
- Accepts the highest-scoring candidate only if its score is
>= threshold.
After a match
Section titled “After a match”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.
Example scores
Section titled “Example scores”- iOS — IP + exact pasteboard token and URL —
500 + 350 = 850. Accepted at the default threshold. - Android — IP + user-agent device signature —
500 + 350 = 850. Accepted at the default threshold. - Timezone + language + screen —
200 + 100 + 200 = 500. Rejected.
Tuning
Section titled “Tuning”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.