Take control of your marketing data.
Mobile MarketingAttribution

Deep linking for developers: keeping routing and attribution in sync

Broken deep links rarely look like bugs; they show up as weak campaigns, generic onboarding and in-app events with no source, so developers and UA teams need to agree on routing, parameters and testing before any link goes live.

K
Kacper WoźniakCo-founder, AdShift
10 November 2026 · 8 min read
Illustration of a two-pin plug on a cable: one link carrying both routing and attribution.

A user taps an ad for a specific product, installs the app and lands on a generic home screen. The campaign reports an install. Product analytics shows a session with no context. Nobody can tell whether the link failed, the app ignored it, or the post-install match never happened. Deep linking problems rarely surface as bugs. They surface as campaigns that underperform for reasons nobody can isolate, and the cause usually sits somewhere between the link domain, the app lifecycle and the measurement SDK.

Developers tend to build deep linking as navigation. Open the URL, parse the path, push the right screen. That solves half the job. The UA and analytics side needs the app to also record which link brought the user in and to attach that context to the session and to downstream in-app events.

When a link opens the app, three things should happen. The app resolves the route. The app captures the link context. An event carrying that context reaches the measurement layer. If nobody owns one of these steps, the data degrades quietly. Routing still works in QA, so the gap goes unnoticed until someone asks why a retargeting campaign shows clicks but no attributed purchases.

Before writing handler code, settle these points between engineering, UA and analytics:

  • Who owns the link domains and the association files hosted on them.
  • Where in the app lifecycle links are handled: cold start, warm start, and resume from background.
  • Which in-app event fires when a deep link resolves, and which parameters it carries.
  • What the app does with an unknown or malformed route.
  • Whether a login wall or onboarding flow interrupts routing, and how the app returns the user to the intended destination afterwards.

Deferred deep linking depends on what survives the install

A direct deep link is the easy case. The app is installed, the operating system hands over the URL, and the app routes. A deferred deep link is harder. The app is not installed, the user goes through the store, and the link context has to be recovered when the app launches for the first time.

The store does not pass the original URL through to the app. On Android, the Play Install Referrer gives the app a referrer string set on the store link, which gives you a deterministic channel for context if the link setup populates it. On iOS there is no equivalent. Recovery depends on the measurement provider matching the first launch back to the click, and that matching is constrained by platform privacy rules and the user's consent status. Some first launches will not get a match. Treat that as a design input, not an edge case.

This has direct consequences for the first-launch code path. The app needs to ask for deferred link data early, wait for it with a timeout, and then proceed either way. Blocking onboarding until the answer arrives hurts every user. Ignoring a late answer wastes the match. A common pattern is to start onboarding immediately and apply the deferred route at a natural break point, such as after account creation.

Hypothetical example: a recipe app runs a campaign that promotes a specific dinner collection. Users who already have the app land on the collection. New iOS users who installed without a recovered match see the default onboarding. If the fallback screen is generic, those users have no reason to connect the ad to the app. If the fallback is a curated set of popular recipes, the gap matters less. The fallback is a product decision, and it should be made deliberately.

Both platforms verify that the app is allowed to open links on a domain. On iOS, that means the apple-app-site-association file on the domain and the Associated Domains entitlement in the app. On Android, that means the assetlinks.json file with the package name and signing certificate fingerprint, plus intent filters with auto-verification enabled.

These setups work at launch and then drift. The usual causes are mundane:

  • The release signing key differs from the upload key, and assetlinks.json lists the wrong fingerprint.
  • A CDN or hosting change adds a redirect or alters the response headers for the association file.
  • Marketing moves campaigns to a new link domain that the app was never associated with.
  • The operating system caches association data, so a fix does not take effect immediately on existing installs.
  • A new app variant or bundle identifier ships without being added to the association files.

When verification fails, nothing crashes. The link opens a web page instead. The web page may still record a click, so click counts look normal while app opens drop. In-app browsers inside social and messaging apps add another layer, because they do not always hand the link off to the operating system the way the default browser does. The only reliable way to catch this is to check the association setup after every release and every infrastructure change on the link domain.

Deep links carry two kinds of information: where to send the user, and where the user came from. Mixing them in an ad hoc query string is how routing breaks after a campaign rename, or how attribution fields leak into screen logic.

Write the schema down and share it across teams. Keep it short:

  1. Routing fields: destination route and content identifier. Engineering owns these and the app validates them.
  2. Campaign fields: campaign, channel, ad group and creative identifiers. UA owns these and the app passes them through without interpreting them.
  3. A schema version, so the app can handle old links that are still live in emails, QR codes or partner placements.
  4. A documented fallback route for every destination that can disappear, such as expired promotions or removed content.

