> For the complete documentation index, see [llms.txt](https://docs.caf.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.caf.io/caf-sdk/flutter/getting-started-with-the-sdk.md).

# Legacy Getting Started with the SDK

{% hint style="warning" %}
**Certta DevX** is the recommended integration path starting with **caf\_sdk 2.4.0** (configure the session once, open modules independently). For the sequential `CafSdk` API, continue using this guide. To adopt DevX, see the [Installation Guide](/caf-sdk/flutter/installation-guide.md) and [Migrating from CafSdk](/caf-sdk/flutter/migrating-from-cafsdk.md).
{% endhint %}

## About CafSDK

This technical documentation covers the **implementation of CafSDK for Flutter**, detailing the configuration, initialization, execution of capture flows, and advanced customizations.

CafSDK is a unified SDK that integrates multiple modules for identity verification: **Face Liveness (FL)** and **Document Detector (DD)**, executed sequentially with a unified configuration interface.

### What is Face Liveness

Face Liveness is the module that validates the authenticity of a face captured by a photo application, ensuring that the image corresponds to a real person and not a spoofing attempt.

**Technical characteristics:**

* URL configuration for authentication (`authBaseUrl`) and liveness verification (`livenessBaseUrl`)
* Support for reverse proxy configuration with certificate pinning
* Flags to enable screen capture and debug mode
* Configurable retry attempts and face authentication execution
* Support for multiple authentication providers

### What is Document Detector

Document Detector is the module that enables the capture and processing of documents (e.g., ID card, social security card, passport, etc.).

**Technical characteristics:**

* Configuration of a step-by-step flow defined by `CafDocumentDetectorFlow` for document capture
* Support for multiple document types (RG, CNH, Passport, etc.)
* Operational parameters, such as timeout, manual capture flags, and other settings
* Possibility of using the camera for framing validations, or document file upload
* Advanced customization options for UI, messages, and behavior

***

## Example

Check out the [example app](https://github.com/combateafraude/caf-sdk-flutter-exemple) for a complete implementation example.

***

## Installation

### Requirements

To use the CafSDK modules in Flutter, ensure that your project meets the minimum requirements:

#### Flutter

| Requirement         | Version |
| ------------------- | ------- |
| **Flutter Version** | 3.3.0+  |
| **Dart**            | 3.9.0+  |

#### Android

| Requirement                                        | Version |
| -------------------------------------------------- | ------- |
| **Android SDK API - minimum version (minSdk)**     | 26      |
| **Android SDK API - compile version (compileSdk)** | 36+     |
| **Kotlin**                                         | 1.8+    |
| **Gradle**                                         | 8.0+    |
| **Android Gradle Plugin (AGP)**                    | 8.0+    |

#### iOS

| Requirement               | Version |
| ------------------------- | ------- |
| **iOS Deployment Target** | 15.0+   |
| **Xcode**                 | 14.0+   |
| **Swift**                 | 5.0+    |

### Step 1: Install the SDK

Install the main SDK package:

```sh
flutter pub add caf_sdk
```

### Step 2: Configure Module Selection

Create a `caf-modules-config.json` file in your project root to specify which modules to include:

> **Note**: If you omit this file, all modules are enabled by default and **`iproov-lite`** is used as the default Face Liveness provider.

```json
{
  "documentDetector": true,
  "faceLiveness": true,
  "documentDetectorUI": true,
  "faceLivenessUI": true,
  "livenessProviders": "iproov-lite",
  "fingerprint": true
}
```

Set to `true` the modules you want to use in your application.

For a single provider, `livenessProviders` can be a string. Allowed values are `iproov-lite`, `iproov-full`, `payface`, and `facetec`. To use several providers, use an array (see the example below).

```json
{
  "documentDetector": true,
  "faceLiveness": true,
  "documentDetectorUI": true,
  "faceLivenessUI": true,
  "livenessProviders": [
    "iproov-lite",
    "payface",
    "facetec"
  ],
  "fingerprint": true
}
```

**iProov and Protobuf**

* **`iproov-lite`**: use when your app targets **Protobuf JavaLite**—the usual choice for a smaller binary footprint on Android.
* **`iproov-full`**: use when you need **Protobuf Java** (full) together with iProov.

You can declare **more than one** provider by setting `livenessProviders` to an array when your integration requires it.

{% hint style="warning" %}
If you include the **PayFace** provider, you must also use **iProov Lite** (`iproov-lite`). PayFace is built against Protobuf JavaLite; mixing it with **`iproov-full`** causes Protobuf dependency conflicts at build time.
{% endhint %}

Set to `true` the modules you want to use in your application.

**Fingerprint**

The Fingerprint module is optional and is configured in `caf-modules-config.json` via the `fingerprint` property (boolean). It defaults to `false`, so you don't need to add the property unless you want to use it. To enable the module, explicitly set `"fingerprint": true`.

**Requires a Face Liveness module.** Fingerprint is collected as part of the liveness flow, so it is only bundled when `faceLiveness` or `faceLivenessUI` is also enabled. Setting `"fingerprint": true` without a liveness module enabled has no effect.

**Important:** Please contact CAF Support to request activation. If it is not enabled on our side, the SDK will not trigger the fingerprint library and no data will be sent, even if the property is set to `true` locally.

### Step 3: Android Configuration

#### Add Maven Repositories

Configure the `repositories` block in your project's `android/build.gradle.kts` (usually at the root level):

{% tabs %}
{% tab title="Kotlin Script" %}

```kotlin
repositories {
    maven { url = uri("https://repo.combateafraude.com/android/release") }
}
```

{% endtab %}

{% tab title="Groovy" %}

```groovy
repositories {
    maven { url "https://repo.combateafraude.com/android/release" }
}
```

{% endtab %}
{% endtabs %}

### Step 4: iOS Configuration

Navigate to the `ios/` directory of your Flutter project and run:

```sh
pod install
```

* This step is mandatory for iOS to correctly link the native modules and their required dependencies.
* Always re-run `pod install` whenever native dependencies are added or updated.

***

## Permissions

### Android

For the modules to operate correctly, you must declare the following permissions in your **AndroidManifest.xml**:

**For Face Liveness**

| Permission                    | Description                                                                             | Necessity |
| ----------------------------- | --------------------------------------------------------------------------------------- | --------- |
| `android.permission.CAMERA`   | Allows access to the camera to capture images and perform face verification (liveness). | Mandatory |
| `android.permission.INTERNET` | Allows communication with authentication and verification services (HTTPS/WSS).         | Mandatory |

**For Document Detector**

| Permission                                 | Description                                                                 | Necessity        |
| ------------------------------------------ | --------------------------------------------------------------------------- | ---------------- |
| `android.permission.CAMERA`                | Allows access to the camera to capture document images.                     | Only for capture |
| `android.permission.INTERNET`              | Allows captured images to be sent to servers for processing and validation. | Mandatory        |
| `android.permission.READ_EXTERNAL_STORAGE` | Allows access to stored files and images for processing, if necessary.      | Only for upload  |

### iOS

For the SDK modules to function correctly, you must declare the following permissions in your **Info.plist**:

**For Face Liveness:**

| Permission                 | Description                                                                             | Necessity |
| -------------------------- | --------------------------------------------------------------------------------------- | --------- |
| `NSCameraUsageDescription` | Allows access to the camera to capture images and perform face verification (liveness). | Mandatory |
| `Network access`           | Allows communication with authentication and verification services (HTTPS/WSS).         | Mandatory |

**For Document Detector:**

| Permission                       | Description                                                                 | Necessity        |
| -------------------------------- | --------------------------------------------------------------------------- | ---------------- |
| `NSCameraUsageDescription`       | Allows access to the camera to capture document images.                     | Only for capture |
| `Network access`                 | Allows captured images to be sent to servers for processing and validation. | Mandatory        |
| `NSPhotoLibraryUsageDescription` | Allows access to stored files and images for processing, if necessary.      | Only for upload  |

***

## Basic Implementation

### Simple Example

Here's a basic implementation example:

```dart
import 'dart:async';
import 'package:caf_sdk/types/index.dart';
import 'package:flutter/material.dart';
import 'package:caf_sdk/caf_sdk.dart';

void main() => runApp(const App());

class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'CAF SDK Example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      home: const SimpleExamplePage(),
    );
  }
}

class SimpleExamplePage extends StatefulWidget {
  const SimpleExamplePage({super.key});

  @override
  State<SimpleExamplePage> createState() => _SimpleExamplePageState();
}

class _SimpleExamplePageState extends State<SimpleExamplePage> {
  final _cafSdk = CafSdk();
  StreamSubscription<CafResponse>? _eventSubscription;

  @override
  void initState() {
    super.initState();
    _initializeEventStream();
  }

  @override
  void dispose() {
    _eventSubscription?.cancel();
    super.dispose();
  }

  void _initializeEventStream() {
    _eventSubscription = _cafSdk.eventStream.listen((event) {
      if (!mounted) return;

      if (event.success != null) {
        print('Success: ${event.success!.moduleName} -> ${event.success!.signedResponse}');
      } else if (event.error != null) {
        print('Error: ${event.error!.type} - ${event.error!.description}');
      } else if (event.failure != null) {
        print('Failure: ${event.failure!.type} - ${event.failure!.description}');
      }
    });
  }

  Future<void> _initialize() async {
    try {
      // SDK Configuration
      final config = CafSdkConfiguration(
        mobileToken: 'token',
        personId: 'personId',
        environment: CafEnvironment.dev,
        configuration: CafSdkBuilderConfiguration(
          presentationOrder: [
            CafModuleType.faceLiveness,
            CafModuleType.documentDetector,
          ],
          enableSecurityModule: true,
          waitForAllServices: true,
        ),
      );

      // Face Liveness Configuration
      final faceLivenessConfig = CafFaceLivenessConfiguration(
        loading: true,
        debugModeEnabled: true,
        payFaceDebugMode: false,
      );

      // Document Detector Configuration
      final documentDetectorConfig = CafDocumentDetectorConfiguration(
        flow: [
          CafDocumentDetectorFlow(document: CafDocument.rgFront),
          CafDocumentDetectorFlow(document: CafDocument.rgBack),
        ],
        uploadSettings: CafDocumentDetectorUploadSettings(enable: false),
        manualCaptureEnabled: false,
        manualCaptureTime: 45,
        showPopup: true,
        previewShow: false,
        securitySettings: CafDocumentDetectorSecuritySettings(
          useAdb: true,
          useDebug: true,
          useDevelopmentMode: true,
        ),
      );

      await _cafSdk.initializeCafSdk(
        cafSdkConfiguration: config,
        faceLivenessConfiguration: faceLivenessConfig,
        documentDetectorConfiguration: documentDetectorConfig,
      );
    } catch (e) {
      print('Error: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('CAF SDK Simple Example'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      ),
      body: SafeArea(
        child: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.center,
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Center(
                child: ElevatedButton(
                  onPressed: _initialize,
                  child: Text('Start')
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
```

***

## Configuration

### Language

#### Android

The language is automatically set according to the language configured on the device without any additional settings.

#### iOS

According to Apple's documentation, configuring `Localizations` and `CFBundleLocalizations` should be done in Xcode:

[Adding support for languages and regions](https://developer.apple.com/documentation/xcode/adding-support-for-languages-and-regions)

[CFBundleLocalizations](https://developer.apple.com/documentation/bundleresources/information-property-list/cfbundlelocalizations)

After these settings, the SDK will recognize the device's language.

### Global Configuration

The `CafSdk` class serves as the central container for all configurations. This configuration defines the execution order of the modules and the visual identity.

**Essential parameters:**

* **mobileToken**: Token that authenticates the request and ensures that only authorized clients start the flow
* **personId**: Unique user identifier for which the flow will be executed
* **environment**: Defines the execution environment (PROD, BETA, DEV)
* **presentationOrder**: Defines the sequence in which the modules will be executed
* **enableSecurityModule**: Enables or disables the security module. Optional, default is `true`

**Code example for creating the global configuration:**

```dart
final _cafSdk = CafSdk();

// SDK Configuration
final config = CafSdkConfiguration(
  mobileToken: "mobile-token",
  personId: "person-id",
  environment: CafEnvironment.prod,
  configuration: CafSdkBuilderConfiguration(
    presentationOrder: [
      CafModuleType.faceLiveness,    // or CafModuleType.faceLivenessUi
      CafModuleType.documentDetector // or CafModuleType.documentDetectorUi
    ],
    enableSecurityModule: true,       // Optional, default is true
    waitForAllServices: true,         // Optional, default is true
    enableTransitionScreens: true,    // Optional, default is true
    colorConfiguration: CafColorConfiguration(
      primaryColor: "#0000FF",
      secondaryColor: "#00FF00",
      backgroundColor: "#FFFFFF",
      contentColor: "#000000",
      mediumColor: "#CCCCCC",
      dialogBackgroundColor: "#FFFFFF",
      dialogBorderColor: "#E5E5E7",
    ),
  ),
);

// Initialize the SDK
await _cafSdk.initializeCafSdk(
  cafSdkConfiguration: config,
  documentDetectorConfiguration: documentDetectorConfig,
  documentDetectorUIConfiguration: documentDetectorUIConfig,
  faceLivenessConfiguration: faceLivenessConfig,
  faceLivenessUIConfiguration: faceLivenessUIConfig,
);
```

### Module-Specific Configuration

#### Face Liveness Configuration

You can configure the Face Liveness module by creating a `CafFaceLivenessConfiguration`. When using the optional PayFace (Fortface) provider, use `payFaceDebugMode` to enable debug mode for that provider.

```dart
final faceLivenessConfig = CafFaceLivenessConfiguration(
  loading: true,                                                  // Displays loading screen during processing
  authBaseUrl: 'https://base-url.com',                            // Optional, endpoint for authentication
  livenessBaseUrl: 'wss://base-url.com',                          // Optional, endpoint for liveness check
  certificates: ['4d69f16113bed7d62ca56feb68d32a0fcb7293d3960='], // Optional, only when using reverse proxy
  screenCaptureEnabled: true,                                     // Allows screen capture if necessary
  debugModeEnabled: true,                                         // Enables debug logging
  executeFaceAuth: false,                                         // Enables face authentication
  maxRetryAttempts: 2,                                            // Maximum retry attempts
  payFaceDebugMode: false,                                        // Optional, enables debug mode for PayFace (Fortface) provider
);
```

#### Document Detector Configuration

You can configure the Document Detector module by creating a `CafDocumentDetectorConfiguration`:

```dart
final documentDetectorConfig = CafDocumentDetectorConfiguration(
  flow: [
    CafDocumentDetectorFlow(document: CafDocument.rgFront),
    CafDocumentDetectorFlow(document: CafDocument.rgBack),
  ],
  securitySettings: CafDocumentDetectorSecuritySettings(
    useAdb: true,
    useDebug: true,
    useDevelopmentMode: true,
  ),
  manualCaptureEnabled: true,
  manualCaptureTime: 30,
  requestTimeout: 60,
  showPopup: true,
  maxRetryAttempts: 2,
  uploadSettings: CafDocumentDetectorUploadSettings(
    enable: true,
    compress: true,
    fileFormats: [CafFileFormat.png, CafFileFormat.jpg],
    maxFileSize: 5, // 5MB
  ),
);
```

***

## Event Handling

{% hint style="warning" %}
Ensure the JWT response is evaluated on the backend. This process must include validating the token's signature and verifying the `isAlive` and `isMatch` fields. Do not perform these validations on the client side.
{% endhint %}

The `eventStream` from the `CafSdk` class provides a **typed** `Stream<CafResponse>` generated during the execution of the capture flow. Each `CafResponse` has exactly one populated field, which indicates the event type:

| Field       | Type                  | Description                                                                             |
| ----------- | --------------------- | --------------------------------------------------------------------------------------- |
| `loading`   | `bool`                | Indicates the start of module processing                                                |
| `loaded`    | `bool`                | Indicates that the module has been processed                                            |
| `success`   | `CafSuccessResponse?` | Upon successful completion; contains `moduleName` and `signedResponse`                  |
| `error`     | `CafErrorResponse?`   | A problem occurred during execution; contains `type` and `description`                  |
| `failure`   | `CafFailureResponse?` | Indicates a Face Liveness failure; contains `type`, `description`, and `response`       |
| `log`       | `CafLog?`             | Log messages with different levels; contains `level` (DEBUG, USAGE, INFO) and `message` |
| `cancelled` | `bool`                | Indicates that the user or system interrupted the flow                                  |

> **Migration note (v2.0.0):** `eventStream` now emits typed `CafResponse` objects instead of raw `Map` payloads. Replace any `event['eventName']` / `event['response']` access with the typed fields shown above.

**Example of event handling:**

```dart
final _cafSdk = CafSdk();
StreamSubscription<CafResponse>? _eventSubscription;

void _initializeEventStream() {
  _eventSubscription = _cafSdk.eventStream.listen((event) {
    if (!mounted) return;

    if (event.loading) {
      print('Loading...');
    } else if (event.loaded) {
      print('Session loaded');
    } else if (event.success != null) {
      print('Success: ${event.success!.moduleName} -> ${event.success!.signedResponse}');
    } else if (event.error != null) {
      print('Error: ${event.error!.type} - ${event.error!.description}');
    } else if (event.failure != null) {
      print('Failure: ${event.failure!.type} - ${event.failure!.description}');
    } else if (event.log != null) {
      print('Log: ${event.log!.level} - ${event.log!.message}');
    } else if (event.cancelled) {
      print('Cancelled by user');
    }
  });
}

@override
void dispose() {
  _eventSubscription?.cancel();
  super.dispose();
}
```

### Error Types (CafErrorType)

| Enum Case                          | Trigger Condition                                  |
| ---------------------------------- | -------------------------------------------------- |
| `CAMERA_PERMISSION`                | Camera access denied                               |
| `UNSUPPORTED_DEVICE`               | Unsupported device specs                           |
| `NETWORK_EXCEPTION`                | Network connectivity issues                        |
| `SERVER_EXCEPTION`                 | Backend processing failure                         |
| `TOKEN_EXCEPTION`                  | Invalid/expired token                              |
| `CAPTURE_ALREADY_ACTIVE_EXCEPTION` | Concurrent capture session                         |
| `UNEXPECTED_ERROR_EXCEPTION`       | Critical unrecoverable error                       |
| `USER_TIMEOUT_EXCEPTION`           | Capture timeout exceeded                           |
| `IMAGE_NOT_FOUND_EXCEPTION`        | Missing image data                                 |
| `TOO_MANY_REQUESTS_EXCEPTION`      | API rate limit exceeded                            |
| `UNKNOWN_EXCEPTION`                | Unclassified error                                 |
| `LIBRARY_EXCEPTION`                | Low-level framework error                          |
| `PERMISSION_EXCEPTION`             | Missing system permissions                         |
| `INVALID_EXCEPTION`                | Invalid response received                          |
| `SEQUENCE_INVALID`                 | Invalid operation sequence                         |
| `LIVENESS_EXCEPTION`               | Face liveness specific error                       |
| `FINGERPRINT_EXCEPTION`            | Fingerprint related error                          |
| `STORAGE_EXCEPTION`                | Storage access error                               |
| `PROXY_EXCEPTION`                  | Proxy configuration error                          |
| `SECURITY_EXCEPTION`               | Security validation error                          |
| `INVALID_RESPONSE_EXCEPTION`       | Invalid response payload received from a module    |
| `CAMERA_EXCEPTION`                 | Camera initialization or runtime failure           |
| `FACE_AUTHENTICATION`              | Error during face authentication (executeFaceAuth) |
| `BRIDGE_EXCEPTION`                 | Native ↔ Flutter bridge communication error        |

### Failure Types (CafFailureType)

|      Enum Case      | Trigger Condition          | GPA |  LA |
| :-----------------: | -------------------------- | :-: | :-: |
|      `UNKNOWN`      | Generic failure            |  ✅  |  ❌  |
| `TOO_MUCH_MOVEMENT` | Excessive head motion      |  ✅  |  ❌  |
|     `TOO_BRIGHT`    | Over-illumination          |  ✅  |  ❌  |
|      `TOO_DARK`     | Low light conditions       |  ✅  |  ❌  |
|  `MISALIGNED_FACE`  | Face alignment failure     |  ✅  |  ❌  |
|    `FACE_TOO_FAR`   | Face too distant           |  ✅  |  ❌  |
|   `FACE_TOO_CLOSE`  | Face too close             |  ✅  |  ❌  |
|     `SUNGLASSES`    | Eye-obscuring eyewear      |  ✅  |  ❌  |
|   `OBSCURED_FACE`   | Partial face obstruction   |  ✅  |  ✅  |
|    `EYES_CLOSED`    | Closed eyes during capture |  ✅  |  ✅  |
|   `MULTIPLE_FACES`  | Multiple faces detected    |  ✅️ |  ✅️ |
|  `BACKGROUND_ISSUE` | Unsuitable background      |  ❌  |  ✅  |
|    `DEVICE_ISSUE`   | Incompatible device        |  ❌  |  ✅  |
|      `EYEWEAR`      | Eyewear detected           |  ❌  |  ✅  |
|   `FACE_NOT_FOUND`  | Face detection failure     |  ❌  |  ✅  |
|   `FRAMES_BLURRY`   | Blurry frames detected     |  ❌  |  ✅  |
|    `MOTION_ISSUE`   | Device motion error        |  ❌  |  ✅  |
|  `LIGHTING_ISSUES`  | Poor lighting conditions   |  ❌  |  ✅  |
|      `REJECTED`     | Transaction rejected       |  ❌  |  ✅  |
|    `SYSTEM_ERROR`   | Internal system error      |  ❌  |  ✅  |
|      `TIMEOUT`      | Session timeout            |  ❌  |  ✅  |
|   `USER_NOT_FOUND`  | User lookup failure        |  ❌  |  ✅  |
|   `DEVICE_RESTART`  | Device state error         |  ❌  |  ✅  |
|  `PROCESSING_FAULT` | Processing error           |  ❌  |  ✅  |

***

## Document Types

### Supported Documents (CafDocument)

| Name         | Description                                                                                                    |
| ------------ | -------------------------------------------------------------------------------------------------------------- |
| `RG_FRONT`   | Front side of the RG document, where the photo is located                                                      |
| `RG_BACK`    | Back side of the RG document                                                                                   |
| `RG_FULL`    | Open RG document, displaying both the front and back sides together                                            |
| `CNH_FRONT`  | Front side of the CNH document, where the photo is located                                                     |
| `CNH_BACK`   | Back side of the CNH document                                                                                  |
| `CNH_FULL`   | Open CNH document, displaying both the front and back sides together                                           |
| `CRLV`       | CRLV document                                                                                                  |
| `RNE_FRONT`  | Front side of the RNE or RNM document                                                                          |
| `RNE_BACK`   | Back side of the RNE or RNM document                                                                           |
| `CTPS_FRONT` | Front side of the CTPS document, where the photo is located                                                    |
| `CTPS_BACK`  | Back side of the CTPS document                                                                                 |
| `PASSPORT`   | Passport document, displaying the photo and personal data                                                      |
| `ANY`        | Allows submission of any type of document, including all those listed above or any other unclassified document |

### Supported File Formats (CafFileFormat)

| Type   | Value             |
| ------ | ----------------- |
| `PNG`  | `image/png`       |
| `JPG`  | `image/jpg`       |
| `JPEG` | `image/jpeg`      |
| `PDF`  | `application/pdf` |
| `HEIF` | `image/heif`      |
| `HEIC` | `image/heic`      |

***

## Advanced Configuration

### Face Liveness UI Configuration

When using the UI module, you can customize instruction screens:

```dart
final faceLivenessUIConfig = CafFaceLivenessUIConfiguration(
  loading: true,
  authBaseUrl: "https://my.proxy.io/v1/faces/", 
  livenessBaseUrl: "wss://my.proxy.io/ws/",    
  certificates: [
    "4d69f16113bed7d62ca56feb68d32a0fcb7293d3960=",
    "50f71c5dda30741ee4be1ac378e12539b0d1d511f99=",
    "9f85e26c1ae41f7ac97adc4099be7f2a40759510ab9=",
  ],
  screenCaptureEnabled: true,
  debugModeEnabled: true,
  executeFaceAuth: false, 
  maxRetryAttempts: 2,
  payFaceDebugMode: false,
  instructionScreen: CafFaceLivenessUIInstructionScreenConfiguration(
    image: "scan_icon", // Image URL or local asset name
    title: "Custom title",
    description: "Follow the steps below:",
    steps: ["Keep the phone steady", "Ensure good lighting"],
    buttonText: "Start scanning",
  ),
);
```

### Document Detector UI Configuration

```dart
final documentDetectorUIConfig = CafDocumentDetectorUIConfiguration(
  flow: [CafDocumentDetectorFlow(document: CafDocument.rgFront)], 
  manualCaptureEnabled: true,
  manualCaptureTime: 45,
  requestTimeout: 60,
  showPopup: true,
  securitySettings: CafDocumentDetectorSecuritySettings(
    useDebug: true,
    useDevelopmentMode: true,
    useAdb: true,
  ),
  maxRetryAttempts: 2,
  instructionScreen: CafDocumentDetectorUIInstructionScreenConfiguration(
    enable: true,
    captureTitle: "Capture your document",
    captureSteps: [
      "Keep the phone steady",
      "Ensure good lighting",
      "Avoid reflections"
    ],
    buttonText: "Start",
  ),
  documentSelectionScreen: CafDocumentDetectorUIDocumentSelectionScreenConfiguration(
    title: "Select the document type",
    description: "Choose which document you want to submit",
  ),
);
```

### Proxy Configuration

For Document Detector proxy settings:

```dart
final documentDetectorConfig = CafDocumentDetectorConfiguration(
  flow: [CafDocumentDetectorFlow(document: CafDocument.rgFront)],
  proxySettings: CafDocumentDetectorProxySettings(
    hostname: "proxy.example.com",
    port: 8080,
    authentication: CafDocumentDetectorProxySettingsAuthentication(
      user: "username",
      password: "password"
    ),
  ),
);
```

### Message Customization

Customize messages displayed during the capture flow:

```dart
final documentDetectorConfig = CafDocumentDetectorConfiguration(
  flow: [CafDocumentDetectorFlow(document: CafDocument.rgFront)],
  messageCustomization: CafDocumentDetectorMessageCustomization(
    waitMessage: "Preparing camera...",
    fitTheDocumentMessage: "Position the document within the frame",
    holdItMessage: "Hold steady...",
    verifyingQualityMessage: "Verifying document quality...",
    lowQualityDocumentMessage: "Document quality is too low. Please try again.",
    uploadingImageMessage: "Uploading document...",
    positiveButtonMessage: "Continue",
  ),
);
```

***

## Complete Implementation Example

Here's a complete example showing both Face Liveness UI and Document Detector UI:

```dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:caf_sdk/caf_sdk.dart';
import 'package:caf_sdk/types/index.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'CAF SDK Example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      home: const CafSdkExamplePage(),
    );
  }
}

class CafSdkExamplePage extends StatefulWidget {
  const CafSdkExamplePage({super.key});

  @override
  State<CafSdkExamplePage> createState() => _CafSdkExamplePageState();
}

class _CafSdkExamplePageState extends State<CafSdkExamplePage> {
  final _cafSdk = CafSdk();
  StreamSubscription<CafResponse>? _eventSubscription;

  final String _mobileToken = "token";
  final String _personId = "personId";
  final CafEnvironment _environment = CafEnvironment.dev;

  @override
  void initState() {
    super.initState();
    _initializeEventStream();
  }

  @override
  void dispose() {
    _eventSubscription?.cancel();
    super.dispose();
  }

  void _initializeEventStream() {
    _eventSubscription = _cafSdk.eventStream.listen((event) {
      if (!mounted) return;

      if (event.loading) {
        print('[CAF SDK EVENT]: loading');
      } else if (event.loaded) {
        print('[CAF SDK EVENT]: loaded');
      } else if (event.success != null) {
        print('[CAF SDK EVENT]: success');
        print('[CAF SDK EVENT]: ${event.success!.moduleName} -> ${event.success!.signedResponse}');
      } else if (event.error != null) {
        print('[CAF SDK EVENT]: error');
        print('[CAF SDK EVENT]: ${event.error!.type} - ${event.error!.description}');
      } else if (event.failure != null) {
        print('[CAF SDK EVENT]: failure');
        print('[CAF SDK EVENT]: ${event.failure!.type} - ${event.failure!.description}');
      } else if (event.log != null) {
        print('[CAF SDK EVENT]: log');
        print('[CAF SDK EVENT]: ${event.log!.level} - ${event.log!.message}');
      } else if (event.cancelled) {
        print('[CAF SDK EVENT]: cancelled');
      }
    });
  }

  Future<void> _initializeCafSdk() async {
    try {
      // SDK Configuration
      final config = CafSdkConfiguration(
        mobileToken: _mobileToken,
        personId: _personId,
        environment: _environment,
        configuration: CafSdkBuilderConfiguration(
          presentationOrder: [
            CafModuleType.faceLivenessUi,
            CafModuleType.documentDetectorUi,
          ],
          enableSecurityModule: true,
          waitForAllServices: true,
        ),
      );

      // Face Liveness UI Configuration
      CafFaceLivenessUIConfiguration?
      faceLivenessUIConfig = CafFaceLivenessUIConfiguration(
        loading: true,
        debugModeEnabled: true,
        payFaceDebugMode: false,
        instructionScreen:
            CafFaceLivenessUIInstructionScreenConfiguration(
              title: "Face Liveness",
              description:
                  "Position your face in the center and follow the instructions",
              steps: [
                "Center your face",
                "Follow the instructions",
                "Stay still",
              ],
              buttonText: "Start",
            ),
      );

      // Document Detector UI Configuration
      CafDocumentDetectorUIConfiguration?
      documentDetectorUIConfig = CafDocumentDetectorUIConfiguration(
        flow: [
          CafDocumentDetectorFlow(document: CafDocument.rgFront),
          CafDocumentDetectorFlow(document: CafDocument.rgBack),
          CafDocumentDetectorFlow(document: CafDocument.rgFull),
          CafDocumentDetectorFlow(document: CafDocument.cnhFront),
          CafDocumentDetectorFlow(document: CafDocument.cnhBack),
          CafDocumentDetectorFlow(document: CafDocument.cnhFull),
          CafDocumentDetectorFlow(document: CafDocument.crlv),
          CafDocumentDetectorFlow(document: CafDocument.rneFront),
          CafDocumentDetectorFlow(document: CafDocument.rneBack),
          CafDocumentDetectorFlow(document: CafDocument.ctpsFront),
          CafDocumentDetectorFlow(document: CafDocument.ctpsBack),
          CafDocumentDetectorFlow(document: CafDocument.passport),
          CafDocumentDetectorFlow(document: CafDocument.any),
        ],
        uploadSettings: CafDocumentDetectorUploadSettings(enable: true),
        manualCaptureEnabled: false,
        manualCaptureTime: 45,
        showPopup: true,
        previewShow: false,
        securitySettings: CafDocumentDetectorSecuritySettings(
          useAdb: true,
          useDebug: true,
          useDevelopmentMode: true,
        ),
        instructionScreen:
            CafDocumentDetectorUIInstructionScreenConfiguration(
              enable: true,
              captureTitle: "Capture Title",
              captureSteps: ["Capture Step 1", "Capture Step 2"],
              uploadTitle: "Upload Title",
              uploadSteps: ["Upload Step 1", "Upload Step 2"],
              buttonText: "Button Text",
            ),
        documentSelectionScreen:
            CafDocumentDetectorUIDocumentSelectionScreenConfiguration(
              title: "Document Selection Title",
              description: "Document Selection Description",
            ),
      );

      await _cafSdk.initializeCafSdk(
        cafSdkConfiguration: config,
        documentDetectorUIConfiguration: documentDetectorUIConfig,
        faceLivenessUIConfiguration: faceLivenessUIConfig,
      );

      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('CAF SDK initialized successfully!'),
            backgroundColor: Colors.green,
          ),
        );
      }
    } catch (e) {
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('Error: $e'), backgroundColor: Colors.red),
        );
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('CAF SDK Example'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            _ConfigurationCard(
              mobileToken: _mobileToken,
              personId: _personId,
              environment: _environment,
            ),
            const SizedBox(height: 16),

            _ActionButtons(onInitialize: _initializeCafSdk),
            const SizedBox(height: 16),
          ],
        ),
      ),
    );
  }
}

class _ConfigurationCard extends StatelessWidget {
  const _ConfigurationCard({
    required this.mobileToken,
    required this.personId,
    required this.environment,
  });

  final String mobileToken;
  final String personId;
  final CafEnvironment environment;

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              'CAF SDK Configuration',
              style: Theme.of(context).textTheme.titleLarge,
            ),
            const SizedBox(height: 16),
            Text(
              'Mobile Token: ${mobileToken.length > 20 ? '${mobileToken.substring(0, 20)}...' : mobileToken}',
              style: Theme.of(context).textTheme.bodyMedium,
            ),
            const SizedBox(height: 8),
            Text(
              'Person ID: $personId',
              style: Theme.of(context).textTheme.bodyMedium,
            ),
            const SizedBox(height: 8),
            Text(
              'Environment: $environment',
              style: Theme.of(context).textTheme.bodyMedium,
            ),
          ],
        ),
      ),
    );
  }
}

class _ActionButtons extends StatelessWidget {
  const _ActionButtons({
    required this.onInitialize,
  });

  final VoidCallback onInitialize;

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Expanded(
          child: ElevatedButton(
            onPressed: onInitialize,
            style: ElevatedButton.styleFrom(
              padding: const EdgeInsets.symmetric(vertical: 16),
              shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(8),
              ),
            ),
            child: Text('Initialize SDK'),
          ),
        ),
        const SizedBox(width: 8),
      ],
    );
  }
}
```

***

## ProGuard/R8 Rules

Add these ProGuard/R8 rules to your `proguard-rules.pro` file for Android:

```proguard
# Flutter ProGuard/R8 rules for Caf SDK
# These rules ensure the CAF SDK plugin works correctly in release builds

# The Android pre-handler for exceptions is loaded reflectively (via ServiceLoader).
-keep class kotlinx.coroutines.experimental.android.AndroidExceptionPreHandler { *; }

### GSON ##################################################################
# Gson uses generic type information stored in a class file when working with fields.
# ProGuard removes such information by default, so configure it to keep all of it.
-keepattributes Signature
# For using GSON @Expose annotation
-keepattributes *Annotation*
### END GSON ##################################################################

### Retrofit ##################################################################
# Preserve generic signatures, inner classes, and enclosing methods for Retrofit reflection.
-keepattributes Signature, InnerClasses, EnclosingMethod
# Retain runtime-visible annotations on methods and parameters.
-keepattributes RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations
# Keep annotation default values.
-keepattributes AnnotationDefault
# Retain service method parameters for interfaces with Retrofit annotations.
-keepclassmembers,allowshrinking,allowobfuscation interface * {
    @retrofit2.http.* <methods>;
}
# Suppress warnings for build tooling and certain JSR 305 annotations.
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
-dontwarn javax.annotation.**
-dontwarn kotlin.Unit
-dontwarn retrofit2.KotlinExtensions
-dontwarn retrofit2.KotlinExtensions$*
# Explicitly keep Retrofit interfaces to prevent nullification by R8.
-if interface * { @retrofit2.http.* <methods>; }
-keep,allowobfuscation interface <1>
-if interface * { @retrofit2.http.* <methods>; }
-keep,allowobfuscation interface * extends <1>
# Preserve continuations used by Kotlin suspend functions.
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
# For R8 full mode: keep generic return types for Retrofit methods.
-if interface * { @retrofit2.http.* public *** *(...); }
-keep,allowoptimization,allowshrinking,allowobfuscation class <3>
# Preserve Retrofit Response class.
-keep,allowobfuscation,allowshrinking class retrofit2.Response
### END Retrofit ##############################################################

### OkHttp ####################################################################
# Suppress warnings for JSR 305 annotations.
-dontwarn javax.annotation.**
# Adapt resource filenames for internal public suffix database.
-adaptresourcefilenames okhttp3/internal/publicsuffix/PublicSuffixDatabase.gz
# Suppress warnings for Animal Sniffer and platform-specific classes.
-dontwarn org.codehaus.mojo.animal_sniffer.*
-dontwarn okhttp3.internal.platform.**
-dontwarn org.conscrypt.**
-dontwarn org.bouncycastle.**
-dontwarn org.openjsse.**
# Keep all OkHttp and Okio classes.
-keep class okhttp3.** { *; }
-dontwarn okhttp3.**
-keep class okio.** { *; }
-dontwarn okio.**
-dontwarn javax.annotation.Nullable
-dontwarn javax.annotation.ParametersAreNonnullByDefault
### END OkHttp ################################################################

### Kotlin Serialization ######################################################
# Keep Companion objects for serializable classes.
-if @kotlinx.serialization.Serializable class **
-keepclassmembers class <1> {
    static <1>$Companion Companion;
}
# Keep serializer functions on companion objects.
-if @kotlinx.serialization.Serializable class ** {
    static **$* *;
}
-keepclassmembers class <2>$<3> {
    kotlinx.serialization.KSerializer serializer(...);
}
# Retain INSTANCE and serializer for serializable objects.
-if @kotlinx.serialization.Serializable class ** {
    public static ** INSTANCE;
}
-keepclassmembers class <1> {
    public static <1> INSTANCE;
    kotlinx.serialization.KSerializer serializer(...);
}
# Preserve Companion objects in kotlinx.serialization.json.
-keepclassmembers class kotlinx.serialization.json.** {
    *** Companion;
}
-keepclasseswithmembers class kotlinx.serialization.json.** {
    kotlinx.serialization.KSerializer serializer(...);
}
# Preserve serializer lookup for serializable classes (adjust package name as needed).
-keepclassmembers @kotlinx.serialization.Serializable class packeage.** {
    *** Companion;
    *** INSTANCE;
    kotlinx.serialization.KSerializer serializer(...);
}
### END Kotlin Serialization #################################################

### AutoValue ################################################################
-dontwarn com.google.auto.**
-dontwarn autovalue.shaded.com.**
-dontwarn sun.misc.Unsafe
-dontwarn javax.lang.model.element.Modifier
### END AutoValue ############################################################

### Flutter Plugin ###########################################################
# Keep Flutter plugin classes
-keep class io.flutter.plugins.** { *; }

# Keep method channel classes
-keep class * extends io.flutter.plugin.common.MethodChannel$MethodCallHandler { *; }
-keep class * extends io.flutter.plugin.common.MethodChannel$Result { *; }

# Keep plugin registrar
-keep class io.flutter.plugins.GeneratedPluginRegistrant { *; }

# Keep CAF SDK Flutter plugin classes
-keep class io.caf.sdk.flutter.** { *; }
### END Flutter Plugin #######################################################

### CAF - Combate a Fraude ######################################################
# Keep exceptions attributes.
-keepattributes Exceptions
# Preserve all classes, interfaces, and class members for CAF modules.
-keep class com.caf.facelivenessiproov.** { *; }
-keep class com.combateafraude.documentdetector.** { *; }
-keep class com.combateafraude.** { *; }
-keep interface com.combateafraude.** { *; }
-keep class io.caf.** { *; }
-keep interface io.caf.** { *; }
-keepclassmembers class com.combateafraude.** { *; }
# Suppress warnings for java.nio.file and certain OkHttp internal classes.
-dontwarn java.nio.file.*
-dontwarn com.squareup.okhttp.internal.Platform
# Keep fields in classes extending GeneratedMessageLite (for Tink usage).
-keepclassmembers class * extends com.google.crypto.tink.shaded.protobuf.GeneratedMessageLite {
  <fields>;
}
# Preserve TensorFlow classes.
-keep class org.tensorflow.** { *; }
-keep class org.tensorflow.**$* { *; }
-dontwarn org.tensorflow.**
# Preserve IProov classes and Protobuf classes.
-keep public class com.iproov.sdk.IProov { public *; }
-keep class com.iproov.** { *; }
-keep class com.iproov.**$* { *; }
-keep class com.google.protobuf.** { *; }
-keep class com.google.protobuf.**$* { *; }
-dontwarn com.google.protobuf.**
# Suppress warnings for concurrent Flow classes.
-dontwarn java.util.concurrent.Flow*
# Preserve Kotlin and kotlinx classes.
-keep class kotlin.** { *; }
-keep class kotlinx.** { *; }
# Preserve all FortFace/PayFace SDK classes to prevent obfuscation issues
-keep class br.com.fortface.** { *; }
-keep interface br.com.fortface.** { *; }
-keepclassmembers class br.com.fortface.** { *; }
# Keep all inner classes and enums
-keepclassmembers class br.com.fortface.**$* { *; }
# Preserve JSON classes completely (critical for PayFace)
-keep class org.json.** { *; }
-keepclassmembers class org.json.** { *; }
# Preserve JSON-related classes used by PayFace SDK
-keepclassmembers class * {
    @org.json.** *;
}
# Suppress warnings for PayFace SDK
-dontwarn br.com.fortface.**
-dontwarn com.android.tools.lint.**
-dontwarn io.caf.sdk.common.jvmshared.lint.**
### END CAF - Combate a Fraude ##################################################
```

***

## Technical Support and Usage Tips

**Technical Support** If you have any questions or difficulties with the integration, contact Caf's technical support.

**Usage Tips**

* **Run tests:** Perform tests on real devices to validate requirements and flow performance
* **Explore customizations:** Use advanced customization options to tailor the flow to your project's needs
* **Monitor performance:** Integrate monitoring tools to track logs and the flow's performance in production
* **Handle errors gracefully:** Implement proper error handling for all possible error and failure scenarios
* **Test with different devices:** Ensure compatibility across various device specifications and screen sizes

***

## Release notes

Release notes for all `caf_sdk` versions, including **2.4.0 (Certta DevX)** and the 1.x → 2.0.0 migration guide, live in the changelog.

{% content-ref url="/pages/HRLcWgCbYm99BQXhyQkh" %}
[Changelog](/caf-sdk/flutter/changelog.md)
{% endcontent-ref %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.caf.io/caf-sdk/flutter/getting-started-with-the-sdk.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
