---
title: "SDK Error Handling"
url: "https://developer.incode.com/general-reference/sdk-error-handling/"
section: "general-reference"
version: "v1.1"
status: "live"
---
# SDK Error Handling


Incode SDKs surface errors differently from the Omni API. Rather than HTTP status codes, SDK errors appear as typed values delivered through callbacks, delegate methods, or state machine transitions — depending on the platform and integration path.

This page covers error handling for the Web SDK 2.0, iOS SDK, and Android SDK. For API error codes, see [API Error Codes](/general-reference/api-error-codes/).

***

## Error Handling Model by Platform

Before diving into platform specifics, it helps to understand the structural difference between how each SDK surfaces errors:

| Platform        | Fatal flow errors                                               | Module-level errors                                   | Configuration errors                               |
| --------------- | --------------------------------------------------------------- | ----------------------------------------------------- | -------------------------------------------------- |
| **Web SDK 2.0** | `error` state on manager; `onError` callback on `<incode-flow>` | `captureStatus === 'uploadError'` on capture managers | Thrown at runtime for unrecognized workflow steps  |
| **iOS**         | `onError(_ error: IncdFlowError)` delegate method               | Per-module result error enums (e.g. `NFCScanError`)   | Runtime. No dedicated configuration exception      |
| **Android**     | `onError(error: Throwable)` listener method                     | `resultCode` on module result objects                 | `ModuleConfigurationException` thrown at `build()` |

***

## Web SDK 2.0

The Web SDK 2.0 uses a state machine model. Every manager exposes an `error` terminal state, and the `<incode-flow>` component surfaces fatal errors via a callback.

### Fatal Errors

**Headless managers** (Phone, Email, Selfie, ID Capture, Workflow, Orchestrated Flow) all share the same `error` state shape:

```typescript
// Subscribe to state changes on any manager
manager.subscribe((state) => {
  if (state.status === 'error') {
    console.error('Fatal error:', state.error);
    // state.error is a string describing the failure
  }
});
```

`<incode-flow>`**&#x20;component** surfaces fatal errors via its `onError` callback:

```typescript
flow.onError = (error: string | undefined, errorCode?: number) => {
  console.error('Flow error:', error, errorCode);
};
```


| Property    | Type                  | Description                                                                              |
| ----------- | --------------------- | ---------------------------------------------------------------------------------------- |
| `error`     | `string \| undefined` | Human-readable description of the error                                                  |
| `errorCode` | `number` (optional)   | Numeric error code providing additional detail. See review note 1 for documentation gap. |

The **Workflow manager** error state also includes an optional `errorCode`:

```typescript
// WorkflowState when status === 'error'
{ status: 'error', error: string, errorCode?: number }
```

### Capture Errors

During active capture, errors surface as sub-state properties rather than as a separate `error` state. These are inline failures the user can retry, not fatal flow terminations.

**Selfie and ID Capture managers** — when `captureStatus === 'uploadError'`:

| Property                 | Type      | Description                               |
| ------------------------ | --------- | ----------------------------------------- |
| `uploadError`            | `string?` | Error code identifying the upload failure |
| `uploadErrorMessage`     | `string?` | Human-readable error message              |
| `uploadErrorDescription` | `string?` | Detailed error description                |

Retry is available when `canRetry === true`. Call `manager.retryCapture()` to retry.

### Camera Permission Errors

When `status === 'permissions'` and `permissionStatus === 'denied'`, the user has denied camera access. This is not a fatal error. Prompt the user to enable camera permissions in their browser settings, then call `manager.requestPermission()` again.

### Unrecognized Workflow Steps (Headless Mode Only)

In headless mode using `createOrchestratedFlowManager`, if the workflow returns a step whose module key is not registered, the manager throws:

```
"No registered module found for: <KEY>"
```

This does not apply to the `<incode-flow>` component, which renders a fallback "Module not available" screen and advances the flow automatically.

***

## iOS SDK

