AppsFlyer Migration
Migrate from AppsFlyer OneLink to WarpLink: link and parameter mapping, SDK swap, and deep linking, install attribution, and analytics.
This guide is for teams moving from AppsFlyer OneLink to WarpLink: mapping OneLink links and parameters, swapping the SDK, and carrying deep linking, install attribution, and analytics over to WarpLink. It assumes a WarpLink account and app are already set up.
For the provider-agnostic playbook (planning, domains, cutover, decommissioning), see the general migration guide. This page covers only what is specific to leaving AppsFlyer OneLink.
Concept Mapping
| AppsFlyer OneLink | WarpLink |
|---|---|
| OneLink template | Link |
| AppsFlyer dashboard | WarpLink dashboard |
onelink.me domain | aplnk.to domain (or a custom domain) |
| Branded domain (CNAME to a OneLink subdomain) | Custom domain |
Unified Deep Linking (onDeepLink callback) | onLink callback (not WarpLink's separate onDeepLink() warm-start-only listener) |
deep_link_value / deep_link_sub1-deep_link_sub10 | Query parameters on destination_url, echoed back as custom_params |
| SKAdNetwork / AdAttributionKit postbacks, ad network partner integrations | Not replaced by WarpLink; see Attribution and Reporting |
AppsFlyer's behavior-control parameters, such as af_force_deeplink, have no WarpLink equivalent: WarpLink always attempts the app open (Universal Link or App Link) when the app is installed, and falls back to the link's fallback URL or the app store only when it is not. See Fallback Cascade.
1. Export Your Links
Dashboard CSV export downloads a CSV of OneLink URLs per template. It only includes links created from the dashboard or a template: links created via the SDK, the OneLink REST API, or bulk upload do not appear in that export.
To get those links out, use the OneLink REST API (current version v2.0) to read them back programmatically. Access is not self-serve: it requires a customer success manager or an email to hello@appsflyer.com to enable it.
For historical click and conversion data, AppsFlyer's Raw Data Pull API caps downloads at 1,000,000 rows per request; larger histories need to be split into narrower time windows. Pull the data you need before decommissioning the account, since there is no single export that covers both link configuration and historical click data together.
2. Recreate Your Links
Recreate each OneLink link in WarpLink via the dashboard or the REST API.
Parameter Mapping
| AppsFlyer Parameter | WarpLink Field |
|---|---|
deep_link_value (in-app routing value) | ios_url / android_url |
af_dp (legacy URI-scheme deep link) | ios_url / android_url |
af_web_dp (web/social preview target) | destination_url |
af_ios_url / af_android_url (fallback URLs) | ios_fallback_url / android_fallback_url |
deep_link_sub1 … deep_link_sub10 (auxiliary values) | Query parameters on destination_url, echoed back as custom_params on resolve |
pid (media source) / c (campaign) | utm_source / utm_campaign |
| Custom title, description, image (set in the OneLink template UI) | og_title / og_description / og_image_url |
pid/c do not map to a literal WarpLink field name: they are AppsFlyer's attribution parameters, and the closest WarpLink equivalent is its own UTM fields, which serve a similar campaign-tracking purpose.
Create via API
curl -X POST https://api.warplink.app/v1/links \
-H "Authorization: Bearer wl_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"destination_url": "https://yourapp.com/product/123?deep_link_sub1=campaign_spring",
"ios_url": "myapp://product/123",
"android_url": "myapp://product/123",
"utm_source": "facebook",
"utm_campaign": "spring_sale"
}'
Custom slugs are 2 to 100 characters, lowercase alphanumeric with hyphens. AppsFlyer's OneLink URL ID defaults to 8 characters and allows up to 50; if you are scripting the migration, decide on a slug mapping up front for any URL ID that does not fit WarpLink's format.
3. Swap the SDK
Remove AppsFlyer:
- Remove the
AppsFlyerFrameworkpackage (CocoaPods, or SPM:AppsFlyerSDK/AppsFlyerFrameworkon SDK versions before 6.12.0,AppsFlyerFramework-Static/-Dynamic/-Stricton 6.12.0 and later) - Remove
AppsFlyerLib.shared().appsFlyerDevKey,.appleAppID, and the.start()call - Remove the
NSAdvertisingAttributionReportEndpointandAdAttributionKitkeys from Info.plist - Remove the
AppsFlyerDeepLinkDelegateconformance anddidResolveDeepLink(_:)(or the legacyonAppOpenAttribution)
Add WarpLink:
// Package.swift
dependencies: [
.package(url: "https://github.com/WarpLinkApp/warplink-ios-sdk", from: "1.1.0")
]Remove AppsFlyer:
// Remove from build.gradle.kts
// implementation("com.appsflyer:af-android-sdk:<version>")Also remove the MultipleInstallBroadcastReceiver manifest entry, the AppsFlyerLib.getInstance().init(...) / .start(context) calls, and any DeepLinkListener implementation.
Add WarpLink:
dependencies {
implementation("app.warplink:sdk:1.1.0")
}Remove AppsFlyer:
npm uninstall react-native-appsflyerAdd WarpLink:
npm install @warplink/react-nativeThe iOS SDK is distributed through Swift Package Manager and needs dynamic linkage, so add this to ios/Podfile inside your app target before installing pods:
# ios/Podfile, inside your app target
use_frameworks! :linkage => :dynamiccd ios && pod install4. Update SDK Initialization
Mobile apps authenticate with an SDK key, created in the dashboard under API Keys > SDK key. An API key looks identical but cannot record installs, so pasting one here leaves deep links working while attribution silently fails.
Before (AppsFlyer):
AppsFlyerLib.shared().appsFlyerDevKey = "<DEV_KEY>"
AppsFlyerLib.shared().appleAppID = "<APPLE_APP_ID>"
AppsFlyerLib.shared().start()After (WarpLink):
import WarpLink
WarpLink.configure(
apiKey: "wl_live_yoursdkkeyhere000000000000000000",
options: WarpLinkOptions(onLink: { result in
if case .success(let link) = result, let link {
navigate(to: link) // taps AND deferred installs (check link.isDeferred)
}
})
)Before (AppsFlyer):
AppsFlyerLib.getInstance().init(devKey, conversionListener, context)
AppsFlyerLib.getInstance().start(context)After (WarpLink):
import app.warplink.WarpLink
import app.warplink.WarpLinkOptions
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
WarpLink.configure(
context = this,
apiKey = "wl_live_yoursdkkeyhere000000000000000000",
options = WarpLinkOptions(onLink = { result ->
result.onSuccess { link -> navigateTo(link) }
})
)
}
}Before (AppsFlyer):
import appsFlyer from 'react-native-appsflyer';
appsFlyer.initSdk();
appsFlyer.startSdk();Method names shown are react-native-appsflyer's current documented API. A newer SDK version may rename these; check your installed version before porting the surrounding config.
After (WarpLink):
import { WarpLink } from '@warplink/react-native';
WarpLink.configure({
apiKey: 'wl_live_yoursdkkeyhere000000000000000000',
onLink: ({ deepLink }) => deepLink && navigateTo(deepLink.destination),
});5. Update Domain Configuration
In Signing & Capabilities > Associated Domains:
- applinks:yoursubdomain.onelink.me
+ applinks:aplnk.toIn AndroidManifest.xml, update the intent filter host:
<data
android:scheme="https"
- android:host="yoursubdomain.onelink.me" />
+ android:host="aplnk.to" />onelink.me is AppsFlyer-owned infrastructure: it cannot be repointed, so links on it stop resolving once you leave AppsFlyer. A branded custom domain is different, since its CNAME sits on your own DNS. Once your links exist on WarpLink with the same paths, repoint that domain's DNS to WarpLink and existing shares, QR codes, and bookmarks keep working. See custom domains.
Moving to a custom domain? Declare it to the SDK too: linkDomains at configure(), or the WarpLinkDomains plist key / app.warplink.DOMAINS manifest entry. See Use it in your app.
6. Migrate Deep Link Handling
The onLink callback set in step 4 already receives resolved links for cold start, warm start, and deferred installs.
Delete the AppsFlyerDeepLinkDelegate conformance and didResolveDeepLink(_:) (or the legacy onAppOpenAttribution). Forward the incoming activity to WarpLink (or set your scene delegate class to WarpLinkSceneDelegate):
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
WarpLink.continue(userActivity)
}Resolved links arrive in your onLink callback.
Delete the DeepLinkListener implementation and its onDeepLinking(deepLinkResult) handling. Cold start is automatic. For warm start, forward new intents and set android:launchMode="singleTask" on the Activity:
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
WarpLink.onNewIntent(intent)
}Resolved links arrive in your onLink callback.
Delete the onDeepLink() and onAppOpenAttribution() listeners. Cold and warm start flow into onLink automatically.
On iOS you are removing the AppsFlyer call from a delegate method that already exists and already forwards to your linking library. Swap the AppsFlyer line for the WarpLink call and leave the rest of the method alone. Do not let handleIncomingURL decide the return value: it returns nothing, and replacing the forwarded result breaks React Navigation, Expo Linking, and OAuth callbacks.
// ios/YourApp/AppDelegate.swift
import React
import warplink_react_native
func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
if userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL {
WarpLinkModule.handleIncomingURL(url) // was AppsFlyerLib...
}
return RCTLinkingManager.application(
application, continue: userActivity, restorationHandler: restorationHandler)
}Android needs no host code, given android:launchMode="singleTask". See the React Native SDK guide for the Objective-C form and Expo prebuild, and the iOS SceneDelegate hook if your app has a SceneDelegate (Info.plist declares UIApplicationSceneManifest): iOS stops calling the AppDelegate method above once a scene exists.
7. Migrate Deferred Deep Links
WarpLink runs the deferred deep link check automatically from configure(). The match arrives in the same onLink callback, flagged with isDeferred. There is no separate deferred call to port.
AppsFlyer's deferred delivery ran through the legacy onAppOpenAttribution callback on first open, or through Unified Deep Linking's onDeepLink, distinguishing a deferred match with isDeferred(). Delete those listeners; WarpLink's onLink replaces both.
Handle the deferred case inside your onLink callback:
options: WarpLinkOptions(onLink: { result in
guard case .success(let deepLink) = result, let deepLink else { return }
if deepLink.isDeferred {
print("Match confidence: \(deepLink.matchConfidence ?? 0)")
}
navigate(to: deepLink)
})Handle the deferred case inside your onLink callback:
options = WarpLinkOptions(onLink = { result ->
result.onSuccess { deepLink ->
if (deepLink.isDeferred) {
Log.d("MyApp", "Match confidence: ${deepLink.matchConfidence}")
}
navigateTo(deepLink)
}
})Handle the deferred case inside your onLink callback:
WarpLink.configure({
apiKey: 'wl_live_yoursdkkeyhere000000000000000000',
onLink: ({ deepLink }) => {
if (!deepLink) return;
if (deepLink.isDeferred) console.log('Match confidence:', deepLink.matchConfidence);
navigateTo(deepLink.destination);
},
});To run the check manually instead, set the deferred opt-out flag and call checkDeferredDeepLink() yourself (see the deferred deep links guides).
8. Attribution and Reporting
WarpLink attributes installs to the link that drove them through a match cascade: a referrer signal (Play Install Referrer) on Android, a device ID (IDFV) on iOS, and an enriched fingerprint as a probabilistic fallback on both platforms, with a confidence score on every result. See the iOS attribution guide (or the Android and React Native equivalents) for match types, confidence bands, and privacy details.
WarpLink does not replace AppsFlyer as a mobile measurement partner for paid ad network postbacks, SKAdNetwork or AdAttributionKit reporting for ad spend, or cost data. If AppsFlyer also serves as your measurement partner for paid campaigns, keep that integration in place alongside WarpLink. AppsFlyer's fraud protection (Protect360) and its ad network partner integrations are similarly outside what WarpLink covers.
If you run AppsFlyer and WarpLink side by side during the transition, only one SDK can own SKAdNetwork and AdAttributionKit conversion-value postbacks at a time. AppsFlyer's own guidance is that only one MMP should update the SKAN conversion value when more than one attribution SDK is present. Decide which SDK owns SKAN/AdAttributionKit before running both, rather than leaving it to whichever fires last.
Beyond that constraint, running both SDKs for a period is a normal part of a migration: it lets you compare install counts and deep link behavior before retiring the old provider. See Run the Cutover in the general guide for the rollout sequencing.
Testing Checklist
After migration, verify on each platform:
- SDK initializes: enable debug logging and check console output
- Deep links open the app: tap a WarpLink URL on a physical device
- Deep link data resolves: destination and custom parameters are correct
- Deferred deep links work: click link, install, verify match. An uninstall is enough to retest on either platform, because the completion marker goes with the app. That second install is reported as a reinstall and still counts as an install
- AASA/assetlinks verified:
curl https://aplnk.to/.well-known/apple-app-site-association - Error handling: test invalid URLs, expired links, no connectivity
- SKAN/AdAttributionKit ownership is unambiguous if AppsFlyer is still installed during testing
- All AppsFlyer code removed: no remaining imports or dependencies
- Clean build: no compilation errors
Next Steps
- General migration guide: the provider-agnostic playbook for planning, domains, cutover, and decommissioning.
- Quickstart: create your first link and get an SDK key.
- iOS SDK, Android SDK, and React Native SDK: full setup and API reference.
- AppsFlyer alternative for small teams: cost comparison and positioning for teams evaluating the move.