Publishing

assetlinks.json SHA-256: Fix the TWA Browser Bar on Play

Your TWA works from Android Studio but shows a browser bar from Google Play. Copy the Play app signing key SHA-256, fix assetlinks.json, test a Play build.

Afrin Asha — Founder, OnTesters
Afrin AshaFounder, OnTesters
21 min read
On this page12 sections

Why your TWA shows a browser bar from Google Play but not from Android Studio

The copy installed from Google Play is signed with a different key than the copy you installed yourself, and your website is only listing the key you built with. Chrome documents the fallback exactly: "When you launch a Trusted Web Activity, the browser verifies the Digital Asset Links. If verification fails, the browser falls back to displaying your website as a Custom Tab." That Custom Tab toolbar is the address bar your testers are looking at.

The repair has four steps and none of them touch the app binary: copy the app signing key SHA-256 out of Play Console, put it in /.well-known/assetlinks.json, serve that file with a direct HTTP 200 and no redirects, then test with a build installed from a Play testing track instead of a sideloaded APK. Because the fix lives on your website, it needs no new release and no resubmission, so it does not disturb a closed test that is already running.

This guide covers that one repair, the association between your website and the build Google Play delivers. It does not cover building a TWA and it does not cover Google's review of your app; those have their own guides, linked where they matter. Every Google page cited here was checked on October 27, 2026, and the full list is at the end.

Which certificate signed the build in front of you

With Play App Signing there are usually two keys. You sign the bundle you upload with your upload key, and Google signs the APKs it delivers with the app signing key. Chrome's guidance for web developers puts it plainly: the app signing key fingerprint "will correspond to your app if you download it from the Google Play Store." A file that only lists your local certificate passes on your desk and fails on every phone that installed from Play.

Google's Play App Signing help describes the same split. The upload key is the key "you use to sign your app bundle before uploading it to the Play Console," and the app signing key is the one Google holds and uses to sign what it distributes. So the first question is not whether your JSON is valid. It is which certificate is on the build sitting in front of you.

Where the build came fromWhat signed itWhat assetlinks.json needs
A closed testing or production track installPlay's app signing keyThe app signing key certificate SHA-256. This is the one that fixes the Play install.
Your own release APK, installed by handYour upload or release keyThat key's SHA-256, only if you want sideloaded builds to verify too.
An Android Studio debug runYour local debug keyThe debug SHA-256, only if you want debug builds to verify.
An internal app sharing linkA separate Internal App Sharing key Google creates for the appNothing useful. Test on a testing track instead.
A build released after a signing key changeThe certificate Play now signs that build withEvery certificate for builds you still support. Do not drop an older one only because a newer one appears.

Two rows explain most of the confusion. A sideloaded release APK can verify perfectly while the Play copy shows a bar, and an internal app sharing link never verifies at all, because Google's internal app sharing help says those uploads "are automatically re-signed with an Internal App Sharing key, which is automatically created for your app by Google." If you are debugging against a sharing link, you are debugging the wrong certificate.

Step 1: copy the app signing key SHA-256 from Play Console

Google's App Links FAQ, last updated October 1, 2026, names this as the most reliable source: "Navigate to your app's Protected with Play, then Play Store protection, then Manage Play app signing. Under App signing key certificate, you will find the SHA-256 certificate fingerprint. This is the same key that Google Play uses to sign your app release." Copy that line in full. It is 32 pairs of hexadecimal characters separated by colons, and a shortened example lifted from a blog post will not verify.

Google page giving the routePath it namesField to copy
App Links FAQ (updated Oct 1, 2026)Protected with Play, Play Store protection, Manage Play app signingApp signing key certificate, SHA-256 certificate fingerprint
Use Play App Signing helpProtected with Play, Play Store protection, Manage Play app signingApp signing key section, the SHA-256 of the required fingerprints
Chrome for Developers, Android concepts for web developers, and PWABuilder's next stepsRelease, Setup, App integrity (older console labels)App signing key certificate, SHA256

The third row is not a different destination, it is an older set of labels. Google and its own Chrome documentation have both been reworded over time, so a guide that sends you to Setup and App integrity is sending you to the same certificate under a name the console used before. If the sidebar does not match, search the app for "app signing" rather than assuming you are in the wrong place.

