Warning
This guide is specific to Web SDK 2.x. For Web SDK 1.x, you can find documentation here.
Do not attempt to upgrade or implement without support. Contact your Incode representative to check if you are a candidate for this upgrade.
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.
This module follows the composite / orchestrator pattern, with an additional asyncResolution state for server-side processing nodes.
Tag
<incode-workflow> is a standard Web Component. Importing the UI subpath registers the custom element; importing the CSS applies the module's styles.
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.
<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.
<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:
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.
disableUnsupportedBrowserScreen controls the browser-not-supported screen. <incode-workflow> always uses this server value and ignores the host uiConfig and per-element options. It does the browser check after the workflow config loads but before the launch screen. In headless mode, the loading state carries workflowConfig after the config loads, so your UI can read this flag while the launch screen shows. See Browser support.
Digital ID bank redirect
When a workflow's ID Capture node offers a bank-redirect digital ID such as iDIN or FTN, pass digitalIdReturnUrl in the component config and digitalIdReturn on the page load that follows the bank's redirect back. These are the same two fields with the same wiring as on <incode-flow>. The return URL defaults to the current page. See Digital ID — Bank Redirect.
When digitalIdReturn is set, the workflow resumes on the node the user left from. The launch screen and the desktop-to-mobile handoff are skipped because the user already went through both before the redirect. Headless integrations get the same behavior by passing isReturnLeg: true to createWorkflowManager.
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.
The public status is loading, carrying homeScreen while resolving the session result on the desktop-QR path. Render a loading presentation. completing is a separate public status for completing the workflow before its terminal node arrives.
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:
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 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 |
finished |
Terminal — workflow's last node was a FINISH node. |
workflowConfig, finishStatus, scoreStatus |
closed |
User dismissed | – |
error |
Fatal error | error?, errorCode?, moduleErrorCode? |
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.isContinueLoadingbecomestruewhile the orchestrator finishes loading the first module. - When
disableLaunchScreenistrue, the SDK never setshomeScreen.visibleand you can skip rendering the overlay entirely. - On a return leg (
isReturnLeg: true, set by<incode-workflow>wheneverdigitalIdReturnis passed), the launch screen is not shown again. See Digital ID bank redirect.
Each WorkflowNode carries:
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?, moduleErrorCode?) |
Report a module failure. Supported module error codes can advance to the next step or completion; other failures enter error. Forward the module code unchanged so the SDK applies the appropriate behavior. |
completeFlow() |
Complete early, skipping remaining steps and notifying the backend. |
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). |
subscribeFlowEvent(listener) |
Subscribe to curated flow milestones; returns an unsubscribe function. See headless flow events. |
Call stop() to release resources on teardown. Start a new manager after a final state; reset() is not a universal manager method.
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.
import { createWorkflowManager } from '@incodetech/core/workflow';
const manager = createWorkflowManager({
interviewId: 'YOUR_SESSION_INTERVIEW_ID',
customModuleCallback: ({ interviewId, nodeId, name, onSuccess, onError }) => {
// Run your custom logic here, then call one of:
onSuccess('Custom check passed');
// or onError('Something went wrong');
},
});
Pass the interview ID from the active session when using custom-module callbacks. The manager fetches workflow configuration itself; it does not accept a config option.
See also
- IncodeFlow Component: the dashboard-driven flow alternative
- Module Patterns → composite
- Headless Mode → Orchestrated Flow Manager
- Individual Modules