The iOS SDK uses a delegate pattern. Fatal flow errors are delivered to a single `onError` method on `IncdOnboardingDelegate`, while module-level errors are returned as typed enum values within each module's result struct.

### Fatal Flow Errors

All fatal errors during an onboarding flow are delivered via:

```swift
func onError(_ error: IncdFlowError) {
    // Handle fatal flow error
}
```

The documented `IncdFlowError` cases are:

| Case                     | Description                                                                                                                                               |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `.interrupted`           | The flow was interrupted programmatically via `forceInterrupt()` or `dismiss(forceInterrupt: true)`.                                                      |
| `.apiKeyRevoked(apiKey)` | The API key used to initialize the SDK was revoked mid-session. The associated value contains the revoked key. Use this case to trigger API key rotation. |


:::info
When `.apiKeyRevoked` is received, reinitialize the SDK with a new API key using `IncdOnboardingManager.shared.initIncdOnboarding(url:apiKey:)`, then restart or resume the session. See [API Key Rotation](/sdk-reference/ios-api-key-rotation) for full details.
:::

### Handling Programmatic Interruption

To force-stop the current flow:

```swift
// Attempt to mark session as finished, then trigger onError(.interrupted)
IncdOnboardingManager.shared.forceInterrupt(tryFinishingFlow: true) { success, error in
    // Cleanup complete
}

// Or dismiss without finishing:
IncdOnboardingManager.shared.dismiss(forceInterrupt: true)
```

### Module Result Errors

Each module callback returns a result struct that includes an error property. Errors here indicate a module-level failure, not necessarily a fatal flow error.

#### AES (Advanced Electronic Signature)

```swift
func onAesCompleted(_ result: AESResult) {
    if let error = result.error {
        // Handle AES error
    }
}
```

`AESError` values:

| Value           | Description                                                    |
| --------------- | -------------------------------------------------------------- |
| `.noDocuments`  | No documents are available for the current onboarding session. |
| `.failedToSign` | The AES signing operation failed.                              |

#### NFC Scan

```swift
func onNFCScanCompleted(_ result: NFCScanResult) {
    if let error = result.error {
        // Handle NFC error
    }
}
```

`NFCScanError` values:

| Value                      | Description                                                                                    |
| -------------------------- | ---------------------------------------------------------------------------------------------- |
| `.error(IncdError)`        | An underlying SDK error occurred. The associated `IncdError` value provides additional detail. |
| `.notAvailable`            | NFC scanning is not available on this device.                                                  |
| `.userDocumentHasNoChip`   | The user indicated their document does not have an NFC chip.                                   |
| `.noScanAttemptsRemaining` | The user exhausted all NFC scan attempts without success.                                      |


#### Face Login/Selfie Scan

Face login results include a `SelfieScanError` in the `error` property of `SelfieScanResult`. Spoof detection is surfaced separately via the `spoofAttempt` boolean:

```swift
IncdOnboardingManager.shared.startFaceLogin() { result in
    if let error = result.error {
        // A SelfieScanError occurred
    }
    if result.spoofAttempt == true {
        // Liveness check failed — spoof detected
    }
}
```


### On-Demand Resources Error

If you are using On-Demand Resources (ODR) and call an onboarding method before the resources have been downloaded, the method returns a `.resourcesNotFound` error. Always call `downloadOnDemandResources()` and wait for `onCompleted` before starting any onboarding modules.

### Simulator Behavior

On iOS Simulator, modules that require the camera (`ID Scan`, `Selfie Scan`, `Video Selfie`, and others) show a black screen for 2 seconds, then return `.simulatorDetected` and advance to the next module. Ensure `testMode: true` is set during initialization when running on Simulator.

***

## Android SDK

The Android SDK uses a listener pattern. Fatal errors are delivered as `Throwable` objects to `onError()` on `OnboardingListener`. Module results carry a `ResultCode` indicating the outcome.

### Fatal Flow Errors

