---
title: "IncodeFlow Component"
url: "https://developer.incode.com/sdk-reference/web-sdk-2-incodeflow-component/"
section: "sdk-reference"
group: "Incode Web SDK 2 Reference"
version: "v1.1"
status: "live"
---
# IncodeFlow Component

:::note
  This guide is specific to Web SDK 2.0. If you are still using 1.x, you can find documentation [here](/sdk-reference/web-sdk-reference/).  Contact your Incode Representative for upgrade information and check if you are a candidate for this upgrade. <br /><br />Full rollout to all clients still TBD.
:::

`<incode-flow>` is the complete, orchestrated identity verification experience as a standard Web Component. It runs the flow sequence configured in your Incode dashboard end-to-end.

**Important:** the recommended integration creates a session via `createSession()` (or your backend) and passes its token to `<incode-flow>`. The component also supports self-loading configuration with `configurationId` and either `apiKey` or `clientId`.

## Tag

```ts
// Side-effect import — registers the <incode-flow> custom element
import '@incodetech/web/flow';
import '@incodetech/web/flow/styles.css';
```

## Properties

Set these as JavaScript properties on the element (not as HTML attributes):

| Property   | Type                                                                                  | Required | Description                           |
| ---------- | ------------------------------------------------------------------------------------- | -------- | ------------------------------------- |
| `config`   | `FlowConfig`                                                                          | ✅        | Flow configuration object (see below) |
| `onFinish` | `(result?: FinishStatus) => void`                                                     | ✅        | Called when the flow completes        |
| `onError`  | `(error: string \| undefined, errorCode?: number, machineErrorCode?: string) => void` | ❌        | Called on fatal errors                |

### FlowConfig

`FlowConfig` has two shapes — a token-based variant (recommended) and a self-loading variant. The two are mutually exclusive.

**Token variant (recommended)**: pass a token obtained from `createSession()` (ideally created on your backend):

| Property                | Type                            | Required | Description                                                                                                                                                                                                                                           |
| ----------------------- | ------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `token`                 | `string`                        | ✅       | Session token                                                                                                                                                                                                                                         |
| `lang`                  | `string`                        | ❌       | Language code (e.g., `'en-US'`, `'es-MX'`)                                                                                                                                                                                                            |
| `enableHome`            | `boolean`                       | ❌       | Show the SDK's built-in home screen                                                                                                                                                                                                                   |
| `authHint`              | `string`                        | ❌       | QR/auth hint when re-entering a flow                                                                                                                                                                                                                  |
| `redirectToMobileUrl`   | `string`                        | ❌       | Override the destination URL for the Redirect to Mobile step. The SDK adds the session parameters to this URL.                                                                                                                                        |
| `wasmConfig`            | `WasmConfig`                    | ❌       | WASM paths (see [WASM Configuration](/sdk-reference/web-sdk-2-wasm/))                                                                                                                                                                                             |
| `injectCss` | `boolean` | ❌ | 2.3.0: load Flow and module styles as screens become visible. Default `false`. Keep base and theme CSS; omit the eager Flow stylesheet when enabled |
| `cssNonce` | `string` | ❌ | CSP nonce for styles inserted by `injectCss` |
| `spinnerConfig`         | `SpinnerConfig`                 | ❌       | Loading spinner customization                                                                                                                                                                                                                         |
| `disableDashboardTheme` | `boolean`                       | ❌       | Skip applying the dashboard's theme tokens                                                                                                                                                                                                            |
| `urlUuid`               | `string`                        | ❌       | QR anti-phishing token from the URL (mobile leg)                                                                                                                                                                                                      |
| `useCPF`                | `boolean`                       | ❌       | Enable CPF-only fields on the dashboard's existing `ID_OCR` step. The step key and configuration are preserved, `cpfOnly: true` is added, and the standard ID OCR editing, validation, and submission lifecycle remains in use. Has no effect when the dashboard flow has no `ID_OCR` step. |
| `onFlowEvent`           | `(event: FlowEvent) => void`    | ❌       | Curated flow milestones                                                                                                                                                                                                                               |
| `onModuleLoading`       | `(moduleKey: string) => void`   | ❌       | Module begins loading (lazy chunk)                                                                                                                                                                                                                    |
| `onModuleLoaded`        | `(moduleKey: string) => void`   | ❌       | Module loaded                                                                                                                                                                                                                                         |
| `onWasmWarmup`          | `(pipelines: string[]) => void` | ❌       | WASM warmup begins                                                                                                                                                                                                                                    |
| `onUrlUuidRefreshed`    | `(urlUuid: string) => void`     | ❌       | New `urlUuid` available — host should update the address bar                                                                                                                                                                                          |
| `assets`                | `AssetOverrides`                | ❌       | Per-instance branded-illustration overrides for this flow, keyed to the published allowlist. Overrides the global `setup({ uiConfig })` value for this flow instance only. See [Asset Overrides](/sdk-reference/web-sdk-2-asset-overrides/)                                    |
| `animations`            | `AnimationOverrides`            | ❌       | Per-instance Lottie-animation overrides for this flow. Same precedence as `assets`. See [Asset Overrides](/sdk-reference/web-sdk-2-asset-overrides/)                                                                                                                            |

