IncodeFlow Component

📘

This guide is specific to Web SDK 2.0. If you are still using 1.x, you can find documentation here. We strongly recommend upgrading - contact your Incode Representative for upgrade information.

<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: create the session via createSession() (or your backend) and call setup() with the resulting token before mounting <incode-flow>. The component does not create sessions internally; it consumes the session token you provide.

Tag

// 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):

PropertyTypeRequiredDescription
configFlowConfigFlow configuration object (see below)
onFinish(result?: FinishStatus) => voidCalled when the flow completes
onError(error: string | undefined, errorCode?: number) => voidCalled 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):

PropertyTypeRequiredDescription
tokenstringSession token
langstringLanguage code (e.g., 'en-US', 'es-MX')
enableHomebooleanShow the SDK's built-in home screen
authHintstringQR/auth hint when re-entering a flow
wasmConfigWasmConfigWASM paths (see WASM Configuration)
spinnerConfigSpinnerConfigLoading spinner customization
disableDashboardThemebooleanSkip applying the dashboard's theme tokens
urlUuidstringQR anti-phishing token from the URL (mobile leg)
useCPFbooleanSwitch the orchestrator's ID_OCR step to CPF_OCR for Brazilian CPF flows. When true, <incode-flow> renders the CPF OCR module instead of the standard ID OCR module at that step. Has no effect when the dashboard flow has no ID_OCR step.
onFlowEvent(event: FlowEvent) => voidCurated flow milestones
onModuleLoading(moduleKey: string) => voidModule begins loading (lazy chunk)
onModuleLoaded(moduleKey: string) => voidModule loaded
onWasmWarmup(pipelines: string[]) => voidWASM warmup begins
onUrlUuidRefreshed(urlUuid: string) => voidNew urlUuid available — host should update the address bar

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

PropertyTypeRequiredDescription
apiKey (or clientId)stringAuth key for session creation
configurationIdstringDashboard flow ID
externalId, externalCustomerId, customFields, uuid, urlUuid, interviewIdvariousForwarded to createSession()
Plus any of the optional fields from the token variant above (lang, enableHome, callbacks, etc.)

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:

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:

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

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) => 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.

Complete React Example

React 18 or earlier: add the JSX augmentation from 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.

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) => 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

The IncodeFlow component does not render start or completion screens internally — you control what the user sees before the flow begins and after it finishes.

Start screen

Build your own. Render whatever start UI suits your product (logo, copy, terms, a "Begin verification" button), and only mount <incode-flow> once the user clicks through. There is no public SDK component for this, by design — the start screen is a branding/UX surface that belongs to the integrator.

Completion screen

For completion, the SDK ships an optional drop-in web component, <incode-flow-completed>, that renders a simple "verification complete" screen with optional auto-redirect. It is registered as a side effect of @incodetech/web/flow (the same import you already have for <incode-flow>):

<incode-flow-completed
  title="All Done!"
  message="Your identity has been verified."
  redirection-url="https://yourapp.com/return"
  action="approved"
  score-status="OK"
  redirect-delay="3000"
></incode-flow-completed>

Attributes:

AttributeTypeDescription
titlestringHeading text. Falls back to a translated default.
messagestringSubtitle text. Falls back to a translated default.
redirection-urlstringIf set, the page redirects here after redirect-delay ms.
redirect-delaynumberMilliseconds to wait before redirecting. Default 3000.
disable-redirectbooleanSkip the redirect even if redirection-url is set.
redirect-origin-onlybooleanSuppress redirect when the page is already a redirect target (?isRedirect=true).
action'approved' | 'rejected' | 'none'The verification outcome (used for analytics/styling hooks).
score-status'OK' | 'WARN' | 'MANUAL_OK' | 'FAIL' | 'UNKNOWN' | 'MANUAL_FAIL'The score classification.
flow-idstringAppended to the redirect URL as a query param.
interview-idstringAppended to the redirect URL as a query param when redirecting cross-origin.

If you'd rather build your own completion screen, the result data you need is on the onFinish payload from <incode-flow> (redirectionUrl, action, scoreStatus, flowId, interviewId). Render whatever you want and skip <incode-flow-completed> entirely.

Language

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

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 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:

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'

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:

flow.onError = (error, code) => {
  console.error('Verification error:', error, code);
  // Show your own error UI or retry logic
};

onModuleLoading / onModuleLoaded

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

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

onWasmWarmup

Called when ML models begin loading:

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

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:

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.

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.hangOn for the title, loadingCircle.validating for the subtitle, which display as "Hang on" / "Validating" in English).

SpinnerConfig Options

You can override the default text using spinnerConfig:

PropertyTypeDescription
titlestringCustom title text (overrides default)
subtitlestringCustom subtitle text (overrides default)
size'small' | 'medium' | 'large'Size of the spinner icon (default: 'medium')

Custom Text

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:

: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 for more details on CSS customization.

Web Component

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

<!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:

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