Docs
grovs.io Start for free

iOS setup checklist

Verify your iOS integration end to end: dashboard values, Associated Domains, URL schemes, SDK logs, and every deep link flow before you ship.

Check an iOS integration from the dashboard to the device before release. Each step has a check and an expected result. If a step fails, stop: later steps depend on it.

Run every check twice, for test and production. Each environment has its own link domain and URL scheme, and most broken setups configure only one.

1. Dashboard values match the app

Compare your project in the Grovs dashboard against Xcode:

Dashboard fieldReal valueCheck
Bundle IdentifierTarget → General → Bundle IdentifierExact match, including case. See How to Get the Bundle Identifier.
Apple App Prefix (Team ID)Target → Signing & Capabilities → Team, or developer.apple.com → MembershipExact match. See How to Get the Apple App Prefix.
Link domainsDashboard project setup, one per environmentNote both for step 2.
URL schemesDashboard project setup, one per environmentNote both for step 3.

Apple's association file identifies your app as TEAMID.bundle.identifier. A typo in either part breaks Universal Links for every user, and shows only once a build with the wrong value is installed.

2. Associated Domains are set up

Under Signing & Capabilities → Associated Domains, add an applinks: entry for each link domain:

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

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

Check the association file. For each domain, open:

https://<your-link-domain>/.well-known/apple-app-site-association

It must return JSON listing your TEAMID.bundle.identifier under applinks. If it doesn't, or returns a 404, see Check the file Grovs serves.

Check the entitlement is in the build. A build signed with a provisioning profile created before you added the capability silently lacks it:

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

The output should list the same applinks: entries.

iOS fetches the association file through Apple's CDN when the app is installed, and the CDN can lag behind a corrected file. After fixing step 1 or 2, delete the app and reinstall it.

Check the association on the device

These tools live under Settings → Developer, which appears once the device has been used with Xcode. On iOS 16 and later, turn on Settings → Privacy & Security → Developer Mode first.

  • Diagnostics. Settings → Developer → Universal Links → Diagnostics: paste a Grovs link and tap Diagnose. Run it for both link domains.
  • Skip the CDN. Add ?mode=developer to each applinks: entry in a development build and turn on Associated Domains Development. The device then fetches the file from your domain directly. Turn it off before testing a release candidate.
  • System logs. Filter Console.app on the swcd process, or read swcutil_show.txt from a sysdiagnose.

Read what iOS decided explains each Diagnostics result and swcutil_show.txt field. On a Mac, sudo swcutil show gives the same state for a Mac Catalyst or Designed for iPad build.

3. URL schemes are registered

Under Info → URL Types, add one entry per scheme from the dashboard, with Role set to Editor. See How to Add a New URL Scheme.

Check from the SDK. After authenticating, the SDK compares the current environment's scheme with your Info.plist. With info logging on (step 5) you see one of:

URL Scheme properly configured.
There's a mismatch between the URL Scheme in the project and the one from the dashboard, deeplinking won't function properly!

A mismatch means the scheme for your useTestEnvironment setting is missing from URL Types, or its role isn't Editor.

Check from the device. Type yourscheme://open into Safari's address bar. Safari should ask to open your app. If not, the scheme isn't in the installed build.

4. Delegate calls are forwarded

The SDK only sees Universal Links and URL schemes if you forward the system callbacks:

  • SceneDelegate apps: scene(_:openURLContexts:), scene(_:continue:) and scene(_:willConnectTo:options:).
  • AppDelegate-only apps: application(_:continue:restorationHandler:) and application(_:open:options:).

Copy them from Delegate setup. willConnectTo is the one people forget. It handles a link that launches the app; without it, links work only while the app is running.

Pass the delegate to configure, or set Grovs.delegate after it. Keep a strong reference: the SDK holds the delegate weakly, so one created inline is released and never called.

SwiftUI apps on the App lifecycle need a UIApplicationDelegateAdaptor, plus a scene delegate through application(_:configurationForConnecting:options:). onOpenURL alone doesn't reach the SDK.

