📘This guide is specific to Web SDK 2.0. If you are still using 1.x, you can find documentation here. We strongly recommend upgrading - contact your Incode Representative for upgrade information.
Common issues integrators hit and how to resolve them. Also see the per-module pages and Getting Started for setup-level questions.
| 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. |
| 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 for the full surface.
| 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. |
| 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. |
| 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. |
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.
| 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. |
| 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. 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. React 19+ can use the simpler form. |
| Bundle too large | Use subpath imports (@incodetech/web/selfie, never the package root). See 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 for performance details.
| 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. |
| 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. |
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. |
- Check the browser console for SDK errors. The SDK prefixes most warnings/errors clearly.
- Inspect the Network tab — failing API requests usually have a meaningful error body.
- Subscribe to flow events via
subscribeEvent from @incodetech/core/events to capture the SDK's analytics stream — useful when reproducing issues for support.
- Contact [email protected] with: SDK alpha version, browser + OS, the failing flow's
configurationId, and the steps to reproduce.