Set `redirectToMobileUrl` to replace the dashboard URL for the Redirect to Mobile step:

```ts
flow.config = {
  token: session.token,
  redirectToMobileUrl: 'https://yourapp.com/mobile-verification',
};
```

The SDK adds the session parameters to this URL. This property has no effect when the flow does not use Redirect to Mobile.

**Self-loading variant** — convenient for prototyping; **avoid in production** because it puts the API key in the browser:

| Property                                                                                             | Type     | Required | Description                    |
| ---------------------------------------------------------------------------------------------------- | -------- | -------- | ------------------------------ |
| `apiKey` (or `clientId`)                                                                             | `string` | ✅        | Auth key for session creation  |
| `configurationId`                                                                                    | `string` | ✅        | Dashboard flow ID              |
| `externalId`, `externalCustomerId`, `customFields`, `uuid`, `urlUuid`, `interviewId`                 | various  | ❌        | Forwarded to `createSession()` |
| Plus any of the optional fields from the token variant above (`lang`, `enableHome`, callbacks, etc.) |          |          |                                |

:::note
`apiURL` **is NOT in **`FlowConfig`**.** It belongs on the global `setup()` call. The component reuses the API URL configured there.
:::

## Basic Usage

### Step 1: Create the session

Create the session via `createSession()` (typically from your backend) and configure the SDK:

```tsx
import { setup } from '@incodetech/core';
import { createSession, initializeSession } from '@incodetech/core/session';

await setup({ apiURL: 'https://demo-api.incodesmile.com' });

const session = await createSession(apiKey, {
  configurationId: 'your-config-id',
  language: 'en-US',
  externalId: 'optional-external-id',
});

await initializeSession({ token: session.token });
```

The `token` is still passed to the component itself via `flowElement.config.token` (see Step 2) — `initializeSession` activates it on the SDK's HTTP client; the component reads it from its own config. `setup({ apiURL, token })` is also supported as a one-shot convenience and delegates to `initializeSession` internally.

### Step 2: Mount the component

**HTML / vanilla TypeScript:**

```html
<incode-flow id="flow"></incode-flow>

<script type="module">
  import '@incodetech/web/themes/light.css';
  import '@incodetech/web/base.css';
  import '@incodetech/web/flow';
  import '@incodetech/web/flow/styles.css';

  const flow = document.getElementById('flow');
  flow.config = { token: session.token, lang: 'en-US', enableHome: true };
  flow.onFinish = (result) => {
    if (result?.redirectionUrl) {
      window.location.href = result.redirectionUrl;
    }
  };
  flow.onError = (error, code) => console.error('Flow error:', error, code);
</script>
```

**React:**

