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 fingerprint built from the request IP plus language and timezone, and a raw-signals fallback the server hashes itself. The first two are deterministic and score 1.0, which is what
match_guaranteedreports; the last two are scored guesses that decay from 0.85 to 0.30 across a match window that defaults to 6 hours and is capped at 24. None of it uses the IDFA or the GAID, so there is no App Tracking Transparency prompt. What a mobile measurement partner still owns is paid user acquisition: ad network postbacks, SKAdNetwork, cost and ROAS data, and fraud detection. This guide covers both halves honestly, so you can tell which one you actually need.
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.
The Two Worlds: Link-Driven Installs and Paid Ad Networks
"Attribution" covers two problems that look similar and are solved completely differently. Confusing them is why so many small teams shop for infrastructure they do not need.
World one: 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.
World two: paid ad networks. You buy installs. The tap happens inside another company's app, on infrastructure you do not control, and the network will not tell you who tapped. Attribution here is a negotiation: the network defines a postback format, you register an endpoint, the network decides what it sends and when. On iOS, Apple sits in the middle too, and SKAdNetwork returns aggregated, delayed, privacy-thresholded conversion data rather than per-install detail.
World one is a matching problem you can own end to end. World two is a reconciliation problem across many counterparties, each with its own attribution rules and a financial incentive to claim credit. Most small teams live almost entirely in world one and buy for world two.
What a Mobile Measurement Partner Is Actually For
This section stays honest even though the post is published by a link infrastructure service.
An enterprise mobile measurement partner exists to solve world two, and it solves it properly. Four things belong to that category and are not replaced by link-based attribution:
- Ad network integrations and postbacks. Hundreds of maintained integrations, each kept current as networks change formats. Rebuilding even a handful yourself is a permanent maintenance job, not a project.
- SKAdNetwork and AdAttributionKit. Apple's privacy-preserving attribution for paid campaigns, including conversion value schema design, postback decoding, and the aggregation and delay rules. If you buy iOS ads at any scale you need this pipeline, and it is genuinely intricate.
- Cost, revenue, and ROAS data. Pulling spend from every network, joining it to installs and in-app revenue, and producing a cost per install and a return on ad spend you can act on. That is a data engineering product, not an attribution trick.
- Fraud detection. Click injection, click spamming, SDK spoofing, device farms. When you pay per install, someone is trying to manufacture installs, and catching that requires a large cross-customer dataset.
If you are spending real money on paid user acquisition, buy the mobile measurement partner. Nothing here replaces those four capabilities, and any vendor telling you otherwise is selling.
What the enterprise category is bad at is being small. Pricing starts where a funded growth team lives, the contract usually involves a sales call, and the deep linking arrives bundled into a measurement suite you pay for whether or not you buy ads. For a team whose entire acquisition story is organic, referral, creator, and email, that is a large invoice for features you will never open.
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 | 1.0 | iOS |
| 3 | Fingerprint | 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: the deterministic rung iOS has, with an asterisk
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.
The asterisk is what it can answer. The IDFV identifies a device already seen, so it answers the same install asking again, and it cannot answer a genuine first install, because there is no earlier record to match. 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 fingerprint 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. Fingerprint: the probabilistic 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 fingerprint: 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 exists because of a gap you cannot close inside the app. A fingerprint keyed on 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 fingerprint 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 two top rungs identify a specific device or a specific click. The two bottom rungs compare a pattern. 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. The IDFV names the device. Confidence is 1.0 because there is nothing to be uncertain about.
A probabilistic match is a hypothesis. The fingerprint keys on a network, not on a device: 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. It does not mean the answer is the right person. That is why a confidence threshold is the wrong control for anything sensitive, and why the response carries a separate boolean:
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 three SDKs asmatchGuaranteed.
The rule is short. Auto sign-in, restoring a session, showing personal data, resuming a purchase: gate all of it on match_guaranteed. Routing to public content, a product page, a shared post, a campaign-specific welcome: a confidence threshold is fine there, because being wrong costs a slightly odd first screen rather than a privacy incident.
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 capped at 24, so the bottom row only applies to links configured past the default. The short window is an accuracy choice, not a limitation: installs that follow a tap overwhelmingly follow it quickly, and every extra hour adds almost no genuine matches while letting more strangers behind the same address into the bucket.
Two multipliers then reduce the starting score, and they only ever reduce it, because a confident wrong answer is worse than an honest uncertain one:
| 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. Scoring them the same would be dishonest.
It also helps to know why an install returns no match, because three of the four reasons are about the network rather than your code. The IP changed between the tap and the launch, which is what WiFi at the tap and mobile data at the launch produces. The tap went out over IPv6 and the launch over IPv4 on the same dual-stack network. The visitor never reached the Play Store, so no referrer was attached and the probabilistic tier had to try. Or the match window had passed, or an earlier install already claimed the click.
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 fingerprints, not raw signals. Probabilistic fingerprints are stored only as a one-way hash. The raw IP is used to compute that hash on the request, not persisted as the matching key.
- Short retention of the matching material. The stored click ages out with the match window, at most 24 hours, so the probabilistic bucket is never a long-lived profile.
- 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 API Keys > SDK key. This matters more than it sounds. An API key is a different credential with the same wl_live_ prefix, and it cannot hold the attribution:write scope. An app configured with an API key still resolves deep links, so the integration looks healthy, while every attribution call returns 403 FORBIDDEN and no installs ever appear.
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 {
restoreSession(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 fingerprint 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 and installs land as they happen rather than in an overnight batch, 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 a flat 60 requests per minute 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 rather than at the cache layer, which is slower to write and correct to read.
When to Graduate to a Mobile Measurement Partner
Being honest about the boundary is more useful than pretending there is not one. Move to an enterprise mobile measurement partner when any of these is true:
- You are spending meaningfully on paid user acquisition across multiple ad networks and need per-network install counts you can reconcile against invoices.
- You need SKAdNetwork or AdAttributionKit pipelines for iOS paid campaigns, including conversion value schemas and postback aggregation.
- You need cost and ROAS joined to installs automatically, pulled from every network's reporting API.
- You are being defrauded, or spending enough that you should assume you are.
None of those triggers is "we got bigger". Organic, referral, creator, and email acquisition do not get harder to attribute at scale; there are simply more of them. The trigger is a paid channel you do not control.
The two are not mutually exclusive either. A team running paid campaigns through a measurement partner while owning its own links for shares, invites, QR codes, and email will usually keep both, because the measurement partner is not where you want your product's deep linking to live.
Frequently Asked Questions
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 completely. You need a mobile measurement partner when you buy paid user acquisition and need ad network postbacks, SKAdNetwork aggregation, cost and ROAS data, or a fraud suite. Those are separate problems from knowing which of your links drove an install.
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 fingerprint 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 a scored guess when one does not. The Play Install Referrer on Android and the IDFV on iOS both score 1.0 and set match_guaranteed to true. A probabilistic fingerprint 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 by the 24 hour ceiling, before the ambiguity and shared-address multipliers reduce it further. Treat anything under 0.5 as a hint rather than a fact.
What happens to attribution under iCloud Private Relay? Private Relay lowers confidence on the probabilistic tier, it does not break the system. The IP is one of three fingerprint components, and a relayed exit address that differs between the tap and the launch produces a different key, so the match comes back weaker or absent. Deterministic matches through the IDFV or the Play Install Referrer are unaffected. Short match windows keep the probabilistic damage contained, which is one reason the default is 6 hours.
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 flat 60 requests per minute limit on every plan.
What is the difference between deterministic and probabilistic attribution?
A deterministic match identifies a specific device or a specific click, so the answer is exact: the Play Install Referrer on Android and the IDFV on iOS both return confidence 1.0 with match_guaranteed: true. A probabilistic match compares a fingerprint shaped by the network rather than the device, so it returns a score between 0.20 and 0.85 and can name the wrong user however high the score reads. Gate anything sensitive, such as auto sign-in or showing personal data, on match_guaranteed, never on a score threshold.
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 and honest notes on what each does better: the first, the second, and the third.
- Reference: the install attribution concept page, the attribution API, and the per-platform attribution docs for iOS, Android, and React Native.
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 fingerprint, 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 parameters.
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 as they happen, filterable 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. The install attribution concept page has the full cascade, scoring, and privacy reference.
WarpLink Team
Building affordable, reliable link infrastructure for mobile teams. Deep linking, install attribution, and real-time analytics in one SDK.
Related Posts
Adjust Alternative for Small Teams: 2026 Pricing and Features
Adjust publishes a free tier capped at 1,500 monthly attributions and quotes everything above it. WarpLink is an Adjust alternative priced from $0 to $149.
Deferred Deep Linking on Android: How to Implement It in Kotlin
Deferred deep linking on Android hands the tapped link to the app on first launch through the Play Install Referrer, with fingerprint matching as the fallback.
Deferred Deep Linking in React Native: One API for iOS and Android
Deferred deep linking in React Native routes the pre-install link on first launch through one JavaScript API, backed by the Play referrer and iOS matching.