Copy the app signing key, not the upload key, and use the SHA-256 line rather than SHA-1. Google's Play App Signing help also warns about a newer configuration: "If your app uses quantum-ready hybrid signing, you must copy the fingerprints for three keys and register each of them with your API providers: your new classical and PQC keys used on newer devices and your classical key used on older devices." That page then tells you directly to "Update your assetlinks.json file with these fingerprints if you use Android App Links." Most apps still show a single app signing certificate, but if your page lists three, list all three.

For a key you hold yourself, such as an upload key or a debug keystore, Java's keytool prints the same format: keytool -list -v -keystore my-upload-key.jks -alias upload. Use it for the fingerprints you add alongside Play's, not instead of it.

Step 2: write the file and serve it the way Google requires

The statement list is a JSON array. For a TWA the relation is delegate_permission/common.handle_all_urls, and the target is your Android app: the android_app namespace, your exact application ID, and an array of SHA-256 fingerprints.

[
  {
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": {
      "namespace": "android_app",
      "package_name": "com.example.twa",
      "sha256_cert_fingerprints": [
        "PLAY_APP_SIGNING_KEY_SHA256",
        "UPLOAD_KEY_SHA256"
      ]
    }
  }
]

Replace the package name with your application ID and each placeholder with a full colon separated SHA-256. Keep the upload key entry only if you also want builds you installed yourself to verify. The file must be strict JSON: no comments, no trailing commas, no SHA-1 values, no markdown.

RequirementWhat Google documentsWhat usually breaks it
Exact locationscheme://domain/.well-known/assetlinks.json, with the dot in .well-knownA copy in /app/.well-known/, or a file named assetlinks.txt
Direct HTTP 200"Any response from the server besides HTTP 200 is treated as an error, and will result in an empty statement list."A single page app host that rewrites every unknown path to index.html and answers 200 with HTML
No redirects"It cannot be a redirect (that is, 301 or 302 response codes are not followed)."An http to https hop, or an apex to www hop, sitting in front of the file
Correct content type"The assetlinks.json file must be served as Content-Type: application/json in the HTTP headers."text/plain from a static host, or text/html from a rewrite rule
A verifiable HTTPS chain"Any connection without a certificate chain that can be verified with the trusted root list will also result in an empty statement list."An expired certificate or a missing intermediate certificate on your CDN

All five rows above are from Google's Digital Asset Links documentation on hosting a statement list. Check the live response from a terminal before you look anywhere else: curl -sI https://example.com/.well-known/assetlinks.json. The first status line must be 200, the content type must be JSON, and no 3xx may appear above it. If you see HTML in the body, you are reading your own error page or your app shell, not a statement list.

One origin, one file. Google's protocol is per origin, so example.com and www.example.com are different principals. If your app opens both hosts, publish the file on both, and make the host in your launch URL match the host that serves the file. Chrome's multi-origin guidance says the same thing from the other side: "When a user navigates off the validated origin, Custom Tab UI is shown." Every extra origin your app can reach needs its own file with the same package and fingerprints.

Step 3: verify the statement before you reach for a device

Four checks, in the order that costs the least. Stop at the first one that fails.

1. The file on your website. The curl check above. Fix any redirect, wrong content type or non-200 status here, because every later check will simply report the same failure in a less readable form.

2. What Google parses from it. Google's Statement List Generator and Tester takes your domain, package name and SHA-256 and tells you whether the statement is there. It is the quickest visual confirmation, and it catches the mistakes that curl cannot, such as a package name typo or a fingerprint with a missing colon pair.

3. The exact question through the API. The assetlinks:check method, documented in the Digital Asset Links API reference, answers one precise question: is this package with this certificate linked to this website? Look for "linked": true. The related statements:list method returns everything the site publishes, which is useful for reading back the array, but an empty result from check is the answer that matters. An authorization or quota error from the API is an access problem, not a failed association.

4. The certificate actually on the installed build. This is the step most guides skip. Pull the certificate off the build that is really on the device with adb shell pm path com.example.twa, then apksigner verify --print-certs on the APK you pulled, and compare that fingerprint against what your file publishes. If they differ, no amount of file editing will help until you know which key Play is signing with now.

