Docs
grovs.io Start for free

Deep link doesn't open the app

Debug links that open the browser instead of your app: Universal Links on iOS, App Links on Android, and native config for Flutter and React Native.

Debug a Grovs link that opens the browser or the store instead of your installed app. The cause is almost always platform link verification, not the SDK.

Quick triage

Check these first. Each row names a cause and how to confirm it.

CauseHow to confirm
The app isn't installedThat's the deferred deep linking flow, not this one.
Only one environment is configuredThe dashboard gives you two link domains and two URL schemes, one per environment. Your applinks: entries and intent filters must list both.
The build and the link are in different environmentsA useTestEnvironment = true build handles test links only. See environment mismatch.
The domain serves no association fileOpen https://<link-domain>/.well-known/apple-app-site-association or /.well-known/assetlinks.json. A 404 means the platform isn't set up in the dashboard (details).
Wrong Team ID, bundle ID, package name or SHA-256The file lists a different app ID or fingerprint than your build.
You're testing the wrong way (iOS)You typed the URL into Safari, or used the Simulator. Tap the link in Notes instead.
The app opens but your handler never runsOS config is fine; the SDK isn't getting the link. Check iOS delegate forwarding or Android lifecycle forwarding.

If links work on one platform but not the other, the SDK integration is fine. The broken platform's native config is the cause.

  1. Your app's Associated Domains entitlement lists applinks:<link-domain>.
  2. When the app is installed or updated, the device asks Apple's CDN for each domain's apple-app-site-association (AASA) file. The device doesn't fetch it from the domain.
  3. iOS checks that the CDN's copy lists your app as TEAMID.bundle.identifier.
  4. If it does, tapping a link on that domain opens your app and the SDK receives the URL.

A fix on the domain reaches a device only after the CDN refreshes and the app is reinstalled. Developer mode skips the CDN while you debug.

1. Check the file Grovs serves

Bash
curl -s https://medly.sqd.link/.well-known/apple-app-site-association

Run it for each link domain in the dashboard, test and production. A correct response:

JSON
{
  "applinks": {
    "apps": [],
    "details": [
      {
        "appID": "ABCDE12345.com.medly.app",
        "paths": ["NOT /__/auth/action/", "NOT /__/auth/handler/", "NOT /_/*", "/*"]
      }
    ]
  }
}

appID is your Apple App Prefix (Team ID), a dot, then your bundle identifier. Both must match the build exactly, including case.

A 404 returns a JSON error:

errorFix
Domain not foundThe hostname isn't a Grovs link domain. Check for typos, or your custom domain setup.
Application not found or Configuration not setThe iOS app isn't registered. Add the bundle ID and Team ID in the dashboard.
Configuration not enablediOS isn't set to Redirect to App in redirect rules.

2. Check Apple's CDN copy

Bash
curl -s https://app-site-association.cdn-apple.com/a/v1/medly.sqd.link

This is the copy devices receive. If it differs from step 1, the CDN hasn't refreshed yet. Use developer mode until it does.

3. Check the entitlement in the build

In Xcode, Signing & Capabilities → Associated Domains must list one entry per link domain:

applinks:medly.sqd.link
applinks:medly.test-sqd.link

Use the exact hostnames from your dashboard, plus any custom domain. See How to Add an Associated Domain.

The entitlement must also be in the provisioning profile. Confirm the built app carries it:

Bash
codesign -d --entitlements :- /path/to/Medly.app | grep -A3 associated-domains

The Team that signs the build must be the one whose ID is in the AASA appID.

iOS reads entitlements at install. After changing Associated Domains, the Team ID or the bundle ID, delete the app and reinstall it.

