> ## Documentation Index
> Fetch the complete documentation index at: https://docs.didit.me/llms.txt
> Use this file to discover all available pages before exploring further.

# SDK KYC para Flutter: verificación móvil en Dart

> SDK KYC de Didit para Flutter (Dart) con iOS y Android nativos, liveness biométrica y NFC de pasaporte. Desde $0.33 por KYC completo, $10 gratis/mes.



Prompt de integración del SDK de Flutter
        Copiar el prompt al portapapeles



      
      

\<pre>

\`Integrate Didit identity verification into my Flutter app using the native SDK.

\## Requirements
\- Flutter 3.3+, Dart 3.11+
\- iOS 13.0+ (NFC requires iOS 15+)
\- Android API 23+ (Android 6.0)

\## Installation
flutter pub add didit_sdk

iOS — the plugin picks the native variant from the $DiditSdkIosVariant Ruby global in ios/Podfile (or the DIDIT_SDK_IOS_VARIANT env var); valid values are 'all' (default), 'core', 'autodetection', 'nfc'. At the top of ios/Podfile:
$DiditSdkIosVariant = 'all'
platform :ios, ['all', 'nfc'].include?($DiditSdkIosVariant) ? '15.0' : '13.0'
Inside the target block, add the matching subspec:
pod 'DiditSDK/All', :podspec => 'https://raw.githubusercontent.com/didit-protocol/sdk-ios/4.9.1/DiditSDK.podspec'
The tag in that URL must equal the native iOS SDK version the didit_sdk release pins (4.9.1 pins 4.9.1); do not use 'main', because the plugin requires that exact version and pod install fails when the URL resolves to a different one.
'all' and 'nfc' require platform :ios, '15.0'; 'core' and 'autodetection' run on iOS 13.0+.
Then: cd ios && pod install
Info.plist must include NSCameraUsageDescription, NSMicrophoneUsageDescription, NSPhotoLibraryUsageDescription (required) and NFCReaderUsageDescription if using NFC.

Android — for no-NFC builds add to android/gradle.properties:
diditSdkAndroidNfcEnabled=false
For NFC-enabled (default), add to android/app/build.gradle.kts inside android block:
packaging {
  resources {
      pickFirsts += "META-INF/versions/9/OSGI-INF/MANIFEST.MF"
  }
}

\## Backend: Create Session (your server)
POST https://verification.didit.me/v3/session/
Headers: { "x-api-key": DIDIT_API_KEY, "Content-Type": "application/json" }
Body: { "workflow_id": DIDIT_WORKFLOW_ID, "vendor_data": "user-id" }
Response: { "session_id": "uuid", "session_token": "short-token", "url": "https://verify.didit.me/session/short-token", "status": "Not Started" }
Send session_token to the Flutter app (never expose DIDIT_API_KEY to the device).

\## Integration
import 'package:didit_sdk/sdk_flutter.dart';

// Option A: With session token from your backend (recommended)
final result = await DiditSdk.startVerification('session-token-from-backend');

// Option B: With workflow ID (SDK creates session internally)
final result = await DiditSdk.startVerificationWithWorkflow('your-workflow-id',
vendorData: 'user-123',
);

// Handle result
switch (result) {
case VerificationCompleted(:final session):
if (session.status == VerificationStatus.approved) { /* grant access */ }
case VerificationCancelled():
// User dismissed the verification flow
case VerificationFailed(:final error):
// SDK error — show error.message
}

\## Result Statuses (VerificationStatus enum)
\- VerificationStatus.approved — user identity verified, grant access
\- VerificationStatus.declined — verification failed, show retry or contact support
\- VerificationStatus.pending  — needs manual review by your compliance team

\## Error Types (VerificationErrorType enum)
sessionExpired, networkError, cameraAccessDenied, notInitialized, apiError, retryBlocked, unknown

\## Advanced Session Parameters
contact_details, expected_details, and metadata are only available via the Session Token method (your backend creates the session). The Dart startVerificationWithWorkflow API only accepts workflowId, vendorData, and config.

\## Full Verification Results
The SDK only returns the status. Full decision data (document fields, face match scores, AML hits, etc.) arrives via webhook to your backend.
Set up webhooks: https://docs.didit.me/integration/webhooks

\## Rate Limits (session creation)
\- 600 session-create requests / minute per x-api-key (see https://docs.didit.me/integration/rate-limiting)
\- On 429, retry honouring Retry-After / X-RateLimit-* headers

\## Environment Variables (backend only — never expose in the app)
\- DIDIT_API_KEY — from Didit Console > API & Webhooks
\- DIDIT_WORKFLOW_ID — from Didit Console > Workflows

\## Docs
\- Flutter SDK: https://docs.didit.me/integration/native-sdks/flutter-sdk
\- GitHub: https://github.com/didit-protocol/sdk-flutter
\- pub.dev: https://pub.dev/packages/didit_sdk
\- Webhooks: https://docs.didit.me/integration/webhooks
\- API Reference: https://docs.didit.me/api-reference/overview`

