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.
| Cause | How to confirm |
|---|---|
| The app isn't installed | That's the deferred deep linking flow, not this one. |
| Only one environment is configured | The 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 environments | A useTestEnvironment = true build handles test links only. See environment mismatch. |
| The domain serves no association file | Open 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-256 | The 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 runs | OS 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.
iOS: Universal Links
How iOS verifies a link
- Your app's Associated Domains entitlement lists
applinks:<link-domain>. - 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. - iOS checks that the CDN's copy lists your app as
TEAMID.bundle.identifier. - 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
curl -s https://medly.sqd.link/.well-known/apple-app-site-associationRun it for each link domain in the dashboard, test and production. A correct response:
{
"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:
error | Fix |
|---|---|
Domain not found | The hostname isn't a Grovs link domain. Check for typos, or your custom domain setup. |
Application not found or Configuration not set | The iOS app isn't registered. Add the bundle ID and Team ID in the dashboard. |
Configuration not enabled | iOS isn't set to Redirect to App in redirect rules. |
2. Check Apple's CDN copy
curl -s https://app-site-association.cdn-apple.com/a/v1/medly.sqd.linkThis 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.linkUse 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:
codesign -d --entitlements :- /path/to/Medly.app | grep -A3 associated-domainsThe 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.
In a development build, append ?mode=developer to each entry:
applinks:medly.sqd.link?mode=developer
applinks:medly.test-sqd.link?mode=developerIt only applies to apps signed with a development profile. Remove it before release.
On iOS 16 and later, open Settings › Privacy & Security › Developer Mode and turn it on. The device restarts.
Open Settings › Developer and turn on Associated Domains Development. Settings › Developer appears once the device has been used with Xcode.
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.
| Result | Meaning |
|---|---|
Opens your app, with your TEAMID.bundle.identifier | The association is verified. Check how you test (step 4) or the SDK (step 7). |
| Opens in Safari, or no matching apps | No verified association. Recheck steps 1 to 3, then reinstall. |
| An error fetching or parsing the file | The 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.
# 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 showsysdiagnose. 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| Field | Meaning |
|---|---|
Site/Fmwk Approval: true | The file was fetched and lists your app. |
Site/Fmwk Approval: false | The fetch failed or the App ID isn't listed. An Error: line names the cause. |
User Approval: false | The user opted out on this device. Long-press the link to restore. |
| No entry for a domain | That applinks: entry isn't in the installed build. |
7. Confirm the SDK received the link
If the app opens but nothing happens, the link didn't reach the SDK. Turn on info logging before configure:
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:
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 userActivityandwillConnectTo(or the AppDelegate equivalents) exactly as in Delegate setup. WithoutwillConnectTo, cold-start links are lost. - Pass the delegate to
configure, or setGrovs.delegateafter it. A delegate set beforeconfigureis 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.
Android: App Links
Check the file Grovs serves
curl -s https://medly.sqd.link/.well-known/assetlinks.jsonA correct response lists your package name and every SHA-256 from the dashboard:
[
{
"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
- The package name in the dashboard must match your
applicationIdexactly. See How to Get the Package Name. - Add the SHA-256 of every key that signs a build you test, debug and release. See How to Get the SHA-256 Fingerprint.
- 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
# 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.appEach 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
# 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'sonStart()andGrovs.onNewIntent(intent, this)inonNewIntent(). 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.plistkeys, Associated Domains, URL schemes and Android intent filters in the Flutter Quick Start. From plugin 3.1,FlutterDeepLinkingEnabledandflutter_deeplinking_enabledmust befalse, 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
schemeandassociatedDomainsinapp.jsonmatch the dashboard, then re-runnpx 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
useTestEnvironmentvalue - The
curloutput 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