# End-to-End Encryption

End-to-end encryption (E2EE) adds a second layer of encryption on top of HTTPS so that any intermediary that terminates TLS — corporate proxy, CDN, transparent firewall, attacker MITM with a trusted root — cannot read PII payloads (selfie images, ID photos, identity fields). The SDK exposes it as a single opt-in flag on `setup()`.

> ⚠️ **Not a self-serve flag — talk to your Incode account team first.** E2EE must be provisioned for your account on the backend. Until it is, flipping `encryption: true` against a regular `apiURL` fails the handshake at `setup()` and the SDK throws.

## What you'll get from your Incode account team

When E2EE is enabled for your account, your account team will provide:

1. **A dedicated `apiURL`.** E2EE traffic is served from a different host than the regular API (typically `*-e2ee-api.incodesmile.com`). Pointing `encryption: true` at your standard `apiURL` fails the handshake.
2. **The MGF1 scheme** the environment expects — `'sha1'` (default; matches most existing Incode environments) or `'sha256'` (some environments are explicitly provisioned for it).

Confirm both values before you start. They are dictated by the environment, not by you.

## Minimum integration

Two lines of `setup()` flip E2EE on:

```ts
import { setup } from '@incodetech/core';

await setup({
  apiURL: 'https://<your-incode-e2ee-host>/0', // see "API key transmission" below
  encryption: true,
});
```

That's the entire client-side change. No per-module config, no extra capability registration. The SDK runs the encrypted-transport handshake (`GET /e2ee/key/v2` + `POST /e2ee/key`) as part of `setup()` itself, before any per-session call.

## API key transmission

E2EE requests still need to identify which Incode tenant they belong to. You must convey the API key in one of two ways:

| Form                                                    | What it does                                                                       | Use when                                                                                            |
| ------------------------------------------------------- | ---------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| `apiURL: 'https://<e2ee-host>/0'`                       | The trailing `/0` instructs the server to derive the API key from the session JWT. | A session token is in play (the common case once `setup({ token })` / `initializeSession` has run). |
| `customHeaders: { 'x-api-key': '<api key>' }` (no `/0`) | Server reads the API key from the explicit header.                                 | Sessionless or pre-session setup, or any flow that supplies the API key in a header.                |

Pick one. Without either, post-handshake requests cannot identify the tenant and fail.

```ts
// Path-based (recommended when you have a session token)
await setup({
  apiURL: 'https://<your-incode-e2ee-host>/0',
  encryption: true,
});

// Header-based (sessionless or pre-session)
await setup({
  apiURL: 'https://<your-incode-e2ee-host>',
  encryption: true,
  customHeaders: { 'x-api-key': process.env.INCODE_API_KEY },
});
```

The handshake URLs (`/e2ee/key/v2` and `/e2ee/key`) are unaffected by the choice above — they're hard-coded by the SDK's binary transport. Only per-request business endpoints depend on the API-key form.

## MGF1 scheme

`encryption` accepts a boolean shorthand or an object form with `mgf1`:

```ts
// Default — MGF1 = SHA-1 (matches most existing Incode environments)
await setup({
  apiURL: 'https://<your-incode-e2ee-host>/0',
  encryption: true,
});

// Pin SHA-256 — only when your environment is provisioned for it
await setup({
  apiURL: 'https://<your-incode-e2ee-host>/0',
  encryption: { mgf1: 'sha256' },
});
```

`encryption: true` is equivalent to `encryption: {}`. Both enable encryption with the SHA-1 default.

The `mgf1` value must match what the environment expects. A mismatch lets the handshake succeed but causes per-request crypto failures — confirm the value with your account team.

## Constraints to know about

- **Requires the binary (WASM) transport.** `setup({ wasm: false, encryption: true })` throws. Either omit `wasm` (the SDK provisions the binary transport with CDN defaults automatically) or pass a `WasmConfig` object alongside `encryption: true`. See [WASM Configuration](doc:web-sdk-2-wasm).
- **Independent of `token`.** The handshake runs before any session-level call, so encryption can be set up before authentication.
- **Locked at boot.** The first `setup()` call decides whether encryption is on and which `mgf1` scheme is used. Subsequent `setup()` calls that would change either value throw. Call `reset()` first if you genuinely need to switch.
- **Required by on-device face capture.** Selfie / Authentication's `onDeviceFaceResultsSubmissionEnabled: true` only works against the E2EE-provisioned host. See [On-Device Face Capture](doc:web-sdk-2-on-device-face-capture).

## Handshake failure modes

If the handshake fails, `setup()` rejects with a descriptive error. There is no built-in retry — catch the error and re-call `setup()` (after `reset()` if needed) when your environment is known-flaky.

| Symptom                                                     | Likely cause                                  | Fix                                                                                                       |
| ----------------------------------------------------------- | --------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `setup()` rejects with a handshake-failure error            | `apiURL` unreachable                          | Verify network / DNS, retry.                                                                              |
| Same, on a known-good network                               | Environment not provisioned for E2EE          | Reach out to your Incode account team. Confirm you're using the E2EE host they provided.                  |
| Handshake succeeds but per-request crypto fails             | `mgf1` mismatch with what the env expects     | Confirm the expected scheme with your contact, pass `encryption: { mgf1: 'sha256' }` (or `'sha1'`).       |
| Post-handshake requests fail with tenant-not-found          | Missing API key                               | Either append `/0` to `apiURL` or pass `customHeaders: { 'x-api-key': ... }`. See "API key transmission". |
| `setup({ encryption: ... })` throws on a re-call            | Encryption is locked at boot                  | Call `reset()` first.                                                                                     |
| `setup({ wasm: false, encryption: true })` throws            | Encryption requires the WASM binary transport | Omit `wasm: false`, or pass a `WasmConfig` object alongside `encryption: true`.                           |

## Full example: self-hosted WASM + E2EE

```ts
await setup({
  apiURL: 'https://<your-incode-e2ee-host>/0',
  encryption: { mgf1: 'sha256' }, // whichever your env expects
  wasm: {
    wasmPath: '/wasm/webLib.wasm',
    glueCodePath: '/wasm/webLib.js',
    modelsBasePath: '/wasm/models',
  },
});
```

## See also

- [API Reference → setup()](doc:web-sdk-2-reference#setup) — the `encryption` option's full reference entry.
- [WASM Configuration](doc:web-sdk-2-wasm) — the binary transport E2EE rides on.
- [On-Device Face Capture](doc:web-sdk-2-on-device-face-capture) — depends on E2EE being configured.
- [Getting Started](doc:web-sdk-2-getting-started) — the basic boot sequence (`setup` → `createSession` → `initializeSession`) you build on top of.
