{"id":1548,"date":"2026-06-22T17:00:00","date_gmt":"2026-06-22T22:00:00","guid":{"rendered":"https:\/\/tolinku.com\/blog\/?p=1548"},"modified":"2026-03-07T03:49:34","modified_gmt":"2026-03-07T08:49:34","slug":"cross-app-deep-linking","status":"publish","type":"post","link":"https:\/\/tolinku.com\/blog\/cross-app-deep-linking\/","title":{"rendered":"Cross-App Deep Linking: Navigating Between Apps"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Cross-app deep linking sends users from one app to another with context preserved. A ride-sharing app linking to a restaurant app with the reservation details. A social media app linking to a shopping app with the product page. A banking app linking to a budgeting app with the transaction imported.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The challenge is not just opening the other app; it is passing the right context so the user does not have to re-enter information or navigate manually. This guide covers how to build cross-app deep linking on both iOS and Android. For cross-promotion strategies, see <a href=\"https:\/\/tolinku.com\/blog\/cross-promotion-strategies\/\">cross-promotion strategies for mobile apps<\/a>. For super app deep linking, see <a href=\"https:\/\/tolinku.com\/blog\/deep-linking-super-apps\/\">deep linking for super apps<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How Cross-App Links Work<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">The Three Methods<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>Method<\/th>\n<th>iOS<\/th>\n<th>Android<\/th>\n<th>Use Case<\/th>\n<\/tr>\n<\/thead>\n<tbody><tr>\n<td>Universal Links \/ App Links<\/td>\n<td>Yes<\/td>\n<td>Yes<\/td>\n<td>Standard web URLs that open apps<\/td>\n<\/tr>\n<tr>\n<td>Custom URL Schemes<\/td>\n<td>Yes<\/td>\n<td>Yes<\/td>\n<td>App-specific schemes (myapp:\/\/)<\/td>\n<\/tr>\n<tr>\n<td>Intents \/ Activities<\/td>\n<td>N\/A<\/td>\n<td>Yes<\/td>\n<td>Direct activity launching<\/td>\n<\/tr>\n<\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Universal Links \/ App Links (Recommended)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The most reliable cross-app method. Your app opens a standard HTTPS URL that the target app has registered:<\/p>\n\n\n\n<pre><code class=\"language-swift\">\/\/ iOS: Open another app via Universal Link\nlet targetURL = URL(string: &quot;https:\/\/restaurantapp.com\/reservations\/abc123&quot;)!\nUIApplication.shared.open(targetURL)\n\/\/ If RestaurantApp is installed \u2192 opens the app\n\/\/ If not installed \u2192 opens in Safari\n<\/code><\/pre>\n\n\n\n<pre><code class=\"language-kotlin\">\/\/ Android: Open another app via App Link\nval intent = Intent(Intent.ACTION_VIEW, Uri.parse(&quot;https:\/\/restaurantapp.com\/reservations\/abc123&quot;))\nstartActivity(intent)\n\/\/ If RestaurantApp is installed \u2192 opens the app\n\/\/ If not installed \u2192 opens in browser\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Custom URL Schemes<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Custom schemes (<code>myapp:\/\/path<\/code>) are less reliable but provide more control:<\/p>\n\n\n\n<pre><code class=\"language-swift\">\/\/ iOS: Check if another app is installed before opening\nlet scheme = URL(string: &quot;restaurantapp:\/\/&quot;)!\nif UIApplication.shared.canOpenURL(scheme) {\n    let deepLink = URL(string: &quot;restaurantapp:\/\/reservations\/abc123&quot;)!\n    UIApplication.shared.open(deepLink)\n} else {\n    \/\/ App not installed, open web fallback or App Store\n    let webFallback = URL(string: &quot;https:\/\/restaurantapp.com\/reservations\/abc123&quot;)!\n    UIApplication.shared.open(webFallback)\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">On iOS, you must declare the schemes you want to query in your <code>Info.plist<\/code>:<\/p>\n\n\n\n<pre><code class=\"language-xml\">&lt;key&gt;LSApplicationQueriesSchemes&lt;\/key&gt;\n&lt;array&gt;\n    &lt;string&gt;restaurantapp&lt;\/string&gt;\n    &lt;string&gt;bankingapp&lt;\/string&gt;\n    &lt;string&gt;fitnessapp&lt;\/string&gt;\n&lt;\/array&gt;\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">iOS limits this to 50 schemes per app.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Passing Context Between Apps<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">URL Parameters<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The simplest approach: encode context in the URL:<\/p>\n\n\n\n<pre><code>https:\/\/restaurantapp.com\/reservations\/new?\n  restaurant=pizza-palace&amp;\n  date=2026-06-22&amp;\n  time=19:00&amp;\n  guests=4&amp;\n  source=rideshare-app&amp;\n  callback=rideshareapp:\/\/rides\/current\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Structured Payloads<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">For complex data, encode a JSON payload in a URL parameter:<\/p>\n\n\n\n<pre><code class=\"language-swift\">\/\/ Sending app: encode context\nlet context: [String: Any] = [\n    &quot;transaction&quot;: [\n        &quot;amount&quot;: 42.50,\n        &quot;currency&quot;: &quot;USD&quot;,\n        &quot;merchant&quot;: &quot;Coffee Shop&quot;,\n        &quot;date&quot;: &quot;2026-06-22T10:30:00Z&quot;\n    ],\n    &quot;source&quot;: &quot;banking-app&quot;,\n    &quot;callback&quot;: &quot;bankingapp:\/\/transactions\/import-result&quot;\n]\n\nlet jsonData = try JSONSerialization.data(withJSONObject: context)\nlet encoded = jsonData.base64EncodedString()\nlet url = URL(string: &quot;https:\/\/budgetapp.com\/import?data=\\(encoded)&quot;)!\nUIApplication.shared.open(url)\n<\/code><\/pre>\n\n\n\n<pre><code class=\"language-swift\">\/\/ Receiving app: decode context\nfunc handleDeepLink(_ url: URL) {\n    guard let encodedData = url.queryParameters[&quot;data&quot;],\n          let jsonData = Data(base64Encoded: encodedData),\n          let context = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any]\n    else { return }\n\n    let transaction = context[&quot;transaction&quot;] as? [String: Any]\n    let callback = context[&quot;callback&quot;] as? String\n    importTransaction(transaction, callbackURL: callback)\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Callback URLs<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The source app can include a callback URL so the target app can return results:<\/p>\n\n\n\n<pre><code>Source app opens: https:\/\/paymentapp.com\/pay?amount=25.00&amp;callback=myapp:\/\/payment-result\n  \u2192 Payment app processes payment\n    \u2192 Payment app opens: myapp:\/\/payment-result?status=success&amp;transaction_id=xyz\n      \u2192 Source app receives the result\n<\/code><\/pre>\n\n\n\n<pre><code class=\"language-kotlin\">\/\/ Target app: return result to source app\nfun returnResult(callbackUrl: String, result: PaymentResult) {\n    val uri = Uri.parse(callbackUrl).buildUpon()\n        .appendQueryParameter(&quot;status&quot;, result.status)\n        .appendQueryParameter(&quot;transaction_id&quot;, result.transactionId)\n        .build()\n\n    startActivity(Intent(Intent.ACTION_VIEW, uri))\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Platform-Specific Patterns<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">iOS: Activity Continuation<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">For richer cross-app experiences, use <a href=\"https:\/\/developer.apple.com\/documentation\/foundation\/nsuseractivity\" rel=\"nofollow noopener\" target=\"_blank\">NSUserActivity<\/a>:<\/p>\n\n\n\n<pre><code class=\"language-swift\">\/\/ Source app: hand off an activity\nlet activity = NSUserActivity(activityType: &quot;com.rideshare.destination-arrived&quot;)\nactivity.title = &quot;Arrived at destination&quot;\nactivity.userInfo = [\n    &quot;address&quot;: &quot;123 Main St&quot;,\n    &quot;restaurant&quot;: &quot;pizza-palace&quot;,\n    &quot;reservation_time&quot;: &quot;19:00&quot;\n]\nactivity.webpageURL = URL(string: &quot;https:\/\/restaurantapp.com\/checkin\/pizza-palace&quot;)\nactivity.isEligibleForHandoff = true\nactivity.becomeCurrent()\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Android: Explicit Intents with Extras<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">On Android, you can send structured data through Intent extras:<\/p>\n\n\n\n<pre><code class=\"language-kotlin\">\/\/ Source app: launch target app with structured data\nval intent = Intent(Intent.ACTION_VIEW).apply {\n    data = Uri.parse(&quot;https:\/\/restaurantapp.com\/reservations\/new&quot;)\n    putExtra(&quot;restaurant_id&quot;, &quot;pizza-palace&quot;)\n    putExtra(&quot;date&quot;, &quot;2026-06-22&quot;)\n    putExtra(&quot;time&quot;, &quot;19:00&quot;)\n    putExtra(&quot;guests&quot;, 4)\n    putExtra(&quot;source_package&quot;, packageName)\n}\n\n\/\/ Check if the target app can handle this\nif (intent.resolveActivity(packageManager) != null) {\n    startActivityForResult(intent, REQUEST_CODE_RESERVATION)\n}\n<\/code><\/pre>\n\n\n\n<pre><code class=\"language-kotlin\">\/\/ Target app: receive the data and return a result\noverride fun onCreate(savedInstanceState: Bundle?) {\n    super.onCreate(savedInstanceState)\n\n    val restaurantId = intent.getStringExtra(&quot;restaurant_id&quot;)\n    val date = intent.getStringExtra(&quot;date&quot;)\n    val guests = intent.getIntExtra(&quot;guests&quot;, 2)\n\n    \/\/ Process the reservation...\n}\n\nfun returnConfirmation(confirmationId: String) {\n    val result = Intent().apply {\n        putExtra(&quot;confirmation_id&quot;, confirmationId)\n        putExtra(&quot;status&quot;, &quot;confirmed&quot;)\n    }\n    setResult(RESULT_OK, result)\n    finish()\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Security Considerations<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Validating Incoming Deep Links<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Always validate deep link data from other apps:<\/p>\n\n\n\n<pre><code class=\"language-swift\">func handleCrossAppLink(_ url: URL) {\n    guard let host = url.host else { return }\n\n    \/\/ Only accept links from known partner apps\/domains\n    let allowedHosts = [&quot;partnerapp.com&quot;, &quot;paymentapp.com&quot;, &quot;restaurantapp.com&quot;]\n    guard allowedHosts.contains(host) else {\n        log.warning(&quot;Rejected deep link from unknown host: \\(host)&quot;)\n        return\n    }\n\n    \/\/ Validate and sanitize all parameters\n    guard let amount = url.queryParameters[&quot;amount&quot;],\n          let parsedAmount = Double(amount),\n          parsedAmount &gt; 0, parsedAmount &lt; 10000\n    else { return }\n\n    \/\/ Process the validated data\n    processPaymentRequest(amount: parsedAmount)\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Preventing URL Injection<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Never construct executable code from deep link parameters:<\/p>\n\n\n\n<pre><code class=\"language-swift\">\/\/ WRONG: SQL injection risk\nlet query = &quot;SELECT * FROM products WHERE id = &#39;\\(productId)&#39;&quot;\n\n\/\/ RIGHT: parameterized query\nlet query = &quot;SELECT * FROM products WHERE id = ?&quot;\ndb.execute(query, [productId])\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Callback URL Validation<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Validate callback URLs before opening them:<\/p>\n\n\n\n<pre><code class=\"language-kotlin\">fun validateCallbackUrl(callbackUrl: String): Boolean {\n    val uri = Uri.parse(callbackUrl)\n\n    \/\/ Only allow registered callback schemes\n    val allowedSchemes = setOf(&quot;partnerapp&quot;, &quot;bankingapp&quot;, &quot;https&quot;)\n    if (uri.scheme !in allowedSchemes) return false\n\n    \/\/ For HTTPS callbacks, only allow known domains\n    if (uri.scheme == &quot;https&quot;) {\n        val allowedDomains = setOf(&quot;partnerapp.com&quot;, &quot;bankingapp.com&quot;)\n        if (uri.host !in allowedDomains) return false\n    }\n\n    return true\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Tolinku for Cross-App Deep Links<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/tolinku.com\/features\/deep-linking\">Tolinku<\/a> handles the web fallback and routing for cross-app deep links. When App A links to App B via a Tolinku-managed URL, users who have App B installed go directly to the app. Users who do not have it see a web page with the content and an install prompt. Configure partner app routes in the <a href=\"https:\/\/tolinku.com\/docs\/concepts\/deep-linking\/\">Tolinku dashboard<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For the broader deep linking trends, see <a href=\"https:\/\/tolinku.com\/blog\/future-mobile-deep-linking\/\">the future of mobile deep linking<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Enable cross-app deep linking for seamless experiences. Build integrations that let users navigate between related apps with context preserved.<\/p>\n","protected":false},"author":2,"featured_media":1547,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"Cross-App Deep Linking: Navigating Between Apps","rank_math_description":"Enable cross-app deep linking for seamless experiences. Build integrations that let users navigate between related apps with context preserved.","rank_math_focus_keyword":"cross-app deep linking","rank_math_canonical_url":"","rank_math_facebook_title":"","rank_math_facebook_description":"","rank_math_facebook_image":"https:\/\/tolinku.com\/blog\/wp-content\/uploads\/2026\/03\/og-cross-app-deep-linking.png","rank_math_facebook_image_id":"","rank_math_twitter_title":"","rank_math_twitter_description":"","rank_math_twitter_image":"https:\/\/tolinku.com\/blog\/wp-content\/uploads\/2026\/03\/og-cross-app-deep-linking.png","footnotes":""},"categories":[11],"tags":[25,409,20,411,24,69,22,412],"class_list":["post-1548","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-deep-linking","tag-android","tag-cross-app","tag-deep-linking","tag-inter-app","tag-ios","tag-mobile-development","tag-universal-links","tag-url-schemes"],"_links":{"self":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1548","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/comments?post=1548"}],"version-history":[{"count":3,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1548\/revisions"}],"predecessor-version":[{"id":2630,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1548\/revisions\/2630"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media\/1547"}],"wp:attachment":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media?parent=1548"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/categories?post=1548"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/tags?post=1548"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}