Skip to main content

Implement Deferred Deep Linking

Deferred deep linking preserves link context across the app install flow. When a user clicks a deep link but does not have your app installed, the link data is stored and restored after the user installs and opens the app for the first time.

Goal

A user who clicks a link before installing your app should land on the same destination screen as a user who already had the app installed. No context should be lost.

How It Works

The deferred deep linking flow has six steps:

  1. Click -- the user taps a deep link.
  2. Store -- OpenLynk stores a pending link with the destination, metadata, and device context.
  3. Redirect -- the user is sent to the App Store, Play Store, or a web fallback.
  4. Install -- the user installs and opens your app.
  5. Restore -- on first launch, the SDK calls the restore API to retrieve pending links.
  6. Navigate -- your app receives the original link data and navigates to the destination screen.
Click -> Pending link stored -> Store redirect -> Install -> SDK init -> Restore -> Navigate

All custom metadata attached during link creation is preserved through this entire flow.

The simplest approach is to let the SDK restore pending links automatically during initialization. Set autoRestoreOnInit: true and provide a userEmailProvider for the best matching accuracy.

Flutter

final sdk = OpenlynkSDK(
appId: 'YOUR_APP_ID',
apiKey: 'YOUR_API_KEY',
config: OpenlynkSDKConfig(
autoRestoreOnInit: true,
userEmailProvider: () async {
return FirebaseAuth.instance.currentUser?.email;
},
onRestoredLinks: (links) {
for (final link in links) {
final path = link.destinationPath ?? '/';
final params = link.parameters ?? link.metadata;
navigateTo(path, params);
}
},
),
);

await sdk.init();

iOS (Swift)

let sdk = OpenlynkSDK(
appId: "YOUR_APP_ID",
apiKey: "YOUR_API_KEY",
config: OpenlynkSDKConfig(
autoRestoreOnInit: true,
userEmailProvider: { callback in
let email = AuthManager.shared.currentUserEmail
callback(email)
},
onRestoredLinks: { links in
for link in links {
let path = link.destinationPath ?? link.destinationUrl
let params = link.parameters ?? link.metadata
navigateTo(path: path, params: params)
}
}
)
)

sdk.initSDK()

Android (Kotlin)

val sdk = OpenlynkSDK.create(
context = this,
appId = "YOUR_APP_ID",
apiKey = "YOUR_API_KEY",
config = OpenlynkSDKConfig(
autoRestoreOnInit = true,
userEmailProvider = {
AuthManager.getCurrentUserEmail()
},
onRestoredLinks = { links ->
for (link in links) {
val path = link.destinationPath ?: link.destinationUrl
val params = link.parameters ?: link.metadata
DeepLinkRouter.navigate(this, path, params)
}
}
)
)

sdk.init()

Android (Java)

OpenlynkSDKConfig config = new OpenlynkSDKConfig();
config.autoRestoreOnInit = true;
config.userEmailProvider = callback -> {
String email = AuthManager.getCurrentUserEmail();
callback.onResult(email);
};
config.onRestoredLinks = links -> {
for (OpenlynkSDK.RestoredLink link : links) {
String path = link.destinationPath != null
? link.destinationPath : link.destinationUrl;
Map<String, Object> params = link.parameters != null
? link.parameters : link.metadata;
DeepLinkRouter.navigate(MyApplication.this, path, params);
}
};

OpenlynkSDK sdk = new OpenlynkSDK(this, "YOUR_APP_ID", "YOUR_API_KEY",
"https://openlynk.io", config);
sdk.init();

Manual Restore

If you need more control over when restoration happens (for example, after a login screen), set autoRestoreOnInit: false and call the restore method yourself.

Flutter

Future<void> restoreLinks() async {
try {
final email = await getCurrentUserEmail();
final restored = email != null
? await sdk.restorePendingLinks(userEmail: email)
: await sdk.restorePendingLinksForAnonymous();

for (final link in restored) {
final path = link.destinationPath ?? link.destinationUrl;
final params = link.parameters ?? link.metadata;
navigateTo(path, params);
}
} catch (e) {
debugPrint('Restore failed: $e');
}
}

iOS (Swift)