5. The SDK authenticates

Turn on info logging before configure and run a debug build:

Swift
Grovs.setDebug(level: .info)
Grovs.configure(APIKey: "your-api-key", useTestEnvironment: true, delegate: self) { success in
    print("Grovs configured: \(success)")
}

Expected in the Xcode console, then Grovs configured: true:

Test environment enabled.          // only when useTestEnvironment is true
Authenticate
URL Scheme properly configured.
Console lineMeaning
[authentication_failed] Authentication failed. Check API key and bundle ID. then Can not initialize the SDK, the Bundle Key combo is invalidThe key isn't for a project with this bundle ID, it's the other environment's key, or there's no network. Check the key, step 1, then connectivity. The SDK retries when the app becomes active.
[authentication_failed] URI schemes are not configured. SDK won't work.The app has no URL types. Go to step 3.
baseURL is not a valid URL: ... The SDK was not configured.A self-hosted baseURL without a host. configure reports false and stops.
The SDK is configured but disabled — skipping authentication.You passed enabled: false. Expected when gating on consent; see Privacy & Consent.

Release builds redact log content to <private> in Console.app.

Match useTestEnvironment to the API key: test key → true, production key → false. A link from the other environment resolves to nothing. See Test vs production environment mismatch.

6. Test each flow

Create a test link in the dashboard with a payload you can recognize, such as {"screen": "checklist", "id": "42"}. Test on a physical device: Universal Links don't work in the Simulator, only the URL scheme flow does.

For Universal Links and deferred links, grovsReceivedPayloadFromDeeplink should fire on the main thread with link set to the URL and payload holding your values.

FlowHow to testIf it fails
Universal Link, app runningPaste the link into Notes and tap it.Recheck step 2. Never test from Safari's address bar: a typed or pasted URL never opens an app.
Universal Link, cold startForce-quit the app, then tap the link again.willConnectTo (or the AppDelegate equivalent) isn't forwarded. Go to step 4.
URL schemeOpen the scheme from Notes, or run xcrun simctl openurl booted "yourscheme://" in the Simulator.Recheck step 3.

The SDK sends every scheme URL to the backend, so a payload only arrives for a URL that identifies a Grovs link. Opening the bare scheme proves the registration and the open:options: / openURLContexts forwarding. Universal Links fall back to this path when a user opts out, so it must work too.

Restore a switched-off Universal Link. If someone tapped Open in Safari on the Universal Link banner, iOS opens Safari for that domain from then on. Long-press the link and choose Open in "YourApp". The setting is per device.

This flow covers users who tap a link before installing. Use a real install path, not Xcode:

  1. Delete the app.
  2. Open the link on the device in Safari. It lands on the store page, or the preview page first if enabled.
  3. Install from TestFlight or the App Store and open the app.

The delegate fires on first launch with the payload. An Xcode install skips the click-to-install path the SDK matches on. See Deferred deep linking.

If copy-to-clipboard is enabled for the link or project, iOS may show a paste prompt on first launch. Allow it. With info logging on, the Clipboard flow - lines show what the SDK found. See Clipboard-assisted deferred deep linking.

Confirm from the dashboard

Each resolved link records an open, and a fresh install records an install. Check that the link's click and open counts moved in the environment you tested. If the device got the payload but the dashboard shows nothing, you're viewing the other environment.

7. Before you ship

  • Repeat steps 2, 3 and 6 on a production build: production API key, useTestEnvironment: false, production link. TestFlight is the right place.
  • Confirm the production applinks: entry and URL scheme are in the target, not only the test ones.
  • Remove ?mode=developer and turn Associated Domains Development off, so the final check goes through Apple's CDN like your users.
  • For a custom domain, confirm its association file loads over HTTPS with a valid certificate. iOS rejects it otherwise. See Custom Domains.
  • Leave setDebug at the default .error, or remove the call. Release builds redact logs either way.

See also

Last updated 2026-10-27
Was this page helpful?