Branch Migration
Migrate from Branch to WarpLink: link and parameter mapping, exporting data, SDK swap, and deep linking, install attribution, and analytics.
This guide is for teams currently using Branch for links, deep linking, install attribution, or analytics who are moving to WarpLink. It covers Branch-specific concept mapping, exporting your existing links and data, and swapping the Branch SDK for the WarpLink SDK on each platform. For the provider-agnostic playbook (inventory, domain planning, cutover, and decommissioning), see Migration Overview.
Concept Mapping
| Branch Concept | WarpLink Concept |
|---|---|
| Quick Link / Ad Link | Link |
| Alias | Slug |
app.link / bnc.lt domain | aplnk.to domain (or custom domain) |
| Custom domain (CNAME or NS delegation) | Custom domain |
$-prefixed link parameters | Link fields (see parameter mapping below) |
| LinkHub (Dashboard → Manager) | WarpLink dashboard Links page |
| Journeys (smart banners) | Not replaced; see Attribution and Reporting |
1. Export Your Links
Branch's LinkHub (Dashboard → Manager) lets you search, filter, and bulk export both Quick Links and Ad Links, meaning dashboard-created and SDK/API-generated links are both reachable there. If you need to script an export instead, the Read endpoint returns one link's configuration at a time:
GET /v1/url?url=<url>&branch_key=<branch key>
There is no single Branch endpoint that exports all historical links and all historical click analytics together. Link configuration and click/event data are separate export surfaces:
- Dashboard Exports: an ad hoc CSV of whatever report you are currently viewing.
- Daily Exports API (
POST https://api2.branch.io/v3/export): gzipped CSV, up to 200,000 rows per file, capped at 7 days per request (1 day recommended), and only accessible via the API for 6 months after the data posts. - Custom Exports API: covers install, commerce, content, user-lifecycle, custom, and fraud events as CSV or JSON, up to 2,000,000 records per request with a 180-day lookback and a 60-day query window per request. Rate limited to 10 requests/minute and 25/hour, so a full-account export has to be paced across multiple calls.
Branch's device-matching attribution data is deleted after 30 days of user inactivity (90 days for some attribution products). Pull your historical exports before that window closes, not after.
2. Recreate Your Links
Recreate your Branch links in WarpLink via the dashboard or the REST API.
Parameter Mapping
| Branch Parameter | WarpLink Field |
|---|---|
| Alias | slug |
$fallback_url | destination_url |
$ios_url | ios_fallback_url |
$android_url | android_fallback_url |
$deeplink_path / $ios_deeplink_path | ios_url |
$android_deeplink_path | android_url |
$og_title | og_title |
$og_description | og_description |
$og_image_url | og_image_url |
$exp_date | expires_at |
$match_duration | match_window_hours (capped at 24 hours, versus Branch's default 7200 seconds) |
Repeated ~tags query parameters | tags |
password (Branch reserved query param) | password |
| Other link data (custom key/value pairs) | Encoded as query parameters on destination_url, echoed back as custom_params on resolve |
Branch's deepview, social-card variants beyond title/description/image, custom meta tags, and attribution-window parameters other than $match_duration have no direct WarpLink field. Fold anything you still need into destination_url query parameters.
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",
"ios_url": "myapp://product/123",
"ios_fallback_url": "https://apps.apple.com/app/id123456789",
"android_url": "myapp://product/123",
"android_fallback_url": "https://play.google.com/store/apps/details?id=com.example.myapp",
"og_title": "Check out this product",
"expires_at": "2026-12-31T00:00:00Z"
}'
3. Swap the SDK
Remove Branch:
- Remove the
BranchSDKpackage (CocoaPodspod 'BranchSDK', SPMhttps://github.com/BranchMetrics/ios-branch-sdk-spm, or Carthagegithub "BranchMetrics/ios-branch-deep-linking") - Remove the
Branch.getInstance()/initSession(launchOptions:)call - Remove the
branch_keyandbranch_universal_link_domainskeys fromInfo.plist - Remove the
applinks:subdomain.app.linkAssociated Domains entries (including the-alternateand.testvariants)
Add WarpLink:
// Package.swift
dependencies: [
.package(url: "https://github.com/WarpLinkApp/warplink-ios-sdk", from: "1.1.0")
]Remove Branch:
// Remove from build.gradle.kts
// implementation("io.branch.sdk.android:library:<version>")Also remove the io.branch.sdk.BranchKey, io.branch.sdk.BranchKey.test, and io.branch.sdk.TestMode manifest meta-data entries, and the custom URI scheme and HTTPS App Links intent filters pointed at your app.link domains.
Add WarpLink:
dependencies {
implementation("app.warplink:sdk:1.1.0")
}Remove Branch:
npm uninstall react-native-branch
cd ios && pod installAdd 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
Use an SDK key here, not an API key. 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 deep links keep resolving while every attribution call is rejected and no installs appear in your dashboard.
Before (Branch):
Branch.getInstance().initSession(launchOptions: launchOptions)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 (Branch):
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
Branch.getAutoInstance(this)
}
}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 (Branch):
import branch from 'react-native-branch';
branch.subscribe(({ error, params }) => {
if (error) return;
// handle params
});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:yourapp.app.link
+ applinks:aplnk.toRemove every applinks: entry for your app.link (and legacy bnc.lt) subdomain, including the -alternate and .test variants Branch adds automatically.
In AndroidManifest.xml, update the intent filter host and remove Branch's custom URI scheme filter:
<data
android:scheme="https"
- android:host="yourapp.app.link" />
+ android:host="aplnk.to" />Remove the android:autoVerify="true" intent filters for both your live and test app.link domains.
Moving your links to a custom domain instead? Use that host here, and declare it to the SDK as well: linkDomains at configure(), or the WarpLinkDomains plist key / app.warplink.DOMAINS manifest entry. That is what makes a custom-domain link resolve on the first launch. See Use it in your app.
Branch's default app.link and bnc.lt domains cannot move with you; only your own custom domain can be repointed. If your custom domain was set up as a Branch CNAME subdomain, your registrar was never handed off, so the CNAME can be repointed to WarpLink once your links exist there. If it was set up as a root-domain NS delegation, Branch's nameservers are authoritative for that domain, so regaining control means moving the NS records back to your registrar (or another DNS provider) before pointing it at WarpLink, a normal DNS change but one Branch does not document as an offboarding step. Branch hosts the AASA file and TLS certificate for both setups; once you repoint to WarpLink, WarpLink issues and renews the certificate for your verified custom domain automatically.
6. Migrate Deep Link Handling
Delete the Branch calls inside application(_:open:options:) and application(_:continue:restorationHandler:) (and the SceneDelegate equivalents, scene(_:continue:) and scene(_:openURLContexts:), if your app uses scenes). Forward the same URL or activity to WarpLink instead:
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
WarpLink.open(url) // was Branch's handler
return true
}
func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
WarpLink.continue(userActivity) // was Branch's handler
return true
}Resolved links arrive in your onLink callback. Or set your delegate class to the drop-in WarpLinkAppDelegate / WarpLinkSceneDelegate and skip writing these methods yourself.
Delete the Branch.sessionBuilder(this).withCallback { ... }.withData(intent.data).init() call from your Activity's onStart(). Cold start is automatic once onLink is set at configure(). For warm start, forward new intents and keep android:launchMode="singleTask" on the Activity:
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
WarpLink.onNewIntent(intent) // was Branch's onStart() session
}Resolved links arrive in your onLink callback.
Delete the branch.subscribe(...) callback. WarpLink.configure()'s onLink callback covers cold start, warm start, and deferred installs in one callback, the same way subscribe did.
On iOS you are removing the Branch line from a delegate method that already exists and already forwards to your linking library. Swap it 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 Branch's handler
}
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.
7. Migrate Deferred Deep Links
Branch's deferred deep linking on iOS 15+ with Private Relay enabled relies on NativeLink (the $ios_nativelink link parameter), delivered through the same session-init callback as a direct tap. WarpLink runs its deferred check automatically from configure() and delivers the match to the same onLink callback, flagged with isDeferred:
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)
})Branch delivers a deferred match through the same sessionBuilder().init() callback used for a direct link open. WarpLink runs its deferred check automatically from configure() and delivers the match to the same onLink callback, flagged with isDeferred:
options = WarpLinkOptions(onLink = { result ->
result.onSuccess { deepLink ->
if (deepLink.isDeferred) {
Log.d("MyApp", "Match confidence: ${deepLink.matchConfidence}")
}
navigateTo(deepLink)
}
})Branch's subscribe callback also surfaces deferred params on cold start: the result is cached natively and delivered if subscribe is called within a TTL (default 5000ms, configurable via branch.initSessionTtl). WarpLink runs its deferred check automatically from configure() and delivers the match to the same onLink callback, flagged with isDeferred, with no TTL to configure:
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 link guides for iOS, Android, and React Native).
8. Attribution and Reporting
WarpLink attributes an install to the link that drove it with a multi-tier match cascade (referrer, device ID, and fingerprint matching), returning a match type and confidence score you can use to decide how to route the user. See Install Attribution for the full match cascade and confidence scoring, and the platform attribution guides for iOS, Android, and React Native.
Branch offers several products WarpLink does not replace: SKAdNetwork postback handling, Journeys (deep-link-aware web-to-app smart banners), branded QR codes, one-tap email/SMS link tools, and fraud protection against suspicious clicks and devices. As with any migration, WarpLink does not replace a mobile measurement partner for paid ad network postbacks, SKAdNetwork reporting, or ad spend and cost data; keep that integration in place if Branch currently serves that role for you.
Branch does not publish guidance for running its SDK alongside another provider during a migration off Branch (its published migration guides only cover switching onto Branch from another vendor). A few gotchas to test for during a dual-SDK window:
- iOS AASA caching. iOS caches the
apple-app-site-associationfile, and a fresh fetch is typically triggered by an app reinstall, so a device that already resolved a Branch link may not immediately pick up your new WarpLink Associated Domains entry. - Android App Links verification. Every manifest-referenced domain must host its own
.well-known/assetlinks.json, and thesha256_cert_fingerprintsvalue must exactly match your release signing key (uppercase), including Play App Signing's value if you use it. An http/https orwwwmismatch also breaks verification. Force a re-check withadb shell pm verify-app-links --re-verify.
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
- All Branch code removed: no remaining imports or dependencies
- Clean build: no compilation errors
Next Steps
- Migration Overview: the provider-agnostic playbook for 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.
- Branch.io alternative for indie developers and Branch pricing compared: how WarpLink compares to Branch.
Firebase Dynamic Links Migration
Recover from the Firebase Dynamic Links shutdown: recreate links, swap the SDK, and restore deep linking, install attribution, and analytics.
AppsFlyer Migration
Migrate from AppsFlyer OneLink to WarpLink: link and parameter mapping, SDK swap, and deep linking, install attribution, and analytics.