Deferred Deep Link Identification
Learn how to guide your users from web to app — there and back again
Appsflyer
This article uses AppsFlyer OneLinks. Appsflyer account is required to complete this type of identification.
In most cases, after user performs a purchase on your funnel you need to guide them to your application. The most seamless way to do this is via DDLs (Deferred Deep Links.)
DDLs allow you to pass information (such as a user identifier or token) through a single link. This link can:
- Redirect new users to the App Store if the app isn’t installed
- Automatically open the app if it is installed
- And most importantly: deliver metadata (like tokens) into the app on first launch, even after installation
This enables a frictionless post-purchase experience — no login, no extra steps.
How to set up
- In your AppsFlyer dashboard generate OneLink. For example:
https://yourapp.onelink.me/example
- In your funnel create a Button with External Link action
- Optionally, mark this button as CTA to be able to see convenient analytics
- Inside this button set the link with
ffkey
query parameter. Use ID variable like this:
https://yourapp.onelink.me/example?ffkey={{user.session_id}}
You can also use you custom user ID or property.
That's it for web configuration! You will also need to handle these links in your application.
How to handle DDL in your app
The goal here is to read the ffkey
query parameter that was included in the link (e.g. https://yourapp.onelink.me/example?ffkey={{user.session_id}}
), and use it to identify or authenticate the user.
In most cases with app not installed, AppsFlyer SDK will invoke a callback (e.g. onConversionDataSuccess
) with the original link parameters. You will need to:
- Listen for this callback
- Extract
ffkey
directly from the conversion data - Use it to identify the user
For complete information refer to AppsFlyer documentation.
Here are minimal examples:
import AppsFlyerLib
class AppsFlyerDelegateHandler: NSObject, AppsFlyerLibDelegate {
static let shared = AppsFlyerDelegateHandler()
func onConversionDataSuccess(_ data: [AnyHashable : Any]) {
guard let status = data["af_status"] as? String, status == "Non-organic" else { return }
if let sessionId = data["ffkey"] as? String {
// TODO: identifyUser(with: sessionId)
}
}
func onConversionDataFail(_ error: Error) {
print("AppsFlyer DDL failed: \(error)")
}
}
object AppsFlyerHandler : AppsFlyerConversionListener {
override fun onConversionDataSuccess(data: Map<String, Any>?) {
if (data == null) return
val status = data["af_status"] as? String
if (status == "Non-organic") {
val sessionId = data["ffkey"] as? String
if (sessionId != null) {
// TODO: identifyUser(sessionId)
}
}
}
override fun onConversionDataFail(errorMessage: String?) {
Log.e("AppsFlyer", "AppsFlyer DDL failed: $errorMessage")
}
override fun onAppOpenAttribution(data: Map<String, String>?) {}
override fun onAttributionFailure(errorMessage: String?) {}
}
appsFlyer.onInstallConversionData((res) => {
const data = res.data;
if (data.af_status === 'Non-organic' && data.ffkey) {
// TODO: identifyUser(data.ffkey);
}
});
Updated 7 days ago