WarpLink
SDKsFlutter

Flutter Deferred Deep Links

Preserve a link's destination through App Store and Play Store installs, then route the user on first launch, in Flutter.

Deferred deep links let you route users to specific content even when they don't have your app installed. The user clicks a link, installs from the App Store or Play Store, and on first launch the SDK matches them back to the original link.

How It Works

  1. User taps a WarpLink URL in a browser
  2. WarpLink records the click. The server derives the IP and stores it with the normalized language and timezone. Clicks that share a fingerprint are kept as a list, newest first, up to 10, so a second click never overwrites the first
  3. User is redirected to the App Store (iOS) or Play Store (Android)
  4. User installs and opens the app
  5. SDK detects first launch. A completion marker is written only after the check completes. It is scoped to one install on both platforms, and no backup restores it, so a reinstall attributes fresh. On iOS, if the backup-excluded file cannot be written, the SDK falls back to a UserDefaults flag that a restore does carry; that rare case is the one exception
  6. SDK collects device signals (preferred language, timezone name and offset, and platform IDs) and sends them to the attribution API. The server derives the IP and computes the fingerprint
  7. Server matches against stored click data
  8. Deep link returned with isDeferred: true

Automatic Check

The deferred check fires automatically from configure(). When the install also came from a link tap, the check starts after that link is delivered or dropped. The result arrives in your onLink callback, where isDeferred is true:

If that check cannot reach the server, for example on a launch with no network, the failure also arrives in onLink. The first-launch gate stays open, so the next launch retries by itself. Treat a WarpLinkNetworkException there as a report, not as something to act on.

Manual Check (Advanced)

To run the check yourself, set automaticDeferredDeepLinks: false and call checkDeferredDeepLink():

Confidence-Based Routing

Confidence Scores

Probabilistic scores depend on the fingerprint variant. enriched_tz hashes the IANA timezone name and is what an SDK that collects a zone name sends. enriched hashes the minute offset instead and stays as the fallback for older SDKs. basic drops the timezone entirely.

ScenarioConfidenceMatch Type
IDFV match, same install asking again (iOS, not a reinstall)1.0deterministic
Play Install Referrer (Android)1.0deterministic
enriched_tz fingerprint, < 1 hour0.85probabilistic
enriched_tz fingerprint, < 3 hours0.65probabilistic
enriched_tz fingerprint, < 6 hours0.50probabilistic
enriched_tz fingerprint, < 24 hours0.30probabilistic

The offset variant scores 0.80 / 0.60 / 0.45 / 0.25 across the same bands, and basic scores 0.70 / 0.50 / 0.35 / 0.20. Two further signals can only reduce the score: more than one distinct link in the fingerprint bucket applies x0.6, and a shared click IP applies x0.6 for carrier-grade NAT or a private address, x0.9 for a household IPv4 address, and x1.0 for IPv6. Past 24 hours there is no match at all.

Both deterministic matches set matchGuaranteed to true on the returned link. Use that flag, like matchConfidence, to pick a destination, not as a credential. A probabilistic match is a best guess made from a network-shaped fingerprint and can name the wrong user, so it should only pick a low-risk destination with a safe fallback; authenticate the user and check authorization separately before anything sensitive, such as signing in or showing personal data.

Match Window

The match window is set per link on the server, not in the SDK. Configure it in the dashboard when you create or edit a link. The default is 6 hours and the ceiling is 24 hours, so the 24 hour band above only applies to links configured past the default.

The window is deliberately short. The fingerprint key is a network (IP, language, timezone), not a device, so every extra hour lets another stranger behind the same shared address join the bucket while adding almost no real matches. Links created before the current limits may still carry a longer stored value, but the server caps every window at 24 hours when it reads them.

This governs probabilistic matching only. The IDFV and Play Install Referrer branches are deterministic and are not affected by the window.

Platform Differences

iOSAndroid
Deterministic matchIDFV (same install asking again, not a reinstall)Play Install Referrer
Completion markerBackup-excluded file in the app containerFile in noBackupFilesDir
Device-seen markerKeychainSharedPreferences (restored by Auto Backup)
ATT required?No (IDFV is exempt)N/A

The completion marker is per install and no backup restores it, so a reinstall re-attributes on both platforms. The device-seen marker outlives an uninstall on both platforms and only sets is_reinstall on the request. The one exception is the iOS fallback described above.

Caching Behavior

  • Attribution check happens once per install (first launch only)
  • Result stored by the native SDK
  • Subsequent calls to checkDeferredDeepLink() resolve with the stored result, the matched deep link if there was one and null if there was not, without another network request
  • A reinstall is a new install, so it gets a new check. See App Reinstall

Edge Cases

Offline First Launch

checkDeferredDeepLink() throws a WarpLinkNetworkException (E_NETWORK_ERROR). The attempt is not consumed, so the SDK retries the check on the next launch.

Check for connectivity before calling checkDeferredDeepLink() if your first-launch experience depends on it.

App Reinstall

A reinstall is a new install and is attributed again on both platforms. The Dart layer does nothing special here: the plugin calls the native SDK, which keeps two markers with two different jobs.

  • Completion marker. Says the check already ran for this install, and it is gone once the app is. iOS keeps it in a backup-excluded file in the app container, Android in noBackupFilesDir. Neither comes back from a restore, so a reinstall starts clean. The one exception is the iOS fallback described above.
  • Device-seen marker. Says the device was attributed at some point, and it outlives an uninstall. iOS keeps it in the Keychain, Android in SharedPreferences, which Auto Backup restores. It gates nothing. Its only job is to set is_reinstall: true on the attribution request

Both installs count. The dashboard shows installs and reinstalls together as one installs number.

Clicks that share a fingerprint are kept as a list, newest first, up to 10. The most recent click your app can claim is the one matched, and the others stay available for the other devices that share the address. When more than one click is claimable, the confidence score is multiplied by 0.6 to report that ambiguity.

Testing

Deleting or uninstalling the app is enough to retest. The completion marker goes with it, so the next install runs the check again:

  1. Delete (iOS) or uninstall (Android) the app from the test device
  2. Open the test link in Safari or Chrome. You're redirected to the App Store, Play Store, or fallback URL
  3. Install the app via Xcode/TestFlight or Android Studio/adb install
  4. Launch the app. The matched deep link should arrive in onLink with isDeferred set to true

The request from step 4 carries is_reinstall: true, because the device-seen marker from the earlier install is still on the device. To test the first-install path instead, use a device or simulator/emulator that has never run the app, or erase it first: on iOS Simulator, Device > Erase All Content and Settings (or xcrun simctl erase); on Android, clear app data along with an uninstall, since SharedPreferences on Android is restored by Auto Backup.

On this page