{"id":1106,"date":"2026-05-26T14:12:20","date_gmt":"2026-05-26T07:12:20","guid":{"rendered":"https:\/\/appress.app\/articles\/wordpress-mobile-app-deep-linking-universal-links-app-links\/"},"modified":"2026-05-26T14:12:20","modified_gmt":"2026-05-26T07:12:20","slug":"wordpress-mobile-app-deep-linking-universal-links-app-links","status":"publish","type":"post","link":"https:\/\/appress.app\/vi\/articles\/wordpress-mobile-app-deep-linking-universal-links-app-links\/","title":{"rendered":"Deep Linking in WordPress Mobile Apps: Universal Links vs App Links vs Custom Schemes"},"content":{"rendered":"\n<style>\n.ap-landing { max-width: 900px; margin: 0 auto; padding: 64px 24px; font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", sans-serif; color: #0f172a; line-height: 1.7; }\n.ap-landing * { box-sizing: border-box; }\n.ap-landing section { padding: 40px 0; border-bottom: 1px solid #e2e8f0; }\n.ap-landing section:last-child { border-bottom: none; }\n.ap-landing h1 { font-size: clamp(36px, 5vw, 52px); font-weight: 800; line-height: 1.1; letter-spacing: -0.025em; margin: 0 0 24px; color: #0f172a; }\n.ap-landing h2 { font-size: clamp(26px, 3vw, 34px); font-weight: 700; line-height: 1.2; letter-spacing: -0.02em; margin: 0 0 20px; color: #0f172a; }\n.ap-landing h3 { font-size: 21px; font-weight: 700; margin: 32px 0 12px; color: #0f172a; }\n.ap-landing p { font-size: 18px; margin: 0 0 18px; color: #334155; }\n.ap-landing p.ap-lead { font-size: 22px; line-height: 1.5; color: #475569; max-width: 760px; margin-bottom: 24px; }\n.ap-landing p.ap-meta { font-size: 14px; color: #64748b; margin-bottom: 32px; }\n.ap-landing ul, .ap-landing ol { margin: 0 0 18px; padding-left: 24px; }\n.ap-landing li { font-size: 18px; color: #334155; margin-bottom: 8px; }\n.ap-landing a { color: #7c3aed; font-weight: 600; }\n.ap-landing strong { color: #0f172a; }\n.ap-landing code { background: #f1f5f9; padding: 2px 7px; border-radius: 4px; font-size: 0.92em; color: #4c1d95; }\n.ap-landing pre { background: #0f172a; color: #e2e8f0; padding: 20px 24px; border-radius: 12px; overflow-x: auto; font-size: 14px; line-height: 1.5; margin: 20px 0; }\n.ap-landing pre code { background: transparent; padding: 0; color: inherit; }\n.ap-landing .ap-cta-row { display: flex; gap: 12px; flex-wrap: wrap; margin: 32px 0 0; }\n.ap-landing .ap-btn { display: inline-flex; align-items: center; padding: 14px 28px; border-radius: 12px; font-size: 16px; font-weight: 600; text-decoration: none; }\n.ap-landing .ap-btn-primary { background: #7c3aed; color: #fff; }\n.ap-landing .ap-btn-primary:hover { color: #fff; }\n.ap-landing .ap-btn-secondary { background: #fff; color: #0f172a; border: 1.5px solid #e2e8f0; }\n.ap-landing .ap-table-wrap { overflow-x: auto; margin: 24px 0; }\n.ap-landing .ap-table { width: 100%; border-collapse: collapse; font-size: 15px; }\n.ap-landing .ap-table th, .ap-landing .ap-table td { text-align: left; padding: 12px 16px; border-bottom: 1px solid #e2e8f0; vertical-align: top; }\n.ap-landing .ap-table thead th { background: #f8fafc; font-weight: 700; }\n.ap-landing .ap-table .ap-yes { color: #16a34a; font-weight: 700; }\n.ap-landing .ap-table .ap-no { color: #dc2626; }\n.ap-landing .ap-table .ap-warn { color: #d97706; }\n.ap-landing blockquote { border-left: 4px solid #7c3aed; padding: 8px 24px; margin: 24px 0; color: #475569; font-size: 19px; font-style: italic; background: #faf5ff; border-radius: 0 8px 8px 0; }\n.ap-landing .ap-callout { background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 12px; padding: 24px; margin: 24px 0; }\n.ap-landing .ap-callout p { margin: 0; }\n.ap-landing .ap-faq-item { padding: 20px 0; border-bottom: 1px solid #e2e8f0; }\n.ap-landing .ap-faq-item:last-child { border-bottom: none; }\n.ap-landing .ap-faq-q { font-size: 19px; font-weight: 700; margin: 0 0 8px; }\n.ap-landing .ap-faq-a { font-size: 17px; color: #475569; margin: 0; line-height: 1.65; }\n.ap-landing .ap-final-cta { text-align: center; padding: 56px 32px; background: linear-gradient(135deg, #faf5ff 0%, #fff 100%); border-radius: 20px; margin: 32px 0 0; }\n.ap-landing .ap-final-cta .ap-cta-row { justify-content: center; }\n@media (max-width: 768px) { .ap-landing { padding: 32px 16px; } .ap-landing p, .ap-landing li { font-size: 17px; } }\n<\/style>\n\n<div class=\"ap-landing\">\n\n<section>\n  <h1>Deep linking in WordPress mobile apps: Universal Links, App Links, and custom schemes explained<\/h1>\n  <p class=\"ap-lead\">A push notification that opens the home screen instead of the specific product page. A marketing email link that opens Safari instead of the user&#8217;s installed app. A QR code that bounces to the App Store instead of jumping into the right screen. All three are symptoms of broken deep linking \u2014 and they leak roughly 70% of the engagement value of every link a mobile app receives. This guide explains how iOS Universal Links, Android App Links, and custom URI schemes actually work in 2026, what breaks them, and how to wire them up correctly for a WordPress-backed app.<\/p>\n  <p class=\"ap-meta\">Published 2026-05-26 \u00b7 Reading time: ~12 minutes \u00b7 iOS 18 + Android 15 \u00b7 Covers Universal Links, App Links, custom schemes, deferred deep linking<\/p>\n<\/section>\n\n<section>\n  <h2>What &#8220;deep linking&#8221; actually means<\/h2>\n  <p>A deep link is a URL that, when tapped, jumps directly to a specific in-app screen instead of the app&#8217;s home screen. The user does not navigate. They land where the link points \u2014 a product page, a chat thread, a saved listing, a checkout step.<\/p>\n  <p>Three distinct technologies make this happen, and they are not interchangeable:<\/p>\n  <div class=\"ap-table-wrap\">\n    <table class=\"ap-table\">\n      <thead><tr><th>Technology<\/th><th>Platform<\/th><th>URL form<\/th><th>When the app is installed<\/th><th>When the app is NOT installed<\/th><\/tr><\/thead>\n      <tbody>\n        <tr><td>Universal Links<\/td><td>iOS only<\/td><td><code>https:\/\/brand.com\/product\/42<\/code><\/td><td>Opens app directly<\/td><td>Opens Safari to the same URL<\/td><\/tr>\n        <tr><td>App Links<\/td><td>Android only<\/td><td><code>https:\/\/brand.com\/product\/42<\/code><\/td><td>Opens app directly (after one-time consent)<\/td><td>Opens Chrome to the same URL<\/td><\/tr>\n        <tr><td>Custom URI scheme<\/td><td>Both<\/td><td><code>brand:\/\/product\/42<\/code><\/td><td>Opens app directly<\/td><td>Error \/ nothing (no fallback)<\/td><\/tr>\n      <\/tbody>\n    <\/table>\n  <\/div>\n  <p>The strategic implication: if you want a single link to work for both installed-app users AND not-yet-installed users (almost always what you want for marketing), you need <strong>Universal Links + App Links<\/strong>, not custom schemes. Custom schemes are now a fallback \/ legacy mechanism.<\/p>\n<\/section>\n\n<section>\n  <h2>Why custom URI schemes lost<\/h2>\n  <p>Until iOS 9 (2015) and Android 6 (2015), the standard pattern was the custom URI scheme \u2014 <code>spotify:\/\/album\/abc<\/code>, <code>twitter:\/\/user?screen_name=brand<\/code>. These worked but had three structural problems:<\/p>\n  <ul>\n    <li><strong>No fallback when the app isn&#8217;t installed.<\/strong> Tapping <code>brand:\/\/product\/42<\/code> on a device without the brand app installed produced a silent error or &#8220;App not found&#8221; dialog. You can&#8217;t recover the user \u2014 they can&#8217;t even see the product page on the web.<\/li>\n    <li><strong>Collision risk.<\/strong> Two apps could register the same scheme. If two banking apps both register <code>bank:\/\/<\/code>, iOS picked one arbitrarily. Users got the wrong app.<\/li>\n    <li><strong>Phishing risk.<\/strong> Any app could register <code>https:\/\/<\/code>-looking scheme like <code>paypl:\/\/<\/code>. iOS later restricted this, but the trust model was broken.<\/li>\n  <\/ul>\n  <p>Apple and Google&#8217;s solution was to anchor deep links to real domains via cryptographic verification. The result was Universal Links and App Links \u2014 same URL works on web AND in-app, with no collision risk because only the domain owner can register the link.<\/p>\n<\/section>\n\n<section>\n  <h2>How iOS Universal Links actually work<\/h2>\n  <p>The full handshake is more involved than most developers expect:<\/p>\n  <ol>\n    <li><strong>You host an Apple App Site Association (AASA) file<\/strong> at <code>https:\/\/brand.com\/.well-known\/apple-app-site-association<\/code>. This file declares which URL paths your app wants to handle.<\/li>\n    <li><strong>Apple&#8217;s CDN fetches the AASA file<\/strong> when iOS device installs your app. iOS caches it locally.<\/li>\n    <li><strong>User taps a link<\/strong> to <code>https:\/\/brand.com\/product\/42<\/code> anywhere \u2014 Messages, Mail, Safari, a Notes URL, a notification.<\/li>\n    <li><strong>iOS checks the cached AASA<\/strong>: does the brand.com domain claim the path <code>\/product\/*<\/code>? If yes, route to the app.<\/li>\n    <li><strong>The app receives the URL<\/strong> via <code>NSUserActivity<\/code> with <code>activityType == NSUserActivityTypeBrowsingWeb<\/code>. Your app&#8217;s deep link router parses the URL path and navigates accordingly.<\/li>\n  <\/ol>\n  <p>The example AASA file for a WordPress site that wants the app to handle product pages and the user account area:<\/p>\n  <pre><code>{\n  \"applinks\": {\n    \"details\": [\n      {\n        \"appIDs\": [\"TEAMID.com.brand.app\"],\n        \"components\": [\n          { \"\/\": \"\/product\/*\" },\n          { \"\/\": \"\/account\/*\" },\n          { \"\/\": \"\/articles\/*\" },\n          { \"\/\": \"*\", \"exclude\": true,\n            \"comment\": \"Do NOT route everything \u2014 only matched paths above\" }\n        ]\n      }\n    ]\n  }\n}<\/code><\/pre>\n  <p>Three common failures that ship to production:<\/p>\n  <ul>\n    <li><strong>AASA file served with wrong Content-Type.<\/strong> Must be <code>application\/json<\/code> (or no extension served as JSON). WordPress + most CDNs serve <code>\/.well-known\/apple-app-site-association<\/code> as a plain file with no extension, which can default to <code>application\/octet-stream<\/code> \u2014 Apple&#8217;s CDN rejects this.<\/li>\n    <li><strong>AASA file behind login or rate limit.<\/strong> Apple&#8217;s CDN fetches the file periodically. If it returns 401, 429, or 503, iOS falls back to opening Safari. Users see &#8220;deep linking is broken&#8221; with no error.<\/li>\n    <li><strong>Wildcard route on root (<code>\/<\/code>).<\/strong> Routing <code>\/*<\/code> to the app means EVERY brand.com link opens the app \u2014 including links to the homepage, the cart, the help center. Users get frustrated. Always declare specific path patterns and exclude the rest.<\/li>\n  <\/ul>\n<\/section>\n\n<section>\n  <h2>How Android App Links work (and why they&#8217;re harder)<\/h2>\n  <p>Android App Links solve the same problem but with a more aggressive verification step:<\/p>\n  <ol>\n    <li><strong>You host <code>assetlinks.json<\/code><\/strong> at <code>https:\/\/brand.com\/.well-known\/assetlinks.json<\/code>. Same well-known location pattern as iOS, different format.<\/li>\n    <li><strong>You sign your Android app with a known key.<\/strong> The signing fingerprint must match the one declared in <code>assetlinks.json<\/code>.<\/li>\n    <li><strong>Android verifies the relationship at install time<\/strong> by fetching <code>assetlinks.json<\/code> over HTTPS and matching the app&#8217;s signing fingerprint against the declared fingerprint.<\/li>\n    <li><strong>If verification passes<\/strong>, Android grants the app &#8220;verified&#8221; status for the declared domain. Tapping a link opens the app directly with no chooser dialog.<\/li>\n    <li><strong>If verification fails or is incomplete<\/strong>, Android falls back to a chooser (&#8220;Open with\u2026&#8221;) on every tap, asking the user every time.<\/li>\n  <\/ol>\n  <p>Example <code>assetlinks.json<\/code>:<\/p>\n  <pre><code>[{\n  \"relation\": [\"delegate_permission\/common.handle_all_urls\"],\n  \"target\": {\n    \"namespace\": \"android_app\",\n    \"package_name\": \"com.brand.app\",\n    \"sha256_cert_fingerprints\": [\n      \"AA:BB:CC:DD:EE:FF:...\"\n    ]\n  }\n}]<\/code><\/pre>\n  <p>The single most common Android App Links failure: <strong>wrong SHA-256 fingerprint<\/strong>. When Google Play uses Play App Signing (the default for new apps in 2026), the fingerprint that ships to production is NOT the same as your local debug fingerprint. The fingerprint in <code>assetlinks.json<\/code> must be the Play App Signing certificate fingerprint \u2014 visible in Google Play Console \u2192 App integrity \u2192 App signing. If you ship with the wrong one, Android falls back to chooser dialog forever.<\/p>\n<\/section>\n\n<section>\n  <h2>WordPress-specific challenges<\/h2>\n  <p>Hosting AASA and assetlinks.json files on a WordPress site has predictable rough edges:<\/p>\n\n  <h3>1. WordPress permalink rewrites can swallow \/.well-known\/<\/h3>\n  <p>WordPress&#8217;s <code>.htaccess<\/code> default forwards every request that doesn&#8217;t match a real file to <code>index.php<\/code>. If WordPress doesn&#8217;t have a route for <code>\/.well-known\/apple-app-site-association<\/code>, it returns the 404 template \u2014 which is HTML, not JSON. Apple rejects.<\/p>\n  <p>The fix is one of three patterns:<\/p>\n  <ul>\n    <li><strong>Server-level rewrite exception<\/strong>: edit <code>.htaccess<\/code> to serve <code>\/.well-known\/*<\/code> as static files, never routed through WordPress.<\/li>\n    <li><strong>WordPress action handler<\/strong>: hook <code>init<\/code>, detect the request URI, emit the JSON file content directly with <code>application\/json<\/code> Content-Type, then <code>exit;<\/code> before WordPress routing.<\/li>\n    <li><strong>Native CDN endpoint<\/strong>: most modern WordPress hosts (Kinsta, WP Engine, Cloudflare) let you set static-file routes outside WordPress. Use this if available.<\/li>\n  <\/ul>\n  <p>The plugin pattern: any time your app builder generates the AASA \/ assetlinks.json content dynamically (e.g., when the user adds a new theme widget that needs app linking), the file content changes \u2014 meaning the static file approach fails. A dynamic handler tied to plugin configuration is more reliable. Plugins like Appress handle this by registering an <code>init<\/code> hook that intercepts <code>\/.well-known\/*<\/code> requests and serves the current configuration as JSON.<\/p>\n\n  <h3>2. CDN caching can leave a stale AASA \/ assetlinks.json file in flight<\/h3>\n  <p>You update the AASA file when adding a new path pattern. Apple&#8217;s CDN fetched the old one 3 hours ago. iOS devices that install your app in the next 24 hours see the old declaration. New paths don&#8217;t deep link.<\/p>\n  <p>Mitigation:<\/p>\n  <ul>\n    <li>Set <code>Cache-Control: max-age=3600, must-revalidate<\/code> (or shorter) on the well-known endpoint<\/li>\n    <li>Purge your CDN edge cache after every AASA \/ assetlinks update<\/li>\n    <li>Wait 24 hours after updating before declaring &#8220;rolled out&#8221;<\/li>\n  <\/ul>\n\n  <h3>3. The www. canonical confusion<\/h3>\n  <p>If your site is served on both <code>brand.com<\/code> and <code>www.brand.com<\/code> (with one redirecting to the other), the AASA file MUST be on the canonical domain (the one that does NOT redirect). Apple&#8217;s CDN does not follow redirects when fetching the well-known endpoint. If <code>brand.com\/.well-known\/apple-app-site-association<\/code> redirects to <code>www.brand.com\/...<\/code>, Apple sees the 301 as failure.<\/p>\n<\/section>\n\n<section>\n  <h2>Deep link payloads in push notifications<\/h2>\n  <p>The single highest-ROI deep linking use case is push notification routing. A WooCommerce order-shipped push that opens to the home screen wastes 80% of its engagement value. A push that opens to the actual order detail page captures it.<\/p>\n  <p>The payload pattern is platform-specific but follows the same shape:<\/p>\n  <pre><code>\/\/ APNs payload (iOS)\n{\n  \"aps\": {\n    \"alert\": { \"title\": \"Order shipped\", \"body\": \"Your order #4892 is on its way\" },\n    \"sound\": \"default\"\n  },\n  \"url\": \"https:\/\/brand.com\/account\/orders\/4892\",\n  \"order_id\": \"4892\"\n}\n\n\/\/ FCM payload (Android)\n{\n  \"notification\": {\n    \"title\": \"Order shipped\",\n    \"body\": \"Your order #4892 is on its way\"\n  },\n  \"data\": {\n    \"url\": \"https:\/\/brand.com\/account\/orders\/4892\",\n    \"order_id\": \"4892\"\n  }\n}<\/code><\/pre>\n  <p>The app&#8217;s push handler reads the <code>url<\/code> key (or the <code>data.url<\/code> field on Android), passes it to the deep link router, and the user lands directly on the order page. The WordPress backend that triggered the push (e.g., a <code>woocommerce_order_status_shipped<\/code> hook) is responsible for populating this URL field. If the WordPress payload-builder forgets to add it, the push still delivers but lands on the home screen \u2014 silent quality regression.<\/p>\n  <p>This is why Appress hooks the WordPress push trigger to automatically include the post permalink, order URL, or listing URL in every push payload. The deep link router in the app handles routing automatically based on URL pattern matching.<\/p>\n<\/section>\n\n<section>\n  <h2>Deferred deep linking \u2014 when the user hasn&#8217;t installed yet<\/h2>\n  <p>The hardest deep linking case is the user who clicks a link without the app installed. The flow looks like:<\/p>\n  <ol>\n    <li>User clicks <code>https:\/\/brand.com\/product\/42<\/code> on a marketing email<\/li>\n    <li>iOS sees no app installed for the domain \u2192 opens Safari<\/li>\n    <li>Safari loads the product page (good \u2014 the web version is the fallback)<\/li>\n    <li>The web page shows a smart App Banner (&#8220;Get the app for a better experience&#8221;)<\/li>\n    <li>User taps &#8220;Get app&#8221; \u2192 App Store opens \u2192 user installs<\/li>\n    <li>User opens the app for the first time \u2192 \u274c the app does NOT know which product the user originally wanted<\/li>\n  <\/ol>\n  <p>Step 6 is the failure. Without extra work, the install-after-clicking flow drops the original deep link. The user lands on the app&#8217;s home screen instead of product 42, and almost certainly gives up.<\/p>\n  <p>Three patterns solve this:<\/p>\n  <ul>\n    <li><strong>Smart App Banners with deep-link parameter (Apple&#8217;s native fix).<\/strong> Add <code>app-argument=PATH<\/code> to the smart banner meta tag. iOS preserves the path across install and passes it to the app on first launch. Works on iOS only.<\/li>\n    <li><strong>Firebase Dynamic Links \/ Branch.io (third-party services).<\/strong> Wrap your link in a third-party shortener that handles cross-platform install-and-restore-context flow. Works but adds a vendor dependency, and Firebase Dynamic Links is being deprecated in 2025.<\/li>\n    <li><strong>Custom postback via clipboard or device fingerprint.<\/strong> When the user clicks the marketing link, the page sets a clipboard token or a server-side device fingerprint. On first launch, the app reads the clipboard \/ fingerprints itself \/ asks the backend for any pending deep link associated with this device. Custom but doesn&#8217;t rely on a deprecating vendor.<\/li>\n  <\/ul>\n  <p>Deferred deep linking is worth implementing only if your acquisition funnel has measurable drop-off between &#8220;click marketing link&#8221; and &#8220;open app for first time&#8221;. Most small-to-mid WordPress sites don&#8217;t need this complexity. Universal Links + App Links are enough for the installed-user case, which is where the engagement actually compounds.<\/p>\n<\/section>\n\n<section>\n  <h2>Testing deep links before shipping<\/h2>\n  <p>Apple and Google both ship validators. Use them BEFORE submitting to the App Store \/ Google Play \u2014 finding a broken deep link after release is much harder to fix.<\/p>\n\n  <h3>iOS Universal Links validator<\/h3>\n  <ul>\n    <li>Apple&#8217;s own AASA validator: <a href=\"https:\/\/branch.io\/resources\/aasa-validator\/\" rel=\"nofollow noopener\" target=\"_blank\">branch.io\/resources\/aasa-validator<\/a> (third-party tool that runs Apple&#8217;s official format check)<\/li>\n    <li>On-device: open Notes app, paste your deep link URL, long-press, choose &#8220;Open&#8221;. If your app opens, the AASA is being honored.<\/li>\n    <li>Console.app while connecting your device: filter for <code>swcd<\/code> process \u2014 shows AASA fetch errors<\/li>\n  <\/ul>\n\n  <h3>Android App Links validator<\/h3>\n  <ul>\n    <li>Google&#8217;s tool: <a href=\"https:\/\/developers.google.com\/digital-asset-links\/tools\/generator\" rel=\"nofollow noopener\" target=\"_blank\">digital-asset-links\/tools\/generator<\/a> (generates AND validates)<\/li>\n    <li>Command line: <code>adb shell pm verify-app-links --re-verify com.brand.app<\/code> forces re-verification<\/li>\n    <li>Check status: <code>adb shell pm get-app-links com.brand.app<\/code> shows verification result per domain<\/li>\n  <\/ul>\n\n  <p>If validation fails on your verified domain, the problem is usually one of: wrong SHA-256 fingerprint, file not served as <code>application\/json<\/code>, file behind authentication, or HTTPS certificate chain issue. Walk these in order.<\/p>\n<\/section>\n\n<section>\n  <h2>How Appress handles deep linking automatically<\/h2>\n  <p>Deep linking is one of the most error-prone parts of mobile app development \u2014 wrong fingerprint, wrong Content-Type, wrong path declaration, all silent failures. Appress handles the entire flow:<\/p>\n  <ul>\n    <li><strong>AASA + assetlinks.json auto-generation<\/strong> \u2014 derived from your active integrations (<a href=\"https:\/\/appress.app\/voxel-mobile-app\/\">Voxel theme<\/a>, <a href=\"https:\/\/appress.app\/woocommerce-mobile-app\/\">WooCommerce<\/a>, <a href=\"https:\/\/appress.app\/bricks-builder-mobile-app\/\">Bricks Builder<\/a>, <a href=\"https:\/\/appress.app\/avada-theme-mobile-app\/\">Avada Builder<\/a>, <a href=\"https:\/\/appress.app\/elementor-mobile-app\/\">Elementor<\/a>). Paths declared correctly per integration.<\/li>\n    <li><strong>Files served via WordPress plugin handler<\/strong> \u2014 never blocked by WordPress 404 routing or .htaccess rewrites. Content-Type always <code>application\/json<\/code>.<\/li>\n    <li><strong>Play App Signing fingerprint auto-included<\/strong> \u2014 assetlinks.json fingerprint matches what Google Play actually signs your app with, not your local debug certificate.<\/li>\n    <li><strong>Deep link router built into the native shell<\/strong> \u2014 every push notification, every external link, every QR code routes through the same URL-pattern parser, opens the correct in-app screen.<\/li>\n    <li><strong>WordPress push triggers auto-populate URL field<\/strong> \u2014 every <code>woocommerce_order_status_*<\/code> hook, every <code>post_published<\/code>, every custom event includes the deep link target by default.<\/li>\n  <\/ul>\n  <p>Most builders ship deep linking as an afterthought. Appress treats it as default behavior \u2014 because in production, the link IS the engagement.<\/p>\n<\/section>\n\n<section>\n  <h2>Frequently asked questions<\/h2>\n\n  <div class=\"ap-faq-item\">\n    <p class=\"ap-faq-q\">Do I need both Universal Links AND a custom URI scheme?<\/p>\n    <p class=\"ap-faq-a\">In 2026, you only need Universal Links (iOS) and App Links (Android). Custom URI schemes are a fallback for legacy integrations \u2014 a third-party app passing you a link via <code>brand:\/\/<\/code> instead of an HTTPS URL. Modern apps use HTTPS for all deep links and treat schemes as legacy.<\/p>\n  <\/div>\n\n  <div class=\"ap-faq-item\">\n    <p class=\"ap-faq-q\">What happens if I serve the AASA file as text\/plain instead of application\/json?<\/p>\n    <p class=\"ap-faq-a\">Apple&#8217;s CDN rejects the file silently. iOS treats the domain as having no Universal Link configuration and falls back to opening links in Safari. Symptom: deep links work on web but never open the app. Fix: serve as <code>application\/json<\/code> and verify with <code>curl -I<\/code>.<\/p>\n  <\/div>\n\n  <div class=\"ap-faq-item\">\n    <p class=\"ap-faq-q\">My Android App Links work in debug builds but break in production. Why?<\/p>\n    <p class=\"ap-faq-a\">The most common cause is fingerprint mismatch. Your debug build is signed with your local debug certificate; Google Play production builds are signed with Play App Signing. The SHA-256 fingerprint in <code>assetlinks.json<\/code> must match the Play App Signing certificate (found in Google Play Console \u2192 Setup \u2192 App integrity), not your local debug fingerprint.<\/p>\n  <\/div>\n\n  <div class=\"ap-faq-item\">\n    <p class=\"ap-faq-q\">How long does Apple take to pick up changes to my AASA file?<\/p>\n    <p class=\"ap-faq-a\">Apple&#8217;s CDN refetches the AASA file roughly every 24 hours, but the actual cache time per device varies. After updating, expect 24 hours for most devices to see the change. If you must propagate faster, ship an app update \u2014 installing the new app version forces an immediate AASA refetch on that device.<\/p>\n  <\/div>\n\n  <div class=\"ap-faq-item\">\n    <p class=\"ap-faq-q\">Can I deep link to a screen that requires the user to be logged in?<\/p>\n    <p class=\"ap-faq-a\">Yes \u2014 and the app should handle this gracefully. The pattern is: the deep link router resolves the URL \u2192 checks if the user is authenticated \u2192 if not, redirects to login screen \u2192 after login, restores the original deep link target. This requires the deep link router to push the target URL onto a &#8220;pending deep link&#8221; stack before showing login.<\/p>\n  <\/div>\n\n  <div class=\"ap-faq-item\">\n    <p class=\"ap-faq-q\">Does deep linking affect SEO?<\/p>\n    <p class=\"ap-faq-a\">Indirectly, yes. When users click a deep link from search results and land in your app instead of mobile web, Google sees the strong engagement signal (long session, low bounce rate). Apple Smart App Banners and Google&#8217;s app indexing also let search engines understand that the same content exists in your app, which boosts both web and app discoverability.<\/p>\n  <\/div>\n\n  <div class=\"ap-faq-item\">\n    <p class=\"ap-faq-q\">Do Universal Links work in iOS Mail and iMessage?<\/p>\n    <p class=\"ap-faq-a\">Yes \u2014 that&#8217;s exactly the case they were designed for. Tapping <code>https:\/\/brand.com\/product\/42<\/code> in Mail, Messages, Notes, or any other system app opens your app directly if installed. The exception is Safari itself: tapping a link from inside Safari opens the link in Safari, not your app. This is intentional \u2014 Apple doesn&#8217;t want to interrupt the browser experience.<\/p>\n  <\/div>\n\n  <div class=\"ap-faq-item\">\n    <p class=\"ap-faq-q\">Should I use Firebase Dynamic Links for deferred deep linking?<\/p>\n    <p class=\"ap-faq-a\">As of late 2025, Google has announced Firebase Dynamic Links is being shut down in August 2025. Migrate off. Alternatives: Branch.io (paid, mature), Apple&#8217;s Smart App Banner with app-argument (iOS-only but free), or a custom server-side deferred link postback (more work but no vendor lock-in).<\/p>\n  <\/div>\n\n<\/section>\n\n<section>\n  <div class=\"ap-final-cta\">\n    <h2 style=\"font-size: 28px; margin-bottom: 16px;\">Ship a WordPress mobile app with deep linking that just works<\/h2>\n    <p>Appress generates the AASA file, the assetlinks.json file, and the in-app deep link router automatically \u2014 derived from your active <a href=\"https:\/\/appress.app\/wordpress-mobile-app\/\">WordPress integrations<\/a>. Push notifications auto-include the correct destination URL. Universal Links pass App Store review on first submission.<\/p>\n    <div class=\"ap-cta-row\">\n      <a class=\"ap-btn ap-btn-primary\" href=\"https:\/\/my.appress.app\/\">Try Appress Free Preview \u2192<\/a>\n      <a class=\"ap-btn ap-btn-secondary\" href=\"https:\/\/appress.app\/pricing\/\">See pricing \u2014 $399 one-time<\/a>\n    <\/div>\n  <\/div>\n<\/section>\n\n<\/div>\n\n","protected":false},"excerpt":{"rendered":"<p>How iOS Universal Links, Android App Links, and custom URI schemes actually work in 2026 for WordPress mobile apps. AASA + assetlinks.json setup, common failures, push deep link payloads, deferred deep linking.<\/p>","protected":false},"author":0,"featured_media":1107,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-1106","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/appress.app\/vi\/wp-json\/wp\/v2\/posts\/1106","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/appress.app\/vi\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/appress.app\/vi\/wp-json\/wp\/v2\/types\/post"}],"replies":[{"embeddable":true,"href":"https:\/\/appress.app\/vi\/wp-json\/wp\/v2\/comments?post=1106"}],"version-history":[{"count":0,"href":"https:\/\/appress.app\/vi\/wp-json\/wp\/v2\/posts\/1106\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/appress.app\/vi\/wp-json\/wp\/v2\/media\/1107"}],"wp:attachment":[{"href":"https:\/\/appress.app\/vi\/wp-json\/wp\/v2\/media?parent=1106"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/appress.app\/vi\/wp-json\/wp\/v2\/categories?post=1106"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/appress.app\/vi\/wp-json\/wp\/v2\/tags?post=1106"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}