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

  1. In your AppsFlyer dashboard generate OneLink. For example: https://yourapp.onelink.me/example
  2. In your funnel create a Button with External Link action
  3. Optionally, mark this button as CTA to be able to see convenient analytics
  4. 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);
  }
});