Common issues integrators hit and how to resolve them. Also see the per-module pages and 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 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. |
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. |
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. 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.
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. |
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
- 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 support@incode.com with: SDK alpha version, browser + OS, the failing flow's
configurationId, and the steps to reproduce.