> ## 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 per a Flutter: verificació mòbil amb Dart

> SDK KYC de Flutter de Didit amb iOS i Android natius, prova de vida i NFC. Des de $0.33 per verificació KYC completa, $10 gratis al mes.



Prompt d'integració del SDK de Flutter
        Copia el prompt al porta-retalls



      
      

\<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.13.2/DiditSDK.podspec'
The tag in that URL must equal the native iOS SDK version the didit_sdk release pins (4.13.2 pins 4.13.2); 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>



      
      

Enganxa a

Un plugin multiplataforma de Flutter que encapsula els SDK natius d'iOS i Android i ofereix una API unificada en Dart per a la verificació d'identitat.> [card] [Repositori de GitHub](https://github.com/didit-protocol/sdk-flutter)
>
> Consulta el codi font i els exemples a GitHub> [card] [Paquet de pub.dev](https://pub.dev/packages/didit_sdk)
>
> didit\_sdk> [!NOTE]
>
> ### **SDK natiu**: Aquesta és l'opció recomanada per a les aplicacions de Flutter. El SDK encapsula els SDK natius d'iOS i Android mitjançant canals de plataforma per oferir la millor experiència d'usuari, una gestió de la càmera optimitzada i un suport complet per a NFC.***## Requisits| Requisit | Versió mínima |
| - | - |
| Flutter | 3.3+ |
| Dart | 3.11+ |### Requisits de plataforma| Plataforma | Versió mínima | Notes |
| - | - | - |
| iOS | 13.0+ | La lectura de passaports NFC requereix iOS 15.0+ |
| Android | API 23+ (6.0) | Java 17+ |> [!NOTE]
>
> **El consentiment biomètric necessita el SDK natiu 4.10.0 o posterior.** Per a persones cobertes per la legislació d'Illinois, Texas o Colorado, Didit pot demanar un [consentiment biomètric](https://docs.didit.me/ca/getting-started/biometric-consent.md) explícit abans de la primera captura facial. Una versió del SDK de Flutter que fixa el SDK natiu d'iOS i Android 4.10.0 o posterior mostra aquest pas. Les versions anteriors no poden mostrar-lo: les seves sessions funcionen com abans i la decisió indica que el consentiment no s'ha recollit (`biometric_consent.reason: "BIOMETRIC_CONSENT_UNSUPPORTED_SDK"`). Actualitza si verifiques persones en aquests estats.***## Instal·lació```bash theme={null}
flutter pub add didit_sdk
```O afegeix-lo manualment al teu `pubspec.yaml`:```yaml theme={null}
dependencies:
  didit_sdk: ^4.13.2
```Després, executa:```bash theme={null}
flutter pub get
```### Variants del SDK natiu`didit_sdk` inclou el SDK natiu complet.
Per reduir la mida del binari, depèn d'un paquet de variant en lloc d'aquest: exposa exactament la mateixa API de Dart i fixa el SDK natiu corresponent a totes dues plataformes.| Paquet | Captura automàtica | Lectura de passaports NFC | iOS mínim |
| - | - | - | - |
| `didit_sdk` (per defecte) | 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.13.2
```Depèn d'exactament un d'aquests paquets i importa des del que hagis triat, per exemple `package:didit_sdk_core/sdk_flutter.dart`.### Configuració d'iOSEl plugin admet els dos gestors de dependències d'iOS de Flutter: Swift Package Manager i CocoaPods.#### Swift Package ManagerSi la teva versió de Flutter té activat el suport de Swift Package Manager (activa-ho amb `flutter config --enable-swift-package-manager` a Flutter 3.24+), el plugin es consumeix automàticament com a paquet Swift.
No cal cap configuració del Podfile: la dependència nativa DiditSDK es resol sola des de GitHub.
Defineix l'objectiu de desplegament d'iOS de la teva aplicació a Xcode (target Runner i projecte) com a mínim per al paquet que hagis triat: 15.0 per a `didit_sdk` i `didit_sdk_nfc`, 13.0 per a `didit_sdk_core` i `didit_sdk_autodetection`.#### CocoaPodsAmb `didit_sdk`, el plugin selecciona la variant del SDK natiu d'iOS a partir de `$DiditSdkIosVariant`, una variable global de Ruby que defineixes al teu `ios/Podfile` (el pod DiditSDK no és al 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.13.2/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 uses un paquet de variant (`didit_sdk_core`, `didit_sdk_autodetection`, `didit_sdk_nfc`), aquest fixa directament el seu subspec natiu i ignora `$DiditSdkIosVariant`. Mantén la línia `pod` del teu Podfile i l'objectiu de desplegament coherents amb aquesta variant (per exemple `pod 'DiditSDK/Core', :podspec => ...` amb `platform :ios, '13.0'`).> [!NOTE]
>
> Les variants `all` i `nfc` requereixen un objectiu de desplegament d'iOS 15.0+; `core` i `autodetection` poden apuntar a iOS 13.0. En canviar de variant, neteja primer CocoaPods (`rm -rf Pods Podfile.lock`) perquè no es reutilitzi la variant anterior del SDK.> [!WARNING]
>
> L'etiqueta de la URL del podspec ha de coincidir amb la versió del SDK natiu d'iOS que fixa la teva versió de `didit_sdk`
> (`4.13.2` fixa el natiu `4.13.2`). Si la URL apunta a una etiqueta anterior, `pod install` falla en resoldre: CocoaPods troba un podspec que declara aquella versió anterior, mentre que el plugin requereix una coincidència exacta.
> En actualitzar `didit_sdk`, canvia l'etiqueta d'aquesta URL a la mateixa versió.### Configuració d'AndroidPer defecte, el plugin depèn del SDK complet d'Android, inclòs NFC. Per compilar sense NFC, afegeix això a `android/gradle.properties`:```properties theme={null}
diditSdkAndroidNfcEnabled=false
```Això canvia la dependència de `me.didit:didit-sdk` a `me.didit:didit-sdk-core`, i elimina el mòdul lector NFC i les seves dependències de JMRTD/SCUBA/BouncyCastle.Quan NFC està activat (per defecte), afegeix aquesta regla d'empaquetatge a `android/app/build.gradle.kts` per resoldre un fitxer de metadades duplicat de BouncyCastle:```kotlin theme={null}
android {
    packaging {
        resources {
            pickFirsts += "META-INF/versions/9/OSGI-INF/MANIFEST.MF"
        }
    }
}
```Sense aquesta regla, la compilació falla amb un error de `mergeDebugJavaResource`. La regla no cal quan `diditSdkAndroidNfcEnabled=false`.> [!NOTE]
>
> El SDK natiu d'Android es resol des d'un repositori Maven remot de GitHub (des del plugin 3.4.0): no cal cap configuració manual de Maven a la teva aplicació.***## Permisos### iOSAfegeix les claus següents al fitxer `Info.plist` de la teva aplicació:| Permís | Clau de Info.plist | Descripció | Obligatori |
| - | - | - | - |
| Càmera | `NSCameraUsageDescription` | Escaneig de documents i verificació facial | Sí |
| Micròfon | `NSMicrophoneUsageDescription` | Enregistrament de vídeo per a les proves de vida | Sí |
| Biblioteca de fotos | `NSPhotoLibraryUsageDescription` | Puja documents des de la galeria del dispositiu | Sí |
| NFC | `NFCReaderUsageDescription` | Llegeix els xips NFC dels passaports i de les targetes d'identitat | Si uses NFC |
| Ubicació | `NSLocationWhenInUseUsageDescription` | Geolocalització per a la prevenció del frau | Opcional |> [!WARNING]
>
> Si falta alguna clau de privacitat obligatòria d'iOS, iOS tanca l'aplicació en el moment en què el SDK intenta accedir a aquest recurs protegit. Per exemple, si falta `NSCameraUsageDescription`, l'aplicació es bloqueja quan l'usuari toca el botó de fer una foto de la càmera de documents.#### Configuració de NFCPer activar la lectura NFC de passaports i targetes d'identitat amb xip:1) **Afegeix la capacitat NFC** a Xcode:
   * Selecciona el teu objectiu > **Signing & Capabilities** > **+ Capability** > **Near Field Communication Tag Reading**

2) **Afegeix els identificadors 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` permet que el SDK llegeixi la targeta My Number del Japó a través de [JPKI](https://docs.didit.me/ca/core-technology/nfc-verification/overview.md#japan-my-number-card-jpki): sense aquest identificador, iOS no informa el SDK de la targeta. La lectura de My Number Card encara no està habilitada en producció, i l'identificador no canvia res per a la resta de documents, així que el pots afegir ara.3) **Afegeix un fitxer d'entitlements** amb la lectura d'etiquetes NFC activada:```xml theme={null}
<key>com.apple.developer.nfc.readersession.formats</key>
<array>
    <string>TAG</string>
</array>
```Assegura't que el perfil d'aprovisionament de l'aplicació inclou la capacitat NFC Tag Reading. Si el bundle ID no està configurat per a NFC al teu compte d'Apple Developer, Xcode fallarà en la signatura perquè falta el dret `com.apple.developer.nfc.readersession.formats`.Aquesta configuració de NFC no és necessària per a les variants `core` o `autodetection` (`$DiditSdkIosVariant`), ni per als paquets `didit_sdk_core` / `didit_sdk_autodetection`: cap d'aquests llegeix xips.### AndroidEls permisos següents es declaren al fitxer `AndroidManifest.xml` del SDK i es fusionen automàticament:| Permís | Descripció | Obligatori |
| - | - | - |
| `INTERNET` | Accés a la xarxa per a la comunicació amb l'API | Sí |
| `ACCESS_NETWORK_STATE` | Detecta la disponibilitat de la xarxa | Sí |
| `CAMERA` | Escaneig de documents i verificació facial | Sí |
| `NFC` | Llegeix els xips NFC dels passaports i de les targetes d'identitat | Si uses NFC |Les funcionalitats de maquinari de càmera i NFC es declaren com a opcionals (`android:required="false"`), de manera que la teva aplicació es pot instal·lar en dispositius sense aquestes funcionalitats. Quan `diditSdkAndroidNfcEnabled=false`, el SDK no afegeix el permís ni la funcionalitat NFC d'Android.#### Permisos en temps d'execucióEl SDK gestiona automàticament les sol·licituds de permisos en temps d'execució d'Android. Quan l'usuari arriba a un pas que requereix accés a la càmera:1) El SDK demana el permís de càmera si encara no s'ha concedit
2) Si l'usuari **denega** el permís, es mostra un missatge d'error amb un botó **Torna-ho a provar**
3) Si l'usuari **concedeix** el permís, el flux de verificació continuaNo cal que sol·licitis el permís de càmera al codi de la teva aplicació abans de cridar `startVerification()`: el SDK ho gestiona internament.***## Inici ràpid```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ètodes d'integracióEl SDK admet dos mètodes d'integració:### Mètode 1: token de sessió (recomanat per a producció)Crea una sessió al teu backend amb l'[API de creació de sessions de verificació](https://docs.didit.me/ca/sessions-api/create-session.md) i després passa 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);
```Aquest enfocament et dona control total sobre:- Associar les sessions amb els teus usuaris (`vendor_data`)
- Definir metadades personalitzades
- Configurar funcions de retorn per sessió### Mètode 2: ID del flux de treball (integració més senzilla)Per a integracions més senzilles, el SDK pot crear sessions directament amb el teu ID de flux de treball, sense necessitat 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]
>
> Els paràmetres avançats de sessió (`contact_details`, `expected_details`, `metadata`) només són compatibles amb el mètode de token de sessió, en què el teu backend crida l'[API de creació de sessions](https://docs.didit.me/ca/sessions-api/create-session.md) amb suport complet per als paràmetres. Passa el `session_token` retornat a `DiditSdk.startVerification()`.***## ConfiguracióPersonalitza el comportament de l'SDK passant un objecte `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
  ),
);
```Per a `startVerificationWithWorkflow`, passa `config` com a paràmetre amb nom:```dart theme={null}
final result = await DiditSdk.startVerificationWithWorkflow(
  'your-workflow-id',
  vendorData: 'user-123',
  config: const DiditConfig(
    languageCode: 'es',
    showLanguageSelector: true,
    loggingEnabled: true,
  ),
);
```### Opcions de configuració| Propietat | Tipus | Valor per defecte | Descripció |
| - | - | - | - |
| `languageCode` | `String?` | Configuració regional del dispositiu | Codi d'idioma ISO 639-1 (p. ex., `"en"`, `"fr"`, `"ar"`) |
| `showLanguageSelector` | `bool` | `false` | Mostra el selector d'idioma d'estil web a la pantalla de benvinguda |
| `fontFamily` | `String?` | Tipus de lletra del sistema | Nom de la família tipogràfica personalitzada (s'ha de registrar de manera nativa) |
| `loggingEnabled` | `bool` | `false` | Activa el registre de depuració de l'SDK |
| `showCloseButton` | `bool` | `true` | Mostra el botó de tancar (X) a les pantalles dels passos de verificació |
| `showExitConfirmation` | `bool` | `true` | Mostra un diàleg de confirmació quan l'usuari intenta sortir |
| `closeOnComplete` | `bool` | `false` | Tanca automàticament la interfície de verificació en completar-la |
| `defaultDocumentCamera` | `CameraLens?` | Valor per defecte natiu (`CameraLens.back`) | Objectiu que s'utilitza quan s'obre la pantalla de captura del document |
| `defaultLivenessCamera` | `CameraLens?` | Valor per defecte natiu (`CameraLens.front`) | Objectiu que s'utilitza quan s'obre la pantalla de captura del rostre (prova de vida) |
| `showDocumentCameraSwitchButton` | `bool` | `true` | Mostra el selector de càmera durant la captura a la pantalla de captura del document |
| `showLivenessCameraSwitchButton` | `bool` | `true` | Mostra el selector de càmera durant la captura a la pantalla de captura del rostre |### Opcions de càmeraLes opcions de càmera coincideixen amb les [opcions de càmera de l'SDK web](https://docs.didit.me/ca/integration/web-sdks/javascript-sdk.md#opcions-de-càmera).
Cada objectiu és `CameraLens.front` o `CameraLens.back`.
Si el dispositiu no té l'objectiu sol·licitat, l'SDK utilitza la primera càmera que tingui.
El selector de càmera també s'amaga en dispositius que només tenen una càmera.
Estableix una opció del selector a `false` per bloquejar l'usuari en l'objectiu per defecte d'aquest pas.```dart theme={null}
config: const DiditConfig(
  defaultLivenessCamera: CameraLens.back,
  showLivenessCameraSwitchButton: false,
),
```> [!NOTE]
>
> **Personalització visual i colors**: els colors, els fons i la configuració de les pantalles d'introducció es configuren a través de la [configuració de Marca blanca](https://business.didit.me) a la Didit Console, i no a la configuració de l'SDK. Així es garanteix una imatge de marca coherent a totes les plataformes.***## Suport d'idiomesL'SDK admet **54 idiomes**. Si no s'especifica cap idioma, l'SDK utilitza la configuració regional del dispositiu, amb l'anglès com a 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),
);
```**[Consulta tots els idiomes admesos →](https://docs.didit.me/ca/integration/supported-languages.md)*****## Paràmetres avançats de sessióParàmetres com `contact_details`, `expected_details` i `metadata` **només** són compatibles amb el mètode del token de sessió. El backend crida [`POST /v3/session/`](https://docs.didit.me/ca/sessions-api/create-session) amb suport complet de paràmetres i després passa el `session_token` retornat a l'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ètode Dart `startVerificationWithWorkflow` només accepta `workflowId`, `vendorData` i `config`. Les versions anteriors a la 3.4.0 exposaven els paràmetres `contactDetails`, `expectedDetails` i `metadata`, però es van eliminar perquè l'endpoint de Unilink no els té en compte.***## Gestió de resultatsTant `startVerification` com `startVerificationWithWorkflow` retornen un `Future<VerificationResult>`. El resultat és una classe segellada: utilitza la coincidència de patrons per determinar el resultat.### Casos de resultat| Cas | Descripció |
| - | - |
| `VerificationCompleted` | El flux de verificació s'ha completat (consulta `session.status` per al resultat) |
| `VerificationCancelled` | L'usuari ha cancel·lat el flux de verificació |
| `VerificationFailed` | S'ha produït un error durant la verificació |### Propietats de SessionData| Propietat | Tipus | Descripció |
| - | - | - |
| `sessionId` | `String` | Identificador únic de la sessió |
| `status` | `VerificationStatus` | `approved`, `pending` o `declined` |### Tipus d'errorL'enumeració `VerificationErrorType` exposa els casos següents:| Error | Descripció |
| - | - |
| `sessionExpired` | La sessió ha caducat |
| `networkError` | Problema de connectivitat de xarxa |
| `cameraAccessDenied` | No s'ha concedit el permís de càmera |
| `notInitialized` | L'SDK no està inicialitzat (només Android) |
| `apiError` | La sol·licitud a l'API ha fallat |
| `retryBlocked` | S'ha superat el nombre màxim de reintents per al pas actual |
| `unknown` | Altre error: consulta `error.message` per a més detalls |### Connexió perdudaSi el dispositiu perd la connexió durant la verificació, l'SDK mostra una pantalla «Sense connexió a Internet» per sobre del flux en lloc de deixar un pas en estat de càrrega.
Segueix comprovant la connexió en segon pla i tanca la pantalla automàticament en el moment en què es pot contactar amb Didit de nou, i l'usuari continua des del pas on era.
La pantalla ofereix **Torna-ho a provar** i, tret que el botó de tancar estigui desactivat, **Surt de la verificació**.
Sortir finalitza la verificació amb un resultat `VerificationFailed` el tipus d'error del qual és `networkError`, de manera que la teva aplicació sap que l'usuari ha sortit per la connexió.### Exemple complet de gestió de resultats```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
  }
}
```***## Exemple de principi a fi (backend, SDK de Flutter i webhook)La integració preparada per a producció: el backend crea la sessió, l'aplicació de Flutter rep el `session_token` i l'SDK executa el flux. La decisió final es lliura al backend mitjançant un webhook: no confiïs mai només en el resultat del client.### 1. Backend: crea la sessió```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: intercanvia i inicia l'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: rep la decisió final mitjançant 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 [guia de webhooks](https://docs.didit.me/ca/integration/webhooks.md) per veure els detalls de la verificació de la signatura HMAC.***## Exemple complet```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.