```kotlin
override fun onError(error: Throwable) {
    // Fatal flow error — log error.message for details
    IncodeWelcome.getInstance().deleteUserLocalData()
}
```

```java
@Override
public void onError(@NonNull Throwable error) {
    // Fatal flow error — log error.getMessage() for details
    IncodeWelcome.getInstance().deleteUserLocalData();
}
```

:::warning
Always call `IncodeWelcome.getInstance().deleteUserLocalData()` in `onError()`, `onSuccess()`, and `onUserCancelled()` to ensure local session data is cleaned up regardless of how the flow exits.
:::

Unlike the iOS SDK, Android does not use a typed error enum for fatal flow errors. The `Throwable` message is the primary source of diagnostic information.

### Module Result Codes

Every module result object includes a `resultCode` property of type `ResultCode`. This indicates the high-level outcome of the module:

| Value               | Description                                                                                                                                                            |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `SUCCESS`           | The module completed successfully.                                                                                                                                     |
| `ERROR`             | The module encountered an error. Check the result object for additional details.                                                                                       |
| `USER_CANCELLED`    | The user cancelled the module.                                                                                                                                         |
| `EMULATOR_DETECTED` | The module was running on an emulator. After a 2-second delay, the module returns this code automatically. Remove `setTestModeEnabled(true)` before production builds. |

For `ID Scan` specifically, emulator detection is returned as `IdResults.RESULT_EMULATOR_DETECTED` on the `frontIdResult` and `backIdResult` properties rather than via `ResultCode`.

### Configuration Errors

`FlowConfig.Builder.build()` throws `ModuleConfigurationException` synchronously if the flow configuration violates module rules; for example, if mandatory modules are omitted or ordering dependencies are violated. Catch this at build time:

```kotlin
try {
    val flowConfig = FlowConfig.Builder()
        .addID(IdScan.Builder().build())
        .addSelfieScan(SelfieScan.Builder().build())
        .addFaceMatch()
        .build()
} catch (e: ModuleConfigurationException) {
    // Invalid flow configuration — fix before running
    Log.e("Incode", "Flow config error: ${e.message}")
}
```

```java
try {
    FlowConfig flowConfig = new FlowConfig.Builder()
        .addID(new IdScan.Builder().build())
        .addSelfieScan(new SelfieScan.Builder().build())
        .addFaceMatch()
        .build();
} catch (ModuleConfigurationException e) {
    // Invalid flow configuration — fix before running
    Log.e("Incode", "Flow config error: " + e.getMessage());
}
```

This is an Android-specific error type with no direct equivalent in the iOS or Web SDKs.

### Delayed Onboarding Sync Errors

When syncing offline (delayed) onboardings, errors are delivered via a dedicated listener:

```kotlin
IncodeWelcome.getInstance().syncDelayedOnboardings(object : SyncDelayedOnboardingListener {
    override fun onError(error: DelayedOnboardingSyncError) {
        // Handle sync error
    }
})
```

### Emulator Behavior

On Android emulators, camera-dependent modules (`ID Scan`, `Selfie Scan`, `Face Match`, `Document Scan`, `Video Selfie`) show a black screen for 2 seconds then return `ResultCode.EMULATOR_DETECTED` automatically. Remove `setTestModeEnabled(true)` before building for production.

***

## Flutter, React Native, and Xamarin


The Flutter, React Native, and Xamarin SDKs wrap the native iOS and Android SDKs. Error handling in these platforms mirrors the underlying native platform:

- On **iOS devices**, errors follow the iOS SDK model described above: typed `IncdFlowError` cases delivered via delegate callbacks, with per-module result error enums.
- On **Android devices**, errors follow the Android SDK model: `Throwable` errors via listener callbacks, with `ResultCode` on module results.

Refer to each platform's integration guide for the platform-specific callback and listener signatures used to receive these errors.

:::note
Detailed error handling documentation for Flutter, React Native, and Xamarin is coming soon. Contact your Incode customer success manager or refer to the native platform sections above in the meantime.
:::