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’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 — 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.
Published 2026-05-26 · Reading time: ~12 minutes · iOS 18 + Android 15 · Covers Universal Links, App Links, custom schemes, deferred deep linking
A deep link is a URL that, when tapped, jumps directly to a specific in-app screen instead of the app’s home screen. The user does not navigate. They land where the link points — a product page, a chat thread, a saved listing, a checkout step.
Three distinct technologies make this happen, and they are not interchangeable:
| Technology | Platform | URL form | When the app is installed | When the app is NOT installed |
|---|---|---|---|---|
| Universal Links | iOS only | https://brand.com/product/42 | Opens app directly | Opens Safari to the same URL |
| App Links | Android only | https://brand.com/product/42 | Opens app directly (after one-time consent) | Opens Chrome to the same URL |
| Custom URI scheme | Both | brand://product/42 | Opens app directly | Error / nothing (no fallback) |
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 Universal Links + App Links, not custom schemes. Custom schemes are now a fallback / legacy mechanism.
Until iOS 9 (2015) and Android 6 (2015), the standard pattern was the custom URI scheme — spotify://album/abc, twitter://user?screen_name=brand. These worked but had three structural problems:
brand://product/42 on a device without the brand app installed produced a silent error or “App not found” dialog. You can’t recover the user — they can’t even see the product page on the web.bank://, iOS picked one arbitrarily. Users got the wrong app.https://-looking scheme like paypl://. iOS later restricted this, but the trust model was broken.Apple and Google’s solution was to anchor deep links to real domains via cryptographic verification. The result was Universal Links and App Links — same URL works on web AND in-app, with no collision risk because only the domain owner can register the link.
The full handshake is more involved than most developers expect:
https://brand.com/.well-known/apple-app-site-association. This file declares which URL paths your app wants to handle.https://brand.com/product/42 anywhere — Messages, Mail, Safari, a Notes URL, a notification./product/*? If yes, route to the app.NSUserActivity with activityType == NSUserActivityTypeBrowsingWeb. Your app’s deep link router parses the URL path and navigates accordingly.The example AASA file for a WordPress site that wants the app to handle product pages and the user account area:
{
"applinks": {
"details": [
{
"appIDs": ["TEAMID.com.brand.app"],
"components": [
{ "/": "/product/*" },
{ "/": "/account/*" },
{ "/": "/articles/*" },
{ "/": "*", "exclude": true,
"comment": "Do NOT route everything — only matched paths above" }
]
}
]
}
}
Three common failures that ship to production:
application/json (or no extension served as JSON). WordPress + most CDNs serve /.well-known/apple-app-site-association as a plain file with no extension, which can default to application/octet-stream — Apple’s CDN rejects this./). Routing /* to the app means EVERY brand.com link opens the app — including links to the homepage, the cart, the help center. Users get frustrated. Always declare specific path patterns and exclude the rest.Android App Links solve the same problem but with a more aggressive verification step:
assetlinks.json at https://brand.com/.well-known/assetlinks.json. Same well-known location pattern as iOS, different format.assetlinks.json.assetlinks.json over HTTPS and matching the app’s signing fingerprint against the declared fingerprint.Example assetlinks.json:
[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.brand.app",
"sha256_cert_fingerprints": [
"AA:BB:CC:DD:EE:FF:..."
]
}
}]
The single most common Android App Links failure: wrong SHA-256 fingerprint. 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 assetlinks.json must be the Play App Signing certificate fingerprint — visible in Google Play Console → App integrity → App signing. If you ship with the wrong one, Android falls back to chooser dialog forever.
Hosting AASA and assetlinks.json files on a WordPress site has predictable rough edges:
WordPress’s .htaccess default forwards every request that doesn’t match a real file to index.php. If WordPress doesn’t have a route for /.well-known/apple-app-site-association, it returns the 404 template — which is HTML, not JSON. Apple rejects.
The fix is one of three patterns:
.htaccess to serve /.well-known/* as static files, never routed through WordPress.init, detect the request URI, emit the JSON file content directly with application/json Content-Type, then exit; before WordPress routing.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 — meaning the static file approach fails. A dynamic handler tied to plugin configuration is more reliable. Plugins like Appress handle this by registering an init hook that intercepts /.well-known/* requests and serves the current configuration as JSON.
You update the AASA file when adding a new path pattern. Apple’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’t deep link.
Mitigation:
Cache-Control: max-age=3600, must-revalidate (or shorter) on the well-known endpointIf your site is served on both brand.com and www.brand.com (with one redirecting to the other), the AASA file MUST be on the canonical domain (the one that does NOT redirect). Apple’s CDN does not follow redirects when fetching the well-known endpoint. If brand.com/.well-known/apple-app-site-association redirects to www.brand.com/..., Apple sees the 301 as failure.
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.
The payload pattern is platform-specific but follows the same shape:
// APNs payload (iOS)
{
"aps": {
"alert": { "title": "Order shipped", "body": "Your order #4892 is on its way" },
"sound": "default"
},
"url": "https://brand.com/account/orders/4892",
"order_id": "4892"
}
// FCM payload (Android)
{
"notification": {
"title": "Order shipped",
"body": "Your order #4892 is on its way"
},
"data": {
"url": "https://brand.com/account/orders/4892",
"order_id": "4892"
}
}
The app’s push handler reads the url key (or the data.url 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 woocommerce_order_status_shipped 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 — silent quality regression.
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.
The hardest deep linking case is the user who clicks a link without the app installed. The flow looks like:
https://brand.com/product/42 on a marketing emailStep 6 is the failure. Without extra work, the install-after-clicking flow drops the original deep link. The user lands on the app’s home screen instead of product 42, and almost certainly gives up.
Three patterns solve this:
app-argument=PATH 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.Deferred deep linking is worth implementing only if your acquisition funnel has measurable drop-off between “click marketing link” and “open app for first time”. Most small-to-mid WordPress sites don’t need this complexity. Universal Links + App Links are enough for the installed-user case, which is where the engagement actually compounds.
Apple and Google both ship validators. Use them BEFORE submitting to the App Store / Google Play — finding a broken deep link after release is much harder to fix.
swcd process — shows AASA fetch errorsadb shell pm verify-app-links --re-verify com.brand.app forces re-verificationadb shell pm get-app-links com.brand.app shows verification result per domainIf validation fails on your verified domain, the problem is usually one of: wrong SHA-256 fingerprint, file not served as application/json, file behind authentication, or HTTPS certificate chain issue. Walk these in order.
Deep linking is one of the most error-prone parts of mobile app development — wrong fingerprint, wrong Content-Type, wrong path declaration, all silent failures. Appress handles the entire flow:
application/json.woocommerce_order_status_* hook, every post_published, every custom event includes the deep link target by default.Most builders ship deep linking as an afterthought. Appress treats it as default behavior — because in production, the link IS the engagement.
Do I need both Universal Links AND a custom URI scheme?
In 2026, you only need Universal Links (iOS) and App Links (Android). Custom URI schemes are a fallback for legacy integrations — a third-party app passing you a link via brand:// instead of an HTTPS URL. Modern apps use HTTPS for all deep links and treat schemes as legacy.
What happens if I serve the AASA file as text/plain instead of application/json?
Apple’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 application/json and verify with curl -I.
My Android App Links work in debug builds but break in production. Why?
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 assetlinks.json must match the Play App Signing certificate (found in Google Play Console → Setup → App integrity), not your local debug fingerprint.
How long does Apple take to pick up changes to my AASA file?
Apple’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 — installing the new app version forces an immediate AASA refetch on that device.
Can I deep link to a screen that requires the user to be logged in?
Yes — and the app should handle this gracefully. The pattern is: the deep link router resolves the URL → checks if the user is authenticated → if not, redirects to login screen → after login, restores the original deep link target. This requires the deep link router to push the target URL onto a “pending deep link” stack before showing login.
Does deep linking affect SEO?
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’s app indexing also let search engines understand that the same content exists in your app, which boosts both web and app discoverability.
Do Universal Links work in iOS Mail and iMessage?
Yes — that’s exactly the case they were designed for. Tapping https://brand.com/product/42 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 — Apple doesn’t want to interrupt the browser experience.
Should I use Firebase Dynamic Links for deferred deep linking?
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’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).
Appress generates the AASA file, the assetlinks.json file, and the in-app deep link router automatically — derived from your active WordPress integrations. Push notifications auto-include the correct destination URL. Universal Links pass App Store review on first submission.