# On-device Face Capture Guide

This guide is specific to Web SDK 2.0. If you are still using 1.x, you can find documentation [here](https://developer.incode.com/docs/web-sdk-reference). Contact your Incode Representative for upgrade information and check if you are a candidate for this upgrade.

Full rollout to all clients still TBD.

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.

## 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.

1. **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 `apiURL` Incode provisioned for your account
   - The `/0` suffix on `apiURL` **or**`customHeaders: { 'x-api-key': '<api key>' }` so the tenant can be identified

See [End-to-End Encryption](https://developer.incode.com/docs/web-sdk-2-e2ee) for the provisioning dance.
2. **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.
3. **The**`onDeviceSelfie` **WASM pipeline.** Optional preload: pass `wasm: { pipelines: ['selfie', 'onDeviceSelfie'] }` to `setup()` 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.
4. **The config flag.** Set `onDeviceFaceResultsSubmissionEnabled: true` on the Selfie or Authentication config:

```ts
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

HTML

```html
<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

```tsx
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} />;
}
```

## 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: true` is 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](https://developer.incode.com/docs/web-sdk-2-module-selfie-1#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](https://developer.incode.com/docs/web-sdk-2-e2ee#constraints-to-know-about).