sdk.restorePendingLinks(userEmail: "[email protected]") { result in
switch result {
case .success(let links):
for link in links {
let path = link.destinationPath ?? link.destinationUrl
navigateTo(path: path, params: link.parameters ?? link.metadata)
}
case .failure(let error):
print("Restore failed: \(error)")
}
}

// For anonymous users (uses device fingerprint)
sdk.restorePendingLinks { result in
// same handling
}

Android (Kotlin)

sdk.restorePendingLinks(userEmail = "[email protected]") { links ->
for (link in links) {
val path = link.destinationPath ?: link.destinationUrl
val params = link.parameters ?: link.metadata
DeepLinkRouter.navigate(this, path, params)
}
}

// For anonymous users (uses device fingerprint)
sdk.restorePendingLinks(userEmail = null) { links ->
// same handling
}

Android (Java)

sdk.restorePendingLinks("[email protected]",
new OpenlynkSDK.RestoreCallback() {
@Override
public void onSuccess(List<OpenlynkSDK.RestoredLink> links) {
for (OpenlynkSDK.RestoredLink link : links) {
String path = link.destinationPath != null
? link.destinationPath : link.destinationUrl;
DeepLinkRouter.navigate(
MyApplication.this, path, link.parameters);
}
}

@Override
public void onError(Exception error) {
Log.e("SDK", "Restore failed: " + error.getMessage());
}
});

// For anonymous users (pass null, uses device fingerprint)
sdk.restorePendingLinks(null, new OpenlynkSDK.RestoreCallback() { ... });

How Matching Works

When restoring pending links, OpenLynk matches using one of two methods depending on what data is available:

PriorityMethodWhen UsedAccuracy
1User emailWhen userEmailProvider returns an emailHighest — deterministic match on a unique identifier
2Device fingerprintWhen no email is available (anonymous users)High — matches on a hashed device identifier
tip

For the best matching accuracy, provide a userEmailProvider that returns the user's email as soon as they log in or sign up. Email-based matching is deterministic and will always return the correct pending link.

Device Fingerprint

When no email is available, the SDK generates a device fingerprint automatically and stores it in persistent storage (SharedPreferences on Android, UserDefaults on iOS). The fingerprint is:

  • Hashed — built from device properties (model, manufacturer, vendor ID) and hashed with SHA-256 before being sent to the server. No raw device information leaves the device.
  • Stable — persisted locally so it remains the same across app launches. The fingerprint only changes if the user reinstalls the app or clears app data.
  • Per-device — different devices produce different fingerprints, even for the same user account.

No additional setup is required. The SDK handles fingerprint generation and storage automatically during init().

When pending links are restored, each link is delivered as a RestoredLink with these fields:

FieldTypeDescription
destinationPathString?The in-app route (e.g. /product/123)
destinationString?Full destination including query parameters
parametersMap?Parsed metadata key-value pairs
metadataMapRaw metadata from the original link
pendingLinkIdStringThe pending link record ID
originalUrlStringThe URL the user originally clicked
destinationUrlStringThe resolved destination URL
linkIdStringThe OpenLynk link ID

UTM Convenience Getters

RestoredLink provides convenience getters for common UTM parameters:

  • utmSource
  • utmMedium
  • utmCampaign
  • utmTerm
  • utmContent
onRestoredLinks: (links) {
final link = links.first;
print(link.utmSource); // "share"
print(link.utmCampaign); // "summer_2025"
}

Best Practices

  1. Call restore early. Trigger restoration as early as possible during app startup so the user sees the destination screen quickly. Using autoRestoreOnInit: true is the easiest way.
  2. Handle multiple links. A user might have clicked more than one link before installing. The onRestoredLinks callback receives a list, not a single link. Navigate to the most recent or most relevant one.
  3. Graceful degradation. If restoration fails (e.g. due to network issues), let the user proceed to the home screen normally. Do not block app startup on the restore call.
  4. Direct links take priority. If the app is opened directly via a deep link (cold start), the SDK skips auto-restore to avoid conflicting navigations. The direct link is always processed first.
  5. Privacy considerations. The SDK collects device fingerprint data (model, manufacturer, vendor ID) for matching purposes. This data is SHA-256 hashed before leaving the device and is not personally identifiable. If your app requires explicit consent before collecting device data, set autoRestoreOnInit: false and call restore only after consent is granted.
note

Pending links expire after 30 days (server default). If a user installs the app after the pending link expires, no link will be restored.

What's Next?