# Troubleshooting

<WebSDK2 />

Common issues integrators hit and how to resolve them. Also see the per-module pages and [Getting Started](doc:web-sdk-2-getting-started) for setup-level questions.

## Camera issues

| Issue                                 | Solution                                                                                                                                                                                                                            |
| ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Black screen / no camera preview      | Use HTTPS (or `localhost` during development) — `getUserMedia` is gated behind a secure context.                                                                                                                                    |
| Permission denied                     | User must grant camera access in the browser. Surface the SDK's `permissions.denied` state and link them to browser-specific instructions.                                                                                          |
| Camera in use / `NotReadableError`    | Another tab or app holds the camera. The SDK can't release a camera held by another process; instruct the user to close other camera consumers.                                                                                     |
| Camera freezes when remounting        | Tear down the previous module before mounting a new camera-using module. Call `manager.stop()` on the old manager / unmount the old element.                                                                                        |
| iOS Safari: video doesn't play inline | The SDK's own `<video>` elements are tagged `playsinline`/`muted`/`autoPlay`. If you build a custom UI in headless mode, replicate those attributes — without `playsinline`, iOS opens video full-screen and breaks face detection. |

## WASM issues

| Issue                                        | Solution                                                                                                                                                                                                             |
| -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| WASM not loading                             | Confirm `setup({ wasm: ... })` actually ran. With CDN defaults, paths are optional; with `setup({ wasm: false })` (or omitted), WASM only loads lazily when a selfie or ID-capture step starts.                      |
| WASM 404                                     | When self-hosting, verify `wasmPath`, `glueCodePath`, and `modelsBasePath` resolve. The defaults assume `models/` lives next to the WASM binary.                                                                     |
| `CompileError: WebAssembly.compile()`        | Server is returning the WASM file with the wrong MIME type. Configure your host to return `Content-Type: application/wasm` for `.wasm` files (Nginx: add to `mime.types`; Apache: `AddType application/wasm .wasm`). |
| `RangeError`, `Failed to load module script` | Browser is too old or doesn't support WASM SIMD. Check browser support; fall back to non-SIMD by setting `setup({ wasm: { useSimd: false } })`.                                                                      |
| Face/document detection never triggers       | Model files are missing. Check the network tab for failing requests under `modelsBasePath`.                                                                                                                          |

See [WASM Configuration](doc:web-sdk-2-wasm) for the full surface.

## Session issues

| Issue                                                        | Solution                                                                                                                                                                                                                                                                                                                   |
| ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `"SDK not configured. Call setup({ apiURL: '...' }) first."` | Call `setup({ apiURL })` before `createSession()` or any module manager method.                                                                                                                                                                                                                                            |
| `"Token is required"`                                        | Provide `token` (from `createSession()`) on `<incode-flow>`'s config, or use the self-loading variant with `apiKey` + `configurationId` (prototyping only).                                                                                                                                                                |
| Token expired mid-flow                                       | Tokens have a TTL set per-tenant in the Dashboard. Long verification sessions can outlive the token. Either bump the TTL in the Dashboard or restart the flow with a fresh session.                                                                                                                                        |
| Invalid API key                                              | Verify the key in the Incode Dashboard under Settings → API Keys. Ensure your domain is allowed under CORS.                                                                                                                                                                                                                |
| Invalid configuration                                        | Make sure the `configurationId` exists, is active, and matches the tenant the API key belongs to.                                                                                                                                                                                                                          |
| `"No registered module found for: <KEY>"`                    | A backend-driven step doesn't have a state machine registered in `createOrchestratedFlowManager({ modules: ... })`. Log `flowState.steps` after `flowManager.load()` to see which keys the backend emitted, and register them. See [Headless Mode → Module Registration](doc:web-sdk-2-headless-mode#module-registration). |

## Iframe embedding

| Issue                                      | Solution                                                                                                                                                                                                                                                                                          |
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Camera prompt doesn't appear in iframe     | Add `allow="camera; microphone"` to the iframe element. `getUserMedia` requires explicit feature-policy delegation across iframe boundaries.                                                                                                                                                      |
| `<incode-flow-completed>` redirect blocked | The default redirect uses `window.location.href`, which fails if the iframe doesn't have `allow-top-navigation` (or `allow-top-navigation-by-user-activation`). Either grant the sandbox permission or set `disable-redirect` on `<incode-flow-completed>` and handle navigation from the parent. |
| Cookies / storage isolation                | The SDK uses `localStorage` for some state (e.g. Deepsight session). Browsers in strict-tracking-prevention mode partition third-party iframe storage; if the iframe is cross-origin, plan for state to be scoped per-iframe and not shared with the host.                                        |

## Mobile Safari

| Issue                                     | Solution                                                                                                                                                                                                                             |
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Camera prompt requires a user gesture     | Don't auto-trigger `manager.requestPermission()` (or auto-mount `<incode-selfie>` / `<incode-id>`) on page load. iOS Safari blocks `getUserMedia` outside of user-gesture handlers. Wire it to a tap on a "Continue" button.         |
| Camera fails after device rotation        | Some iOS versions invalidate the existing `MediaStream` on orientation change. Listen for `orientationchange` (or use a `screen.orientation`-based hook) and call `manager.reset()` or remount the module to acquire a fresh stream. |
| Tutorial Lottie animations don't autoplay | iOS suspends `requestAnimationFrame` in background tabs and after user inactivity. Re-trigger the tutorial on focus if needed.                                                                                                       |

## Content Security Policy