4. Test the right way

  • Tap the link from another app. Paste it into Notes or Messages and tap it.
  • Long-press the link. If the association works, the menu shows Open in "Medly". If it only offers Safari, iOS has no verified association.
  • Don't type or paste into Safari's address bar. That never opens an app, by design.
  • Don't tap a link to the domain you're on. In Safari, a link to the current page's domain stays in Safari.
  • Undo an opt-out. If anyone tapped Open in Safari on the Universal Link banner, iOS remembers it for that domain on that device. Long-press the link and choose Open in "Medly" to restore it.
  • Use a physical device. Universal Links don't work in the Simulator. Test the URL scheme path there instead: typing yoururlscheme:// into Safari should open the app.

5. Bypass Apple's CDN with developer mode

Developer mode makes the device fetch the AASA from the link domain directly, so a fix shows on the next install.

1
Add the developer mode suffix

In a development build, append ?mode=developer to each entry:

applinks:medly.sqd.link?mode=developer
applinks:medly.test-sqd.link?mode=developer

It only applies to apps signed with a development profile. Remove it before release.

2
Turn on Developer Mode

On iOS 16 and later, open Settings › Privacy & Security › Developer Mode and turn it on. The device restarts.

3
Turn on Associated Domains Development

Open Settings › Developer and turn on Associated Domains Development. Settings › Developer appears once the device has been used with Xcode.

4
Reinstall and test

Delete the app, install it again from Xcode, and tap a link in Notes.

6. Read what iOS decided

Diagnostics on the device. Open Settings › Developer › Universal Links › Diagnostics, paste a Grovs link and tap Diagnose.

ResultMeaning
Opens your app, with your TEAMID.bundle.identifierThe association is verified. Check how you test (step 4) or the SDK (step 7).
Opens in Safari, or no matching appsNo verified association. Recheck steps 1 to 3, then reinstall.
An error fetching or parsing the fileThe file isn't reachable over HTTPS with a valid certificate. Custom domains fail here most often.

Console.app. Connect the device to a Mac, open Console.app, select the device and filter on the process swcd. Reinstall the app to watch each domain's fetch and any error.

swcutil on macOS. swcutil talks to the Mac's own swcd and needs sudo.

Bash
# Download a domain's AASA the way swcd does, and show errors
sudo swcutil dl -d medly.sqd.link
 
# Stream swcd logging live
sudo swcutil watch --verbose
 
# Show swcd's stored state (Mac Catalyst or Designed for iPad builds)
sudo swcutil show

sysdiagnose. For a device, swcutil_show.txt inside a sysdiagnose holds the same state. Press both volume buttons and the side button together for about a second. After a few minutes, share it from Settings › Privacy & Security › Analytics & Improvements › Analytics Data (named sysdiagnose_...). Find your domain:

Service:    applinks
App ID:     ABCDE12345.com.medly.app
Domain:     medly.sqd.link
User Approval: true
Site/Fmwk Approval: true
FieldMeaning
Site/Fmwk Approval: trueThe file was fetched and lists your app.
Site/Fmwk Approval: falseThe fetch failed or the App ID isn't listed. An Error: line names the cause.
User Approval: falseThe user opted out on this device. Long-press the link to restore.
No entry for a domainThat applinks: entry isn't in the installed build.

If the app opens but nothing happens, the link didn't reach the SDK. Turn on info logging before configure:

Swift
Grovs.setDebug(level: .info)
Grovs.configure(APIKey: "your-api-key", useTestEnvironment: true, delegate: self)

Lines start with GROVS [INFO] in the Xcode console, under the io.grovs.sdk subsystem. Release builds redact them to <private>, so use a debug build. At startup you should see URL Scheme properly configured. If you see the mismatch error instead, add the environment's scheme under Info → URL Types.

Log in the delegate to see each link arrive:

Swift
func grovsReceivedPayloadFromDeeplink(link: String?, payload: [String: Any]?, tracking: [String: Any]?) {
    print("Grovs link: \(link ?? "nil") payload: \(payload ?? [:])")
}
 
func grovsDidEncounterError(_ error: GrovsError, message: String) {
    print("Grovs error: \(error) \(message)")
}

Both run on the main thread. Grovs.lastReceivedPayload { payload in ... } returns the last payload if you need to check later.

