---
title: "Workflow Module"
url: "https://developer.incode.com/sdk-reference/web-sdk-2-module-workflow/"
section: "sdk-reference"
group: "Incode Web SDK 2 Reference / Web SDK 2 Individual Modules"
version: "v1.1"
status: "live"
---
# Workflow Module

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

The Workflow module runs server-driven multi-step workflows where step ordering and configuration come from the backend per session. Unlike the dashboard-defined Flow, a Workflow lets the server emit nodes dynamically — including custom-module nodes that callbacks back into your code.

> Follows the [composite / orchestrator pattern](/sdk-reference/web-sdk-2-module-patterns/#5-composite--orchestrator-modules), with an additional `asyncResolution` state for server-side processing nodes. See the patterns page for the shared lifecycle.

## Tag

`<incode-workflow>` is a standard Web Component. Importing the UI subpath registers the custom element; importing the CSS applies the module's styles.

```ts
import '@incodetech/web/workflow';
import '@incodetech/web/workflow/styles.css';
```

## Self-loading vs. token mode

`<incode-workflow>` supports two bootstrap modes.

**Token mode**: create the session on your backend, then pass the resulting token to the component. This is the recommended production path because the API key stays server-side.

```ts
<incode-workflow config={{ token: sessionToken }} onFinish={handleFinish} />
```

**Self-loading mode**: the component creates its own session by calling `/omni/start` with a `configurationId` and either an `apiKey` or a `clientId`. The API key is exposed in the browser, so this mode is suitable for prototyping and internal tools only.

```ts
<incode-workflow
  config={{ apiKey: process.env.INCODE_API_KEY, configurationId: 'cfg-id' }}
  onFinish={handleFinish}
/>
```

When self-loading mode receives a 4028 error (workflow not activated), the SDK renders its built-in terminal error screen. No additional error handling is required from the host page.

## Properties

| Property   | Type                      | Required | Description                        |
| ---------- | ------------------------- | -------- | ---------------------------------- |
| `config`   | `WorkflowComponentConfig` | ❌       | Token-mode or self-loading config  |
| `onFinish` | `() => void`              | ❌       | Called when the workflow completes |
| `onError`  | `(error: string) => void` | ❌       | Called when an error occurs        |

## Configuration

`WorkflowConfig` is fetched from the backend via `GET /omni/workflow/info`:

```typescript
type WorkflowConfig = {
  id: string;
  name: string;
  redirectDesktopToMobile: boolean;
  disableSmsOption?: boolean;
  addContinueToDesktop?: boolean;
  qrPhishingResistance?: boolean;
  disableUnsupportedBrowserScreen?: boolean;
  oauth2Secured?: boolean;
  ds?: boolean; // Deepsight enabled
  mergeSessionRecordings?: boolean;
  ageAssurance?: boolean;
  disableLaunchScreen?: boolean;
  showFinishScreenBySessionStatus?: boolean;
};
```

The shape is largely informational — the workflow nodes themselves drive what runs. The boolean flags propagate to sub-modules (e.g., `mergeSessionRecordings` and `ageAssurance` to ID Capture; `ds` to Selfie).

`disableLaunchScreen` controls the SDK's built-in launch / home screen. When the backend sets it to `true`, the SDK skips the launch screen and goes straight to the first workflow node. When `false` (or omitted), the workflow surfaces a `homeScreen.visible === true` state — the consumer must render the launch screen and call `continueFromHome()` on user confirmation.

### Status-specific finish screen

With `showFinishScreenBySessionStatus` enabled, `<incode-workflow>` renders one of three built-in terminal screens based on the session's overall result instead of a single generic completion screen. Enable it per workflow in Dashboard, not in code.

Resolving the result takes an extra backend call, so the workflow passes through a `resolvingScore` status on the desktop-QR path before it reaches `finished`. Render a processing state for it, the same way you handle `completing`.

The session's score maps to one of three variants:

| Variant   | Session score                    | Meaning                                             |
| --------- | -------------------------------- | --------------------------------------------------- |
| `success` | `OK`, `WARN`, `MANUAL_OK`        | Verification passed.                                |
| `error`   | `FAIL`, `UNKNOWN`, `MANUAL_FAIL` | Verification failed. An absent score maps here too. |
| `review`  | `MANUAL`, `MANUAL_PENDING`       | Awaiting manual review.                             |

To classify the result yourself, to render your own screens, or to branch after `onFinish`, `@incodetech/core/flow` exports the same helpers the SDK uses:

```ts
import {
  getTotalScoreStatus,
  classifyScoreStatus,
  type TotalScore,
  type FinishScreenVariant,
} from '@incodetech/core/flow';

const score: TotalScore = await getTotalScoreStatus();
const variant: FinishScreenVariant = classifyScoreStatus(score); // 'success' | 'error' | 'review'
```

`classifyScoreStatus` is pure and returns `'error'` for an unknown or absent score, so it always yields a variant you can render.

## State machine

`WorkflowState` is a discriminated union over `status`. `idle`, `loading`, and `ready` additionally carry a `homeScreen: { visible: boolean; isContinueLoading: boolean }` property — see [Launch screen](#launch-screen) below.

| Status            | Description                                                                                                                                                                                                    | Properties                                                             |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| `idle`            | Initial state before `load()`                                                                                                                                                                                  | `homeScreen`                                                           |
| `loading`         | Fetching the next node from the workflow server                                                                                                                                                                | `homeScreen`                                                           |
| `completing`      | Finishing up after the last module completes, just before the terminal node arrives. Shown as its own status so the UI can render a "Hold on…" processing screen instead of a text-less transition spinner.    | `homeScreen`                                                           |
| `ready`           | Current `WorkflowNode` is active. Render the corresponding sub-module.                                                                                                                                         | `workflowConfig`, `currentNode`, `config`, `moduleState`, `homeScreen` |
| `asyncResolution` | Server is processing async; UI shows progress until the next node arrives.                                                                                                                                     | `workflowConfig`, `currentNode`                                        |
| `resolvingScore`  | Resolving the session's overall result before the status-specific finish screen. Only when `showFinishScreenBySessionStatus` is enabled — see [Status-specific finish screen](#status-specific-finish-screen). | `workflowConfig`                                                       |
| `finished`        | Terminal — workflow's last node was a `FINISH` node.                                                                                                                                                           | `workflowConfig`, `finishStatus`                                       |
| `closed`          | User dismissed                                                                                                                                                                                                 | –                                                                      |
| `error`           | Fatal error                                                                                                                                                                                                    | `error`, `errorCode?`                                                  |

### Launch screen

When the backend does not set `WorkflowConfig.disableLaunchScreen`, the workflow exposes a launch / home screen overlay before running the first node. The overlay is signalled via `state.homeScreen.visible`:

- Render your launch screen when `homeScreen.visible === true`.
- Call `manager.continueFromHome()` on user confirmation; `homeScreen.isContinueLoading` becomes `true` while the orchestrator finishes loading the first module.
- When `disableLaunchScreen` is `true`, the SDK never sets `homeScreen.visible` and you can skip rendering the overlay entirely.

Each `WorkflowNode` carries:

```typescript
type WorkflowNode = {
  id: string;
  nodeType: 'MODULE' | 'FINISH' | 'ASYNC_RESOLUTION';
  moduleKey: string; // e.g., 'PHONE', 'SELFIE', 'CUSTOM_MODULE'
  moduleConfiguration: Record<string, unknown>;
  status: string;
  workflowId: string;
};
```

## API methods

| Method                 | Description                                                                                                                                                                                                                                                  |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `load()`               | Start loading the workflow configuration and the first node from the backend. Call once.                                                                                                                                                                     |
| `completeModule()`     | Mark the current module as complete. Call from the active module's `onFinish` callback to advance to the next node.                                                                                                                                          |
| `errorModule(error)`   | Mark the current module as failed and transition the workflow to a terminal `error` state.                                                                                                                                                                   |
| `completeFlow()`       | Skip remaining nodes and go straight to the completion step. Used when the workflow was completed externally — e.g. via the desktop → mobile redirect handoff.                                                                                               |
| `finishWorkflow()`     | Land on the completion screen without asking the backend for a finish status. Call this — not `completeFlow()` — from `REDIRECT_TO_MOBILE`'s `onFinish`, because mobile already reported completion and the desktop's current node is not the `FINISH` node. |
| `continueFromHome()`   | Advance past the SDK's launch / home screen. Only meaningful when `state.homeScreen.visible === true`; no-op otherwise (e.g. when `disableLaunchScreen` is `true`).                                                                                          |
| `getModuleConfig<T>()` | Returns the current node's `moduleConfiguration` merged with workflow-level flags (`ds`). Typed via the generic parameter.                                                                                                                                   |
| `getState()`           | Get the current workflow state synchronously.                                                                                                                                                                                                                |
| `subscribe(callback)`  | Subscribe to workflow state changes (returns an unsubscribe function).                                                                                                                                                                                       |

Plus the universal manager lifecycle: `reset`, `stop`.

## Custom module callback

Workflows can include `CUSTOM_MODULE` nodes that hand control back to your code. Wire a `CustomModuleCallback` to handle them — it receives `interviewId`, `nodeId`, and the configured `name`, plus `onSuccess` / `onError` hooks that advance the workflow.

```typescript
import { createWorkflowManager } from '@incodetech/core/workflow';

const manager = createWorkflowManager({
  config: {
    /* WorkflowConfig fetched server-side */
  },
  customModuleCallback: ({ interviewId, nodeId, name, onSuccess, onError }) => {
    // Run your custom logic here, then call one of:
    onSuccess('Custom check passed');
    // or onError('Something went wrong');
  },
});
```

## See also

- [IncodeFlow Component](/sdk-reference/web-sdk-2-incodeflow-component/): the dashboard-driven flow alternative
- [Module Patterns → composite](/sdk-reference/web-sdk-2-module-patterns/#5-composite--orchestrator-modules)
- [Headless Mode → Orchestrated Flow Manager](/sdk-reference/web-sdk-2-headless-mode/#orchestrated-flow-manager)
- [Individual Modules](/sdk-reference/web-sdk-2-individual-modules/)