# Module Patterns

<WebSDK2 />

Most SDK modules fall into one of five implementation patterns. Each pattern dictates how the manager state machine is shaped, which API methods exist, and what UI affordances you'll need to build (or which screens the SDK ships).

This page is the shared lifecycle reference. Per-module pages link here for the pattern they follow, then only document what's specific to that module — config shape, custom states, error codes — without repeating the lifecycle prose.

> If you're skimming, the [universal lifecycle](#universal-lifecycle) section is the only thing every module shares. The five patterns below describe how that lifecycle is filled in.

## Universal lifecycle

Every manager — regardless of pattern — exposes the same lifecycle methods:

| Method                | Purpose                                                                                   |
| --------------------- | ----------------------------------------------------------------------------------------- |
| `load()`              | Start the state machine. Always call first. Idempotent within a flow.                     |
| `subscribe(callback)` | Listen to state transitions. Returns an unsubscribe function.                             |
| `getState()`          | Current state synchronously, without subscribing.                                         |
| `reset()`             | Return to initial state. Only valid from `finished` or `error`.                           |
| `stop()`              | Tear down resources (camera streams, timers, server polling). Always call before unmount. |

Every state has a `status: string` discriminator. Use TypeScript's discriminated-union narrowing:

```ts
manager.subscribe((state) => {
  switch (state.status) {
    case 'idle':
      // initial state, before load()
      break;
    case 'finished':
      // success — TypeScript knows the success-state shape here
      manager.stop();
      break;
  }
});
```