If your app uses a strict CSP, the SDK needs:

| Directive                                                               | Why                                                                 |
| ----------------------------------------------------------------------- | ------------------------------------------------------------------- |
| `script-src 'wasm-unsafe-eval'` (or `'unsafe-eval'` for older browsers) | WASM compilation/instantiation needs eval-style permissions.        |
| `worker-src blob:`                                                      | Some ML model loading paths spawn workers from blob URLs.           |
| `connect-src https://*.incodesmile.com` (replace with your environment) | XHR/fetch to Incode API endpoints.                                  |
| `img-src 'self' blob: data:`                                            | Captured frames are rendered to canvas/`<img>` from blob/data URIs. |
| `media-src 'self' blob:`                                                | Camera streams.                                                     |
| `style-src 'self' 'unsafe-inline'` (or hash/nonce)                      | Some module styles inline values during render.                     |

Test against your CSP before deploying — browsers fail silently for some directives, especially WASM-related ones.

## Styling issues

| Issue                               | Solution                                                                                                                                                                                                                 |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Styles not applied                  | Import `@incodetech/web/base.css` plus a theme (`@incodetech/web/themes/light.css` or `dark.css`), plus per-module CSS like `@incodetech/web/flow/styles.css`.                                                           |
| Overrides not taking effect         | Override at `:root` with `--variable: value;`. Make sure the override loads after the SDK theme.                                                                                                                         |
| Component not visible               | Parent container needs explicit dimensions. The element has `display: block` but no intrinsic size — use `height: 100vh` (or any height) on the parent or a `style="display:block; height:100vh"` on the element itself. |
| Brand color tweak doesn't propagate | Override the `--primitive-color-brand-*` scale rather than individual component tokens — semantic and component tokens cascade from the primitives. See [Theming & Styling](doc:web-sdk-2-theming).                      |

## Build / TypeScript issues

| Issue                                                                   | Solution                                                                                                                                                                                                                                                                                                     |
| ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `Property 'incode-flow' does not exist on type 'JSX.IntrinsicElements'` | React 18 strict-checks JSX. Add the JSX augmentation from [Framework Integration → TypeScript: JSX support for `incode-*` tags](doc:web-sdk-2-framework-integration#typescript-jsx-support-for-incode--tags). React 19+ doesn't need this.                                                                   |
| `<incode-flow config={{token}}>` renders nothing on React 18            | React 18 serializes JSX props through `setAttribute`, so object/function props become `"[object Object]"`. Use the ref + `useEffect` pattern from [Framework Integration → Universal pattern](doc:web-sdk-2-framework-integration#universal-pattern-works-on-react-168). React 19+ can use the simpler form. |
| Bundle too large                                                        | Use subpath imports (`@incodetech/web/selfie`, never the package root). See [Bundle Optimization](doc:web-sdk-2-bundle-optimization).                                                                                                                                                                        |
| Tree-shaking not working                                                | Use subpath imports (`@incodetech/web/<module>`), not the package root. Verify with your bundler's size analyzer.                                                                                                                                                                                            |
| Circular import or `Cannot find module '@incodetech/infra'`             | `@incodetech/infra` is internal — never import from it directly. Use the `@incodetech/core/<subpath>` re-exports.                                                                                                                                                                                            |

See [Bundle Optimization](doc:web-sdk-2-bundle-optimization) for performance details.

## Network issues

| Issue                                                 | Solution                                                                                                                                   |
| ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| CORS errors on `/omni/start` (or other API endpoints) | Whitelist your domain in the Incode Dashboard.                                                                                             |
| API requests failing                                  | Verify the `apiURL` matches the environment your API key was issued for (demo vs. production tenants are separate).                        |
| Firewall blocking                                     | Allow `*.incodesmile.com` in your egress allow-list (and the CDN domain you point WASM paths at, if self-hosting).                         |
| Upload progress stuck at 0%                           | Older browsers don't expose upload progress on `fetch`. The SDK falls back to `XMLHttpRequest` when an `onUploadProgress` callback is set. |

## Flow-level edge cases

| Issue                                                       | Solution                                                                                                                                                                                                                                                                             |
| ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Selfie ends in `closed` state and the flow doesn't continue | `closed` is final — the manager won't transition further on its own. In orchestrated flows, call `flowManager.completeModule()`. In standalone usage, navigate the user out. See [Module: Selfie → Handling cancellation](doc:web-sdk-2-module-selfie#handling-cancellation-closed). |
| ID capture stuck on `capture.success`                       | Call `idManager.nextStep()` to advance to `frontFinished` or `processing`. The state machine waits for the explicit advance.                                                                                                                                                         |
| `expired` state — `reset()` does nothing                    | The `expired` state only responds to `RETRY_CAPTURE`. Call `idManager.retryCapture()`.                                                                                                                                                                                               |
| Mandatory consent screen shows for every flow               | The screen only renders when the upload response sets `showMandatoryConsent: true`, which is driven by `regulationType` in the session. Verify the configuration in the Dashboard.                                                                                                   |

## Getting help

1. Check the browser console for SDK errors. The SDK prefixes most warnings/errors clearly.
2. Inspect the Network tab — failing API requests usually have a meaningful error body.
3. Subscribe to flow events via `subscribeEvent` from `@incodetech/core/events` to capture the SDK's analytics stream — useful when reproducing issues for support.
4. Contact [support@incode.com](mailto:support@incode.com) with: SDK alpha version, browser + OS, the failing flow's `configurationId`, and the steps to reproduce.
