Attribution
How attribution works
Appy matches each new install to the link that brought it, or counts it as organic. This page explains where Appy looks for the link, which installs are verified, and what is stored and for how long.
How Appy finds the link
The SDK reports every cold start and every Appy link that opens the app. When a link opens the app, Appy returns that link. On the first launch after install, Appy looks for the link in this order and stops at the first answer:
- 1
The link that opened the app
A Universal Link or App Link on your link domain opened the installed app, and the SDK sent that URL.
- 2
A click id from the redirect page
In-app browsers such as Instagram often ignore Universal Links. Appy’s redirect page then opens your custom scheme with an
appy_click_id, which points to the exact tap. - 3
The store’s install referrer
On Android, Google Play and Huawei AppGallery hand the link to the app after install. The SDK reads it on the first launch.
- 4
A recent tap that fits the install
When none of the above exists, as on most iOS installs, Appy looks for a recent tap on one of your links that fits the new install. A tap can be claimed by one install only.
The result is one of two states. An attributed install carries the link, its parameters, the UTM source, medium and campaign of the tap, and the tap time. An organic install did not come from an Appy link, or Appy could not connect it to one.
Attributed and organic installs
| Where | What you see |
|---|---|
| The app, on the first launch | onDeepLink receives found (with isDeferred true), notFound or failed, and onAttribution receives the attribution: organic, or the link’s slug and title with source, medium, campaign and tap time. |
| Your backend | GET /v1/apps/{appId}/installs/{installId}, GET /v1/apps/{appId}/installs?userId= and the filtered install list GET /v1/apps/{appId}/installs return each install with attribution.status (attributed or organic) and attribution.verified. |
| App statistics | GET /v1/apps/{appId}/stats, the dashboard and the MCP tool get_app_attribution: installs first seen in a window, attributed, verified and organic, installs by store, a row per day, results by UTM source, medium and campaign, events and revenue, and the links that brought them. Add a link slug to see one link’s results. See App statistics. |
The first launch is answered once. If the app retries because a response was lost, Appy returns the same answer instead of looking again, and later launches without a link never pick up someone else’s tap.
Verified installs
The install lookup returns attribution.verified. It is true only when Appy can prove the link: the link opened the app, a click id came back, or a store install referrer carried the link. An install matched to a recent tap is attributed but not verified.
{
"installId": "7f0c2b9e-3a61-4c2e-9d0b-5c1e8f2a9b10",
"platform": "ios",
"userId": "u_123",
"store": "app_store",
"attribution": {
"status": "attributed",
"verified": true,
"link": {
"id": "8d3f5a0b-2c4e-4f7a-9b1d-6e8f0a2c4d6e",
"slug": "spring",
"url": "https://acme.appy.to/spring"
},
"source": "referral",
"medium": null,
"campaign": null,
"clickedAt": "2026-09-25T10:04:05Z"
}
}Use verified installs for money
Routing a new user to the right screen is a good use of any attributed install. Granting a referral reward, a discount or account access is not: require verified from your backend, or ask the user to confirm the others.
Strict attribution
Strict attribution is a per-app setting: only count installs Appy can verify; everything else is organic. Turn it on in the dashboard under Apps & SDK > your app > Strict attribution, or set strictAttribution with POST /v1/apps or PATCH /v1/apps/{appId}. It is off by default and needs no change in the app.
- On Android, installs from Google Play or AppGallery that started from the link stay attributed, because the install referrer carries it.
- On iOS, the App Store passes nothing from the tap to the install, so a first launch from the home screen reports
notFoundand the install counts as organic. - Links that open an installed app are always delivered, with or without strict attribution.
Deferred deep link windows
| Window | Length | What it means |
|---|---|---|
| Tap record | 24 hours | A tap on a link can be delivered as a deferred deep link, with its parameters and tap time, for 24 hours. A click id older than that no longer resolves. |
| Matching a tap to a new install | 2 hours | Without a click id or install referrer, Appy only considers taps from the last two hours. Installs opened later count as organic. |
| Store install referrer | Kept by the store | Google Play keeps the referrer for 90 days after the install. After the tap record has expired, Appy still resolves the link from the referrer, without the tap time. |
| First-launch answer | 5 seconds by default | The SDK waits deferredDeepLinkTimeout (iOS) or deferredDeepLinkTimeoutMillis (Android) for the answer before reporting a timeout. Attribution still arrives later. |
What is stored, and for how long
| Data | What it contains | Kept for |
|---|---|---|
| Tap records | The link, the clicked URL and its public parameters, platform, OS version, the device model on Android when the browser reports it, and the tap time. No IP address. | 24 hours |
| Network index for matching | Tap references filed under a keyed hash of the network address. The hash changes every day and cannot be turned back into an address without Appy’s secret. | 2 hours |
| Installs | Install id, platform, OS version, device model, app and SDK version, locale, the installing store, the user id you set, the matched link and its parameters. | Until you delete them through the API, or the account is deleted |
| Events | Event name, properties, revenue, currency, user id, whether it came from the SDK or your server, timestamps. | Until you delete them through the API, or the account is deleted |
- Tap records are written only for accounts with at least one registered app, and only for iOS and Android taps. The regular click statistics of your links are separate and unchanged.
- Parameters whose names start with
appy_are never stored or returned. - With tracking turned off in the app, Appy stores nothing for that device and only resolves links the user opened.
Deleting an app keeps its data
Deleting an app revokes its publishable key and keeps its installs and events, which can then no longer be reached or deleted through the API. Erase a user’s data before you delete the app.
Deleting a user’s data
When someone closes their account or asks to be forgotten, erase their data from your backend with a secret key. The SDKs have no delete call, because your backend owns the relationship with the user.
curl -X DELETE "https://api.appy.to/v1/apps/$APP_ID/installs?userId=u_123" \
-H "Authorization: Bearer $APPY_SECRET_KEY"This deletes every install of the app that reported the user id, every event of those installs, and every other event sent with that user id, and answers with the counts: {"deletedInstalls": 1, "deletedEvents": 3}. To delete one install and its events, call DELETE /v1/apps/{appId}/installs/{installId}. Deleting is permanent, and app statistics computed afterwards no longer include the deleted data. Details are in the REST API guide.
Deleting stored data does not stop an installed app from sending more. Before you delete, clear the user id in the app (setUserId(nil) on iOS, setUserId(null) on Android) or turn tracking off (isTrackingEnabled = false); otherwise the next launch is stored as a new, organic install.
Privacy
- The SDKs do not read advertising identifiers (IDFA, GAID), do not show the App Tracking Transparency prompt and send nothing to ad networks.
- The matching layer never stores an IP address.
- The install id is random, lives in the app’s own storage and disappears with the app.
- One switch,
isTrackingEnabled, stops events and the first-launch lookup. Set it tofalsebefore the SDK starts to wait for consent.
Apple does not allow deriving data from a device to identify it uniquely, whatever the user answered to the tracking prompt, so review deferred deep linking with your privacy team. Apps that want only verified installs attributed turn on strict attribution.
Apple campaign analytics
App Store installs cannot be matched one by one without a tap on a link, but Apple counts them per campaign. When an app has ios.appStoreId and ios.providerToken (the App Store Connect provider token), every Appy link that sends someone to that app’s App Store page gets pt, ct (the link slug, cut to Apple’s 30-character limit) and mt=8.
- App Store Connect then reports impressions, product page views and first-time downloads per Appy link under App Analytics > Sources > Campaigns.
- Apple shows a campaign after 24 hours and once it has at least five first-time downloads. Use slugs of up to 30 characters to keep campaigns apart.
- These numbers are Apple’s own aggregate count, next to Appy’s per-install attribution, not part of it. Campaign parameters you set on a target yourself are kept.
Check a setup
curl -s https://acme.appy.to/.well-known/apple-app-site-association | jq .
curl -s https://acme.appy.to/.well-known/assetlinks.json | jq .
curl -s "https://app-site-association.cdn-apple.com/a/v1/acme.appy.to" | jq .
adb shell pm get-app-links com.acme.shop- Both association files must answer
200with JSON and no redirect, and list your Team ID and bundle id, or your package name and signing certificate fingerprints. - The third command shows what Apple’s CDN has cached, which can lag behind the live file by up to a day. During development, add
?mode=developerto the entitlement and turn on Associated Domains Development on the device to bypass the CDN. - The free Universal Link Validator checks a domain’s association files from the browser.