📘This guide is specific to Web SDK 2.0. If you are still using 1.x, you can find documentation here. Contact your Incode Representative for upgrade information and check if you are a candidate for this upgrade.
Full rollout to all clients still TBD.
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.