\</pre>



      
      

Pegar en

Un plugin multiplataforma de Flutter que encapsula los SDK nativos de iOS y Android y ofrece una API de Dart unificada para la verificación de identidad.> [card] [Repositorio de GitHub](https://github.com/didit-protocol/sdk-flutter)
>
> Consulta el código fuente y los ejemplos en GitHub> [card] [Paquete de pub.dev](https://pub.dev/packages/didit_sdk)
>
> didit\_sdk> [!NOTE]
>
> ### **SDK nativo**: Es el enfoque recomendado para apps de Flutter. El SDK envuelve los SDK nativos de iOS y Android mediante platform channels para ofrecer la mejor experiencia de usuario, una gestión optimizada de la cámara y compatibilidad completa con NFC.***## Requisitos| Requisito | Versión mínima |
| - | - |
| Flutter | 3.3+ |
| Dart | 3.11+ |### Requisitos de plataforma| Plataforma | Versión mínima | Notas |
| - | - | - |
| iOS | 13.0+ | La lectura NFC de pasaportes requiere iOS 15.0+ |
| Android | API 23+ (6.0) | Java 17+ |> [!NOTE]
>
> **El consentimiento biométrico requiere la versión nativa 4.10.0 o posterior.** Para las personas amparadas por la legislación de Illinois, Texas o Colorado, Didit puede solicitar un [consentimiento biométrico](https://docs.didit.me/es/getting-started/biometric-consent.md) explícito antes de la primera captura facial. Las versiones del SDK de Flutter que fijan los SDK nativos de iOS y Android en 4.10.0 o posterior muestran ese paso. Las versiones anteriores no pueden mostrarlo: sus sesiones se ejecutan como hasta ahora y la decisión indica que el consentimiento no se recopiló (`biometric_consent.reason: "BIOMETRIC_CONSENT_UNSUPPORTED_SDK"`). Actualiza si verificas a personas en esos estados.***## Instalación```bash theme={null}
flutter pub add didit_sdk
```O añádelo manualmente a tu `pubspec.yaml`:```yaml theme={null}
dependencies:
  didit_sdk: ^4.9.1
```Después ejecuta:```bash theme={null}
flutter pub get
```### Variantes del SDK nativo`didit_sdk` incluye el SDK nativo completo.
Para distribuir un binario más pequeño, usa como dependencia un paquete de variante: expone exactamente la misma API de Dart y fija el SDK nativo correspondiente en ambas plataformas:| Paquete | Captura automática | Lectura NFC de pasaportes | iOS mínimo |
| - | - | - | - |
| `didit_sdk` (predeterminado) | Sí | Sí | 15.0 |
| `didit_sdk_core` | No | No | 13.0 |
| `didit_sdk_autodetection` | Sí | No | 13.0 |
| `didit_sdk_nfc` | No | Sí | 15.0 |```yaml theme={null}
dependencies:
  didit_sdk_core: ^4.9.1
```Usa como dependencia exactamente uno de estos paquetes e importa desde el que hayas elegido, por ejemplo `package:didit_sdk_core/sdk_flutter.dart`.### Configuración de iOSEl plugin es compatible con los dos gestores de dependencias de iOS de Flutter: Swift Package Manager y CocoaPods.#### Swift Package ManagerSi tu versión de Flutter tiene habilitada la compatibilidad con Swift Package Manager (actívala con `flutter config --enable-swift-package-manager` en Flutter 3.24+), el plugin se integra automáticamente como paquete de Swift.
No hace falta configurar el Podfile: la dependencia nativa DiditSDK se resuelve desde GitHub por sí sola.
Establece en Xcode el deployment target de iOS de tu app (target Runner y proyecto) en el valor mínimo correspondiente al paquete elegido: 15.0 para `didit_sdk` y `didit_sdk_nfc`, 13.0 para `didit_sdk_core` y `didit_sdk_autodetection`.#### CocoaPodsCon `didit_sdk`, el plugin selecciona la variante del SDK nativo de iOS a partir de `$DiditSdkIosVariant`, una variable global de Ruby que defines en el `ios/Podfile` de tu app (el pod DiditSDK no está en el trunk de CocoaPods):```ruby theme={null}
# Pick one: 'all', 'core', 'autodetection', 'nfc'
$DiditSdkIosVariant = 'all'

didit_sdk_ios_deployment_target = ['all', 'nfc'].include?($DiditSdkIosVariant) ? '15.0' : '13.0'
platform :ios, didit_sdk_ios_deployment_target

didit_sdk_ios_pod = case $DiditSdkIosVariant
                    when 'all' then 'DiditSDK/All'
                    when 'core' then 'DiditSDK/Core'
                    when 'autodetection' then 'DiditSDK/AutoDetection'
                    when 'nfc' then 'DiditSDK/NFC'
                    end
didit_sdk_ios_podspec = 'https://raw.githubusercontent.com/didit-protocol/sdk-ios/4.9.1/DiditSDK.podspec'

target 'Runner' do
  use_frameworks!

  pod didit_sdk_ios_pod, :podspec => didit_sdk_ios_podspec

  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end

post_install do |installer|
  installer.pods_project.targets.each do |target|
    flutter_additional_ios_build_settings(target)
    target.build_configurations.each do |config|
      config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = didit_sdk_ios_deployment_target
    end
  end
end
```Si usas un paquete de variante (`didit_sdk_core`, `didit_sdk_autodetection`, `didit_sdk_nfc`), este fija directamente su subspec nativo e ignora `$DiditSdkIosVariant`: mantén la línea `pod` y el deployment target de tu Podfile acordes con esa variante (por ejemplo, `pod 'DiditSDK/Core', :podspec => ...` con `platform :ios, '13.0'`).> [!NOTE]
>
> Las variantes `all` y `nfc` requieren un deployment target de iOS 15.0+; `core` y `autodetection` pueden usar iOS 13.0. Al cambiar de variante, limpia primero CocoaPods (`rm -rf Pods Podfile.lock`) para que no se reutilice la variante anterior del SDK.> [!WARNING]
>
> La etiqueta de la URL del podspec debe coincidir con la versión del SDK nativo de iOS que fija tu versión de `didit_sdk`
> (`4.9.1` fija la versión nativa `4.9.1`). Si la URL apunta a una etiqueta anterior, `pod install` no logra resolver la dependencia:
> CocoaPods encuentra un podspec que declara esa versión anterior, mientras que el plugin exige una coincidencia exacta.
> Cuando actualices `didit_sdk`, cambia la etiqueta de esta URL a la misma versión.### Configuración de AndroidDe forma predeterminada, el plugin depende del SDK completo de Android, que incluye NFC. Para compilar sin NFC, añade esto a `android/gradle.properties`:```properties theme={null}
diditSdkAndroidNfcEnabled=false
```Esto cambia la dependencia de `me.didit:didit-sdk` a `me.didit:didit-sdk-core`, lo que elimina el módulo lector NFC y sus dependencias JMRTD/SCUBA/BouncyCastle.Cuando NFC está habilitado (opción predeterminada), añade esta regla de empaquetado a `android/app/build.gradle.kts` para resolver un archivo de metadatos duplicado procedente de BouncyCastle:```kotlin theme={null}
android {
    packaging {
        resources {
            pickFirsts += "META-INF/versions/9/OSGI-INF/MANIFEST.MF"
        }
    }
}
```Sin esta regla, la compilación falla con un error `mergeDebugJavaResource`. La regla no es necesaria cuando `diditSdkAndroidNfcEnabled=false`.> [!NOTE]
>
> El SDK nativo de Android se resuelve desde un repositorio Maven remoto en GitHub (desde la versión 3.4.0 del plugin): no hace falta ninguna configuración manual de Maven en tu app.***## Permisos### iOSAñade las siguientes claves al `Info.plist` de tu aplicación:| Permiso | Clave de Info.plist | Descripción | Obligatorio |
| - | - | - | - |
| Cámara | `NSCameraUsageDescription` | Escaneo de documentos y verificación facial | Sí |
| Micrófono | `NSMicrophoneUsageDescription` | Grabación de video para las comprobaciones de prueba de vida | Sí |
| Fototeca | `NSPhotoLibraryUsageDescription` | Subida de documentos desde la galería del dispositivo | Sí |
| NFC | `NFCReaderUsageDescription` | Lectura de chips NFC en pasaportes y documentos de identidad | Si usas NFC |
| Ubicación | `NSLocationWhenInUseUsageDescription` | Geolocalización para la prevención del fraude | Opcional |> [!WARNING]
>
> Si falta alguna clave de privacidad obligatoria de iOS, iOS cierra la aplicación en cuanto el SDK intenta acceder a ese recurso protegido. Por ejemplo, si falta `NSCameraUsageDescription`, la aplicación falla cuando el usuario pulsa el botón para tomar fotos de la cámara de documentos.#### Configuración de NFCPara habilitar la lectura NFC de pasaportes y documentos de identidad con chip:1) **Añade la capacidad NFC** en Xcode:
   * Selecciona tu target > **Signing & Capabilities** > **+ Capability** > **Near Field Communication Tag Reading**

2) **Añade los identificadores ISO7816** a `Info.plist`:```xml theme={null}
<key>com.apple.developer.nfc.readersession.iso7816.select-identifiers</key>
<array>
    <string>D23300000045737445494420763335</string>
    <string>A0000002471001</string>
    <string>A0000002472001</string>
    <string>00000000000000</string>
    <string>D392F000260100000001</string>
</array>
````D392F000260100000001` permite que el SDK lea la tarjeta My Number de Japón mediante [JPKI](https://docs.didit.me/es/core-technology/nfc-verification/overview.md#my-number-card-de-japón-jpki): sin él, iOS no comunica la tarjeta al SDK. La lectura de la tarjeta My Number todavía no está habilitada en producción, y el identificador no afecta a los demás documentos, así que puedes añadirlo ya.3) **Añade un archivo de entitlements** con la lectura de etiquetas NFC habilitada:```xml theme={null}
<key>com.apple.developer.nfc.readersession.formats</key>
<array>
    <string>TAG</string>
</array>
```Asegúrate de que el perfil de aprovisionamiento de la aplicación incluya la capacidad NFC Tag Reading. Si el bundle ID no está configurado para NFC en tu cuenta de Apple Developer, Xcode fallará al firmar por falta del entitlement `com.apple.developer.nfc.readersession.formats`.Esta configuración de NFC no es necesaria para las variantes `core` o `autodetection` (`$DiditSdkIosVariant`), ni para los paquetes `didit_sdk_core` / `didit_sdk_autodetection`: ninguno de ellos lee chips.### AndroidLos siguientes permisos están declarados en el `AndroidManifest.xml` del SDK y se fusionan automáticamente:| Permiso | Descripción | Obligatorio |
| - | - | - |
| `INTERNET` | Acceso a la red para la comunicación con la API | Sí |
| `ACCESS_NETWORK_STATE` | Detección de la disponibilidad de red | Sí |
| `CAMERA` | Escaneo de documentos y verificación facial | Sí |
| `NFC` | Lectura de chips NFC en pasaportes y documentos de identidad | Si usas NFC |Las características de hardware de cámara y NFC se declaran como opcionales (`android:required="false"`), de modo que tu aplicación puede instalarse en dispositivos que no las tengan. Cuando `diditSdkAndroidNfcEnabled=false`, el SDK no añade el permiso ni la característica NFC de Android.#### Permisos en tiempo de ejecuciónEl SDK gestiona automáticamente las solicitudes de permisos en tiempo de ejecución de Android. Cuando el usuario llega a un paso que requiere acceso a la cámara:1) El SDK solicita el permiso de cámara si aún no se ha concedido
2) Si el usuario **deniega** el permiso, se muestra un mensaje de error con un botón **Try Again**
3) Si el usuario **concede** el permiso, el flujo de verificación continúaNo necesitas solicitar el permiso de cámara en el código de tu aplicación antes de invocar `startVerification()`: el SDK lo gestiona internamente.***## Inicio rápido```dart theme={null}
import 'package:didit_sdk/sdk_flutter.dart';

// Start verification with a session token from your backend
final result = await DiditSdk.startVerification('your-session-token');

switch (result) {
  case VerificationCompleted(:final session):
    if (session.status == VerificationStatus.approved) {
      print('Identity verified!');
    }
  case VerificationCancelled():
    print('User cancelled');
  case VerificationFailed(:final error):
    print('Error: ${error.message}');
}
```***## Métodos de integraciónEl SDK admite dos métodos de integración:### Método 1: token de sesión (recomendado para producción)Crea una sesión en tu backend con la [API de creación de sesiones de verificación](https://docs.didit.me/es/sessions-api/create-session.md) y luego pasa el token al SDK:```dart theme={null}
import 'package:didit_sdk/sdk_flutter.dart';

// Your backend creates a session and returns the token
final sessionToken = await yourBackend.createVerificationSession(userId);

// Pass the token to the SDK
final result = await DiditSdk.startVerification(sessionToken);
```Este enfoque te da control total sobre:- La asociación de las sesiones con tus usuarios (`vendor_data`)
- La definición de metadatos personalizados
- La configuración de callbacks por sesión### Método 2: ID de workflow (integración más sencilla)Para integraciones más sencillas, el SDK puede crear sesiones directamente con el ID de tu flujo de verificación (workflow), sin necesidad de backend:```dart theme={null}
import 'package:didit_sdk/sdk_flutter.dart';

final result = await DiditSdk.startVerificationWithWorkflow(
  'your-workflow-id',
  vendorData: 'user-123',
  config: const DiditConfig(loggingEnabled: true),
);
```> [!NOTE]
>
> Los parámetros avanzados de sesión (`contact_details`, `expected_details`, `metadata`) **solo** se admiten con el método de token de sesión, en el que tu backend llama a la [API de creación de sesiones](https://docs.didit.me/es/sessions-api/create-session.md) que admite todos los parámetros. Pasa el `session_token` devuelto a `DiditSdk.startVerification()`.***## ConfiguraciónPersonaliza el comportamiento del SDK pasando un objeto `DiditConfig`:```dart theme={null}
final result = await DiditSdk.startVerification(
  'your-session-token',
  config: const DiditConfig(
    languageCode: 'es',       // Force Spanish language
    showLanguageSelector: true, // Let the user change language
    fontFamily: 'Avenir',     // Custom font
    loggingEnabled: true,     // Enable debug logging
  ),
);
```Con `startVerificationWithWorkflow`, pasa `config` como parámetro con nombre:```dart theme={null}
final result = await DiditSdk.startVerificationWithWorkflow(
  'your-workflow-id',
  vendorData: 'user-123',
  config: const DiditConfig(
    languageCode: 'es',
    showLanguageSelector: true,
    loggingEnabled: true,
  ),
);
```### Opciones de configuración| Propiedad | Tipo | Valor por defecto | Descripción |
| - | - | - | - |
| `languageCode` | `String?` | Configuración regional del dispositivo | Código de idioma ISO 639-1 (p. ej., `"en"`, `"fr"`, `"ar"`) |
| `showLanguageSelector` | `bool` | `false` | Muestra el selector de idioma de estilo web en la pantalla de bienvenida |
| `fontFamily` | `String?` | Fuente del sistema | Nombre de la familia tipográfica personalizada (debe estar registrada de forma nativa) |
| `loggingEnabled` | `bool` | `false` | Activa los registros de depuración del SDK |
| `showCloseButton` | `bool` | `true` | Muestra el botón de cierre (X) en las pantallas de los pasos de verificación |
| `showExitConfirmation` | `bool` | `true` | Muestra un cuadro de diálogo de confirmación cuando el usuario intenta salir |
| `closeOnComplete` | `bool` | `false` | Cierra automáticamente la interfaz de verificación al completarse |
| `defaultDocumentCamera` | `CameraLens?` | Valor nativo por defecto (`CameraLens.back`) | Lente que se usa al abrirse la pantalla de captura del documento |
| `defaultLivenessCamera` | `CameraLens?` | Valor nativo por defecto (`CameraLens.front`) | Lente que se usa al abrirse la pantalla de captura facial (prueba de vida) |
| `showDocumentCameraSwitchButton` | `bool` | `true` | Muestra el selector de cámara durante la captura en la pantalla de captura del documento |
| `showLivenessCameraSwitchButton` | `bool` | `true` | Muestra el selector de cámara durante la captura en la pantalla de captura facial |### Opciones de cámaraLas opciones de cámara coinciden con las [opciones de cámara del Web SDK](https://docs.didit.me/es/integration/web-sdks/javascript-sdk.md#opciones-de-cámara).
Cada lente es `CameraLens.front` o `CameraLens.back`.
Si el dispositivo no tiene la lente solicitada, el SDK usa la primera cámara disponible.
El selector de cámara también se oculta en los dispositivos que solo tienen una cámara.
Establece una opción del selector en `false` para restringir al usuario a la lente por defecto de ese paso.```dart theme={null}
config: const DiditConfig(
  defaultLivenessCamera: CameraLens.back,
  showLivenessCameraSwitchButton: false,
),
```> [!NOTE]
>
> **Temas y colores**: los colores, los fondos y la configuración de la pantalla inicial se definen en tu [configuración de Marca blanca](https://business.didit.me) en la Didit Console, no en la configuración del SDK. Así se garantiza una imagen de marca coherente en todas las plataformas.***## Idiomas compatiblesEl SDK es compatible con **54 idiomas**. Si no se especifica ningún idioma, el SDK usa la configuración regional del dispositivo, con inglés como alternativa.```dart theme={null}
// Use device locale (default)
await DiditSdk.startVerification(token);

// Force specific language
await DiditSdk.startVerification(token,
  config: const DiditConfig(languageCode: 'fr'),
);

// Let the user change language on the welcome screen
await DiditSdk.startVerification(token,
  config: const DiditConfig(showLanguageSelector: true),
);
```**[Ver todos los idiomas compatibles →](https://docs.didit.me/es/integration/supported-languages.md)*****## Parámetros avanzados de sesiónLos parámetros como `contact_details`, `expected_details` y `metadata` **solo** son compatibles mediante el método de token de sesión. Tu backend invoca [`POST /v3/session/`](https://docs.didit.me/es/sessions-api/create-session) con compatibilidad con todos los parámetros y, después, pasa el `session_token` devuelto al SDK.```dart theme={null}
// Your backend handles the full session creation:
// POST /v3/session/ with contact_details, expected_details, metadata, etc.
final sessionToken = await yourBackend.createSession(userId);

// The SDK only needs the token
final result = await DiditSdk.startVerification(sessionToken);
```> [!NOTE]
>
> El método `startVerificationWithWorkflow` de Dart solo acepta `workflowId`, `vendorData` y `config`. Las versiones anteriores a la 3.4.0 exponían los parámetros `contactDetails`, `expectedDetails` y `metadata`, pero se eliminaron porque el endpoint de Unilink no los tiene en cuenta.***## Gestión de resultadosTanto `startVerification` como `startVerificationWithWorkflow` devuelven un `Future<VerificationResult>`. El resultado es una clase sellada: usa la coincidencia de patrones para determinar el desenlace.### Casos de resultado| Caso | Descripción |
| - | - |
| `VerificationCompleted` | El flujo de verificación se completó (consulta `session.status` para conocer el resultado) |
| `VerificationCancelled` | El usuario canceló el flujo de verificación |
| `VerificationFailed` | Se produjo un error durante la verificación |### Propiedades de SessionData| Propiedad | Tipo | Descripción |
| - | - | - |
| `sessionId` | `String` | Identificador único de la sesión |
| `status` | `VerificationStatus` | `approved`, `pending` o `declined` |### Tipos de errorEl enum `VerificationErrorType` expone los siguientes casos:| Error | Descripción |
| - | - |
| `sessionExpired` | La sesión ha expirado |
| `networkError` | Problema de conectividad de red |
| `cameraAccessDenied` | No se concedió el permiso de cámara |
| `notInitialized` | SDK no inicializado (solo Android) |
| `apiError` | La solicitud a la API falló |
| `retryBlocked` | Se superó el número máximo de reintentos para el paso actual |
| `unknown` | Otro error: consulta `error.message` para ver los detalles |### Pérdida de conexiónSi el dispositivo pierde la conexión durante la verificación, el SDK muestra una pantalla de "Sin conexión a internet" en lugar de dejar un paso cargando.
Sigue comprobando la conexión en segundo plano y cierra la pantalla por sí solo en cuanto vuelve a poder comunicarse con Didit, y el usuario continúa desde el paso en el que estaba.
La pantalla ofrece **Try Again** y, salvo que el botón de cierre esté desactivado, **Exit Verification**.
Al salir, la verificación termina con un resultado `VerificationFailed` cuyo tipo de error es `networkError`, para que tu aplicación sepa que el usuario salió debido a la pérdida de conexión.### Ejemplo completo de gestión de resultados```dart theme={null}
import 'package:didit_sdk/sdk_flutter.dart';

Future<void> verify(String token) async {
  final result = await DiditSdk.startVerification(token);

  switch (result) {
    case VerificationCompleted(:final session):
      switch (session.status) {
        case VerificationStatus.approved:
          print('Approved! Session: ${session.sessionId}');
          // User is verified — grant access
        case VerificationStatus.pending:
          print('Under review. Session: ${session.sessionId}');
          // Show "verification in progress" UI
        case VerificationStatus.declined:
          print('Declined. Session: ${session.sessionId}');
          // Handle declined verification
      }

    case VerificationCancelled(:final session):
      print('User cancelled.');
      if (session != null) {
        print('Session: ${session.sessionId}');
      }
      // Maybe show retry option

    case VerificationFailed(:final error):
      print('Error [${error.type}]: ${error.message}');
      // Handle error — show retry or contact support
  }
}
```***## Ejemplo de extremo a extremo (sesión en el backend → Flutter SDK → webhook)La integración lista para producción: tu backend crea la sesión, tu aplicación Flutter recibe el `session_token` y el SDK ejecuta el flujo. La decisión final se entrega a tu backend mediante webhook: nunca confíes únicamente en el resultado del lado del cliente.### 1. Backend: crea la sesión```ts theme={null}
// Node.js / Express example. Run on your server. NEVER ship DIDIT_API_KEY to the device.
import express from "express";

const app = express();
app.use(express.json());

app.post("/api/verification/start", async (req, res) => {
  const response = await fetch("https://verification.didit.me/v3/session/", {
    method: "POST",
    headers: {
      "x-api-key": process.env.DIDIT_API_KEY!,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      workflow_id: process.env.DIDIT_WORKFLOW_ID,
      vendor_data: req.body.userId, // your stable user id
    }),
  });

  if (!response.ok) {
    return res.status(500).json({ error: `Didit ${response.status}` });
  }

  const { session_id, session_token } = await response.json();
  // Persist session_id <-> userId so you can match the webhook later.
  res.json({ session_id, session_token });
});
```### 2. Flutter: intercambia el token e inicia el SDK```dart theme={null}
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:didit_sdk/sdk_flutter.dart';

class VerifyButton extends StatefulWidget {
  const VerifyButton({super.key, required this.userId});
  final String userId;

  @override
  State<VerifyButton> createState() => _VerifyButtonState();
}

class _VerifyButtonState extends State<VerifyButton> {
  bool _loading = false;

  Future<void> _verify() async {
    setState(() => _loading = true);
    try {
      final resp = await http.post(
        Uri.parse('https://your-backend.example.com/api/verification/start'),
        headers: const {'Content-Type': 'application/json'},
        body: jsonEncode({'userId': widget.userId}),
      );
      final sessionToken = (jsonDecode(resp.body) as Map)['session_token'] as String;

      final result = await DiditSdk.startVerification(sessionToken);

      switch (result) {
        case VerificationCompleted(:final session):
          if (session.status == VerificationStatus.approved) {
            // Optimistic UI; rely on the webhook delivered to your backend as the source of truth.
          }
        case VerificationCancelled():
          // User dismissed the flow
          break;
        case VerificationFailed(:final error):
          // Show retry — error.type / error.message
          break;
      }
    } finally {
      if (mounted) setState(() => _loading = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: _loading ? null : _verify,
      child: Text(_loading ? 'Starting...' : 'Verify identity'),
    );
  }
}
```### 3. Backend: recibe la decisión final mediante webhook```ts theme={null}
app.post("/api/webhooks/didit", express.raw({ type: "application/json" }), (req, res) => {
  // 1. Verify the X-Signature header (see Webhooks docs)
  // 2. Parse the body
  const event = JSON.parse(req.body.toString());
  if (event.webhook_type === "status.updated") {
    // event.session_id, event.status ("Approved" | "Declined" | "In Review" | ...)
    // Look up the user by session_id and update their verification state.
  }
  res.sendStatus(200);
});
```Consulta la [guía de webhooks](https://docs.didit.me/es/integration/webhooks.md) para ver los detalles de la verificación de firma HMAC.***## Ejemplo completo```dart theme={null}
import 'package:flutter/material.dart';
import 'package:didit_sdk/sdk_flutter.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Didit SDK Example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF1A1A1A)),
        useMaterial3: true,
      ),
      home: const VerificationScreen(),
    );
  }
}

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

  @override
  State<VerificationScreen> createState() => _VerificationScreenState();
}