If the delegate never fires:

  • Forward openURLContexts, continue userActivity and willConnectTo (or the AppDelegate equivalents) exactly as in Delegate setup. Without willConnectTo, cold-start links are lost.
  • Pass the delegate to configure, or set Grovs.delegate after it. A delegate set before configure is dropped.
  • Keep a strong reference to the delegate. The SDK holds it weakly.

For an end-to-end pass, use the iOS setup checklist. See also iOS Deep Linking.

Check the file Grovs serves

Bash
curl -s https://medly.sqd.link/.well-known/assetlinks.json

A correct response lists your package name and every SHA-256 from the dashboard:

JSON
[
  {
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": {
      "namespace": "android_app",
      "package_name": "com.medly.app",
      "sha256_cert_fingerprints": ["F5:09:10:F7:..."]
    }
  }
]

A 404 returns the same error values as the iOS file. Configuration not enabled means Android isn't set to Redirect to App.

Check the package name and fingerprints

  1. The package name in the dashboard must match your applicationId exactly. See How to Get the Package Name.
  2. Add the SHA-256 of every key that signs a build you test, debug and release. See How to Get the SHA-256 Fingerprint.
  3. With Play App Signing, Google re-signs your app before distribution, so store installs carry Google's key, not your upload key. In the Play Console, open Setup → App integrity and add the SHA-256 under App signing key certificate.

If links work in debug builds but not from the Play Store, step 3 is the cause.

Check the intent filter

Your https intent filter needs android:autoVerify="true", the VIEW action, the DEFAULT and BROWSABLE categories, and both link hosts from the dashboard. Copy it from the Android Quick Start. For the URL scheme filter, see How to Add a New Intent Filter.

Verification runs at install. Reinstall the app after changing the filter or the dashboard values.

Check verification with adb

Bash
# Verification state of each domain (Android 12+)
adb shell pm get-app-links com.medly.app
 
# Re-run verification after a fix
adb shell pm verify-app-links --re-verify com.medly.app

Each link domain should show verified. On Android 12 and later, a domain that fails verification opens in the browser, not an app chooser.

Test with adb

Bash
# App Link
adb shell am start -W -a android.intent.action.VIEW -d "https://medly.sqd.link/summer-sale"
 
# URL scheme fallback
adb shell am start -W -a android.intent.action.VIEW -d "myapp://example.com/path"

If the scheme test fails too, the intent filter is missing or incomplete.

In Chrome, a link to the domain of the page you're on stays in the browser. Test from another app, such as a messaging app.

App opens but the listener never fires

The link reached your activity, but not the SDK.

  • Call Grovs.onStart(this) in your launcher activity's onStart() and Grovs.onNewIntent(intent, this) in onNewIntent(). See Forward lifecycle events.
  • Register a listener with Grovs.setOnDeeplinkReceivedListener. See Deep Linking.

Flutter, React Native and Unity

The native layer opens the app. Dart, JS and C# only receive the payload afterwards, so the iOS and Android sections apply unchanged.

  • Flutter: check the Info.plist keys, Associated Domains, URL schemes and Android intent filters in the Flutter Quick Start. From plugin 3.1, FlutterDeepLinkingEnabled and flutter_deeplinking_enabled must be false, otherwise links can open Safari or push a route.
  • React Native: check the native setup in the React Native Quick Start.
  • Unity: check the URI scheme, link domains and signing fingerprints in Project Settings › Grovs. See the Unity Quick Start.
  • Expo: the config plugin writes the native config. Check that scheme and associatedDomains in app.json match the dashboard, then re-run npx expo prebuild. It needs a development build, not Expo Go.

Still stuck

Email [email protected] with:

  • Platform, OS version and SDK version
  • The link you tapped, and where you tapped it
  • The environment (test or production) and the build's useTestEnvironment value
  • The curl output for the domain's association file
  • iOS: the Diagnostics result and your applinks: entries
  • Android: the output of adb shell pm get-app-links <package>
  • SDK logs at info level, from app launch to the link tap
Last updated 2026-10-27
Was this page helpful?