Configure Deeplinks (SDK part)
Deferred Deeplinking lets you attribute users who install and open the app after clicking an Apphud deep link. The SDK supports two attribution flows:
- Direct deeplinking — the app is opened from a Universal Link / App Link or custom scheme, and the SDK sends the opened URL to Apphud.
- Deferred deeplinking — the app requests attribution for the current installation after the first launch, even when the app was installed before the link could be opened directly.
Use this guide after the Apphud SDK is installed and initialized. See Initialization and Identification if you have not initialized the SDK yet.
Platform setup requiredApphud receives the attribution result through the SDK, but your app must still be configured to open links on each platform:
- iOS: configure Universal Links using Apple's guide: Supporting universal links in your app
- Android: configure Android App Links using Google's guide: Android App Links
Requirements
- Use an Apphud SDK version that includes Deferred Deeplinking support. On Flutter, use 3.3.0 or later.
- Configure Universal Links on iOS and/or Android App Links on Android for your domain.
- Register a deeplink handler in Apphud SDK to receive attribution results.
- Request deferred attribution after SDK initialization, typically once on the first launch after install.
- Handle direct deeplinks to Apphud SDK from your app lifecycle callbacks.
ImportantThe deeplink handler may be called multiple times during the app lifecycle: for direct link opens and for deferred attribution requests. If Apphud does not find a match, the handler receives an empty attribution dictionary/map.
Attribution Result
The deeplink handler receives:
attribution— attribution data returned by Apphud. It is empty when no match is found.kind— attribution kind:direct/DIRECT: the user opened an actual deep link.deferred/DEFERRED: attribution was resolved for the current installation.
url/uri— the original link for direct deeplinking, ornil/nullfor deferred deeplinking.
On Flutter these values arrive as a single ApphudDeeplinkAttribution object with attribution (Map<String, dynamic>), kind (ApphudDeeplinkAttributionKind.direct / .deferred) and url (String?).
1. Configure Xcode / Android Studio for deep links
iOS: Configure Universal Links
Configure Associated Domains in Xcode and host the apple-app-site-association file for your link domain. Follow Apple's official guide: Supporting universal links in your app.
You need to add an associated domain that was configured in Apphud -> Deeplinks -> Config tab.
Example Associated domain:
applinks:example.aphd.ccAndroid: Configure Android App Links
Add an ACTION_VIEW intent filter to the Activity that should receive your links and host the Digital Asset Links file for your domain. Follow Google's official guide: Android App Links.
You need to add an intent filter to your Manifest file that shows up in Apphud -> Deeplinks -> Config tab.
Example AndroidManifest.xml:
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- here must be your actual subdomain -->
<data
android:host="example.aphd.cc"
android:scheme="YOURAPPSCHEME" />
</intent-filter>
Replace example.com and /deeplink with your actual link domain and path.
On Flutter, the intent filter goes into android/app/src/main/AndroidManifest.xml inside the <activity> element of your MainActivity, and the Associated Domains entitlement is added to ios/Runner in Xcode.
Flutter configuration
Set FlutterDeepLinkingEnabled to false in your iOS Info.plist unless you intentionally use Flutter's built-in route deep linking for the same Universal Links. When Flutter deep linking stays enabled and no Dart route matches the https URL, the engine opens that URL in Safari after ~1–2 seconds (app opens, then bounces out to the browser).
<key>FlutterDeepLinkingEnabled</key>
<false/>A few more Flutter specifics:
Apphud.start()has nodeeplinkHandlerparameter. Register the handler withApphud.setDeeplinkHandler(...)instead. You can call it beforeApphud.start()— registering early is recommended so attribution triggered by a cold-start link is not missed.- iOS: direct deep links (
open url, Universal Links viacontinue userActivity, launch options) are forwarded to Apphud automatically by the plugin, so no native code is required. This relies on yourios/Runner/AppDelegate.swiftsubclassingFlutterAppDelegate; if you overrideapplication(_:didFinishLaunchingWithOptions:),application(_:open:options:)orapplication(_:continue:restorationHandler:), callsuperin those overrides. - iOS: the plugin reports a Universal Link as handled only when it is hosted on Apphud's
aphd.ccdomain, so other deep link SDKs (Firebase Dynamic Links, OneSignal, Branch) keep receiving their own links. - Android: forward incoming intents from your
MainActivity(see step 3).com.apphud.sdk.Apphudis available in your app module without adding a dependency, because the Flutter plugin exposes the Android SDK transitively.
Migrating from Flutter SDK 3.2.x
Apphud.attributeFromDeeplink()was removed in 3.3.0. UseApphud.setDeeplinkHandler()together withApphud.requestDeferredDeeplinkAttribution()instead.
2. Implement deferred deep linking
If the app is not installed, clicking the link will redirect the user to the appropriate app store. After installing and opening the app, the user will be attributed to that link. Implement the following methods to support deferred deep linking.
import ApphudSDK
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Optionally specify deeplink handler
Apphud.start(apiKey: "YOUR_API_KEY", deeplinkHandler: { attribution, kind, url in
switch kind {
case .direct:
print("Direct deeplink attribution", url?.absoluteString ?? "", attribution)
case .deferred:
print("Deferred deeplink attribution", attribution)
}
// Use attribution values here, for example to route the user,
// unlock onboarding content, or store campaign parameters.
})
// Required. Request deeplink matching, call once after initization.
Apphud.requestDeferredDeeplinkAttribution()
// Required. Handle direct links that launched the app.
Apphud.handleLaunchOptions(launchOptions: launchOptions)
// Optional. You can also set or replace the handler after initialization:
// Apphud.setDeeplinkHandler { attribution, kind, url in
// print("Deeplink attribution", kind, url?.absoluteString ?? "", attribution)
// }
return true
}
}
import android.app.Application
import android.util.Log
import com.apphud.sdk.Apphud
import com.apphud.sdk.ApphudDeeplinkAttributionKind
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// Optionally specify deeplink handler
Apphud.start(
context = this,
apiKey = "YOUR_API_KEY",
deeplinkHandler = { attribution, kind, uri ->
when (kind) {
ApphudDeeplinkAttributionKind.DIRECT -> {
Log.d("Apphud", "Direct deeplink: uri=$uri attribution=$attribution")
}
ApphudDeeplinkAttributionKind.DEFERRED -> {
Log.d("Apphud", "Deferred deeplink: attribution=$attribution")
}
}
// Use attribution values here, for example to route the user,
// unlock onboarding content, or store campaign parameters.
},
)
// Required. Request deeplink matching, call once after initization.
Apphud.requestDeferredDeeplinkAttribution(this)
// Optional. You can also set or replace the handler after initialization:
/// Apphud.setDeeplinkHandler { attribution, kind, uri -> }
}
}
import 'package:apphud/apphud.dart';
Future<void> startApphud() async {
// Required. Register the handler for both direct and deferred results.
// Registering it before initialization is recommended, so attribution
// triggered by a cold-start link is not missed. Pass null to remove it.
Apphud.setDeeplinkHandler((ApphudDeeplinkAttribution result) {
switch (result.kind) {
case ApphudDeeplinkAttributionKind.direct:
print('Direct deeplink: url=${result.url} attribution=${result.attribution}');
break;
case ApphudDeeplinkAttributionKind.deferred:
print('Deferred deeplink: attribution=${result.attribution}');
break;
}
// Use attribution values here, for example to route the user,
// unlock onboarding content, or store campaign parameters.
});
await Apphud.start(apiKey: 'YOUR_API_KEY');
// Required. Request deeplink matching, call once after initialization.
// On Android this requires a foreground Activity.
try {
await Apphud.requestDeferredDeeplinkAttribution();
} catch (error) {
print('Deferred deeplink attribution request failed: $error');
}
}
Main SDK method
Apphud.requestDeferredDeeplinkAttribution()is the primary method and must be called unless the user has already been attributed. Subsequent calls are unnecessary for non-organic users.
3. Implement direct deep linking
If the app is already installed, clicking the link will open the app directly and attribute the user to that link. Implement the following methods to support direct deep linking.
// For custom scheme links or links delivered through `application(_:open:options:)`:
func application(
_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey: Any] = [:]
) -> Bool {
...
// Required. Handle incoming URL
Apphud.handleOpen(url: url)
return true
}
// For Universal Links:
func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
....
// Required. Handle incoming user activity
Apphud.continueUserActivity(userActivity)
return true
}// Forward the Activity intent from both `onCreate` and `onNewIntent`:
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.apphud.sdk.Apphud
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Required. Handle incoming intents
Apphud.handleIntent(intent)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent)
// Required. Handle incoming intents
Apphud.handleIntent(intent)
}
}
// IMPORTANT:
// `Apphud.handleIntent(intent)` processes only `ACTION_VIEW` intents with a link URI. You can also pass a URI directly:
Apphud.handleUri(uri)
Flutter
On iOS no code is required: the plugin receives the app delegate lifecycle callbacks and forwards open url, Universal Links and launch options to Apphud automatically. Keep your AppDelegate subclassing FlutterAppDelegate and call super in any of those lifecycle methods you override.
On Android, forward the intent from your MainActivity in android/app/src/main/kotlin/.../MainActivity.kt:
import android.content.Intent
import android.os.Bundle
import com.apphud.sdk.Apphud
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Required. Handle the link that launched the app from a cold start.
Apphud.handleIntent(intent)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent)
// Required. Handle links that arrive while the app is already running.
Apphud.handleIntent(intent)
}
}The result of both flows is delivered to the same Dart handler registered with Apphud.setDeeplinkHandler(...), with kind set to ApphudDeeplinkAttributionKind.direct.
Testing
- Install a build with the Apphud SDK version that supports Deferred Deeplinking.
- Verify that Universal Links / App Links open the app directly.
- Open a direct link and confirm that the handler receives
direct/DIRECTattribution with the original URL. - For deferred deeplinking, click the link before installing the app, install and open the app, then call the deferred attribution request once after initialization.
- Confirm that the handler receives
deferred/DEFERREDattribution. If there is no match, the attribution payload will be empty.
Troubleshooting
- If direct links do not open the app, verify Universal Links / App Links configuration first:
- iOS: Bundle Identifier, Associated Domains entitlement must match with
apple-app-site-associationon record. - Android: intent filter, package name, signing certificate fingerprint must match with
assetlinks.json
- iOS: Bundle Identifier, Associated Domains entitlement must match with
- Make sure Apphud SDK is initialized before requesting deferred attribution.
- Make sure incoming links are forwarded to Apphud from all relevant lifecycle callbacks.
- Do not assume the handler is called only once; handle both direct and deferred results idempotently.
- An empty attribution payload means Apphud did not find a matching attribution for that request.
- Flutter, iOS: if the app opens and then bounces to Safari after a second,
FlutterDeepLinkingEnabledis still enabled inInfo.plist. - Flutter, iOS: if the handler never fires for direct links, check that your
AppDelegatesubclassesFlutterAppDelegateand callssuperin the lifecycle methods it overrides. - Flutter, Android:
requestDeferredDeeplinkAttribution()throws aPlatformExceptionwith codeno_activitywhen there is no attached Activity. Call it while the app is in the foreground.
Updated 2 days ago
