{"id":1270,"date":"2026-05-31T09:00:00","date_gmt":"2026-05-31T14:00:00","guid":{"rendered":"https:\/\/tolinku.com\/blog\/?p=1270"},"modified":"2026-03-07T03:49:03","modified_gmt":"2026-03-07T08:49:03","slug":"android-app-links-multiple-domains","status":"publish","type":"post","link":"https:\/\/tolinku.com\/blog\/android-app-links-multiple-domains\/","title":{"rendered":"Handling Multiple Domains with Android App Links"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Most apps start with a single domain for deep linking: <code>links.yourapp.com<\/code>. Then growth adds complexity. Marketing wants <code>go.yourapp.com<\/code> for campaigns. The rebrand adds <code>newbrand.com<\/code>. A partner integration needs <code>partner.co\/yourapp<\/code>. Each domain needs its own App Links configuration, its own Digital Asset Links file, and its own intent filter entries.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Getting multi-domain App Links right requires understanding how Android verifies each domain independently, how intent filters interact when multiple domains are declared, and how to avoid the common pitfalls that cause verification to fail on some domains while working on others.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For the single-domain setup, see the <a href=\"https:\/\/tolinku.com\/blog\/android-app-links-complete-guide\/\">Android App Links complete guide<\/a>. For the Digital Asset Links file format, see the <a href=\"https:\/\/tolinku.com\/blog\/digital-asset-links-setup\/\">setup and verification guide<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How Multi-Domain Verification Works<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Android verifies each domain in your intent filters independently. When your app is installed, the system:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Scans all <code>&lt;intent-filter&gt;<\/code> elements with <code>android:autoVerify=&quot;true&quot;<\/code>.<\/li>\n<li>Extracts every unique <code>android:host<\/code> value.<\/li>\n<li>Fetches <code>https:\/\/{host}\/.well-known\/assetlinks.json<\/code> for each host.<\/li>\n<li>Verifies that each file contains your app&#39;s package name and SHA-256 fingerprint.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Critical rule (Android 12+):<\/strong> If ANY domain fails verification, ALL domains may be treated as unverified on some devices. On Android 11 and below, each domain was verified independently: a failure on one domain didn&#39;t affect others. Android 12 changed this to an all-or-nothing model for some OEMs.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This means every domain you declare must have a valid, accessible <code>assetlinks.json<\/code> file. A single misconfigured domain can break App Links for all your domains.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Manifest Configuration<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Declaring Multiple Domains<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Each domain gets its own <code>&lt;data&gt;<\/code> element within the intent filter:<\/p>\n\n\n\n<pre><code class=\"language-xml\">&lt;activity\n    android:name=&quot;.DeepLinkRouter&quot;\n    android:exported=&quot;true&quot;&gt;\n    &lt;intent-filter android:autoVerify=&quot;true&quot;&gt;\n        &lt;action android:name=&quot;android.intent.action.VIEW&quot; \/&gt;\n        &lt;category android:name=&quot;android.intent.category.DEFAULT&quot; \/&gt;\n        &lt;category android:name=&quot;android.intent.category.BROWSABLE&quot; \/&gt;\n\n        &lt;data android:scheme=&quot;https&quot; android:host=&quot;links.yourapp.com&quot; \/&gt;\n        &lt;data android:scheme=&quot;https&quot; android:host=&quot;go.yourapp.com&quot; \/&gt;\n        &lt;data android:scheme=&quot;https&quot; android:host=&quot;newbrand.com&quot; \/&gt;\n    &lt;\/intent-filter&gt;\n&lt;\/activity&gt;\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Separate Intent Filters per Domain<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">If different domains route to different Activities or have different path patterns, use separate intent filters:<\/p>\n\n\n\n<pre><code class=\"language-xml\">&lt;!-- Marketing links: go.yourapp.com --&gt;\n&lt;intent-filter android:autoVerify=&quot;true&quot;&gt;\n    &lt;action android:name=&quot;android.intent.action.VIEW&quot; \/&gt;\n    &lt;category android:name=&quot;android.intent.category.DEFAULT&quot; \/&gt;\n    &lt;category android:name=&quot;android.intent.category.BROWSABLE&quot; \/&gt;\n    &lt;data android:scheme=&quot;https&quot;\n          android:host=&quot;go.yourapp.com&quot;\n          android:pathPrefix=&quot;\/c\/&quot; \/&gt;\n&lt;\/intent-filter&gt;\n\n&lt;!-- Product links: links.yourapp.com\/products\/* --&gt;\n&lt;intent-filter android:autoVerify=&quot;true&quot;&gt;\n    &lt;action android:name=&quot;android.intent.action.VIEW&quot; \/&gt;\n    &lt;category android:name=&quot;android.intent.category.DEFAULT&quot; \/&gt;\n    &lt;category android:name=&quot;android.intent.category.BROWSABLE&quot; \/&gt;\n    &lt;data android:scheme=&quot;https&quot;\n          android:host=&quot;links.yourapp.com&quot;\n          android:pathPrefix=&quot;\/products&quot; \/&gt;\n&lt;\/intent-filter&gt;\n\n&lt;!-- Brand domain: newbrand.com (all paths) --&gt;\n&lt;intent-filter android:autoVerify=&quot;true&quot;&gt;\n    &lt;action android:name=&quot;android.intent.action.VIEW&quot; \/&gt;\n    &lt;category android:name=&quot;android.intent.category.DEFAULT&quot; \/&gt;\n    &lt;category android:name=&quot;android.intent.category.BROWSABLE&quot; \/&gt;\n    &lt;data android:scheme=&quot;https&quot;\n          android:host=&quot;newbrand.com&quot; \/&gt;\n&lt;\/intent-filter&gt;\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Digital Asset Links for Each Domain<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Every domain must serve its own <code>assetlinks.json<\/code> at <code>\/.well-known\/assetlinks.json<\/code>. The content is identical across all domains (same app, same fingerprint), but the file must be accessible on each one.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The File (Same for All Domains)<\/h3>\n\n\n\n<pre><code class=\"language-json\">[{\n  &quot;relation&quot;: [&quot;delegate_permission\/common.handle_all_urls&quot;],\n  &quot;target&quot;: {\n    &quot;namespace&quot;: &quot;android_app&quot;,\n    &quot;package_name&quot;: &quot;com.example.yourapp&quot;,\n    &quot;sha256_cert_fingerprints&quot;: [\n      &quot;AB:CD:EF:01:23:45:67:89:AB:CD:EF:01:23:45:67:89:AB:CD:EF:01:23:45:67:89:AB:CD:EF:01:23:45:67:89&quot;\n    ]\n  }\n}]\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Hosting Requirements<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Each domain must serve this file with:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>HTTPS<\/strong> (not HTTP). No redirects from the <code>\/.well-known\/<\/code> path.<\/li>\n<li><strong>Content-Type: application\/json<\/strong><\/li>\n<li><strong>HTTP 200 status code<\/strong><\/li>\n<li><strong>No authentication<\/strong> (publicly accessible)<\/li>\n<li><strong>Response within 5 seconds<\/strong> (Android&#39;s verification timeout)<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Common Multi-Domain Hosting Patterns<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Pattern 1: Static file on each server<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you control the web server for each domain, place the file directly:<\/p>\n\n\n\n<pre><code>links.yourapp.com\/.well-known\/assetlinks.json\ngo.yourapp.com\/.well-known\/assetlinks.json\nnewbrand.com\/.well-known\/assetlinks.json\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Pattern 2: Reverse proxy \/ CDN rule<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If domains are served through a CDN (Cloudflare, AWS CloudFront), add a rule that serves the file from a central location:<\/p>\n\n\n\n<pre><code class=\"language-nginx\"># Nginx: serve assetlinks.json for all domains\nlocation = \/.well-known\/assetlinks.json {\n    alias \/etc\/assetlinks\/assetlinks.json;\n    default_type application\/json;\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Pattern 3: Redirect (risky)<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Some teams try to redirect <code>\/.well-known\/assetlinks.json<\/code> from one domain to another. This is unreliable. Android&#39;s verification may not follow redirects, especially on older versions. Host the file directly on each domain.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Pattern 4: Tolinku managed<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">When using <a href=\"https:\/\/tolinku.com\/features\/deep-linking\">Tolinku<\/a>, the platform serves the <code>assetlinks.json<\/code> file automatically on your configured domains. Add your domains in the <a href=\"https:\/\/tolinku.com\/docs\/user-guide\/domains\/\">Tolinku dashboard<\/a> and the verification files are generated and hosted for you.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Routing Logic<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">With multiple domains, your deep link router needs to know which domain the link came from:<\/p>\n\n\n\n<pre><code class=\"language-kotlin\">class DeepLinkRouter : AppCompatActivity() {\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        val uri = intent.data ?: return finish()\n        val host = uri.host\n        val path = uri.path\n\n        when (host) {\n            &quot;go.yourapp.com&quot; -&gt; {\n                \/\/ Marketing campaign links\n                val campaignId = uri.getQueryParameter(&quot;c&quot;)\n                handleCampaignLink(campaignId, uri)\n            }\n            &quot;links.yourapp.com&quot; -&gt; {\n                \/\/ Product deep links\n                when {\n                    path?.startsWith(&quot;\/products&quot;) == true -&gt;\n                        handleProductLink(uri)\n                    path?.startsWith(&quot;\/profile&quot;) == true -&gt;\n                        handleProfileLink(uri)\n                    else -&gt; handleGenericLink(uri)\n                }\n            }\n            &quot;newbrand.com&quot; -&gt; {\n                \/\/ New brand domain (same app, different branding)\n                handleBrandedLink(uri)\n            }\n            else -&gt; handleGenericLink(uri)\n        }\n\n        finish()\n    }\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Verification Debugging<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Check All Domains at Once<\/h3>\n\n\n\n<pre><code class=\"language-bash\">adb shell pm get-app-links --user cur com.example.yourapp\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Output shows the status for each domain:<\/p>\n\n\n\n<pre><code>com.example.yourapp:\n    Domains:\n      links.yourapp.com:\n        Status: verified\n      go.yourapp.com:\n        Status: verified\n      newbrand.com:\n        Status: legacy_failure    \u2190 This domain failed!\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Test Each Domain&#39;s Asset Links File<\/h3>\n\n\n\n<pre><code class=\"language-bash\"># Check each domain manually\ncurl -I &quot;https:\/\/links.yourapp.com\/.well-known\/assetlinks.json&quot;\ncurl -I &quot;https:\/\/go.yourapp.com\/.well-known\/assetlinks.json&quot;\ncurl -I &quot;https:\/\/newbrand.com\/.well-known\/assetlinks.json&quot;\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">All should return <code>200 OK<\/code> with <code>Content-Type: application\/json<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Force Re-Verification<\/h3>\n\n\n\n<pre><code class=\"language-bash\">adb shell pm set-app-links --package com.example.yourapp 0 all\nadb shell pm verify-app-links --re-verify com.example.yourapp\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Google&#39;s Verification Tool<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Use the <a href=\"https:\/\/developers.google.com\/digital-asset-links\/tools\/generator\" rel=\"nofollow noopener\" target=\"_blank\">Digital Asset Links API<\/a> to validate your setup:<\/p>\n\n\n\n<pre><code>https:\/\/digitalassetlinks.googleapis.com\/v1\/statements:list?source.web.site=https:\/\/links.yourapp.com&amp;relation=delegate_permission\/common.handle_all_urls\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Common Pitfalls<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Pitfall 1: Forgotten Domain<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">You add a new domain to your intent filter but forget to host <code>assetlinks.json<\/code> on it. On Android 12+, this can break verification for ALL your domains.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Prevention:<\/strong> Add asset links hosting to your domain setup checklist. Automate it if possible.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Pitfall 2: Wildcard Subdomains<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Android doesn&#39;t support wildcards in <code>android:host<\/code>. You can&#39;t do <code>*.yourapp.com<\/code>. Each subdomain must be listed explicitly.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Pitfall 3: Different Signing Keys<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">If you use different signing keys for debug and release builds, the SHA-256 fingerprints differ. Your <code>assetlinks.json<\/code> should include both fingerprints during development:<\/p>\n\n\n\n<pre><code class=\"language-json\">[{\n  &quot;relation&quot;: [&quot;delegate_permission\/common.handle_all_urls&quot;],\n  &quot;target&quot;: {\n    &quot;namespace&quot;: &quot;android_app&quot;,\n    &quot;package_name&quot;: &quot;com.example.yourapp&quot;,\n    &quot;sha256_cert_fingerprints&quot;: [\n      &quot;RELEASE_FINGERPRINT_HERE&quot;,\n      &quot;DEBUG_FINGERPRINT_HERE&quot;\n    ]\n  }\n}]\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Pitfall 4: CDN Caching Stale Files<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">If your <code>assetlinks.json<\/code> is served through a CDN and you update it (e.g., new signing key), the CDN may serve the stale version. Set a short cache TTL (5-10 minutes) for this file.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Pitfall 5: Domain Migration<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">When migrating from an old domain to a new one, keep the <code>assetlinks.json<\/code> on the old domain active until all users have updated to the latest app version. Removing it breaks App Links for users on older versions.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For managing multiple domains with <a href=\"https:\/\/tolinku.com\/docs\/user-guide\/domains\/\">Tolinku<\/a>, you can add custom domains and subdomains in the dashboard. The platform handles <code>assetlinks.json<\/code> hosting for each domain automatically. See the <a href=\"https:\/\/tolinku.com\/docs\/developer\/app-links\/\">App Links documentation<\/a> for the full setup.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For understanding the intent filter configuration that underpins multi-domain routing, see the <a href=\"https:\/\/tolinku.com\/blog\/android-intent-filters-deep-links\/\">Android intent filters guide<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Configure Android App Links for multiple domains. Set up assetlinks.json on each domain, manage intent filters, and handle routing across branded and tracking domains.<\/p>\n","protected":false},"author":2,"featured_media":1269,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"Handling Multiple Domains with Android App Links","rank_math_description":"Configure Android App Links for multiple domains. Set up assetlinks.json on each domain and manage intent filters for multi-domain routing.","rank_math_focus_keyword":"android app links multiple domains","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-android-app-links-multiple-domains.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-android-app-links-multiple-domains.png","footnotes":""},"categories":[10],"tags":[25,23,101,20,35,324,69],"class_list":["post-1270","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-android","tag-android","tag-app-links","tag-configuration","tag-deep-linking","tag-digital-asset-links","tag-domains","tag-mobile-development"],"_links":{"self":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1270","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=1270"}],"version-history":[{"count":3,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1270\/revisions"}],"predecessor-version":[{"id":2574,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1270\/revisions\/2574"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media\/1269"}],"wp:attachment":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media?parent=1270"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/categories?post=1270"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/tags?post=1270"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}