# SDK Error Handling

{/* TECHNICAL REVIEW NOTES (not published by ReadMe)

1. WEB SDK errorCode VALUES: The <incode-flow> onError callback and Workflow manager error state
   both surface an optional numeric errorCode. The values this code can take are not documented
   in any source files reviewed. Confirm the full enumeration with the Web SDK team before
   publishing, or remove the errorCode row from the Web SDK error surface table.

2. iOS IncdFlowError ENUMERATION: Only two cases are documented in source files (.interrupted
   and .apiKeyRevoked). Confirm the full IncdFlowError enum with the iOS SDK team — there are
   likely additional cases not surfaced in the documentation files reviewed.

3. iOS SelfieScanError VALUES: Referenced in face login result documentation but never
   enumerated. Confirm values with iOS SDK team and add to the Face Login section.

4. iOS IncdError VALUES: Referenced as the associated value of NFCScanError.error() but never
   enumerated. Confirm values with iOS SDK team and add to the NFC section.

5. ANDROID ResultCode DESCRIPTIONS: The Javadoc enum lists SUCCESS, ERROR, USER_CANCELLED,
   and EMULATOR_DETECTED with no descriptions. Confirm whether descriptions exist or should
   be authored, and whether any additional ResultCode values exist beyond these four.

6. FLUTTER / REACT NATIVE / XAMARIN: These platforms are noted as wrapping the native SDKs
   but were not reviewed. Verify that the error models are derivative before publishing the
   note in the "Other Platforms" section, and replace with platform-specific detail if the
   error surface differs meaningfully.

7. PLACEHOLDER LINKS: Several cross-reference links use placeholder slugs (marked [LINK]).
   Replace with correct doc slugs before publishing.

END REVIEW NOTES */}

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](#).

***

## 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);
};
```

{/* REVIEW: Confirm errorCode numeric values with Web SDK team. See review note 1. */}

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

{/* REVIEW: Confirm the full IncdFlowError enumeration with iOS SDK team. See review note 2. */}

<Callout icon="📘" theme="info">
  ### **API key rotation**

  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 the [API Key Rotation guide](#) for full details.
</Callout>

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

{/* REVIEW: Confirm IncdError enum values with iOS SDK team. See review note 4. */}

#### 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
    }
}
```

{/* REVIEW: Confirm SelfieScanError enum values with iOS SDK team. See review note 3. */}

### 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();
}
```

<Callout icon="⚠️" theme="warn">
  ### **Always call&#x20;**`deleteUserLocalData()`

  Call `IncodeWelcome.getInstance().deleteUserLocalData()` in `onError()`, `onSuccess()`, and `onUserCancelled()` to ensure local session data is cleaned up regardless of how the flow exits.
</Callout>

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

{/* REVIEW: Verify that Flutter, React Native, and Xamarin wrap the native SDKs before
     publishing this section. Replace with platform-specific detail if the error surface
     differs meaningfully. See review note 6. */}

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.

<Callout icon="🚧" theme="warn">
  ### **Platform-specific error documentation**

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

<br />