With a device connected you can also read the browser's own verdict: adb logcat -v brief | grep -e OriginVerifier -e digital_asset_links. GoogleChromeLabs' TWA sample documents what the output means. Statement failure matching fingerprint. Verification failed. means the site's statement does not list the certificate of the installed build, and Verification succeeded. means it does. Force-stop the app and reopen it so the check runs again while logcat is watching.

Step 4: test the build a tester installs

Install from your closed testing track through the opt-in link, not from a sideloaded APK and not from an internal app sharing link. This is the single difference between a fix that looks done on your desk and a fix that is done for your testers.

Our own campaigns are run on physical devices, Samsung, Pixel, Xiaomi and OnePlus hardware running Android 11 to 15 across more than 80 countries, and every tester installs the Play signed build from the opt-in link. Based on our records across more than 1,500 closed-testing campaigns, that is precisely where a wrong fingerprint shows up first: the developer's sideloaded build is full screen while the testers see a toolbar. Those are our platform records, not Google statistics, and we publish no success rate from them.

Two smaller traps while you test. Android's verification guide is asynchronous, and it says to "wait at least 20 seconds for the asynchronous verification process to complete" before judging a result. And the TWA check belongs to the browser, not to Play Console, so make Chrome the default browser on the test device: Chrome's TWA overview says the default browser is used when it provides the required capabilities, otherwise another browser that does, otherwise a Custom Tab.

The fingerprint matches and the browser bar is still there

A visible bar does not prove the SHA-256 is wrong. Once the API returns linked: true and the toolbar is still there, the cause is somewhere else on this list.

What you seeLikely causeWhat to do
The bar appears after a link, a login or a redirectYou left the validated origin. Chrome: "When a user navigates off the validated origin, Custom Tab UI is shown."Declare every origin your app opens and publish assetlinks.json on each one.
The bar is there from the first screen and your apex domain redirectsThe launch URL host and the declared origin are different hostsMake the launch URL, the declared origin and the file's host the same, for example all of them on www.
The bar only appears on a third party's login pageAn identity provider is an origin you do not control and cannot publish a file forExpected behavior. Browser UI there is the documented signal that the user is outside your association.
Full screen in Chrome, a bar with another default browserNot every browser implements the TWA protocolRetest with Chrome as the default browser before you change the file again.
A fresh install on a bad or missing networkThe verification request could not run; Android's guide requires the test device to be onlineReconnect, force-stop and reopen the app, then judge again.
Android reports verified but the TWA still shows browser UIAndroid's domain verification and the browser's TWA check are separate checks with separate logsRead the browser's OriginVerifier output over adb instead of the Android state.
The bar is gone for you and still there for your testersYour file went live after their install, or their device has not re-verified yetHave them update from the testing track, or uninstall and reinstall from the opt-in link.

Google publishes no single wait time, because several caches sit between your edit and your tester's phone, and each has its own rule. The documented timings come from the Digital Asset Links update latency section of Android's App Links guide.

LayerWhat Google publishesWhat to do
Your server or CDNNo published wait time, it is your infrastructurePurge the cache, then curl the live file and read the response yourself.
Android 15 and higher"Changes can take up to seven days to propagate to all end-user devices due to the system's caching and scheduled re-verification."Do not plan a launch around a same-day change on these devices.
Android 14 and lower"Updates to the file are typically only picked up when the app is installed or updated."Push a new build, or have the tester reinstall from the testing track.
Server side caches during testing"Server-side caches might still delay the delivery of the updated assetlinks.json file to the device, even after reinstalling... the delay typically resolves automatically if you try again after a few hours."Wait a few hours and reinstall rather than editing the file again.
The browser's own stateNo published ruleClear the browser's site data on a test device. That is a diagnostic step for you, not something to ask every user to do.

Google's own testing advice for this situation is blunt: "To force a re-verification on a specific device during testing, you can uninstall and reinstall the app." Purge your CDN, confirm the live response with curl, then have the tester update or reinstall from the opt-in link. Editing the file a second time before the first edit has propagated is how developers end up chasing a problem that was already fixed.

What this fix does to your 14-day closed test

