> ## 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 Flutter: Verifikasi Identitas Dart Mobile

> SDK Flutter Didit: KYC native iOS & Android, liveness biometrik, NFC paspor. Mulai $0.33 per verifikasi; paket gratis $10/bulan; tanpa kontrak.



Prompt Integrasi Flutter SDK
        Salin prompt ke papan klip



      
      

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



      
      

Tempel ke

Plugin Flutter lintas platform yang membungkus SDK native iOS dan Android, menyediakan API Dart terpadu untuk verifikasi identitas.> [card] [Repositori GitHub](https://github.com/didit-protocol/sdk-flutter)
>
> Lihat kode sumber dan contoh di GitHub> [card] [Paket pub.dev](https://pub.dev/packages/didit_sdk)
>
> didit\_sdk> [!NOTE]
>
> ### **Native SDK**: Ini adalah pendekatan yang direkomendasikan untuk aplikasi Flutter. SDK membungkus SDK native iOS dan Android melalui platform channels untuk pengalaman pengguna terbaik, penanganan kamera yang dioptimalkan, dan dukungan NFC penuh.***## Persyaratan| Persyaratan | Versi Minimum |
| - | - |
| Flutter | 3.3+ |
| Dart | 3.11+ |### Persyaratan Platform| Platform | Versi Minimum | Catatan |
| - | - | - |
| iOS | 13.0+ | Pembacaan paspor NFC memerlukan iOS 15.0+ |
| Android | API 23+ (6.0) | Java 17+ |> [!NOTE]
>
> **Persetujuan biometrik memerlukan SDK native versi 4.10.0 atau yang lebih baru.** Untuk pengguna yang tunduk pada hukum Illinois, Texas, atau Colorado, Didit dapat meminta [persetujuan biometrik](https://docs.didit.me/id/getting-started/biometric-consent.md) secara eksplisit sebelum pengambilan wajah pertama. Rilis Flutter SDK yang mengunci SDK native iOS dan Android pada versi 4.10.0 atau yang lebih baru akan menampilkan langkah tersebut. Versi yang lebih lama tidak dapat menampilkan langkah ini: sesinya berjalan seperti biasa, dan keputusan melaporkan persetujuan sebagai tidak dikumpulkan (`biometric_consent.reason: "BIOMETRIC_CONSENT_UNSUPPORTED_SDK"`). Tingkatkan versi jika Anda memverifikasi pengguna di negara bagian tersebut.***## Instalasi```bash theme={null}
flutter pub add didit_sdk
```Atau tambahkan secara manual ke `pubspec.yaml` Anda:```yaml theme={null}
dependencies:
  didit_sdk: ^4.13.2
```Lalu jalankan:```bash theme={null}
flutter pub get
```### Varian SDK native`didit_sdk` menyertakan SDK native lengkap.
Untuk memperkecil ukuran biner, gunakan paket varian sebagai gantinya. Paket ini menyediakan API Dart yang sama persis dan mengunci SDK native yang sesuai di kedua platform:| Paket | Pengambilan otomatis | Pembacaan paspor NFC | iOS minimum |
| - | - | - | - |
| `didit_sdk` (bawaan) | Ya | Ya | 15.0 |
| `didit_sdk_core` | Tidak | Tidak | 13.0 |
| `didit_sdk_autodetection` | Ya | Tidak | 13.0 |
| `didit_sdk_nfc` | Tidak | Ya | 15.0 |```yaml theme={null}
dependencies:
  didit_sdk_core: ^4.13.2
```Tambahkan tepat satu paket ini sebagai dependensi dan impor dari paket yang Anda pilih, misalnya `package:didit_sdk_core/sdk_flutter.dart`.### Pengaturan iOSPlugin ini mendukung kedua manajer dependensi iOS Flutter: Swift Package Manager dan CocoaPods.#### Swift Package ManagerJika versi Flutter Anda sudah mengaktifkan dukungan Swift Package Manager (aktifkan dengan `flutter config --enable-swift-package-manager` pada Flutter 3.24+), plugin ini otomatis digunakan sebagai paket Swift.
Tidak diperlukan konfigurasi Podfile. Dependensi DiditSDK native akan diambil sendiri dari GitHub.
Atur target deployment iOS aplikasi Anda di Xcode (target Runner dan proyek) ke versi minimum untuk paket yang Anda pilih: 15.0 untuk `didit_sdk` dan `didit_sdk_nfc`, 13.0 untuk `didit_sdk_core` dan `didit_sdk_autodetection`.#### CocoaPodsDengan `didit_sdk`, plugin memilih varian SDK iOS native dari `$DiditSdkIosVariant`, yaitu variabel global Ruby yang Anda atur di `ios/Podfile` aplikasi Anda (pod DiditSDK tidak tersedia di CocoaPods trunk):```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
```Jika Anda menggunakan paket varian (`didit_sdk_core`, `didit_sdk_autodetection`, `didit_sdk_nfc`), paket tersebut mengunci subspec native-nya secara langsung dan mengabaikan `$DiditSdkIosVariant`. Pastikan baris `pod` di Podfile dan target deployment Anda sesuai dengan varian tersebut (misalnya `pod 'DiditSDK/Core', :podspec => ...` dengan `platform :ios, '13.0'`).> [!NOTE]
>
> Varian `all` dan `nfc` memerlukan target deployment iOS 15.0+. `core` dan `autodetection` dapat menargetkan iOS 13.0. Saat berpindah varian, bersihkan CocoaPods terlebih dahulu (`rm -rf Pods Podfile.lock`) agar varian SDK sebelumnya tidak ikut digunakan.> [!WARNING]
>
> Tag pada URL podspec harus sama dengan versi SDK iOS native yang dikunci oleh rilis `didit_sdk` Anda
> (`4.13.2` mengunci native `4.13.2`). Jika URL mengarah ke tag yang lebih lama, `pod install` gagal menyelesaikan dependensi. CocoaPods menemukan podspec yang menyatakan versi lama tersebut, sementara plugin mewajibkan kecocokan persis.
> Saat Anda memutakhirkan `didit_sdk`, perbarui tag di URL ini ke versi yang sama.### Pengaturan AndroidSecara bawaan, plugin ini bergantung pada SDK Android lengkap termasuk NFC. Untuk membangun tanpa NFC, tambahkan baris berikut ke `android/gradle.properties`:```properties theme={null}
diditSdkAndroidNfcEnabled=false
```Ini mengubah dependensi dari `me.didit:didit-sdk` menjadi `me.didit:didit-sdk-core`, sehingga modul pembaca NFC beserta dependensi JMRTD/SCUBA/BouncyCastle-nya dihapus.Jika NFC diaktifkan (bawaan), tambahkan aturan packaging berikut ke `android/app/build.gradle.kts` untuk menyelesaikan file metadata duplikat dari BouncyCastle:```kotlin theme={null}
android {
    packaging {
        resources {
            pickFirsts += "META-INF/versions/9/OSGI-INF/MANIFEST.MF"
        }
    }
}
```Tanpa aturan ini, build gagal dengan error `mergeDebugJavaResource`. Aturan ini tidak diperlukan saat `diditSdkAndroidNfcEnabled=false`.> [!NOTE]
>
> SDK native Android diambil dari repositori Maven jarak jauh di GitHub (sejak plugin 3.4.0), sehingga tidak diperlukan konfigurasi Maven manual di aplikasi Anda.***## Izin### iOSTambahkan kunci berikut ke `Info.plist` aplikasi Anda:| Izin | Kunci Info.plist | Deskripsi | Wajib |
| - | - | - | - |
| Kamera | `NSCameraUsageDescription` | Pemindaian dokumen dan verifikasi wajah | Ya |
| Mikrofon | `NSMicrophoneUsageDescription` | Perekaman video untuk pemeriksaan liveness | Ya |
| Pustaka Foto | `NSPhotoLibraryUsageDescription` | Unggah dokumen dari galeri perangkat | Ya |
| NFC | `NFCReaderUsageDescription` | Membaca chip NFC pada paspor/kartu identitas | Jika menggunakan NFC |
| Lokasi | `NSLocationWhenInUseUsageDescription` | Geolokasi untuk pencegahan penipuan | Opsional |> [!WARNING]
>
> Jika salah satu kunci privasi iOS yang wajib tidak ada, iOS akan menghentikan aplikasi begitu SDK mencoba mengakses sumber daya yang dilindungi tersebut. Misalnya, jika `NSCameraUsageDescription` tidak ada, aplikasi akan crash saat pengguna mengetuk tombol ambil foto pada kamera dokumen.#### Konfigurasi NFCUntuk mengaktifkan pembacaan NFC pada paspor dan kartu identitas yang memiliki chip:1) **Tambahkan kapabilitas NFC** di Xcode:
   * Pilih target Anda > **Signing & Capabilities** > **+ Capability** > **Near Field Communication Tag Reading**

2) **Tambahkan pengidentifikasi ISO7816** ke `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` memungkinkan SDK membaca Kartu My Number Jepang melalui [JPKI](https://docs.didit.me/id/core-technology/nfc-verification/overview.md#japan-my-number-card-jpki). Tanpa pengidentifikasi ini, iOS tidak akan melaporkan kartu tersebut ke SDK. Pembacaan Kartu My Number belum diaktifkan di produksi, dan pengidentifikasi ini tidak berpengaruh pada dokumen lain, jadi Anda sudah bisa menambahkannya sekarang.3) **Tambahkan file entitlements** dengan pembacaan tag NFC diaktifkan:```xml theme={null}
<key>com.apple.developer.nfc.readersession.formats</key>
<array>
    <string>TAG</string>
</array>
```Pastikan profil provisioning aplikasi menyertakan capability NFC Tag Reading. Jika bundle ID belum dikonfigurasi untuk NFC di akun Apple Developer Anda, Xcode akan gagal melakukan penandatanganan karena entitlement `com.apple.developer.nfc.readersession.formats` tidak ada.Konfigurasi NFC ini tidak diperlukan untuk varian `core` atau `autodetection` (`$DiditSdkIosVariant`), atau untuk paket `didit_sdk_core` / `didit_sdk_autodetection`. Tidak satu pun dari varian tersebut membaca chip.### AndroidIzin berikut dideklarasikan di `AndroidManifest.xml` milik SDK dan digabungkan secara otomatis:| Izin | Deskripsi | Wajib |
| - | - | - |
| `INTERNET` | Akses jaringan untuk komunikasi API | Ya |
| `ACCESS_NETWORK_STATE` | Mendeteksi ketersediaan jaringan | Ya |
| `CAMERA` | Pemindaian dokumen dan verifikasi wajah | Ya |
| `NFC` | Membaca chip NFC pada paspor/kartu identitas | Jika menggunakan NFC |Fitur perangkat keras kamera dan NFC dideklarasikan sebagai opsional (`android:required="false"`), sehingga aplikasi Anda tetap bisa dipasang di perangkat yang tidak memiliki fitur tersebut. Jika `diditSdkAndroidNfcEnabled=false`, SDK tidak menambahkan izin dan fitur NFC Android.#### Izin RuntimeSDK menangani permintaan izin runtime Android secara otomatis. Ketika pengguna mencapai langkah yang memerlukan akses kamera:1) SDK meminta izin kamera jika belum diberikan
2) Jika pengguna **menolak** izin, pesan kesalahan ditampilkan dengan tombol **Try Again**
3) Jika pengguna **memberikan** izin, alur verifikasi akan dilanjutkanAnda tidak perlu meminta izin kamera di kode aplikasi sebelum memanggil `startVerification()`. SDK mengelolanya secara internal.***## Mulai Cepat```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}');
}
```***## Metode IntegrasiSDK mendukung dua metode integrasi:### Metode 1: Token Sesi (Direkomendasikan untuk Produksi)Buat sesi di backend Anda menggunakan [API Create Verification Session](https://docs.didit.me/id/sessions-api/create-session.md), lalu teruskan token ke 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);
```Pendekatan ini memberi Anda kendali penuh atas:- Mengaitkan sesi dengan pengguna Anda (`vendor_data`)
- Menetapkan metadata kustom
- Mengonfigurasi callback per sesi### Metode 2: ID Alur Kerja (Integrasi Lebih Sederhana)Untuk integrasi yang lebih sederhana, SDK dapat membuat sesi langsung menggunakan ID alur kerja Anda. Backend tidak diperlukan:```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]
>
> Parameter sesi lanjutan (`contact_details`, `expected_details`, `metadata`) **hanya** didukung melalui metode Token Sesi, ketika backend Anda memanggil [API Create Session](https://docs.didit.me/id/sessions-api/create-session.md) dengan dukungan parameter lengkap. Teruskan `session_token` yang dikembalikan ke `DiditSdk.startVerification()`.***## KonfigurasiSesuaikan perilaku SDK dengan meneruskan objek `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
  ),
);
```Untuk `startVerificationWithWorkflow`, teruskan `config` sebagai parameter bernama:```dart theme={null}
final result = await DiditSdk.startVerificationWithWorkflow(
  'your-workflow-id',
  vendorData: 'user-123',
  config: const DiditConfig(
    languageCode: 'es',
    showLanguageSelector: true,
    loggingEnabled: true,
  ),
);
```### Opsi Konfigurasi| Properti | Tipe | Default | Deskripsi |
| - | - | - | - |
| `languageCode` | `String?` | Locale perangkat | Kode bahasa ISO 639-1 (misalnya `"en"`, `"fr"`, `"ar"`) |
| `showLanguageSelector` | `bool` | `false` | Tampilkan pemilih bahasa bergaya web di layar selamat datang |
| `fontFamily` | `String?` | Font sistem | Nama keluarga font kustom (harus didaftarkan di sisi native) |
| `loggingEnabled` | `bool` | `false` | Aktifkan logging debug SDK |
| `showCloseButton` | `bool` | `true` | Tampilkan tombol tutup (X) di layar langkah verifikasi |
| `showExitConfirmation` | `bool` | `true` | Tampilkan dialog konfirmasi saat pengguna mencoba keluar |
| `closeOnComplete` | `bool` | `false` | Otomatis menutup antarmuka verifikasi saat selesai |
| `defaultDocumentCamera` | `CameraLens?` | Default native (`CameraLens.back`) | Lensa yang digunakan saat layar pengambilan dokumen dibuka |
| `defaultLivenessCamera` | `CameraLens?` | Default native (`CameraLens.front`) | Lensa yang digunakan saat layar pengambilan wajah (liveness) dibuka |
| `showDocumentCameraSwitchButton` | `bool` | `true` | Tampilkan pengalih kamera selama pengambilan di layar pengambilan dokumen |
| `showLivenessCameraSwitchButton` | `bool` | `true` | Tampilkan pengalih kamera selama pengambilan di layar pengambilan wajah |### Opsi kameraOpsi kamera sama dengan [opsi kamera Web SDK](https://docs.didit.me/id/integration/web-sdks/javascript-sdk.md#opsi-kamera).
Setiap lensa adalah `CameraLens.front` atau `CameraLens.back`.
Jika perangkat tidak memiliki lensa yang diminta, SDK menggunakan kamera pertama yang dimilikinya.
Pengalih kamera juga disembunyikan pada perangkat yang hanya memiliki satu kamera.
Atur opsi pengalih ke `false` untuk mengunci pengguna agar menggunakan lensa default pada langkah tersebut.```dart theme={null}
config: const DiditConfig(
  defaultLivenessCamera: CameraLens.back,
  showLivenessCameraSwitchButton: false,
),
```> [!NOTE]
>
> **Tema & Warna**: Warna, latar belakang, dan pengaturan layar pengantar dikonfigurasi melalui [pengaturan White Label](https://business.didit.me) Anda di Didit Console, bukan di konfigurasi SDK. Ini memastikan branding yang konsisten di semua platform.***## Dukungan BahasaSDK mendukung **54 bahasa**. Jika tidak ada bahasa yang ditentukan, SDK menggunakan locale perangkat dengan bahasa Inggris sebagai cadangan.```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),
);
```**[Lihat Semua Bahasa yang Didukung →](https://docs.didit.me/id/integration/supported-languages.md)*****## Parameter Sesi LanjutanParameter seperti `contact_details`, `expected_details`, dan `metadata` **hanya** didukung melalui metode Session Token. Backend Anda memanggil [`POST /v3/session/`](https://docs.didit.me/id/sessions-api/create-session) dengan dukungan parameter lengkap, lalu meneruskan `session_token` yang dikembalikan ke 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]
>
> Metode Dart `startVerificationWithWorkflow` hanya menerima `workflowId`, `vendorData`, dan `config`. Versi sebelum 3.4.0 menyediakan parameter `contactDetails`, `expectedDetails`, dan `metadata`, tetapi parameter tersebut dihapus karena endpoint Unilink tidak memprosesnya.***## Menangani HasilBaik `startVerification` maupun `startVerificationWithWorkflow` mengembalikan `Future<VerificationResult>`. Hasilnya adalah sealed class; gunakan pattern matching untuk menentukan hasilnya.### Kasus Hasil| Kasus | Deskripsi |
| - | - |
| `VerificationCompleted` | Alur verifikasi selesai (periksa `session.status` untuk hasilnya) |
| `VerificationCancelled` | Pengguna membatalkan alur verifikasi |
| `VerificationFailed` | Terjadi error selama verifikasi |### Properti SessionData| Properti | Tipe | Deskripsi |
| - | - | - |
| `sessionId` | `String` | Pengenal sesi yang unik |
| `status` | `VerificationStatus` | `approved`, `pending`, atau `declined` |### Tipe ErrorEnum `VerificationErrorType` menyediakan kasus-kasus berikut:| Error | Deskripsi |
| - | - |
| `sessionExpired` | Sesi telah kedaluwarsa |
| `networkError` | Masalah konektivitas jaringan |
| `cameraAccessDenied` | Izin kamera belum diberikan |
| `notInitialized` | SDK belum diinisialisasi (khusus Android) |
| `apiError` | Permintaan API gagal |
| `retryBlocked` | Batas maksimum tindakan coba lagi untuk langkah saat ini telah terlampaui |
| `unknown` | Error lainnya, periksa `error.message` untuk detailnya |### Koneksi terputusJika perangkat kehilangan koneksi selama verifikasi, SDK menampilkan layar "No internet connection" di atas alur alih-alih membiarkan langkah terus memuat.
SDK terus memeriksa di latar belakang dan menutup layar dengan sendirinya begitu Didit dapat dijangkau kembali, lalu pengguna melanjutkan dari langkah yang sedang mereka jalankan.
Layar ini menampilkan **Try Again** dan, kecuali tombol tutup dinonaktifkan, **Exit Verification**.
Keluar mengakhiri verifikasi dengan hasil `VerificationFailed` yang memiliki tipe error `networkError`, sehingga aplikasi Anda mengetahui bahwa pengguna keluar karena masalah koneksi.### Contoh Lengkap Penanganan Hasil```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
  }
}
```***## Contoh End-to-End (Sesi Backend → Flutter SDK → Webhook)Integrasi siap produksi: backend Anda membuat sesi, aplikasi Flutter Anda menerima `session_token`, dan SDK menjalankan alurnya. Keputusan akhir dikirim ke backend Anda melalui webhook. Jangan pernah hanya mengandalkan hasil di sisi klien.### 1. Backend: buat sesi```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: tukar dan jalankan 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: terima keputusan akhir melalui 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);
});
```Lihat [panduan webhook](https://docs.didit.me/id/integration/webhooks.md) untuk detail verifikasi tanda tangan HMAC.***## Contoh Lengkap```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.