Install Attribution
Understand how the WarpLink Android SDK attributes installs using the Play Install Referrer, fingerprint matching, and privacy controls.
WarpLink uses two tiers of attribution matching to connect app installs to the links that drove them.
Match Cascade
The SDK tries matching methods in order. The first successful match wins.
| Tier | Method | Signal | Confidence |
|---|---|---|---|
| 1 | Play Install Referrer | utm_source=warplink&utm_content={link_id} | 1.0 |
| 2 | Enriched fingerprint | IP (server-derived) + normalized language + timezone | Up to 0.85 |
Deterministic Matching (Play Install Referrer)
Used for first-install attribution via the Google Play Store.
When a user clicks a WarpLink URL and is redirected to the Play Store, the referrer URL includes utm_source=warplink&utm_content={link_id}. After install, the SDK reads the referrer via InstallReferrerClient and extracts the link ID for a direct match.
- Confidence: always 1.0
- Requires Google Play Services
- No user permission needed
- 2-second timeout (falls back to fingerprint if exceeded)
Probabilistic Matching (Enriched Fingerprint)
Used when the Play Install Referrer is unavailable (sideloaded apps, non-Google-Play devices).
The SDK collects the device's preferred language and timezone and sends them to the attribution API. The server derives the request's IP address, computes the fingerprint from all three signals, and checks for a match.
Fingerprint Variants
| Variant | Timezone component | Sent by |
|---|---|---|
enriched_tz | IANA zone name, for example America/Toronto | SDKs that collect a zone name |
enriched | Minute offset | Older SDKs, and devices that cannot resolve a zone |
basic | None | Fallback when neither matches |
The zone name carries far more entropy than the offset (roughly 340 zones against 38 offsets) and does not shift at a daylight-saving boundary, which previously broke a click-then-install pair across the change. It therefore earns a higher confidence ceiling.
Confidence by Elapsed Time
| Time Since Click | enriched_tz | enriched | basic |
|---|---|---|---|
| < 1 hour | 0.85 | 0.80 | 0.70 |
| < 3 hours | 0.65 | 0.60 | 0.50 |
| < 6 hours | 0.50 | 0.45 | 0.35 |
| < 24 hours | 0.30 | 0.25 | 0.20 |
Past 24 hours there is no match at all. The 24 hour band only applies to links whose match window is set past the 6 hour default.
Two further signals can only reduce the score, because a confident wrong answer is worse than an honest uncertain one:
- Bucket ambiguity. More than one claimable click shared the fingerprint: x0.6
- IP sharing. Carrier-grade NAT or a private address: x0.6. A household IPv4 address: x0.9. IPv6: x1.0
Reinstalls
A reinstall counts as an install. When a device uninstalls your app and installs it again, the SDK runs attribution again and the result lands in your install numbers like any other install.
The attribution request carries is_reinstall: true in that case. The SDK reads it from a SharedPreferences marker that Auto Backup restores, while the marker that stops a duplicate check inside one install lives in noBackupFilesDir and is never restored. The flag labels the install and never blocks the request. See Reinstall Persistence.
Interpreting Results
WarpLink.checkDeferredDeepLink { result ->
result.onSuccess { deepLink ->
if (deepLink != null) {
val confidence = deepLink.matchConfidence ?: 0.0
when {
confidence >= 0.5 -> navigateTo(deepLink.destination)
confidence >= 0.3 -> showSuggestion(deepLink.destination)
else -> showOnboarding()
}
}
}
}
| Confidence | Recommended Action |
|---|---|
| 1.0 (deterministic) | Route directly to content |
| > 0.5 (probabilistic) | Route to content: high confidence |
| 0.3–0.5 | Show content with confirmation |
| < 0.3 | Show generic onboarding |
Gate Sensitive Actions on matchGuaranteed
matchGuaranteed is true only for a deterministic match, which on Android means the Play Install Referrer. Gate anything sensitive, such as auto sign-in or showing personal data, on this flag rather than on a matchConfidence threshold. A probabilistic match is a best guess made from a network-shaped fingerprint and can name the wrong user.
if (deepLink.matchGuaranteed) {
restoreSession(deepLink)
} else {
navigateTo(deepLink.destination) // public content only
}
Privacy
Signals Collected
| Signal | Purpose | Source |
|---|---|---|
| Preferred language | Fingerprint (server normalizes to the primary subtag) | Locale.getDefault().toLanguageTag() |
| Timezone name | Fingerprint, sent as timezone by SDKs that collect a zone name | ZoneId.systemDefault().id |
| Timezone offset | Fingerprint, the fallback for older SDKs | TimeZone.getDefault().getOffset(...) (DST-aware) |
| Play Install Referrer | Deterministic match | Google Play InstallReferrerClient |
The IP address is the third fingerprint component and is derived server-side from the request. The SDK does not collect a User-Agent or screen dimensions: they never match reliably from a browser to a native app.
Not Collected
- GAID (Google Advertising ID)
- Android ID
- Location data
- Contacts or personal data
- Cross-app identifiers
All signals are sent over HTTPS and used solely for attribution matching. Play Install Referrer data is read once at first launch and not stored beyond the attribution result.