Hypothetical example: a fitness app launches a campaign pointing to a specific workout. A typo in the content identifier sends every user to an empty screen. With validation in place, the app detects the unknown identifier, routes to the workout library and logs a resolution failure. Without it, the campaign looks like it has poor retention, and the UA team cuts budget on a creative that was never the problem.

Before you ship links

Keep the parameter schema in the repository next to the link handler, with one named owner, and make the app log every parameter it does not recognise. A renamed campaign field then shows up in a log the same day, instead of in a report a month later.

Testing has to cover the paths users actually take

Most deep link testing happens on a developer device, with the app installed, from a notes app or a terminal command. Real users arrive from in-app browsers, email clients, messaging apps and the store. Each path behaves differently.

A useful test matrix crosses these dimensions: app installed or not, cold or warm start, iOS or Android, default browser or in-app browser, logged in or logged out, and tracking consent granted or declined. Not every combination needs manual testing on every release. The combinations that carry paid traffic do.

Test deferred deep linking with builds installed from the store or an official testing track, not only with local debug builds. Install-time behavior, referrer data and first-launch timing can differ between a sideloaded build and a real store install, and those differences are exactly where deferred flows break.

Measurement will not fix what deep linking cannot carry

A clean deep link setup improves routing and makes post-install events more useful. It has clear limits, and it helps to state them before a campaign review turns into a debate about the link layer.

  • It does not recover attribution that platform privacy rules withhold. On iOS, aggregated and delayed signals still apply to many installs. Our [SKAN measurement playbook](/blog/skan-2026-measurement-playbook) covers how to work within those constraints.
  • It does not guarantee a deferred match. Some new users will always arrive without link context, so the fallback experience carries real weight.
  • It does not fix a weak destination. Routing a user to the right screen does not make that screen convert.
  • It does not measure incrementality. A deep-linked retargeting campaign can show strong attributed revenue from users who would have returned anyway.
  • It does not repair broken flows elsewhere in the app. If login or onboarding drops the intended route, the link worked and the experience still failed.

Knowing these limits keeps diagnosis honest. When a campaign underperforms, check the link layer first because it is testable. Then move on to creative, audience and product questions instead of assuming the link explains everything.

Deep links produce the context that makes post-install data interpretable: which campaign, which creative, which destination. That context has value only if it reaches the same place as attribution, in-app events, revenue and cohort data. Deep linking is one piece of the wider picture we describe in [what mobile teams actually need from measurement beyond the install](/blog/beyond-the-install-mobile-measurement).

The same link context decides how much the numbers are worth further down the stack. What to settle before a re-engagement budget starts is covered in our article on measurement before the first retargeting campaign, and the wider case for looking past the install itself is in beyond the install.

AdShift connects attribution with in-app events, ROAS and LTV reporting, ad revenue, and export to your own warehouse. When deep link context travels with those events, analysts can separate routing failures from campaign performance without stitching data together by hand. For engineering, the practical goal stays simple: every link resolves to a defined route, every resolution emits an event with the agreed parameters, and every failure leaves a trace someone can query.

TagsAttributionMobile Marketing

Frequently asked questions

Should the app wait for deferred deep link data before showing onboarding?+

Not indefinitely. Request the data early, wait with a short timeout, and proceed with onboarding if nothing arrives. Apply the deferred route at a natural break point, such as after sign-up, so a late match is still used without blocking users who will never get one.

Why do Universal Links work in testing but not from some social apps?+

In-app browsers do not always hand links off to the operating system the way the default browser does. The link then opens as a web page instead of the app. Include the in-app browsers that carry your paid traffic in the test matrix and design the web fallback to offer a clear path into the app.

Who should own the deep link parameter schema?+

Ownership should be split by field type. Engineering owns routing fields and validation, UA owns campaign fields, and analytics owns the event that records link resolution. The schema itself should be a shared, versioned document that all three teams review before a new link type goes live.

Can deep linking improve iOS attribution coverage?+

No. Deep linking improves routing and the quality of in-app events once the user is in the app. It does not change what platform privacy rules allow a measurement provider to match, so attribution limits on iOS remain the same with or without a well-built link setup.

About the author

K
Kacper WoźniakCo-founder, AdShift

Co-founder of AdShift, building a mobile measurement platform for app teams that need attribution, ROAS/LTV and ad revenue data they can defend.

See it on your data

Want attribution you can defend in a budget meeting?

We will walk through your current measurement setup, show where the signal is leaking, and map what AdShift would report differently.

Deep Linking for Developers: A Measurement Guide