# Testing & Troubleshooting

Testing deferred linking has platform constraints. The complete deferred flow requires the store redirect chain, that is the App Store or Google Play.

:::caution[Use a native app build]
Expo Go cannot use your app's Associated Domains, intent filters, custom scheme,
or signing identity, so it cannot validate the complete Detour flow. Use a
development build for Universal/App Links and a real device for end-to-end
deferred-link testing.
:::

## Testing iOS

If your app is not yet released:

1. Remove any existing install from the device so the next open counts as a first launch.
2. Open a Detour link in a mobile browser.
3. Expect the App Store redirect to fail. With no public listing yet, the store has no listing to open, which does not affect the test.
4. Install your development build.
5. Launch the app and confirm the link resolves within the matching window (currently 15 minutes).

## Testing Android

For non-production builds, the install referrer often does not work in store test tracks, so deterministic matching can be unavailable.
You can still verify the probabilistic flow:

1. Uninstall the app from the device.
2. Open a Detour link in a mobile browser.
3. Expect the Play Store redirect to fail when the production listing is unavailable.
4. Install your development build.
5. Launch the app and confirm the link resolves within the matching window (currently 15 minutes).

For local install referrer testing workarounds, see
[How to test the Play Store Install Referrer API](https://medium.com/@madicdjordje/how-to-test-the-play-store-install-referrer-api-78a63d59945b).

:::note[Missing `click_id`]
If you encounter click_id missing in your build see limitations section [Play Store referrer missing](https://detour.swmansion.com/docs/platform/architecture/architecture-limitations#play-store-referrer-missing)
:::

## Testing Android App Links

Whether an installed app opens a Detour link depends on the Dashboard SHA-256 matching the key that signed **the build on your device**:

| Installation method                                     | SHA-256 to configure                        | Where to find it                                                            |
| :-------------------------------------------------------- | :--------------------------------------------- | :------------------------------------------------------------------------------ |
| Google Play, any track                                  | Play App Signing key                        | Play Console → **Protected with Play** → **Manage Play app signing** → *App signing key certificate* |
| `eas build`, any profile, including `development`       | Keystore in your EAS account                | `eas credentials -p android` → profile → **Keystore** → `SHA256 Fingerprint`   |
| `npx expo run:android` / `npx react-native run-android` | Project's `android/app/debug.keystore`      | `keytool -list -v -keystore android/app/debug.keystore -storepass android`, or `./gradlew signingReport` |

To check any build directly: `apksigner verify --print-certs app-debug.apk`. The complete procedure is described in [Finding your Android SHA-256 fingerprint](https://detour.swmansion.com/docs/platform/fundamentals/universal-app-links#finding-your-android-sha-256-fingerprint).

## Common issues

<details id="no-match-found">
<summary>No match found (`404`)</summary>

Expected when no deferred link matches, for example on a normal first launch. The SDK treats it as benign: it logs `🔗[Detour:WARNING] No matching deferred link found` and resolves to `null` instead of raising an error.

If you did expect a match, check that:

- the click is not older than the matching window
- the click was recorded in the Dashboard logs
- for the deterministic Android flow, `click_id` is present in the referrer

</details>

<details id="too-many-requests">
<summary>`429 Too Many Requests`</summary>

Add retry/backoff, and avoid heavy testing with one key across many devices.

</details>

<details id="ios-clipboard-prompt-not-appearing">
<summary>iOS clipboard prompt not appearing</summary>

- Check that `shouldUseClipboard` is `true`.
- Verify the copy-link flow is enabled in the Dashboard.

</details>

<details id="universal-link-opens-the-browser">
<summary>iOS Universal Link opens the browser instead of the app during development</summary>

Before your domain and App Store listing have fully propagated, iOS resolves the `apple-app-site-association` file through Apple's CDN, which may not have it yet.

Append `?mode=developer` to the matching entry in your iOS `associatedDomains` (e.g. `applinks:yourorg.godetour.link?mode=developer`) so the device fetches the association file directly from your server, bypassing the CDN. This also requires **Developer Mode** on the test device (Settings → Privacy & Security → Developer Mode).

Remove the `?mode=developer` suffix before releasing the app.

</details>

<details id="click-limit-error">
<summary>Universal/App link not delivered, `[Detour:CLICK_LIMIT_ERROR]` in logs</summary>

Your app exceeded its click limit for the current period. Check your plan's limits in [Billing & Payments](https://detour.swmansion.com/docs/platform/fundamentals/billing).

The dedicated `CLICK_LIMIT_ERROR` is emitted only for Universal/App links. Deferred and short links share the same plan limits, but over the limit they fail to resolve (they return `null`, with no dedicated error), and custom scheme links are not limit-checked at all.

A temporary network or backend error never blocks a link, because the check fails open.

</details>

<details id="link-processed-twice">
<summary>Link processed twice</summary>

- Confirm you call `clearLink()` after navigation.
- Confirm exactly one `DetourProvider` instance is mounted.

</details>

<details id="link-not-processed-on-expo-web">
<summary>Link is not processed on Expo Web</summary>

The web runtime is intentionally unsupported: on web the provider returns a no-op context and skips SDK initialization.

</details>

## Bare React Native CLI build issues

These apply only to bare React Native CLI projects that added Expo Modules with `install-expo-modules` (see [Bare React Native projects](https://detour.swmansion.com/docs/sdk/react-native/sdk-installation#bare-react-native-projects)). They are bundler/environment issues, not SDK bugs. The SDK itself is plain JavaScript.

<details id="expo-modules-not-linked">
<summary>iOS crashes at startup with `TypeError: Cannot read property '…' of undefined`</summary>

**Cause** — Expo Modules native code is not linked.

**Fix** — run `npx install-expo-modules@latest`, then reinstall CocoaPods.

</details>

<details id="serializer-did-not-return-expected-format">
<summary>Android release build fails with `Serializer did not return expected format`</summary>

The full message is `Unexpected token 'v', "var __BUND"... is not valid JSON`.

**Cause** — `metro.config.js` uses `@react-native/metro-config` while the build bundles with Expo CLI (`export:embed`).

**Fix** — make `metro.config.js` extend `expo/metro-config`. See [Expo Modules setup](https://detour.swmansion.com/docs/sdk/react-native/sdk-installation#expo-modules-setup-react-native-cli).

</details>

<details id="node-env-not-specified">
<summary>`The NODE_ENV environment variable is required but was not specified`</summary>

**Cause** — `export:embed` ran without an environment set.

**Fix** — set `NODE_ENV=production` for the release build.

</details>

<details id="clifile-is-not-a-file">
<summary>`cliFile … is not a file … it was a directory`</summary>

**Cause** — `@expo/cli` is not hoisted to the top-level `node_modules`.

**Fix** — install it at the project root (`npm install @expo/cli@<version>`), or do a clean reinstall.

</details>

<details id="node-not-found">
<summary>Bundling fails because `node` is not found</summary>

**Cause** — `node` is not on the PATH of the Gradle process. Common with `nvm`.

**Fix** — make sure `node` is on PATH for the build, then restart the Gradle daemon with `./gradlew --stop`.

</details>

## Debugging

The SDK runs entirely in JavaScript, so its output goes wherever you already
read `console.log`: the Metro terminal, React Native DevTools, or your IDE's log
panel. Filter on `Detour`.

Every message carries a prefix that says what happened. Logging is
unconditional, and there is no level to raise:

| Prefix | What it means |
| --- | --- |
| `🔗[Detour]` | Normal flow worth seeing: an infrastructure URL was ignored, a short link did not resolve and the original URL was used, no storage implementation was provided. |
| `🔗[Detour:WARNING]` | No matching deferred link was found. Expected on a second test with the same link on the same device. |
| `🔗[Detour:ERROR]` | Link processing failed. |
| `🔗[Detour:RUNTIME_ERROR]` | `useDetourContext()` was called outside a `DetourProvider`. |
| `🔗[Detour:NETWORK_ERROR]` | The deferred-link request failed. Check the API key and connectivity. |
| `🔗[Detour:CLICK_LIMIT_ERROR]` | The click was over your plan's limit. See [Click limits](https://detour.swmansion.com/docs/sdk/react-native/sdk-usage#click-limits). Carries `clicksInPeriod` and `effectiveLimit`. |
| `🔗[Detour:ANALYTICS_ERROR]` | An event or retention call failed, or analytics were disabled by a storage failure or by a second mounted provider. |
| `🔗[Detour:ANALYTICS_WARNING]` | A `DetourAnalytics` method ran with no provider mounted. The event was dropped. |
| `🔗[Detour:WEB_UNSUPPORTED]` | Running on Expo Web, where the provider is a no-op. |

For integration patterns that are easy to test end-to-end, use [Examples](https://detour.swmansion.com/docs/sdk/react-native/examples).