On-Device Face Capture
On-device face capture runs the full face-analysis pipeline (detection, positioning feedback, liveness, age estimation) inside the user's browser via WebAssembly. The captured selfie image never leaves the device — the SDK submits only the analysis results JSON to the server. Video recording is skipped on this path.
Opt-in. Default Selfie / Authentication runs server-side face analysis, where the captured frame is encrypted and uploaded for the backend pipeline to score.
When to enable it
Reach for on-device face capture when:
- Privacy or data-residency posture requires keeping biometric image bytes off Incode's infrastructure.
- Bandwidth-sensitive environments (slow / unstable networks) make image upload unreliable but result JSON is small enough to send.
- Regulatory requirements bar the raw image from leaving the client.
If none of those apply, the server-side default is the recommended path — the backend pipeline has access to additional anti-spoof signals that aren't replicated on-device.
Requirements
Four things must line up. Missing any one of them and the path fails — usually at submission time, when the server rejects the request.
- E2EE configured at
setup()— the on-device face-results endpoint (/omni/add/face-results) is only served on the E2EE-provisioned host. You need both:encryption: true(or{ mgf1: 'sha256' }, whichever your environment expects)- The dedicated E2EE
apiURLIncode provisioned for your account - The
/0suffix onapiURLorcustomHeaders: { 'x-api-key': '<api key>' }so the tenant can be identified See End-to-End Encryption for the provisioning dance.
- WASM binary transport — implied by E2EE, but worth stating. The on-device pipeline runs inside the same WASM binary as the encrypted transport.
setup({ wasm: false, ... })is incompatible. - The
onDeviceSelfieWASM pipeline. Optional preload: passwasm: { pipelines: ['selfie', 'onDeviceSelfie'] }tosetup()so the models are warm before the user reaches the camera step. If you omit'onDeviceSelfie', the SDK lazy-loads it on first camera open — fine for slow flows, noticeable for fast ones. - The config flag. Set
onDeviceFaceResultsSubmissionEnabled: trueon the Selfie or Authentication config:const selfie = document.querySelector('incode-selfie'); selfie.config = { onDeviceFaceResultsSubmissionEnabled: true, // ...other SelfieConfig fields };
Full integration example
Use the standard <incode-selfie> web component and set onDeviceFaceResultsSubmissionEnabled: true on its config. The component drives capture and submission; you only need to wire onFinish / onError.
Vanilla HTML / TypeScript
<incode-selfie></incode-selfie>
<script type="module">
import { setup } from '@incodetech/core';
import { initializeSession } from '@incodetech/core/session';
import '@incodetech/web/selfie';
import '@incodetech/web/selfie/styles.css';
// 1. Configure the SDK with E2EE + the on-device pipeline pre-warmed.
await setup({
apiURL: 'https://<your-incode-e2ee-host>/0', // E2EE host + /0 for JWT-derived API key
encryption: { mgf1: 'sha256' }, // pin SHA-256 when your E2EE env is provisioned for it
wasm: { pipelines: ['selfie', 'onDeviceSelfie'] },
});
// 2. Activate your session token.
await initializeSession({ token: 'your-session-token' });
// 3. Configure <incode-selfie> with the on-device flag enabled.
const selfie = document.querySelector('incode-selfie');
selfie.config = {
showTutorial: true,
showPreview: false,
assistedOnboarding: false,
enableFaceRecording: false, // ignored on the on-device path anyway
autoCaptureTimeout: 10,
captureAttempts: 3,
validateLenses: true,
validateFaceMask: true,
validateHeadCover: true,
validateClosedEyes: true,
validateBrightness: true,
deepsightLiveness: 'SINGLE_FRAME',
onDeviceFaceResultsSubmissionEnabled: true, // opt in
};
selfie.onFinish = () => console.log('On-device face capture complete');
selfie.onError = (err) => console.error('Selfie error:', err);
</script>React
React 18 or earlier: add the one-time JSX augmentation from Framework Integration → TypeScript: JSX support for
incode-*tags so TypeScript recognizes the Incode tags. The example below uses a ref +useEffectto assignconfig/onFinish/onErrorbecause JSX attributes only accept strings. React 19+ doesn't need this — see Framework Integration → React 19+ shortcut for the simpler inline-prop form.
import { useEffect, useRef } from 'react';
import { setup } from '@incodetech/core';
import { initializeSession } from '@incodetech/core/session';
import type { SelfieConfig } from '@incodetech/core/selfie';
import '@incodetech/web/selfie';
import '@incodetech/web/selfie/styles.css';
type SelfieElement = HTMLElement & {
config?: SelfieConfig;
onFinish: () => void;
onError: (error: string) => void;
};
await setup({
apiURL: 'https://<your-incode-e2ee-host>/0',
encryption: { mgf1: 'sha256' }, // pin SHA-256 when your E2EE env is provisioned for it
wasm: { pipelines: ['selfie', 'onDeviceSelfie'] },
});
await initializeSession({ token: 'your-session-token' });
export function OnDeviceSelfie() {
const ref = useRef<SelfieElement>(null);
useEffect(() => {
const el = ref.current;
if (!el) return;
el.config = {
showTutorial: true,
showPreview: false,
assistedOnboarding: false,
enableFaceRecording: false,
autoCaptureTimeout: 10,
captureAttempts: 3,
validateLenses: true,
validateFaceMask: true,
validateHeadCover: true,
validateClosedEyes: true,
validateBrightness: true,
deepsightLiveness: 'SINGLE_FRAME',
onDeviceFaceResultsSubmissionEnabled: true,
};
el.onFinish = () => console.log('On-device face capture complete');
el.onError = (err) => console.error('Selfie error:', err);
}, []);
return <incode-selfie ref={ref} />;
}Inside
<incode-flow>? When<incode-selfie>runs as part of<incode-flow>(orcreateOrchestratedFlowManager), the orchestrator passes the dashboard-configuredFlowModuleConfig['SELFIE']to the component automatically — you do not setselfie.configyourself. EnableonDeviceFaceResultsSubmissionEnabledon the SELFIE module configuration in the Incode dashboard instead, and the flag flows through.
Going fully custom UI, or using Authentication? The same
onDeviceFaceResultsSubmissionEnabled: trueflag works on the headless managers. See Headless Mode.
What changes on this path
| Aspect | Default (server-side) | On-device |
|---|---|---|
| Image bytes leave the device | Yes — encrypted upload | No — only analysis results JSON |
Video recording (enableFaceRecording) | Honored — local recording is assembled and uploaded if configured | Skipped — the recording service is never created |
| Required WASM pipelines | selfie | selfie + onDeviceSelfie |
| Setup complexity | Standard setup({ apiURL, token }) | E2EE provisioning + dedicated apiURL + /0 or x-api-key + the onDeviceSelfie pipe |
Caveats
- Video recording is skipped. The recording service is never created on this path, so
enableFaceRecording: trueis silently ignored. If you need a local video artifact, the on-device path isn't compatible — use the server-side default or the capture-only flow. - Locked at boot with E2EE. You cannot flip between on-device and server-side at runtime in the same SDK boot — see End-to-End Encryption → Constraints to know about.
See also
- End-to-End Encryption — required prerequisite.
- Module: Selfie — config catalogue and full Selfie reference.
- Module: Authentication — same flag, returning-user re-auth.
- WASM Configuration — the
onDeviceSelfiepipeline entry. - Headless Mode — driving the Selfie / Authentication managers from custom UI.
Updated about 14 hours ago