Nothing, and that is the practical reason to do it now rather than after your application is reviewed. The statement list lives on your website, so correcting it requires no new bundle, no version code bump and no resubmission to a track. Play never sees the change, so there is no review to wait for. Reasoning from where Google documents the file being hosted, a server side edit cannot restart a clock that measures your closed test, because nothing about the test release changed.

Testers are the reason this matters during a run rather than after it. They install the Play signed build from the opt-in link, which is exactly the build that fails verification when your file lists only your local certificate, so they meet the toolbar before you do. A toolbar on its own is a packaging bug, not an engagement finding, and it is not documented by Google as a rejection reason: what Chrome documents is the fallback to a Custom Tab, and Play's functionality policy covers apps that "crash, force close, freeze, or otherwise function abnormally."

If Google has already come back asking for more testing, the reasons it names are separate ones: "having fewer than 12 opted-in testers or insufficient tester engagement during the testing period." Fix the fingerprint before you reapply anyway, because a toolbar sitting on top of your product is the first thing a tester or a reviewer sees, and it is one of the cheap fixes. The broader question of whether Google treats TWA apps differently at review time is covered in our TWA rejection guide, which also lays out the two routes if you decide the wrapper itself has to change.

Do not rebuild your TWA as a WebView just because of a browser bar. That is a different decision with a different cost, and it belongs to the section of our TWA guide that compares keeping the TWA against rebuilding under the same package name. If your rejection notice names a functionality problem instead, start from the broken functionality checks and come back here when the notice is about the toolbar.

Bubblewrap and PWABuilder projects

If the project came out of Bubblewrap, the fingerprints are managed from the CLI rather than by hand: bubblewrap fingerprint add stores Play's SHA-256, bubblewrap fingerprint list shows what the project holds, and bubblewrap fingerprint generateAssetLinks rewrites the file from that configuration. Generating it is not deploying it. Upload the new assetlinks.json to your website and confirm it with curl, because the CLI writes to your project directory, not to your server.

One command in that family is easy to misread. bubblewrap validate checks your PWA against the quality criteria for use in a Trusted Web Activity. It says nothing about your signing association, so a pass there proves nothing about the fingerprint.

PWABuilder projects fail in the same place for the same reason. PWABuilder's own next steps page is explicit: "Once you've uploaded your .aab file, Google Play re-signs your app. Because of this, you'll need to update your asset links file." The zip it hands you contains the fingerprint from your local key, which is correct for the APK you tested and wrong for the copy Play distributes until you add the app signing key. If you are still deciding what format to upload, our explainer on the Android App Bundle covers why Play re-signs the bundle at all.

Frequently asked questions

Which SHA-256 goes into assetlinks.json when I use Play App Signing?

The SHA-256 of the app signing key certificate shown under Play app signing in Play Console, because that certificate signs the builds people install from Google Play. Add your upload or debug fingerprint alongside it only if you also want builds you install yourself to verify. If your console shows three certificates under quantum-ready hybrid signing, list all three.

Why does my TWA work from Android Studio but show the URL bar from Google Play?

Because the two builds are signed with different keys. Android Studio installs with your local debug or upload key, while Google Play delivers the app signed with the app signing key. If your website lists only your local certificate, verification fails for the Play install and Chrome falls back to a Custom Tab with a visible toolbar.

Where is the app signing key in the current Play Console?

Google's App Links FAQ, updated October 1, 2026, says Protected with Play, then Play Store protection, then Manage Play app signing, and the SHA-256 under App signing key certificate. Older Google pages and third-party guides still say Release, Setup, App integrity. Both describe the same certificate, so follow whichever label your console shows and copy the app signing key, not the upload key.

Does fixing assetlinks.json reset my 14-day closed test?

No. The file is hosted on your website, so the correction needs no new build, no version bump and no resubmission, and Play never sees the change. Your closed test keeps running on the release it already has. Reinstalling the app from the testing track does not reset the opt-in period either, it only forces the device to fetch the updated file.

Why is the bar still there after I updated the file?

Usually one of three things: the live URL still serves the old file because your CDN cache was not purged, the device has not re-verified yet, which can take up to seven days on Android 15 and higher, or you are testing a sideloaded or internally shared build signed with a different key. Confirm the live response with curl, then reinstall from the testing track, then compare the installed build's certificate against your file with apksigner.