> **React 18 or earlier:** add a one-time JSX augmentation so TypeScript recognizes `<incode-flow>` and the other Incode tags. See [Framework Integration → TypeScript: JSX support for `incode-*` tags](/sdk-reference/web-sdk-2-framework-integration/#typescript-jsx-support-for-incode--tags). React 19+ doesn't need this.
>
> **React 19+:** you can also drop the ref ceremony entirely and pass `config`/`onFinish`/`onError` as ordinary JSX props. See [Framework Integration → React 19+ shortcut](/sdk-reference/web-sdk-2-framework-integration/#react-19-shortcut).

```tsx
import { useEffect, useRef } from 'react';
import type { FlowConfig } from '@incodetech/web/flow';
import type { FinishStatus } from '@incodetech/core/flow';
import '@incodetech/web/flow';
import '@incodetech/web/flow/styles.css';

type FlowElement = HTMLElement & {
  config: FlowConfig;
  onFinish: (result?: FinishStatus) => void;
  onError: (
    error: string | undefined,
    code?: number,
    machineErrorCode?: string,
  ) => void;
};

export function FlowMount({ token }: { token: string }) {
  const ref = useRef<FlowElement>(null);

  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    el.config = { token, lang: 'en-US', enableHome: true };
    el.onFinish = (result) => {
      if (result?.redirectionUrl) window.location.href = result.redirectionUrl;
    };
    el.onError = (error, code) => console.error('Flow error:', error, code);
  }, [token]);

  return (
    <incode-flow ref={ref} style={{ display: 'block', height: '100vh' }} />
  );
}
```

For Angular and Vue, see [Framework Integration](/sdk-reference/web-sdk-2-framework-integration/).

### Complete React Example

> **React 18 or earlier:** add the JSX augmentation from [Framework Integration → TypeScript: JSX support for `incode-*` tags](/sdk-reference/web-sdk-2-framework-integration/#typescript-jsx-support-for-incode--tags). React 19+ doesn't need it, and can also use the simpler form from [Framework Integration → React 19+ shortcut](/sdk-reference/web-sdk-2-framework-integration/#react-19-shortcut).

```tsx
import { useState, useEffect, useRef } from 'react';
import { setup } from '@incodetech/core';
import { createSession, initializeSession } from '@incodetech/core/session';
import type { FlowConfig } from '@incodetech/web/flow';
import type { FinishStatus } from '@incodetech/core/flow';
import '@incodetech/web/themes/light.css';
import '@incodetech/web/base.css';
import '@incodetech/web/flow';
import '@incodetech/web/flow/styles.css';

type FlowElement = HTMLElement & {
  config: FlowConfig;
  onFinish: (result?: FinishStatus) => void;
  onError: (
    error: string | undefined,
    code?: number,
    machineErrorCode?: string,
  ) => void;
};

function MyVerificationFlow() {
  const [sessionToken, setSessionToken] = useState<string | null>(null);
  const [error, setError] = useState<string | null>(null);
  const flowRef = useRef<FlowElement>(null);

  useEffect(() => {
    const initialize = async () => {
      try {
        await setup({ apiURL: 'https://demo-api.incodesmile.com' });
        const session = await createSession('your-api-key', {
          configurationId: 'your-config-id',
          language: 'en-US',
        });
        await initializeSession({ token: session.token });
        setSessionToken(session.token);
      } catch (err) {
        setError(
          err instanceof Error ? err.message : 'Failed to create session',
        );
      }
    };
    initialize();
  }, []);

  useEffect(() => {
    const el = flowRef.current;
    if (!el || !sessionToken) return;
    el.config = { token: sessionToken, lang: 'en-US', enableHome: true };
    el.onFinish = (result) => console.log('Flow completed:', result);
    el.onError = (error) => console.error('Flow error:', error);
  }, [sessionToken]);

  if (error) return <div>Error: {error}</div>;
  if (!sessionToken) return <div>Initializing...</div>;

  return (
    <incode-flow ref={flowRef} style={{ display: 'block', height: '100vh' }} />
  );
}
```

## Custom Start and Completion Screens

### Start screen

Set `enableHome: true` in the flow config to request the SDK launch screen. Dashboard configuration can suppress it. For a host-owned start screen, render your introduction and mount `<incode-flow>` when the user chooses to begin; omit `enableHome` or set it to `false` to avoid a second start screen.

### Completion screen

Render your own completion screen from `<incode-flow>`'s `onFinish` callback. Its optional `FinishStatus` result contains `redirectionUrl`, `action`, `scoreStatus`, `endScreenTitle`, and `endScreenText`. The end-screen copy can be `null`; provide your own fallback copy and handle an absent result. See [Finish Status](/sdk-reference/web-sdk-2-event-callbacks/#finish-status) for the full type.

Keep any session `interviewId` or Flow configuration ID your host needs from the original session/configuration inputs. These identifiers are not fields of the `onFinish` result. Completion alone does not mean approval; use the returned outcome when choosing your screen.

### Language

Set the UI language via the `lang` property in your config:

```ts
flow.config = { token: session.token, lang: 'es' /* Spanish */ };
```

The SDK ships translations for \~85 locales (including regional variants like `en-DG`, `es-MX`, `pt-BR`, `zh-TW`, `fr-CA`). See [Internationalization](/sdk-reference/web-sdk-2-i18n/) for the full list, fallback behavior, and dashboard interaction.

## Event Callbacks

### onFinish

**Required callback** called when the flow finishes successfully. The component does not render a completion screen — you must handle the finish status yourself:

```ts
flow.onFinish = (result) => {
  console.log('Action:', result?.action); // 'approved' | 'rejected' | 'none'
  console.log('Status:', result?.scoreStatus); // 'OK' | 'WARN' | 'FAIL' | ...
  console.log('Redirect:', result?.redirectionUrl);

  if (result?.action === 'approved' && result.redirectionUrl) {
    window.location.href = result.redirectionUrl;
  } else if (result?.action === 'rejected') {
    // Show rejection message
  }
};
```

The `result` object (typed as `FinishStatus`) contains:

- `redirectionUrl`: URL to redirect to (if configured)
- `action`: `'approved' | 'rejected' | 'none'`
- `scoreStatus`: `'OK' | 'WARN' | 'MANUAL_OK' | 'FAIL' | 'UNKNOWN' | 'MANUAL_FAIL'`
- `endScreenTitle`, `endScreenText`: dashboard completion copy (`string | null`)

Note that `result` may be `undefined` (the callback signature is `(result?: FinishStatus) => void`), so always null-check before reading fields.

### onError

Called when an error occurs:

```ts
flow.onError = (error, code, machineErrorCode) => {
  console.error('Verification error:', error, code, machineErrorCode);
};
```

When a flow does not allow multiple onboardings, `<incode-flow>` checks the session status before loading the first module. If that session is already complete, the component renders its localized finished-session screen and calls `onError` with `machineErrorCode` set to `SESSION_FINISHED`. This applies to token-based, self-loading, and preloaded configurations. Hosts that want to keep the SDK screen visible should not remove or replace the component from this callback.

### onModuleLoading / onModuleLoaded

Track lazy-loading of each module's UI chunk via config callbacks:

```ts
flow.config = {
  token: session.token,
  onModuleLoading: (moduleKey) => console.log(`Loading module: ${moduleKey}`),
  onModuleLoaded: (moduleKey) => console.log(`Loaded module: ${moduleKey}`),
};
```

### onWasmWarmup

Called when the flow requests background warmup, with only the pipelines needed for that request. It can fire multiple times during one flow: an ID → Selfie flow reports `['idCapture']` after Start, then `['selfie']` once ID is active and ready. Repeated state updates and already-ready pipelines do not produce additional notifications. This callback reports the request, not completion; capture modules still await readiness.

With Home enabled, automatic capture warmup waits for Start, then warms the first reachable capture requirement during consent, document selection, or tutorials. Flows without Home warm after mounting. Fast entry into capture may show its existing loading state while the shared warmup finishes. Progress warms the next requirement; a Redirect to Mobile step stops lookahead. `preloadIncodeFlow()` preloads metadata and UI only. Explicit `setup({ wasm: ... })` preloading remains eager.

```ts
flow.config = {
  token: session.token,
  onWasmWarmup: (pipelines) => {
    console.log('Warming up:', pipelines); // e.g., ['idCapture'], later ['selfie']
  },
};
```

## WASM Configuration

For ML-powered modules (selfie, ID capture), preload WASM with the `wasm` option on `setup()` so models are ready by the time the user reaches a camera step. The Incode CDN serves sensible defaults — you only need to pick which pipelines to preload:

```ts
await setup({
  apiURL: 'https://demo-api.incodesmile.com',
  token: session.token,
  wasm: { pipelines: ['selfie', 'idCapture'] },
});

// Then mount <incode-flow> as shown in Basic Usage above.
```

If you omit the `wasm` option, the SDK loads WASM lazily when the user reaches the first selfie or ID-capture step. Pass `wasm: false` to explicitly disable preload (useful when this page only runs phone/email flows). For self-hosted paths, custom model files, or the lower-level `warmupWasm()` API, see [WASM Configuration](/sdk-reference/web-sdk-2-wasm/).

## Loading Spinner Customization

`<incode-flow>` shows a transition spinner during initialization, while a module's lazy chunk loads, and between flow steps. The same spinner is used in every phase.

**Default text behavior:** if you don't pass `spinnerConfig`, the spinner renders with **no title or subtitle** — only the spinning icon. As soon as you set either `spinnerConfig.title` or `spinnerConfig.subtitle`, the missing field falls back to the SDK's translated defaults (i18n keys `loadingCircle.holdOn` for the title, `loadingCircle.validating` for the subtitle, which display as "Hold on" / "Validating" in English).

### SpinnerConfig Options

| Property     | Type                                              | Description                                                   |
| ------------ | ------------------------------------------------- | ------------------------------------------------------------- |
| `renderMode` | `'spinnerAndText' \| 'spinnerOnly' \| 'textOnly'` | Which parts of the loader render.                             |
| `size`       | `'small' \| 'medium' \| 'large'`                  | Size of the spinner icon (default: `'medium'`)                |
| `title`      | `string`                                          | **Deprecated** — set copy via i18n instead. Removed in 2.3.0. |
| `subtitle`   | `string`                                          | **Deprecated** — set copy via i18n instead. Removed in 2.3.0. |

> **Deprecation:** `title` and `subtitle` still work in 2.2.0 but will be removed in 2.3.0. Change loader copy through translations instead — override `loadingCircle.holdOn` and `loadingCircle.validating` via [Custom Translations](/sdk-reference/web-sdk-2-i18n/#custom-translations). Copy set in translations follows a runtime language change; a literal string here does not.
>
> To control loader presentation across **every** SDK loader — including the module-internal ones this config never reached — use `spinner` in [Theming & Styling](/sdk-reference/web-sdk-2-theming/#loader-presentation).

### Custom Text

```ts
flow.config = {
  token: session.token,
  spinnerConfig: {
    title: 'Verifying your identity',
    subtitle: "This won't take long...",
    size: 'large',
  },
};
```

### CSS Variable Customization

You can also customize the spinner appearance using CSS variables:

```css
:root {
  /* Spinner colors */
  --spinner-surface-primary: #0066cc;
  --spinner-surface-secondary: #e6f0ff;

  /* Spinner text */
  --spinner-text-title: #1a1a1a;
  --spinner-text-subtitle: #666666;

  /* Spinner overlay background */
  --spinner-surface-overlay: #ffffff;
  --spinner-surface-overlay-opacity: 1;
}
```

See [Theming & Styling](/sdk-reference/web-sdk-2-theming/) for more details on CSS customization.

### Replacing the Spinner Graphic

The options above recolor and resize the built-in spinner. To replace it entirely with your own SVG or Lottie asset, set the `loader.spinner` key in `assets` or `animations` — see [Asset Overrides § The loading spinner](/sdk-reference/web-sdk-2-asset-overrides/#the-loading-spinner).

## Web Component

`IncodeFlow` is also available as a standard Web Component for framework-agnostic usage:

```html
<!DOCTYPE html>
<html>
  <head>
    <style>
      html,
      body,
      incode-flow {
        height: 100%;
        margin: 0;
      }
      incode-flow {
        display: block;
      }
    </style>
  </head>
  <body>
    <incode-flow></incode-flow>

    <script type="module">
      import { setup } from '@incodetech/core';
      import {
        createSession,
        initializeSession,
      } from '@incodetech/core/session';
      import '@incodetech/web/themes/light.css';
      import '@incodetech/web/base.css';
      import '@incodetech/web/flow';
      import '@incodetech/web/flow/styles.css';

      async function initializeFlow() {
        try {
          await setup({ apiURL: 'https://demo-api.incodesmile.com' });

          const session = await createSession('your-api-key', {
            configurationId: 'your-config-id',
            language: 'en-US',
          });

          await initializeSession({ token: session.token });

          const flow = document.querySelector('incode-flow');
          flow.config = {
            token: session.token,
            lang: 'en-US',
            enableHome: true,
          };
          flow.onFinish = (result) => {
            if (result.action === 'approved' && result.redirectionUrl) {
              window.location.href = result.redirectionUrl;
            }
          };
          flow.onError = (error, code) => console.error('Error:', error, code);
        } catch (error) {
          console.error('Failed to initialize:', error);
        }
      }

      initializeFlow();
    </script>
  </body>
</html>
```

## TypeScript

Full type definitions are included. Import `FlowConfig` from `@incodetech/web/flow` and `FinishStatus` from `@incodetech/core/flow`:

```typescript
import type { FlowConfig } from '@incodetech/web/flow';
import type { FinishStatus } from '@incodetech/core/flow';
import '@incodetech/web/flow';

// Automatically typed via HTMLElementTagNameMap augmentation
const flow = document.createElement('incode-flow');
flow.config = {
  // ✅ typed as FlowConfig
  token: 'session-token-from-createSession',
  lang: 'en-US',
};
flow.onFinish = (result) => {
  // ✅ result typed as FinishStatus | undefined
  console.log(result?.action);
};
```

See [TypeScript Types](/sdk-reference/web-sdk-2-typescript-types/#web-component-element-types) for more details.

## Unsupported Modules

If the orchestrated flow returns a step whose UI module isn't bundled in this SDK build, `<incode-flow>` renders a fallback screen titled **"Module not available"** with a **Next** button. Clicking Next calls `flowManager.completeModule()` and advances the flow.

This keeps the rest of a multi-step flow working when one step is unrecognized — useful when the dashboard configuration runs ahead of the SDK release. It applies only to the `<incode-flow>` component; in headless mode (`createOrchestratedFlowManager` with your own UI), an unrecognized step key throws `"No registered module found for: <KEY>"`.