{"id":577,"date":"2026-03-23T09:00:00","date_gmt":"2026-03-23T14:00:00","guid":{"rendered":"https:\/\/tolinku.com\/blog\/?p=577"},"modified":"2026-03-07T03:32:55","modified_gmt":"2026-03-07T08:32:55","slug":"assetlinks-json-generator","status":"publish","type":"post","link":"https:\/\/tolinku.com\/blog\/assetlinks-json-generator\/","title":{"rendered":"How to Generate and Validate assetlinks.json"},"content":{"rendered":"\n<p>Android App Links will not work without a correctly configured <code>assetlinks.json<\/code> file. This file is what Android uses to confirm your app is authorized to handle URLs at your domain. Without it, links open in the browser instead of your app, even if everything else in your manifest is configured correctly.<\/p>\n\n\n\n<p>This guide covers what <code>assetlinks.json<\/code> contains, how to extract the SHA-256 fingerprint from your signing keystore, how to use Google&#39;s Statement List Generator tool, how to handle multiple signing certificates, and how to validate your setup before and after deployment.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What assetlinks.json Does<\/h2>\n\n\n\n<p>The <code>assetlinks.json<\/code> file declares an association between a website and an Android app. It answers one question: does this website authorize this app to handle its URLs?<\/p>\n\n\n\n<p>Android fetches the file from a fixed path on your domain: <code>https:\/\/yourdomain.com\/.well-known\/assetlinks.json<\/code>. The file must be served over HTTPS, with a valid certificate, and without redirects. Android will not follow redirects when fetching this file.<\/p>\n\n\n\n<p>The verification check happens at app install time. If verification succeeds, Android registers your app as a verified handler for the declared URLs. If it fails (because the file is missing, returns an error, or the fingerprint doesn&#39;t match), App Links verification fails and links fall back to browser behavior.<\/p>\n\n\n\n<p>The <a href=\"https:\/\/developer.android.com\/training\/app-links\/verify-android-applinks\" rel=\"nofollow noopener\" target=\"_blank\">Android App Links verification documentation<\/a> describes the full verification process, including retry behavior and how to check verification status on a device.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The File Structure<\/h2>\n\n\n\n<p>A minimal <code>assetlinks.json<\/code> file looks like this:<\/p>\n\n\n\n<pre><code class=\"language-json\">[\n  {\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.yourcompany.yourapp&quot;,\n      &quot;sha256_cert_fingerprints&quot;: [\n        &quot;AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99&quot;\n      ]\n    }\n  }\n]\n<\/code><\/pre>\n\n\n\n<p>The file is a JSON array. Each object in the array is a &quot;statement&quot; that declares a relationship between the site and an app. For deep linking, the relation is always <code>delegate_permission\/common.handle_all_urls<\/code>.<\/p>\n\n\n\n<p>The three fields in the <code>target<\/code> object:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>namespace<\/code>: always <code>android_app<\/code> for Android App Links<\/li>\n<li><code>package_name<\/code>: your app&#39;s application ID, exactly as declared in your <code>build.gradle<\/code> file<\/li>\n<li><code>sha256_cert_fingerprints<\/code>: an array of SHA-256 fingerprints of the signing certificates that can sign a valid build of your app<\/li>\n<\/ul>\n\n\n\n<p>The fingerprint format is 32 pairs of uppercase hex characters separated by colons. A common mistake is using the wrong separator (dashes instead of colons) or lowercase hex. Both will cause verification to fail.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Extracting the SHA-256 Fingerprint from Your Keystore<\/h2>\n\n\n\n<p>Android apps are signed with a Java KeyStore (JKS or PKCS12) file. You need the SHA-256 fingerprint of the certificate stored in that keystore.<\/p>\n\n\n\n<p>Use the <code>keytool<\/code> command, which ships with the Java Development Kit:<\/p>\n\n\n\n<pre><code class=\"language-bash\">keytool -list -v \\\n  -keystore \/path\/to\/your.keystore \\\n  -alias your_key_alias\n<\/code><\/pre>\n\n\n\n<p>You will be prompted for the keystore password. The output includes fingerprints in multiple formats. You want the SHA256 line:<\/p>\n\n\n\n<pre><code>Certificate fingerprints:\n     MD5:  ...\n     SHA1: ...\n     SHA256: AA:BB:CC:DD:EE:FF:...\n<\/code><\/pre>\n\n\n\n<p>Copy the SHA256 value exactly, including the colons.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Debug Keystore<\/h3>\n\n\n\n<p>During development, Android signs your app with a debug keystore. Its location depends on your OS:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>macOS\/Linux: <code>~\/.android\/debug.keystore<\/code><\/li>\n<li>Windows: <code>%USERPROFILE%\\.android\\debug.keystore<\/code><\/li>\n<\/ul>\n\n\n\n<p>The debug keystore uses standard credentials:<\/p>\n\n\n\n<pre><code class=\"language-bash\">keytool -list -v \\\n  -keystore ~\/.android\/debug.keystore \\\n  -alias androiddebugkey \\\n  -storepass android \\\n  -keypass android\n<\/code><\/pre>\n\n\n\n<p>Include the debug keystore&#39;s SHA-256 fingerprint in your <code>assetlinks.json<\/code> during development so you can test App Links without a production-signed build.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Release Keystore<\/h3>\n\n\n\n<p>Your release keystore is the file you use to sign APKs and App Bundles for the Play Store. Extract its fingerprint the same way, substituting your actual keystore path, alias, and password.<\/p>\n\n\n\n<p>If you use Google Play App Signing (which Google strongly recommends), the certificate that actually signs your app on the Play Store is managed by Google, not your local keystore. The fingerprint in your <code>assetlinks.json<\/code> must match the Play-managed certificate, not your upload certificate.<\/p>\n\n\n\n<p>To get the Play-managed certificate fingerprint:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Open the <a href=\"https:\/\/play.google.com\/console\" rel=\"nofollow noopener\" target=\"_blank\">Google Play Console<\/a><\/li>\n<li>Select your app<\/li>\n<li>Go to Release &gt; Setup &gt; App signing<\/li>\n<li>Find the &quot;App signing key certificate&quot; section<\/li>\n<li>Copy the SHA-256 certificate fingerprint listed there<\/li>\n<\/ol>\n\n\n\n<p>This fingerprint is different from your upload key fingerprint. Using the upload key fingerprint in <code>assetlinks.json<\/code> when Play App Signing is enabled is a common mistake that causes verification to fail on production builds.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Using Google&#39;s Statement List Generator<\/h2>\n\n\n\n<p>Google provides a web tool for generating <code>assetlinks.json<\/code> without writing JSON by hand: the <a href=\"https:\/\/developers.google.com\/digital-asset-links\/tools\/generator\" rel=\"nofollow noopener\" target=\"_blank\">Statement List Generator<\/a>.<\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/tolinku.com\/blog\/wp-content\/uploads\/2026\/03\/doc-play-app-signing-diagram-2.png\" alt=\"Google Play App Signing diagram showing the distinction between upload key and Play-managed signing key\"><\/p>\n\n\n\n<p><em>Source: <a href=\"https:\/\/developer.android.com\/studio\/publish\/app-signing\" rel=\"nofollow noopener\" target=\"_blank\">Android Developer Documentation<\/a><\/em><\/p>\n\n\n\n<p>The tool asks for:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Your domain (e.g., <code>yourdomain.com<\/code>)<\/li>\n<li>Your app&#39;s package name<\/li>\n<li>Your app&#39;s SHA-256 fingerprint<\/li>\n<\/ul>\n\n\n\n<p>It generates the JSON and optionally verifies the file if you&#39;ve already hosted it. This is useful for a quick sanity check on a new setup, but it does not replace extracting the fingerprint from your actual keystore (the tool takes the fingerprint as input, so you still need to get it from <code>keytool<\/code> or the Play Console).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Handling Multiple Signing Certificates<\/h2>\n\n\n\n<p>The <code>sha256_cert_fingerprints<\/code> field is an array. You can include multiple fingerprints, and App Links verification will succeed if the installed app is signed with any of them. This is important for several common scenarios:<\/p>\n\n\n\n<p><strong>Debug and release in parallel.<\/strong> Include both your debug keystore fingerprint and your release keystore fingerprint so you can test App Links on debug builds while production users on release builds also benefit from verification.<\/p>\n\n\n\n<p><strong>Play App Signing plus upload key.<\/strong> Include both the Play-managed signing certificate and your upload certificate. Apps installed via the Play Store will be signed with the Play-managed key; sideloaded or internal test builds might be signed with your upload key.<\/p>\n\n\n\n<p><strong>Certificate rotation.<\/strong> If you rotate your signing key, include both the old and new fingerprints during the transition period. Remove the old one once you&#39;re confident no users are on the old signed build.<\/p>\n\n\n\n<pre><code class=\"language-json\">[\n  {\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.yourcompany.yourapp&quot;,\n      &quot;sha256_cert_fingerprints&quot;: [\n        &quot;AA:BB:CC:...(debug keystore fingerprint)...&quot;,\n        &quot;11:22:33:...(Play-managed signing certificate fingerprint)...&quot;,\n        &quot;44:55:66:...(upload key fingerprint, if needed)...&quot;\n      ]\n    }\n  }\n]\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Multiple Apps on the Same Domain<\/h2>\n\n\n\n<p>If you have multiple apps that should handle URLs at the same domain (uncommon but valid), include a statement object for each:<\/p>\n\n\n\n<pre><code class=\"language-json\">[\n  {\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.yourcompany.mainapp&quot;,\n      &quot;sha256_cert_fingerprints&quot;: [&quot;AA:BB:CC:...&quot;]\n    }\n  },\n  {\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.yourcompany.otherapp&quot;,\n      &quot;sha256_cert_fingerprints&quot;: [&quot;DD:EE:FF:...&quot;]\n    }\n  }\n]\n<\/code><\/pre>\n\n\n\n<p>Each app&#39;s <code>AndroidManifest.xml<\/code> will declare which paths it handles through its intent filters. The domain association in <code>assetlinks.json<\/code> authorizes both apps to handle URLs at the domain, and path specificity in the manifest determines which app opens which URLs.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Hosting Requirements<\/h2>\n\n\n\n<p>The file must be served at exactly this path: <code>\/.well-known\/assetlinks.json<\/code><\/p>\n\n\n\n<p>Specific requirements from the Android documentation:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Served over HTTPS with a valid, trusted TLS certificate<\/li>\n<li>No redirects. Android does not follow HTTP redirects when fetching this file<\/li>\n<li>Content-Type header should be <code>application\/json<\/code><\/li>\n<li>HTTP 200 response code (not 3xx, not 4xx)<\/li>\n<li>Accessible without authentication (no basic auth, no login walls)<\/li>\n<li>Accessible to Google&#39;s servers (not blocked by IP allowlists or firewall rules)<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Caching<\/h3>\n\n\n\n<p>Android caches the <code>assetlinks.json<\/code> file. Changes to the file do not take effect immediately on already-installed apps. Re-verification happens at system intervals or when the app is reinstalled.<\/p>\n\n\n\n<p>If you push an update to <code>assetlinks.json<\/code>, do not expect App Links behavior to change on devices that have already verified. Testing certificate changes requires uninstalling and reinstalling the app.<\/p>\n\n\n\n<p>There is no way to set the cache duration from the server side for Android&#39;s internal verification cache. Do not set a <code>Cache-Control: no-cache<\/code> header expecting it to force re-verification; the Android system ignores this for its own verification fetches.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Validating Your Setup<\/h2>\n\n\n\n<p>Several tools can check your <code>assetlinks.json<\/code> before and after deployment.<\/p>\n\n\n\n<p><strong>adb verification check.<\/strong> After installing your app on a test device, run:<\/p>\n\n\n\n<pre><code class=\"language-bash\">adb shell pm get-app-links com.yourcompany.yourapp\n<\/code><\/pre>\n\n\n\n<p>The output shows the verification status per domain. A <code>verified<\/code> status means App Links is working. <code>none<\/code> or <code>failed<\/code> means something is wrong.<\/p>\n\n\n\n<p><strong>Google&#39;s Digital Asset Links API.<\/strong> You can query Google&#39;s verification endpoint directly to see whether Google can successfully verify your association:<\/p>\n\n\n\n<pre><code>https:\/\/digitalassetlinks.googleapis.com\/v1\/statements:list?source.web.site=https:\/\/yourdomain.com&amp;relation=delegate_permission\/common.handle_all_urls\n<\/code><\/pre>\n\n\n\n<p>This returns a list of all statements Google finds for your domain. If the response is empty or missing your app, Google cannot verify the association.<\/p>\n\n\n\n<p><strong>Manual HTTP check.<\/strong> Fetch the file with <code>curl<\/code> to confirm it is reachable without redirects:<\/p>\n\n\n\n<pre><code class=\"language-bash\">curl -I https:\/\/yourdomain.com\/.well-known\/assetlinks.json\n<\/code><\/pre>\n\n\n\n<p>Look for HTTP 200, no Location headers (which would indicate a redirect), and a JSON content type. Then fetch the content itself:<\/p>\n\n\n\n<pre><code class=\"language-bash\">curl https:\/\/yourdomain.com\/.well-known\/assetlinks.json\n<\/code><\/pre>\n\n\n\n<p>Confirm the package name and fingerprints in the output match what you expect.<\/p>\n\n\n\n<p><strong>Statement List Generator validation.<\/strong> Enter your domain into Google&#39;s <a href=\"https:\/\/developers.google.com\/digital-asset-links\/tools\/generator\" rel=\"nofollow noopener\" target=\"_blank\">Statement List Generator<\/a> tool and use the verification feature to confirm Google can read your file.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Common Mistakes<\/h2>\n\n\n\n<p><strong>Wrong fingerprint format.<\/strong> The SHA-256 must use colons as separators and uppercase hex. Dashes, spaces, or lowercase hex will not match.<\/p>\n\n\n\n<p><strong>Upload key vs. Play-managed key.<\/strong> As noted above, these are different. Use the fingerprint from the Play Console&#39;s App Signing page for production.<\/p>\n\n\n\n<p><strong>File served with redirects.<\/strong> Many web servers redirect <code>http:\/\/<\/code> to <code>https:\/\/<\/code>. If your server also redirects <code>\/.well-known\/assetlinks.json<\/code> to a different path, verification will fail. Check that the exact path returns 200 over HTTPS.<\/p>\n\n\n\n<p><strong>Returning 404 for empty directories.<\/strong> Some web servers return 404 for requests to the <code>\/.well-known\/<\/code> directory if the directory does not physically exist on disk. Create the directory and place the file there, or configure a server-side route to serve the file.<\/p>\n\n\n\n<p><strong>Incorrect package name.<\/strong> The <code>package_name<\/code> must exactly match the <code>applicationId<\/code> in your app&#39;s <code>build.gradle<\/code>. A typo here will cause every verification attempt to fail silently.<\/p>\n\n\n\n<p>For a complete walkthrough of how <code>assetlinks.json<\/code> fits into the overall Android App Links setup, see the <a href=\"https:\/\/tolinku.com\/blog\/android-app-links-complete-guide\/\">Android App Links complete guide<\/a>. For how to configure your manifest to declare which paths your app handles, see the <a href=\"https:\/\/tolinku.com\/blog\/android-manifest-deep-link-configuration\/\">Android manifest deep link configuration guide<\/a>.<\/p>\n\n\n\n<p>Tolinku&#39;s <a href=\"https:\/\/tolinku.com\/docs\/developer\/app-links\/\">App Links documentation<\/a> covers how to connect your verified App Links setup to the platform&#39;s routing, analytics, and fallback page features.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The assetlinks.json file is the trust anchor for Android App Links. This guide covers how to generate it correctly, extract SHA-256 fingerprints from your keystore, handle multiple signing certificates, and validate everything before going live.<\/p>\n","protected":false},"author":2,"featured_media":576,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"How to Generate and Validate assetlinks.json (2026)","rank_math_description":"Learn how to generate assetlinks.json for Android App Links. Extract SHA-256 fingerprints, use Google's Statement List Generator, handle multiple certificates, and validate hosting.","rank_math_focus_keyword":"assetlinks.json generator","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-assetlinks-json-generator.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-assetlinks-json-generator.png","footnotes":""},"categories":[10],"tags":[25,23,92,101,20,102,93],"class_list":["post-577","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-android","tag-android","tag-app-links","tag-assetlinks","tag-configuration","tag-deep-linking","tag-keystore","tag-security"],"_links":{"self":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/577","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=577"}],"version-history":[{"count":1,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/577\/revisions"}],"predecessor-version":[{"id":578,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/577\/revisions\/578"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media\/576"}],"wp:attachment":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media?parent=577"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/categories?post=577"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/tags?post=577"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}