**Terminal states.** `finished` and `closed` are XState `type: 'final'` — the manager won't re-emit further updates from them, and `reset()` doesn't apply (it's only valid from `finished` or `error`). `error` is recoverable; call `reset()` to retry.

**Lifecycle events.** Every manager automatically emits `moduleOpened` when `load()` runs and `moduleClosed` when it finishes. Subscribe via `subscribeEvent` from `@incodetech/core/events` if you want to track them. See [Dashboard Events](doc:web-sdk-2-events).

***

## 1. Form-based modules

The user fills inputs, clicks submit, the server validates, and the flow advances. The simplest, most common pattern.

**Modules using this pattern:** [Phone](doc:web-sdk-2-module-phone), [Email](doc:web-sdk-2-module-email), [ID OCR](doc:web-sdk-2-module-id-ocr), [Consent](doc:web-sdk-2-module-consent), [CURP Validation](doc:web-sdk-2-module-curp-validation), [Government Validation](doc:web-sdk-2-module-gov-validation-1), [Custom Fields](doc:web-sdk-2-module-custom-fields), [Watchlist for Business](doc:web-sdk-2-module-watchlist-for-business), [Identity Reuse](doc:web-sdk-2-module-identity-reuse) (with a candidate-selection variant).

### State progression

```mermaid
flowchart LR
    idle -->|load| inputting
    inputting -->|submit| submitting
    submitting --> finished
    submitting -.->|error| error
```

When `prefill` is enabled, `load` transitions through an extra `loadingPrefill` state before reaching `inputting`.

For modules with OTP verification (Phone, Email), the `submitting` state expands into an OTP sub-loop:

```mermaid
flowchart LR
    submitting --> sendingInitialOtp
    sendingInitialOtp --> awaitingOtp
    awaitingOtp -->|submitOtp| verifyingOtp
    verifyingOtp --> finished
    verifyingOtp -.->|wrong code| otpError
    otpError -->|submitOtp| verifyingOtp
    awaitingOtp -->|resendOtp| resendingOtp
    resendingOtp --> awaitingOtp
```

For Identity Reuse, the `inputting` step is replaced by candidate-selection states (`oneCandidateFound`, `multiCandidatesFound`, `noCandidatesFound`) that present existing identity matches for the user to pick or skip.

### Common API methods

| Method                                 | Purpose                                                                                 |
| -------------------------------------- | --------------------------------------------------------------------------------------- |
| `load()`                               | Start the machine. Fetches prefill data if `prefill` is enabled in config.              |
| `set<Field>(value, isValid?)`          | Update a form field. Pass `isValid` so the manager knows when submit can be enabled.    |
| `submit()`                             | Submit the form. Transitions to `submitting`.                                           |
| `setOtpCode(code)` / `submitOtp(code)` | OTP-specific. `setOtpCode` updates without submitting; `submitOtp` updates and submits. |
| `resendOtp()`                          | Re-request the OTP. Only valid when `state.canResend === true`.                         |
| `back()`                               | Return to a previous step (e.g. `awaitingOtp` → `inputting`).                           |
| `reset()`                              | After `finished` or `error`, go back to `idle`.                                         |

### Skeleton

```ts
const manager = createPhoneManager({
  config: { otpVerification: true, otpExpirationInMinutes: 5, prefill: false },
});

manager.subscribe((state) => {
  switch (state.status) {
    case 'inputting':
      // render input form, hook setPhoneNumber + submit
      break;
    case 'awaitingOtp':
      // render OTP entry form, hook submitOtp + resendOtp
      break;
    case 'finished':
      manager.stop();
      break;
  }
});

manager.load();
```

***

## 2. Camera-capture modules

Real-time camera feed with on-device ML detection, capture (auto or manual), upload, and server-side processing. The SDK handles the camera and ML; you handle the layout and instructions.

**Modules using this pattern:** [Selfie](doc:web-sdk-2-module-selfie-1), [ID Capture](doc:web-sdk-2-module-id-capture), [Document Capture](doc:web-sdk-2-module-document-capture), [Authentication](doc:web-sdk-2-module-authentication) (re-auth via selfie).

### State progression

```mermaid
flowchart LR
    idle -->|load| loading
    loading --> tutorial
    tutorial -->|nextStep| permissions
    permissions -->|granted| capture
    capture -->|upload done| processing
    processing -->|success| finished
    capture -->|retryCapture| capture
    capture -->|close| closed
```

The `capture` state has rich internal sub-state exposed as flat properties on the state object:

| Property            | Meaning                                                                                                           |
| ------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `stream`            | The `MediaStream` to bind to your `<video>` element.                                                              |
| `captureStatus`     | Sub-state: `initializing`, `detecting`, `capturing`, `uploading`, `uploadError`, `success`.                       |
| `detectionStatus`   | Per-frame feedback: `noFace`, `tooClose`, `blur`, `glare`, `manualCapture`, etc. Use to drive on-screen guidance. |
| `attemptsRemaining` | Capture attempts left before the module errors out.                                                               |

Specific modules add their own properties (Selfie has `assistedOnboarding`; ID adds `currentMode: 'front' | 'back'` and `counterValue` for the auto-capture countdown; Document Capture has multi-page state). Per-module pages document the diffs.

### Common API methods

| Method                      | Purpose                                                                                                                                     |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `load()`                    | Start. Goes to `tutorial` if `showTutorial: true`, else straight to `permissions`.                                                          |
| `nextStep()`                | Advance from `tutorial` → `permissions`. For ID capture, also advance from `capture.success` → `frontFinished` / `processing`.              |
| `requestPermission()`       | Prompt the user for camera access. Only effective in `permissions.idle` / `permissions.learnMore`.                                          |
| `goToLearnMore()`, `back()` | Help-screen navigation when permission is denied.                                                                                           |
| `capture()`                 | Manual capture. Only effective when `detectionStatus === 'manualCapture'`.                                                                  |
| `retryCapture()`            | Retry after upload error or expiration.                                                                                                     |
| `close()`                   | User dismisses; transitions to `closed` (terminal — see [Handling cancellation](doc:web-sdk-2-module-selfie-1#handling-cancellation-closed)). |

### WASM requirement

Camera-capture modules need WASM for face/document detection and quality scoring. Preload via `setup({ wasm: { pipelines: ['selfie', 'idCapture'] } })`. See [WASM Configuration](doc:web-sdk-2-wasm).

### Skeleton

```ts
const manager = createSelfieManager({
  config: { showTutorial: true, autoCaptureTimeout: 10, /* ... */ },
});

manager.subscribe((state) => {
  switch (state.status) {
    case 'tutorial':
      // render tutorial; nextStep() to advance
      break;
    case 'permissions':
      // prompt; requestPermission() on user click
      break;
    case 'capture':
      // bind state.stream to a <video> element with playsinline + muted + autoplay
      // render guidance based on state.detectionStatus
      if (state.detectionStatus === 'manualCapture') {
        // show a tap-to-capture button → manager.capture()
      }
      break;
    case 'processing':
      // server-side ML processing — show a spinner
      break;
    case 'finished':
      manager.stop();
      break;
    case 'closed':
      // user cancelled — see Handling cancellation
      break;
  }
});

manager.load();
```

***

## 3. Backend-process modules

No live user interaction. The module runs a server-side check using data already collected (by the session, by previous modules, or from the user's device) and reports a result. Often runs as a background step in an orchestrated flow rather than standalone.

**Modules using this pattern:** [Face Match](doc:web-sdk-2-module-face-match), [Antifraud](doc:web-sdk-2-module-antifraud), [Watchlist](doc:web-sdk-2-module-watchlist), [Custom Watchlist](doc:web-sdk-2-module-custom-watchlist), [Geolocation](doc:web-sdk-2-module-geolocation), [Cross-Document Data Match](doc:web-sdk-2-module-cross-doc-match).

### State progression

```mermaid
flowchart LR
    idle -->|load| loading
    loading --> result
    result -->|continue| finished
    loading -.->|error| error
```

Some modules add a transition state (Face Match has `animating` for its result reveal animation) but the core shape is minimal. The result data is on the state when `status === 'result'`.

### Common API methods

| Method                    | Purpose                                                                       |
| ------------------------- | ----------------------------------------------------------------------------- |
| `load()`                  | Trigger the backend call. Result lands in `state` when `status === 'result'`. |
| `continue()` / `submit()` | Acknowledge the result and finish. Naming varies by module.                   |
| `reset()`                 | Return to `idle` after `finished` or `error`.                                 |

These modules typically need no user input. They're often invoked from an orchestrated flow that calls `load()` automatically when the step starts.

### Skeleton

```ts
const manager = createFaceMatchManager({ config: {} });

manager.subscribe((state) => {
  switch (state.status) {
    case 'result':
      // render the match result (state.result.action / confidence)
      // call manager.continue() to acknowledge
      break;
    case 'finished':
      manager.stop();
      break;
  }
});

manager.load();
```

***

## 4. Document-signing modules

Render documents to be signed, collect consent checkboxes, capture a signature (handwritten on canvas or accept-via-button), submit. Used for legal/compliance signing flows.

**Modules using this pattern:** [Signature](doc:web-sdk-2-module-signature) (handwritten on canvas), [Electronic Signature](doc:web-sdk-2-module-electronic-signature) (multi-document PDF flow, plus AE and QE variants on the same page).

> **AE / QE are variants.** `createAeSignatureManager` and `createQeSignatureManager` are thin wrappers around `createElectronicSignatureManager` that hardcode `variant: 'ae'` or `'qe'` — same state machine, same API, different consent keys (`AE_CONSENT_KEYS` vs `QE_CONSENT_KEYS`). One mental model covers all three.

### State progression

```mermaid
flowchart LR
    loading --> reviewing
    reviewing -->|sign| signing
    signing --> uploading
    uploading --> processing
    processing --> finished
    reviewing -->|close| closed
    signing -.->|error| error
```

The handwritten signature module is structurally simpler — it skips the multi-document review and goes straight from `idle` → `capture` (canvas drawing) → `submitting` → `finished`.

### Common API methods

| Method                                                             | Purpose                                                                  |
| ------------------------------------------------------------------ | ------------------------------------------------------------------------ |
| `load()`                                                           | Fetch the documents to sign.                                             |
| `setConsent(name, checked)`                                        | Toggle a consent checkbox.                                               |
| `confirmFile()`, `selectFile()`, `replaceFile()`, `viewDocument()` | Document review interactions (Electronic Signature).                     |
| `setSignatureValid(isValid)`, `submit(image)`                      | Canvas signature interactions (Signature).                               |
| `sign()`                                                           | Trigger signing once consents are checked and the document is confirmed. |
| `finish()`, `retry()`, `close()`                                   | Terminal transitions.                                                    |

### Skeleton

```ts
import { createElectronicSignatureManager } from '@incodetech/core/electronic-signature';
import { areAllConsented } from '@incodetech/core/electronic-signature';

const manager = createElectronicSignatureManager({
  config: { variant: 'aes', uploadDocument: false, downloadDocument: true },
});

manager.subscribe((state) => {
  switch (state.status) {
    case 'reviewing':
      // render documents + consent checkboxes
      // setConsent on each checkbox change
      // when areAllConsented(state.consents), enable Sign button → manager.sign()
      break;
    case 'signing':
      // collect signature, then manager.sign()
      break;
    case 'finished':
      manager.stop();
      break;
  }
});

manager.load();
```

***

## 5. Composite / orchestrator modules

These don't capture data themselves — they sequence other modules based on a flow definition (from the dashboard or a workflow definition). The state describes which sub-module is currently active, and your code instantiates that sub-module and drives its lifecycle.

**Modules using this pattern:** [Flow / Orchestrated Flow](doc:web-sdk-2-incodeflow-component), [Workflow](doc:web-sdk-2-module-workflow), [eKYC](doc:web-sdk-2-module-ekyc), [eKYB](doc:web-sdk-2-module-ekyb).

### State progression

```mermaid
flowchart LR
    idle -->|load| loading
    loading --> ready
    ready -->|completeModule| ready
    ready -->|all steps done| finished
    ready -.->|error| error
```

The `ready` state cycles back to itself once per sub-module: the orchestrator emits a new `ready` snapshot with an updated `currentStep` each time you call `completeModule()`. The `ready` state exposes:

| Property           | Meaning                                                                                         |
| ------------------ | ----------------------------------------------------------------------------------------------- |
| `flow`             | The full flow configuration from the backend.                                                   |
| `steps`            | Ordered list of step keys.                                                                      |
| `currentStep`      | Step key currently active (e.g. `'PHONE'`, `'ID'`, `'SELFIE'`).                                 |
| `currentStepIndex` | Position in `steps`.                                                                            |
| `config`           | Sub-module config for the current step. Type is `unknown`; cast via `FlowModuleConfig[<step>]`. |
| `moduleState`      | Sub-module state if the orchestrator is also driving the sub-module.                            |

As each sub-module finishes, you call `flowManager.completeModule()` and the orchestrator advances to the next step.

### Common API methods

| Method                                                  | Purpose                                                   |
| ------------------------------------------------------- | --------------------------------------------------------- |
| `load()`                                                | Fetch flow configuration from the backend.                |
| `completeModule()`                                      | Mark the current sub-module as complete; advance to next. |
| `getModuleConfig(stepKey)` / `isModuleEnabled(stepKey)` | Query the resolved flow definition.                       |
| `reset()`                                               | Return to `idle`.                                         |

### Where to learn more

[Headless Mode → Orchestrated Flow Manager](doc:web-sdk-2-headless-mode#orchestrated-flow-manager) is the canonical reference. [Module Registration](doc:web-sdk-2-headless-mode#module-registration) covers how to wire sub-module state machines into the orchestrator.

***

## See also

* [Headless Mode](doc:web-sdk-2-headless-mode): full per-module reference for the four documented modules
* [Individual Modules](doc:web-sdk-2-individual-modules): catalog of every module in the SDK
* [WASM Configuration](doc:web-sdk-2-wasm): required for camera-capture modules
* [Dashboard Events](doc:web-sdk-2-events): automatic and custom event tracking
* [Troubleshooting](doc:web-sdk-2-troubleshooting): common integration issues
