explain it from Android developer point of view: concept first, then manifest setup, flow, App Links, testing, and common interview points.
Deep Linking in Android
1. What is Deep Linking?
Deep linking opens a specific screen inside an application instead of always opening its home screen.
Example URL:
https://shop.example.com/product/101Expected result:
Open application → Product Details screen → Product 101flowchart LR
L["User taps link"] --> OS["Android resolves URL"]
OS --> A["Opens application"]
A --> P["Product Details"]Deep links can come from:
* Website
* Social media
* Push notification
* QR code
* SMS
* Another application
---
2. Types of Deep Links
Android mainly supports:
1. Custom-scheme deep links
2. Web deep links
3. Verified Android App Links
| Type | Example | Verification |
|---|---|---|
| Custom scheme | shopapp://product/101 | No domain verification |
| Web deep link | https://shop.example.com/product/101 | Not necessarily verified |
| Android App Link | https://shop.example.com/product/101 | Domain and app are verified |
---
3. Custom-Scheme Deep Link
Example:
shopapp://product/101Manifest:
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name=
"android.intent.category.DEFAULT" />
<category android:name=
"android.intent.category.BROWSABLE" />
<data
android:scheme="shopapp"
android:host="product" />
</intent-filter>Limitation
Any application can register the same custom scheme.
flowchart TD
L["shopapp://product/101"] --> A1["Your application"]
L --> A2["Another application using same scheme"]
A1 --> C["User may see chooser"]
A2 --> CCustom schemes are useful for controlled app-to-app communication, but HTTPS App Links are normally safer for URLs belonging to your website.
---
4. Web Deep Link
Example:
https://shop.example.com/product/101A standard web deep link uses an HTTPS URL and a manifest intent filter.
However, without domain verification:
* Android may open the browser.
* Android may show an application chooser.
* Another application might also claim the URL.
For URLs owned by your organization, Android recommends verified App Links. [Android: Create deep links](https://developer.android.com/training/app-links/create-deeplinks)---
5. Android App Links
An Android App Link is a verified HTTPS deep link.
Android verifies that:
* The application claims the website domain.
* The website authorizes the application.
* The signing certificate matches.
flowchart TD
M["App manifest claims domain"] --> V["Android verification"]
W["Website assetlinks.json<br/>authorizes app"] --> V
V -->|Passed| O["Link opens app directly"]
V -->|Failed| F["Browser or normal link handling"]Benefits:
* Uses normal HTTPS URLs
* Opens the application directly after successful verification
* Reduces chooser dialogs
* Prevents an unrelated application from claiming your verified website
* Opens the website when the application is not installed
---
6. Manifest Configuration
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter android:autoVerify="true">
<action android:name=
"android.intent.action.VIEW" />
<category android:name=
"android.intent.category.DEFAULT" />
<category android:name=
"android.intent.category.BROWSABLE" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="shop.example.com" />
</intent-filter>
</activity>Important properties
| Property | Purpose |
|---|---|
VIEW | Tells Android the Activity handles viewable data |
DEFAULT | Allows normal implicit Intent resolution |
BROWSABLE | Allows links from browsers and external sources |
autoVerify="true" | Requests domain verification |
scheme | Usually http and https for App Links |
host | Website domain |
Android: Add App Link intent filters
---
7. assetlinks.json
The website must authorize the Android application.
Create:
https://shop.example.com/.well-known/assetlinks.jsonExample:
[
{
"relation": [
"delegate_permission/common.handle_all_urls"
],
"target": {
"namespace": "android_app",
"package_name": "com.example.shop",
"sha256_cert_fingerprints": [
"AA:BB:CC:DD:EE:FF:..."
]
}
}
]It connects:
flowchart LR
D["Website domain"] --> P["Application package"]
P --> C["Signing certificate fingerprint"]The file must:
* Be named assetlinks.json
* Be placed under /.well-known/
* Be publicly accessible through HTTPS
* Return valid JSON
* Avoid redirects
* Contain the correct package name
* Contain the correct SHA-256 signing fingerprint
Android: Configure website association
---
8. Play App Signing Fingerprint
When Play App Signing is enabled, the key used to sign the installed Play Store application can differ from the upload key.
Use the fingerprint from:
Play Console
→ Release
→ Setup
→ App integrity/App signingA common mistake is adding only the local keystore or upload-key fingerprint.
For different builds, `assetlinks.json` can contain multiple fingerprints:"sha256_cert_fingerprints": [
"PRODUCTION_APP_SIGNING_FINGERPRINT",
"DEBUG_SIGNING_FINGERPRINT"
]Only include debug signing information on domains where that trust is intentionally allowed.
---
9. Internal Working
When a user clicks:
https://shop.example.com/product/101Android performs the following steps:
sequenceDiagram
actor U as User
participant OS as Android
participant W as Website
participant A as Application
participant N as Navigation
U->>OS: Tap URL
OS->>OS: Find matching intent filter
OS->>W: Check domain association
W-->>OS: assetlinks.json
OS->>OS: Verify package and fingerprint
OS->>A: Send ACTION_VIEW Intent
A->>N: Parse and validate URI
N->>N: Open product 101The Activity receives an ACTION_VIEW Intent containing the URI.
val uri = intent?.data
val productId =
uri?.pathSegments?.getOrNull(1)Do not depend on a fixed index without verifying the path structure.
---
10. Deep Linking with Navigation Compose
Define a route:
@Serializable
data class ProductDetails(
val productId: String
)Configure the destination:
composable<ProductDetails>(
deepLinks = listOf(
navDeepLink<ProductDetails>(
basePath =
"https://shop.example.com/product"
)
)
) { backStackEntry ->
val route =
backStackEntry.toRoute<ProductDetails>()
ProductDetailsScreen(
productId = route.productId
)
}The manifest is still required so Android can deliver external links to the Activity.
flowchart LR
URL["External URL"] --> IF["Manifest intent filter"]
IF --> A["Activity"]
A --> NH["NavHost"]
NH --> D["Matching destination"]---
11. Multiple Deep Links
Suppose the application supports:
https://shop.example.com/product/101
https://shop.example.com/category/mobileNavigation destinations:
@Serializable
data class ProductDetails(
val productId: String
)
@Serializable
data class Category(
val categoryName: String
)composable<ProductDetails>(
deepLinks = listOf(
navDeepLink<ProductDetails>(
basePath =
"https://shop.example.com/product"
)
)
) {
// Product destination
}
composable<Category>(
deepLinks = listOf(
navDeepLink<Category>(
basePath =
"https://shop.example.com/category"
)
)
) {
// Category destination
}Route decision
flowchart TD
U["Incoming URI"] --> P{"Path?"}
P -->|product/id| PD["Product Details"]
P -->|category/name| CL["Category List"]
P -->|Unknown| H["Safe fallback or website"]Keep a central route definition so the manifest, navigation graph, web team and backend notification team agree on URL formats.
---
12. Multiple Domains
Suppose the app handles:
https://shop.example.com/product/101
https://payment.example.com/status/5001Use separate intent filters when URLs have different scheme/host/path combinations.
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name=
"android.intent.category.DEFAULT" />
<category android:name=
"android.intent.category.BROWSABLE" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="shop.example.com" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name=
"android.intent.category.DEFAULT" />
<category android:name=
"android.intent.category.BROWSABLE" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="payment.example.com" />
</intent-filter>Each domain needs its own accessible assetlinks.json.
Important manifest rule
Different <data> elements inside one intent filter are combined. This can create URL combinations you did not intend.
Use separate filters for distinct URL patterns.
---
13. Deep Link with Login Check
A deep link may target a screen that requires authentication.
Example:
https://bank.example.com/payment/5001If the user is logged out:
flowchart TD
D["Deep link received"] --> V{"Link valid?"}
V -->|No| H["Safe fallback"]
V -->|Yes| L{"User logged in?"}
L -->|Yes| T["Open target screen"]
L -->|No| S["Store pending destination"]
S --> LG["Open Login"]
LG --> R{"Login successful?"}
R -->|Yes| T
R -->|No| C["Clear or safely retain request"]Pending destination
sealed interface PendingDestination {
data class Product(
val productId: String
) : PendingDestination
data class Payment(
val transactionId: String
) : PendingDestination
}Handling:
fun handleDeepLink(uri: Uri) {
val destination = parseAndValidate(uri)
?: return openHome()
if (sessionManager.isLoggedIn()) {
navigate(destination)
} else {
pendingDestination = destination
navigateToLogin()
}
}After successful login:
fun onLoginSuccess() {
val destination = pendingDestination
pendingDestination = null
if (destination != null) {
navigate(destination)
} else {
navigateToHome()
}
}Security rule
Do not treat possession of a deep link as authorization.
The user must still pass:
* Authentication
* Account ownership validation
* Role checks
* Transaction validation
* Step-up authentication when required
---
14. Cold Start vs Warm Start
Cold start
The application process is not running.
flowchart LR
L["Tap link"] --> A["Create Activity"]
A --> I["Read initial Intent"]
I --> N["Navigate"]Read the Intent during initial Activity creation.
Warm start
The Activity may already exist.
flowchart LR
L["Tap another link"] --> O["onNewIntent"]
O --> I["Process new Intent"]
I --> N["Navigate"]For manual handling:
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent)
handleDeepLink(intent)
}Test both cold and warm cases. A common bug is supporting only the initial Intent.
Navigation libraries can handle much of this, but lifecycle and launch-mode behaviour should still be verified.
---
15. Push Notification Deep Link
A notification can include navigation data:
{
"type": "product",
"productId": "101"
}The application converts trusted notification data into an internal destination or App Link.
flowchart LR
N["Notification"] --> PI["PendingIntent"]
PI --> A["Activity"]
A --> V["Validate data"]
V --> D["Destination"]Use unique PendingIntent identities when different notifications must open different content.
Do not pass sensitive information such as tokens, OTPs or complete account details in the deep-link URL.
---
16. Security Considerations
Deep links are external input and must be treated as untrusted.
Validate:
* Scheme
* Host
* Path
* Argument type
* Argument length
* Allowed values
* Authentication state
* User authorization
* Redirect destination
Avoid open redirects
Dangerous URL:
https://example.com/open?url=https://evil.exampleDo not blindly open arbitrary URLs.
Use an allowlist:
val allowedHosts = setOf(
"shop.example.com",
"support.example.com"
)
if (uri.host !in allowedHosts) {
return
}Never place sensitive data in URLs
Avoid:
https://example.com/login?token=SECRET_TOKENURLs can appear in:
* Browser history
* Logs
* Analytics
* Screenshots
* Referrer information
* Clipboard
Use a short-lived opaque identifier when necessary, then securely exchange it with the server.
---
17. Testing with ADB
Test a deep link:
adb shell am start \
-W \
-a android.intent.action.VIEW \
-c android.intent.category.BROWSABLE \
-d "https://shop.example.com/product/101" \
com.example.shopTest:
* App not running
* App running in background
* App already open
* User logged in
* User logged out
* Valid ID
* Invalid ID
* Missing argument
* Unknown path
* HTTP and HTTPS
* App installed and not installed
---
18. Verify App Link Status
Reset current state:
adb shell pm set-app-links \
--package com.example.shop 0 allForce verification:
adb shell pm verify-app-links \
--re-verify com.example.shopCheck result:
adb shell pm get-app-links \
com.example.shopExpected domain status:
shop.example.com: verifiedVerification may be asynchronous, so wait before checking the result. Android: Verify App Links
---
19. Dynamic App Links
On supported Android 15+ devices, Dynamic App Links can refine URL rules through assetlinks.json.
They can control:
* Paths
* Query parameters
* Fragments
* Exclusions
This allows some rule updates without releasing a new app version.
flowchart LR
M["Manifest defines allowed domain scope"] --> D["Dynamic rules refine it"]
D --> P["Paths and parameters"]Dynamic rules cannot expand beyond the URL scope permitted by the manifest. Older Android versions still depend on the manifest configuration.
---
20. Common Mistakes
Mistake 1: Using only a custom scheme
Another application may register the same scheme.
Prefer verified HTTPS App Links for organization-owned web URLs.
Mistake 2: Missing BROWSABLE
The browser cannot safely deliver the URL to the Activity.
Mistake 3: Missing android:exported="true"
An Activity handling external links must be accessible to the system.
Mistake 4: Incorrect signing fingerprint
With Play App Signing, use the production app-signing fingerprint, not only the upload-key fingerprint.
Mistake 5: Redirecting assetlinks.json
Serve it directly at:
https://domain/.well-known/assetlinks.jsonRedirects can cause verification failure.
Mistake 6: Mixing unrelated <data> elements
The manifest combines elements in the same intent filter and may create unintended URL matches.
Use separate intent filters.
Mistake 7: Handling only cold start
Also handle a new deep link when the Activity/application is already running.
Mistake 8: Skipping login and authorization
A deep link chooses a destination; it does not give permission to access that destination.Mistake 9: Passing complete objects
Pass a stable ID and load the latest data after authentication and authorization.
Mistake 10: Trusting URI parameters
Validate all input and safely reject malformed or unknown links.---
21. Interview Questions and Answers
Q1. What is deep linking?
Deep linking opens a specific application screen or content using a URI instead of opening only the home screen.
Q2. What is the difference between a deep link and an App Link?
A deep link can use a custom scheme or web URL and may show a chooser. An App Link is a verified HTTP/HTTPS link associated with an owned website.
Q3. What does android:autoVerify="true" do?
It asks Android to verify the declared domain against the website’s assetlinks.json.
Q4. What is assetlinks.json?
It is a Digital Asset Links file hosted by the website that authorizes a package name and signing-certificate fingerprint to handle the website’s URLs.
Q5. Where is assetlinks.json hosted?
https://domain-name/.well-known/assetlinks.jsonQ6. What happens if the app is not installed?
An HTTPS App Link can open the matching webpage instead.
Q7. How do you handle a protected deep link when the user is logged out?
Validate and store the intended destination, open Login, and navigate to the stored destination only after successful authentication and authorization.Q8. What should be passed in a deep link?
Pass a minimal identifier such as a product ID or transaction reference. Do not pass tokens, OTPs or complete sensitive objects.
Q9. How do you handle multiple URLs?
Define separate routes and map validated host/path patterns to their correct destinations. Use separate manifest filters for distinct URL combinations.Q10. How do you test a deep link?
Use ADB am start for routing, then use pm verify-app-links and pm get-app-links to check domain verification.
Q11. How do cold-start and warm-start deep links differ?
During a cold start, the Activity receives the initial Intent. During a warm start, an existing Activity may receive the new link through onNewIntent().
Q12. Are deep-link arguments secure?
No. They are external input and must be validated. Authentication and authorization must still be enforced.
---
Final Interview Answer
> Deep linking allows a URI to open specific content inside an Android application. For organization-owned web URLs, I prefer verified Android App Links. The Activity declares an HTTP/HTTPS intent filter with autoVerify, and the website hosts an assetlinks.json containing the package name and signing-certificate fingerprint. Android verifies this association and routes matching links to the application. Inside the app, I validate the URI, extract only safe identifiers and navigate to the appropriate screen. For protected content, I store the pending destination, complete login and authorization, and then continue. I test cold start, warm start, invalid input, multiple URLs and domain-verification status.