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, flippingencryption: trueagainst a regularapiURLfails the handshake atsetup()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:
- A dedicated
apiURL. E2EE traffic is served from a different host than the regular API (typically*-e2ee-api.incodesmile.com). Pointingencryption: trueat your standardapiURLfails the handshake. - 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:
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.
// 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:
// 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 omitwasm(the SDK provisions the binary transport with CDN defaults automatically) or pass aWasmConfigobject alongsideencryption: true. See WASM Configuration. - 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 whichmgf1scheme is used. Subsequentsetup()calls that would change either value throw. Callreset()first if you genuinely need to switch. - Required by on-device face capture. Selfie / Authentication's
onDeviceFaceResultsSubmissionEnabled: trueonly works against the E2EE-provisioned host. See 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
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() — the
encryptionoption's full reference entry. - WASM Configuration — the binary transport E2EE rides on.
- On-Device Face Capture — depends on E2EE being configured.
- Getting Started — the basic boot sequence (
setup→createSession→initializeSession) you build on top of.
Updated about 14 hours ago
