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:

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:

FormWhat it doesUse 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 omit wasm (the SDK provisions the binary transport with CDN defaults automatically) or pass a WasmConfig object alongside encryption: 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 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.

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.

SymptomLikely causeFix
setup() rejects with a handshake-failure errorapiURL unreachableVerify network / DNS, retry.
Same, on a known-good networkEnvironment not provisioned for E2EEReach out to your Incode account team. Confirm you're using the E2EE host they provided.
Handshake succeeds but per-request crypto failsmgf1 mismatch with what the env expectsConfirm the expected scheme with your contact, pass encryption: { mgf1: 'sha256' } (or 'sha1').
Post-handshake requests fail with tenant-not-foundMissing API keyEither append /0 to apiURL or pass customHeaders: { 'x-api-key': ... }. See "API key transmission".
setup({ encryption: ... }) throws on a re-callEncryption is locked at bootCall reset() first.
setup({ wasm: false, encryption: true }) throwsEncryption requires the WASM binary transportOmit 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