Is the browser bar the reason Google rejected my app?

Google does not document a browser bar as a rejection reason. The documented fallback is a Custom Tab, and the functionality policy covers apps that crash, force close, freeze or otherwise function abnormally. If your notice asks for more testing, Google names fewer than 12 opted-in testers or insufficient tester engagement. Fix the bar anyway, then answer the notice from the reason it actually gives.

Do I need a separate assetlinks.json for example.com and www.example.com?

Yes, if your app can open both. The statement list is hosted per origin, so each host needs its own file at /.well-known/assetlinks.json carrying the same package name and fingerprints. The simplest fix is to make your launch URL, your declared origin and your file's host identical, and redirect one host to the other before the file is requested rather than after.

Sources

  • Quick start to Trusted Web Activities - Chrome for Developers. The fallback to a Custom Tab when Digital Asset Links verification fails, and how signing keys are used.
  • Android concepts for web developers - Chrome for Developers. Why the app signing key fingerprint corresponds to the app downloaded from the Google Play Store, and the older Play Console route to the SHA256.
  • Multi-origin Trusted Web Activities - Chrome for Developers. The rule that Custom Tab UI is shown when a user navigates off the validated origin.
  • Overview of Trusted Web Activities - Chrome for Developers. Default browser selection and fallback behavior when a browser does not support the protocol.
  • App Links FAQ - Android Developers, last updated October 1, 2026. The most reliable route to the SHA-256 fingerprint for assetlinks.json.
  • Verify App Links - Android Developers, last updated October 16, 2026. The 20 second wait for asynchronous verification and the Digital Asset Links update latency table, including the seven day propagation on Android 15 and higher.
  • Creating a Statement List - Google Digital Asset Links. The HTTP 200, no redirect, Content-Type and trusted certificate chain requirements quoted in this article.
  • Getting Started - Google Digital Asset Links. The statement list location, the relation type and how a target app is matched by certificate hash.
  • Statement List Generator and Tester - Google Digital Asset Links. The console check used in step 3.
  • Digital Asset Links API reference - Google Digital Asset Links. The assetlinks:check and statements:list methods used in step 3.
  • Use Play App Signing - Google Play Console Help. Upload key versus app signing key, the quantum-ready hybrid signing fingerprints, and the instruction to update assetlinks.json with them.
  • Share app bundles and APKs internally - Google Play Console Help. Internal app sharing uploads are automatically re-signed with an Internal App Sharing key.
  • Functionality, Content, and User Experience - Google Play Developer Policy. The Broken Functionality rule quoted in the closed testing section.
  • App testing requirements for new personal developer accounts - Google Play Console Help. The 12 tester, 14 continuous day requirement, the seven day or less review estimate, and the two documented reasons for required continued testing.
  • svgomg-twa sample - GoogleChromeLabs. The adb logcat filter and the Statement failure matching fingerprint message used in step 3.
  • Bubblewrap CLI reference - GoogleChromeLabs. The fingerprint subcommands and the scope of bubblewrap validate.
  • Next steps for your PWA on Google Play - PWABuilder. The instruction to update assetlinks.json after Google Play re-signs the app.
  • OnTesters campaign records. First-hand observations in this article come from our own records across more than 1,500 closed-testing campaigns on physical devices running Android 11 to 15 in more than 80 countries. They are our platform records, not Google statistics, and no success rate is implied by them.
Closed testing

Need testers who stay opted in for the full 14 days?

OnTesters provides 12 real testers on physical Android devices, with replacements for dropouts and free retesting if Google asks for more testing. From $14.99.

We do not sell or guarantee production access

See pricing
Afrin Asha — Founder, OnTesters

Afrin Asha

Founder, OnTesters

Android developer and QA specialist. Built OnTesters after working through Google Play’s 12-tester closed testing requirement on real devices. Read the full story

More on Publishing

The other guides in this cluster.

All Publishing guides

All 65 guides in the Google Play closed testing library.

Get 12 testers for Google Play closed testingMoney-back guaranteeMatched in 6-24 hours