class _VerificationScreenState extends State<VerificationScreen> {
  final _tokenController = TextEditingController();
  bool _loading = false;

  @override
  void dispose() {
    _tokenController.dispose();
    super.dispose();
  }

  Future<void> _startVerification() async {
    final token = _tokenController.text.trim();
    if (token.isEmpty) {
      _showAlert('Error', 'Please enter a session token.');
      return;
    }

    setState(() => _loading = true);

    try {
      final result = await DiditSdk.startVerification(
        token,
        config: const DiditConfig(loggingEnabled: true),
      );

      switch (result) {
        case VerificationCompleted(:final session):
          _showAlert(
            'Verification Complete',
            'Status: ${session.status.name}\nSession: ${session.sessionId}',
          );
        case VerificationCancelled():
          _showAlert('Cancelled', 'The user cancelled the verification.');
        case VerificationFailed(:final error):
          _showAlert('Failed', '${error.type.name}: ${error.message}');
      }
    } catch (e) {
      _showAlert('Error', 'Unexpected error: $e');
    } finally {
      setState(() => _loading = false);
    }
  }

  void _showAlert(String title, String message) {
    showDialog(
      context: context,
      builder: (ctx) => AlertDialog(
        title: Text(title),
        content: Text(message),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(ctx),
            child: const Text('OK'),
          ),
        ],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Padding(
          padding: const EdgeInsets.all(24),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              TextField(
                controller: _tokenController,
                decoration: InputDecoration(
                  hintText: 'Enter session token...',
                  border: OutlineInputBorder(
                    borderRadius: BorderRadius.circular(8),
                  ),
                ),
                autocorrect: false,
              ),
              const SizedBox(height: 16),
              ElevatedButton(
                onPressed: _loading ? null : _startVerification,
                style: ElevatedButton.styleFrom(
                  backgroundColor: const Color(0xFF1A1A1A),
                  foregroundColor: Colors.white,
                  padding: const EdgeInsets.symmetric(vertical: 16),
                  shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(8),
                  ),
                ),
                child: _loading
                    ? const SizedBox(
                        height: 20,
                        width: 20,
                        child: CircularProgressIndicator(
                          strokeWidth: 2,
                          color: Colors.white,
                        ),
                      )
                    : const Text(
                        'Start Verification',
                        style: TextStyle(fontWeight: FontWeight.w600),
                      ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
```***


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.