Skip to main content

Handle Deep Links

This guide shows how to receive and process incoming deep links so your app navigates users to the correct screen when they open an OpenLynk link.

Goal

When a user taps an OpenLynk deep link, your app should:

  1. Intercept the incoming URL.
  2. Let the SDK resolve the link.
  3. Navigate to the destination screen with all attached metadata.

The SDK handles the heavy lifting of link resolution:

  1. Intercepts the URL -- on Flutter this is automatic via the app_links package; on iOS and Android you pass the URL to the SDK manually.
  2. Extracts the slug from the URL path (e.g. 1709876543-x7k9m2 from https://myapp.openlynk.to/1709876543-x7k9m2).
  3. Fetches link details from the OpenLynk API using the slug.
  4. Calls your onDeepLink callback with a ParsedDeepLink object containing all the resolved data.

When the SDK resolves a link, it delivers a ParsedDeepLink to your callback with the following fields:

FieldTypeDescription
destinationPathStringThe in-app route (e.g. /product/123)
parametersMapAll metadata key-value pairs from link creation
destinationStringFull destination including query parameters
metadataMapRaw metadata from the link
linkIdStringThe OpenLynk link ID
slugStringThe URL slug

Platform-Specific Handling

Flutter

The Flutter SDK automatically listens for incoming Universal Links and App Links via the app_links package. You only need to provide the onDeepLink callback during initialization.

final sdk = OpenlynkSDK(
appId: 'YOUR_APP_ID',
apiKey: 'YOUR_API_KEY',
config: OpenlynkSDKConfig(
onDeepLink: (parsed) {
// Navigate to the destination
navigatorKey.currentState?.pushNamed(
parsed.destinationPath,
arguments: parsed.parameters,
);
},
),
);

await sdk.init();
info

init() also processes a cold-start link automatically. If the app was launched by tapping a deep link, onDeepLink fires during initialization.

Manual Parsing

If you receive a link URL from another source (e.g. clipboard or a custom channel), you can parse it manually:

final uri = Uri.parse('https://myapp.openlynk.to/1709876543-x7k9m2');
final parsed = await sdk.parseDeepLink(uri);

if (parsed != null) {
navigatorKey.currentState?.pushNamed(
parsed.destinationPath,
arguments: parsed.parameters,
);
}

iOS

The iOS SDK does not start an automatic link listener. You must call handleIncomingURL(_:) from your SceneDelegate or AppDelegate whenever a Universal Link arrives.

// SceneDelegate — cold start
func scene(
_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions
) {
// ... SDK initialization with onDeepLink callback ...

if let userActivity = connectionOptions.userActivities.first,
userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL {
openlynkSDK.handleIncomingURL(url)
}
}

// SceneDelegate — app already running
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL else { return }
openlynkSDK.handleIncomingURL(url)
}

// SceneDelegate — URL contexts
func scene(_ scene: UIScene,
openURLContexts URLContexts: Set<UIOpenURLContext>) {
guard let url = URLContexts.first?.url else { return }
openlynkSDK.handleIncomingURL(url)
}

For apps without SceneDelegate:

// AppDelegate
func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL else { return false }
openlynkSDK.handleIncomingURL(url)
return true
}
warning

If you forget to call handleIncomingURL, the SDK will never know about incoming links and onDeepLink will never fire on iOS.

Android

The Android SDK does not start an automatic link listener. You must call handleIncomingUri from your Activity's onCreate and onNewIntent.

Kotlin

class MainActivity : AppCompatActivity() {
private val sdk: OpenlynkSDK
get() = (application as MyApplication).openlynkSDK

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

intent?.data?.let { uri ->
sdk.handleIncomingUri(uri)
}
}

override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
intent.data?.let { uri ->
sdk.handleIncomingUri(uri)
}
}
}

Java

public class MainActivity extends AppCompatActivity {
private OpenlynkSDK sdk;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

sdk = ((MyApplication) getApplication()).getOpenlynkSDK();

Uri uri = getIntent().getData();
if (uri != null) {
sdk.handleIncomingUri(uri, null);
}
}

@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (intent.getData() != null) {
sdk.handleIncomingUri(intent.getData(), null);
}
}
}
note

You must handle the URI in both onCreate (cold start) and onNewIntent (app already in memory). Missing either one causes deep links to silently fail in that scenario.

Building a Router

Once the SDK calls onDeepLink, you need to route the user to the correct screen. Here is a simple path-based routing pattern for each platform.

Flutter

void navigateTo(String path, Map<String, dynamic> params) {
final navigator = navigatorKey.currentState;
if (navigator == null) return;

if (path.startsWith('/product/')) {
final productId = path.split('/').last;
navigator.push(MaterialPageRoute(
builder: (_) => ProductPage(productId: productId),
));
} else if (path.startsWith('/profile/')) {
final userId = path.split('/').last;
navigator.push(MaterialPageRoute(
builder: (_) => ProfilePage(userId: userId),
));
} else {
navigator.pushNamed(path, arguments: params);
}
}

iOS (Swift)

func navigateTo(path: String, params: [String: Any]) {
guard let nav = window?.rootViewController
as? UINavigationController else { return }

if path.hasPrefix("/product/") {
let productId = String(path.split(separator: "/").last ?? "")
let vc = ProductViewController(productId: productId)
nav.pushViewController(vc, animated: true)
} else if path.hasPrefix("/profile/") {
let userId = String(path.split(separator: "/").last ?? "")
let vc = ProfileViewController(userId: userId)
nav.pushViewController(vc, animated: true)
}
}

Android (Kotlin)

object DeepLinkRouter {
fun navigate(context: Context, path: String, params: Map<String, Any>) {
val intent = when {
path.startsWith("/product/") -> {
val productId = path.split("/").last()
Intent(context, ProductActivity::class.java).apply {
putExtra("product_id", productId)
}
}
path.startsWith("/profile/") -> {
val userId = path.split("/").last()
Intent(context, ProfileActivity::class.java).apply {
putExtra("user_id", userId)
}
}
else -> Intent(context, MainActivity::class.java)
}
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(intent)
}
}

On Flutter, push notifications that contain an OpenLynk destination can be processed using handlePushPayload(). This creates the same ParsedDeepLink object and delivers it through the onDeepLink callback, so your navigation code stays unified.

FirebaseMessaging.onMessageOpenedApp.listen((message) {
if (message.data.containsKey('destinationPath')) {
sdk.handlePushPayload(message.data);
// This triggers onDeepLink with a ParsedDeepLink
}
});

See Set Up Push Notifications for the full push integration guide.

Flutter

Run the app in debug mode and use adb (Android) or the Notes app (iOS) to open a link. Check your console logs for onDeepLink output.

iOS

  1. Open the Notes app on a physical device.
  2. Type or paste a link URL and long-press it.
  3. Tap "Open in [Your App]".
  4. Verify onDeepLink fires with the correct destinationPath and parameters.
caution

Universal Links do not work in the iOS Simulator. Always test on a physical device.

Android

Use ADB to simulate an App Link:

adb shell am start -a android.intent.action.VIEW \
-d "https://YOUR_APP_SLUG.openlynk.to/test-link" \
com.example.myapp

Or send yourself a link via email or messaging and tap it on your device.

What's Next?