Install Attribution Without an MMP: A Guide for Small Mobile Teams
Install attribution connects a new install back to the click that caused it. How to attribute link-driven installs on iOS and Android without an MMP.
TL;DR: Install attribution answers one question: which link, campaign, or share drove this install. For installs that came from your own links, you do not need a mobile measurement partner to answer it. A match cascade does the work in four rungs, strongest first: the Play Install Referrer on Android, the IDFV on iOS, a probabilistic match key built from the request IP plus language and timezone, and a raw-signals fallback the server hashes itself. Only the first is deterministic and scores 1.0, which is what
match_guaranteedreports, and the IDFV only returns an install the device already has; the last two return a confidence score from 0.85 to 0.30 across a match window that defaults to 6 hours and is set per link, up to 24 hours. None of it uses the IDFA or the GAID, so there is no App Tracking Transparency prompt. This is first-party attribution: no MMP, no ad-network SDK in the middle.
What Install Attribution Actually Answers
Install attribution is the practice of connecting a new app install back to the click that produced it, along with the campaign, channel, or share behind that click.
Strip away the vocabulary and it is one join. On one side there is a click: someone tapped a link at 14:02 on a phone in Toronto. On the other side there is an install: a fresh app opened for the first time at 14:09. Attribution is the claim that those two events belong to the same person, and the confidence you are entitled to have in it.
Everything useful follows from the join. Once you know which click produced the install, you know which link, which campaign, which channel, which creator's share, and which of the two onboarding funnels you were testing. Without it you have two disconnected counters: clicks that go nowhere and installs that came from nowhere.
Mobile makes that join genuinely hard. A web signup can carry a query string from the click all the way to the account record. A mobile install cannot: the tap happens in a browser, the store takes over, and the app that finally launches has no memory of the session that sent it there. The connection has to be reconstructed from whatever survived. One store passes a referrer through the install and the other does not, and most of what follows comes out of that asymmetry.
How to Attribute Installs Without a Click ID
You attribute an install without a click ID by matching it to the click through signals that survive the trip through the app store, strongest first, and by scoring the weaker matches. Only Android hands the app anything from the click: the Play Install Referrer carries the link's ID through the store, which is a link identifier rather than a unique per-click one. On iOS, a first install is matched probabilistically and returns a confidence score. WarpLink's install attribution walks these four methods in order and stops at the first one that succeeds:
- Play Install Referrer on Android (deterministic). The redirect appends
utm_source=warplink&utm_content={link_id}to the Play Store URL, and the SDK reads it back on first launch. Confidence is 1.0 andmatch_guaranteedis true. It needs Google Play Services and a visitor who actually reachedhttps://play.google.com. - IDFV repeat check on iOS (not an install match). The Identifier for Vendor finds a device the server has already seen, so it answers the same install asking again, and it returns the install the device already has, with that install's own match type and score, so it never turns a probabilistic match into a guaranteed one. The IDFV rung is skipped when the SDK reports a reinstall, so the reinstall is recorded as a new install.
- Probabilistic match within a time window (probabilistic). The server compares the request IP, normalized language, and timezone seen at the tap with the same signals at first launch, hashed one way. A match must land inside the link's window: 6 hours by default, 24 at most. Confidence runs from 0.85 down to 0.20 and shrinks further when several links, a carrier address, or a household address share the bucket.
- Raw device signals hashed server-side with the request IP (probabilistic). The app cannot see its own public IP, so the SDKs send
accept_language,timezone, andtimezone_offsetinstead of a hash. The server adds the IP from the request and tries the finest key first, then coarser ones, which is the path every SDK takes to reach the probabilistic tier.
None of this uses the IDFA or the GAID, so no tracking prompt is involved. Each rung is explained in The Match Cascade, One Rung at a Time, and the scoring is in Reading Confidence Without Fooling Yourself.
Link-Driven Installs
Someone taps a URL you own. A creator's bio link, a share sheet inside your app, an invite, a QR code on a poster, a link in a newsletter or a support reply. You control the link, so you control the click record. Attribution here means recording the tap with enough signal to recognize the install that follows. No third party has to cooperate, because no third party is involved.
That makes it a matching problem you can own end to end, and the cascade below does the matching for you.
The Match Cascade, One Rung at a Time
Link-based attribution resolves an install by walking a cascade of match strategies, strongest first. The first one that succeeds wins, and the response tells you which rung answered.
| Priority | Method | Signal | Confidence | Platform |
|---|---|---|---|---|
| 1 | Referrer | Play Install Referrer | 1.0 | Android |
| 2 | Device ID | IDFV | The stored install's own score | iOS |
| 3 | Probabilistic match | IP (server-derived) plus normalized language plus timezone | 0.20 to 0.85 | Both |
| 4 | Raw signals | accept_language plus timezone plus timezone_offset, IP added server-side | 0.20 to 0.85 | Both |
1. Referrer: the deterministic handoff Android gives you
Android is the easy platform, for one reason. When a visitor is sent to the Play Store from a link, the store carries a referrer string through the install and hands it to the app afterwards. WarpLink sets that referrer to utm_source=warplink&utm_content={link_id}, and the SDK reads it back through InstallReferrerClient on first launch, with a 2 second timeout before it falls through. The link ID is right there. No inference, no scoring, no window: the store told you which click this was.
That handoff needs Google Play Services, and it needs the visitor to actually reach the Play Store. If your link sends Android visitors without the app to a web page instead of https://play.google.com, no referrer is attached and Android drops to the probabilistic tier for no good reason. A market://details?id=... address counts as a custom scheme rather than a store address, so it loses the referrer too. Use the https://play.google.com URL as your Android store URL and this rung does the work for you.
2. Device ID: a repeat check iOS has, not an install match
Apple passes nothing through the App Store. No referrer, no receipt of the tap, nothing for a fresh install to read. What iOS does offer is the IDFV, the Identifier for Vendor, read from UIDevice.current.identifierForVendor. It is stable across every app from the same vendor on one device, and it is not the advertising identifier.
What it can answer is narrow. The IDFV identifies a device already seen, so it answers the same install asking again, and it returns the install the device already has, with that install's own match type and score, so it never turns a probabilistic match into a guaranteed one. A reinstall is deliberately left to the tier below: when the SDK sends is_reinstall: true, the server skips the device ID branch on purpose so the probabilistic match records the reinstall as the new install it is. Apple also resets the IDFV when the last app from a vendor is deleted, so for a single-app vendor a reinstall usually arrives with a new value anyway.
3. Probabilistic match: the signal-based rung
For a genuine first install on iOS, and for any Android install without a referrer, no deterministic identifier is left. What remains is a probabilistic match: the signals visible at the tap compared against the signals visible at the launch. Three components, and only three: the IP address, the normalized primary language, and the timezone. User-Agent strings and screen dimensions are not collected, because they never match reliably from a browser to a native app.
There are three variants, and which one you get depends on what the SDK could send:
| 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 is available |
The zone name earns a higher ceiling than the offset for two reasons: roughly 340 zones against 38 offsets is far more entropy, and a zone name does not shift at a daylight-saving boundary, which used to break an otherwise good tap-and-install pair outright.
4. Raw signals: why the server does the hashing
The bottom rung lets the server add the request IP. A match key that includes the IP requires knowing the IP, and an app cannot see the public address its packets exit from. Behind a relay, a VPN, or carrier NAT it has no idea.
So the SDK does not compute the hash. It sends the raw signals, accept_language and timezone and timezone_offset, and the server derives the IP from the request itself and computes the hash. The click was recorded at the edge with the same request-IP logic, so both sides hash the same way and the values can actually match. The SDKs always take this path: they never send a pre-computed match key and never send an IP.
One more field matters once your organization has more than one app. An SDK key is scoped to an organization rather than to a single app, and device-wide signals such as the IDFV are shared across every app from the same vendor, so the SDK sends app_bundle_id on iOS or app_package_name on Android and the server resolves which app is asking. Both are optional. Older SDKs send neither, which keeps the organization-wide behaviour and, in an organization with more than one app, records the attempt as ambiguous rather than guessing.
Deterministic Versus Probabilistic, and Why match_guaranteed Exists
The top rung identifies a specific click. The two bottom rungs compare a pattern. The device ID rung returns the install the device already has. That difference is not a matter of degree, and it is the most important idea in the subject.
A deterministic match is a fact. The referrer names the link ID. Confidence is 1.0 because there is nothing to be uncertain about.
A probabilistic match returns a confidence score from 0.20 to 0.85. The match key is a keyed hash of the request IP address and primary language, with time zone in enriched matches, so every phone behind one address that shares a language and a timezone lands in the same bucket. A high score means the bucket was small and the gap was short. The response carries a separate boolean, match_guaranteed:
match_type:deterministicorprobabilistic, which rung answered.match_confidence: a score from 0.0 to 1.0.match_guaranteed: true only for a deterministic match, exposed on all four SDKs asmatchGuaranteed.
The rule is short. match_guaranteed is not a credential, so use it to pick a destination, not to authorize anything. Keep auto sign-in, sessions, personal data, and purchases on your own authentication. Routing to public content, a product page, a shared post, a campaign-specific welcome: a confidence threshold is fine there.
Reading Confidence Without Fooling Yourself
A probabilistic score starts from how long ago the click happened and which variant the SDK could send:
| 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 nothing is matched at all. The match window is set per link on the server, defaults to 6 hours, and is set per link, up to 24 hours, so the bottom row applies to links configured past the default. Installs that follow a tap overwhelmingly follow it quickly.
Two multipliers then adjust the starting score, and they only ever lower it:
| Condition | Multiplier |
|---|---|
| The bucket offered more than one distinct link | 0.6 |
| Click IP was carrier-grade NAT or private | 0.6 |
| Click IP was a household IPv4 address | 0.9 |
| Click IP was IPv6 | 1.0 |
A carrier address fronts hundreds of unrelated subscribers. A home router fronts one household. The multipliers score them accordingly.
The Privacy Posture: No IDFA, No ATT Prompt, No Raw IPs
Most attribution anxiety comes from a mental model built around advertising identifiers. Link-based attribution does not use them, and the consequences are worth stating plainly.
- No IDFA on iOS, no GAID on Android. Advertising identifiers are not collected at all. Nothing in the cascade reads one.
- No App Tracking Transparency prompt. ATT gates the IDFA. The IDFV is vendor-scoped, cannot follow a user into another company's apps, and is exempt from ATT. Attribution works identically whether a user allows tracking or taps "Ask App Not to Track", and it does not interfere with an ATT strategy you run for other reasons.
- No cross-app tracking. The IDFV is scoped to your vendor by design and cannot be joined against another company's data.
- Hashed match keys, not raw signals. Probabilistic match keys are hashed one way. The raw IP is used to compute that hash on the request. The key exists only to restore the link a user tapped on the first open after install. It is never used to follow a user across other companies' apps or websites. Each tap can be matched only within the app's match window, and pending entries expire automatically. The raw IP address is not stored.
- HTTPS only. Every signal is transmitted encrypted.
There is a useful second-order effect. Because attribution and deferred deep linking are the same match, this posture is not a compromise you accept to get measurement. It is the same posture that routes a new user to the screen they asked for.
Wiring It Up
Attribution is recorded by a mobile SDK, and that SDK must be configured with an SDK key, created in the dashboard under Keys > SDK keys. An API key is a different credential with the same wl_live_ prefix and cannot hold the attribution:write scope, so configure the SDK with an SDK key.
On iOS, configure at launch and branch on the attribution fields:
import WarpLink
func attributeInstall() {
WarpLink.configure(apiKey: "wl_live_yoursdkkeyhere000000000000000000")
WarpLink.checkDeferredDeepLink { result in
guard case .success(let deepLink) = result, let deepLink = deepLink else {
showOnboarding()
return
}
if deepLink.matchGuaranteed {
navigateToSignIn(for: deepLink)
} else if (deepLink.matchConfidence ?? 0) >= 0.5 {
navigateTo(deepLink.deepLinkUrl ?? deepLink.destination)
} else {
showWelcome(suggesting: deepLink.destination)
}
}
}
On Android the referrer rung usually answers before a probabilistic match is needed:
import android.app.Application
import app.warplink.WarpLink
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
WarpLink.configure(this, "wl_live_yoursdkkeyhere000000000000000000")
WarpLink.checkDeferredDeepLink { result ->
result.onSuccess { deepLink ->
if (deepLink == null) return@onSuccess
trackInstall(
linkId = deepLink.linkId,
matchType = deepLink.matchType,
guaranteed = deepLink.matchGuaranteed,
)
}
}
}
}
In React Native the attribution result is available on its own, separate from routing:
import { WarpLink, type AttributionResult } from '@warplink/react-native';
export async function reportInstallSource(): Promise<void> {
const attribution: AttributionResult | null =
await WarpLink.getAttributionResult();
if (!attribution) {
analytics.track('install_organic');
return;
}
analytics.track('install_attributed', {
linkId: attribution.linkId,
matchType: attribution.matchType,
matchConfidence: attribution.matchConfidence,
matchGuaranteed: attribution.matchGuaranteed,
isDeferred: attribution.isDeferred,
});
}
Under all three is one request, POST /v1/attribution/match, returning matched, match_type, match_confidence, match_guaranteed, link_id, deep_link_url, destination_url, custom_params, install_id, and app_id. Route on deep_link_url ?? destination_url. A matched response always names a link, so an install whose link has since been deleted comes back as no match rather than as a match you cannot act on.
What to Do With the Data
An attributed install that only exists inside a callback is not measurement. Four surfaces make it usable.
Real-time analytics. Clicks are recorded at tap time and recent taps show in the dashboard within minutes, which changes what the numbers are for. A creator posts at 09:00 and by 09:20 you know whether the link is converting, in time to change the copy. Breakdowns cover top links, countries, referrers, devices, and recent activity, and retention scales from 90 days on the free plan to 3 years.
UTM and campaign fields. Every click carries the standard UTM set, utm_source, utm_medium, utm_campaign, utm_term, and utm_content, and those fields live on the click record the install is matched against. The link can add them for you rather than you hand-writing them into every share: the parameters object holds injected params appended to the destination, each with a mode of always to override an incoming value or default to fill only when absent, plus a forwarding allowlist controlling which incoming params pass through. Organization-level defaults are inherited and merged, and the link wins on a key conflict.
The REST API. Everything is reachable from https://api.warplink.app/v1 with a Bearer API key, at 60 requests per minute per key on every plan. links:read and analytics:read cover the reporting side, links:write covers programmatic link creation, which is what you want when links are generated per user, per creator, or per campaign in a script rather than by hand. Pagination is limit and offset, and errors come back as { error: { code, message, details } }.
MCP access. The same data is reachable from an AI client over the Model Context Protocol at https://api.warplink.app/mcp, connected with a one-time browser sign-in or an API key, free on all plans and sharing the same 60 requests per minute limit. In practice that means asking a question in the editor instead of building a report: get_link_analytics takes a link ID and a day count and returns total clicks, unique clicks, and a time series, while list_links and resolve_link cover finding and debugging a link.
One accuracy note that matters when comparing numbers across services: click uniqueness is computed server-side at write time, and a click is unique per combination of link, IP address, and 24 hour window. Deduplication happens in the database, so click counts are exact.
Frequently Asked Questions
How do you attribute app installs without a click ID? Match the install to the click through signals that survive the store round trip, strongest first. WarpLink tries four in order and the first success wins: the Play Install Referrer on Android, the IDFV for an install asking again on iOS, a probabilistic match inside a time window, and raw device signals the server hashes with the request IP. Only the Play Install Referrer is deterministic and scores 1.0. The IDFV returns an install the device already has, with its original score. The last two score 0.20 to 0.85, and the response reports the score with every match.
Do I need a mobile measurement partner for install attribution? Not for installs that came from your own links. A link-based match cascade connects the install back to the click that caused it, with no ad network in the middle, and it covers organic, referral, creator, email, and QR traffic.
Does install attribution work without the ATT prompt? Yes, and link-based attribution never touches the IDFA on iOS or the GAID on Android, so App Tracking Transparency does not apply and no prompt is needed. On iOS it uses the IDFV, which is vendor-scoped and exempt from ATT, plus a match key the server computes from the request. On Android it uses the Play Install Referrer, which needs no user permission at all. Results are identical whether a user allows tracking or taps "Ask App Not to Track".
How accurate is install attribution without an MMP?
It is exact when a deterministic signal exists and carries a 0.20 to 0.85 confidence score when one does not. The Play Install Referrer on Android scores 1.0 and sets match_guaranteed to true. A first install on iOS is matched probabilistically and returns a confidence score. A probabilistic match starts at 0.85 inside the first hour on the strongest variant, 0.80 on enriched and 0.70 on basic, and decays to 0.30 at 24 hours, before the ambiguity and shared-address multipliers reduce it further.
Can I export my attribution and click data?
Yes. Every click and every attributed install is your data. The dashboard exports analytics as CSV, the REST API at https://api.warplink.app/v1 serves links and apps to any script holding an API key with the right scopes, and an MCP client can read link analytics with get_link_analytics. All of it shares the same limit of 60 requests per minute per key on every plan.
What is the difference between deterministic and probabilistic attribution?
A deterministic match identifies a specific click, so the answer is exact: the Play Install Referrer on Android returns confidence 1.0 with match_guaranteed: true, and it is the only deterministic signal. A probabilistic match compares a match key of IP, language, and timezone, and returns a score between 0.20 and 0.85. match_guaranteed is not a credential: use it to pick a destination, and authenticate the user and check authorization separately before anything sensitive, such as signing in or showing personal data.
Related Guides
- The mechanism, per platform: attribution and deferred deep linking are the same match, so the platform guides are the implementation detail behind this post. Deferred Deep Linking on iOS, Deferred Deep Linking on Android, and Deferred Deep Linking in React Native.
- Start from the beginning: Deep Linking: The Complete Guide for Mobile Developers covers how Universal Links, App Links, and deferred links fit together.
- Weighing a specific vendor: three comparison guides cover the enterprise incumbents by name, with verified pricing: the first, the second, and the third.
- The short version: the install attribution page covers the cascade, the confidence scores, and what reaches your app on first launch, on one screen.
- Reading the numbers this produces: Mobile Link Analytics: What to Track and Why It Matters covers what to measure once installs are matching, and Creator and Influencer Link Attribution for Mobile Apps applies this same cascade to per-creator reporting.
- Reference: the install attribution concept page, the attribution API, and the per-platform attribution docs for iOS, Android, React Native, and Flutter.
- Choosing an option: 6 Install Attribution Options for Small Apps compares store reports, a DIY referrer, and link-based services by what each one measures.
How WarpLink Helps
Everything above is the mechanism, and the mechanism is the same wherever it runs. WarpLink's job is the two halves you cannot host inside your app: the click recorder at the edge that captures signals before the store takes over, and the attribution endpoint that derives the IP from the request, computes the match key, and walks the cascade for you. In your app it collapses to one call and one result carrying matchType, matchConfidence, matchGuaranteed, and the original link's deep link URL.
That result is where the three pillars meet. The linking pillar routes the user to the screen they asked for, the attribution pillar names the link and campaign that sent them, and the analytics pillar shows both within minutes, broken down by device, country, referrer, and UTM fields, exportable, and reachable from the REST API or an MCP client. One SDK, one match, three answers.
Create a free WarpLink account and get deep linking, install attribution, and real-time analytics in one SDK, with 10,000 clicks a month on the free tier and no time limit. Attribution is on every plan, and the click limit that separates them is on the pricing page. The install attribution concept page has the full cascade, scoring, and privacy reference.
See which link drove each install
Deep linking, install attribution, and real-time analytics in one SDK. Free for 10K clicks a month, no sales call.
Stuck on setup? Email support@warplink.app and we will help you get one real link working.
WarpLink Team
Building affordable, reliable link infrastructure for mobile teams. Deep linking, install attribution, and real-time analytics in one SDK.
Related Posts
How to Track App Installs From a Link on iOS and Android
How to track app installs from a link on iOS and Android: the redirect, the first-launch match, what the confidence scores mean, and a seven step setup.
Deferred Deep Linking in Flutter: One Callback for iOS and Android
Deferred deep linking Flutter guide: route the first open after install with one WarpLink 1.1.1 callback, plus first-open test steps and confidence scoring.
5 Deferred Deep Linking SDKs Compared
Compare five deferred deep linking SDKs for iOS, Android, React Native, and Flutter, including first-open matching, setup, analytics, and current pricing.