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

# Flutter KYC SDK: Dart 모바일 신원 인증

> Didit의 Flutter Dart KYC SDK로 네이티브 iOS·Android를 지원하며 생체 라이브니스와 NFC 여권 인증을 제공합니다. 전체 KYC 건당 $0.33부터, 월 $10 무료, 계약 없음.



Flutter SDK 연동 프롬프트
        프롬프트를 클립보드에 복사



      
      

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



      
      

붙여넣을 곳

네이티브 iOS 및 Android SDK를 감싸고 신원 인증을 위한 통합 Dart API를 제공하는 크로스 플랫폼 Flutter 플러그인입니다.> [card] [GitHub 저장소](https://github.com/didit-protocol/sdk-flutter)
>
> GitHub에서 소스 코드와 예제를 확인하세요> [card] [pub.dev 패키지](https://pub.dev/packages/didit_sdk)
>
> didit\_sdk> [!NOTE]
>
> ### **네이티브 SDK**: Flutter 앱에 권장되는 방식입니다. SDK는 플랫폼 채널을 통해 네이티브 iOS 및 Android SDK를 감싸며, 최상의 사용자 경험, 최적화된 카메라 처리, NFC를 완전히 지원합니다.***## 요구 사항| 요구 사항 | 최소 버전 |
| - | - |
| Flutter | 3.3+ |
| Dart | 3.11+ |### 플랫폼 요구 사항| 플랫폼 | 최소 버전 | 비고 |
| - | - | - |
| iOS | 13.0+ | NFC 여권 판독에는 iOS 15.0+가 필요합니다 |
| Android | API 23+ (6.0) | Java 17+ |> [!NOTE]
>
> **생체 인식 동의에는 네이티브 4.10.0 이상이 필요합니다.** 일리노이주, 텍사스주 또는 콜로라도주 법률이 적용되는 사용자의 경우, Didit은 첫 얼굴 촬영 전에 명시적인 [생체 인식 동의](https://docs.didit.me/ko/getting-started/biometric-consent.md)를 요청할 수 있습니다. iOS 및 Android 네이티브 SDK 4.10.0 이상을 고정하는 Flutter SDK 릴리스에서는 이 단계가 표시됩니다. 이전 버전에서는 이 단계를 표시할 수 없습니다. 해당 세션은 이전과 같이 실행되며, 결정 결과에는 동의가 수집되지 않은 것으로 보고됩니다(`biometric_consent.reason: "BIOMETRIC_CONSENT_UNSUPPORTED_SDK"`). 해당 주에서 사용자를 인증하는 경우 업그레이드하십시오.***## 설치```bash theme={null}
flutter pub add didit_sdk
```또는 `pubspec.yaml`에 직접 추가할 수도 있습니다:```yaml theme={null}
dependencies:
  didit_sdk: ^4.13.2
```그런 다음 다음 명령을 실행합니다:```bash theme={null}
flutter pub get
```### 네이티브 SDK 변형`didit_sdk`에는 전체 네이티브 SDK가 포함되어 있습니다.
바이너리 크기를 줄이려면 대신 변형 패키지에 의존하십시오. 변형 패키지도 동일한 Dart API를 제공하며, 두 플랫폼 모두에서 일치하는 네이티브 SDK 버전을 고정합니다:| 패키지 | 자동 촬영 | NFC 여권 판독 | 최소 iOS |
| - | - | - | - |
| `didit_sdk` (기본값) | 예 | 예 | 15.0 |
| `didit_sdk_core` | 아니요 | 아니요 | 13.0 |
| `didit_sdk_autodetection` | 예 | 아니요 | 13.0 |
| `didit_sdk_nfc` | 아니요 | 예 | 15.0 |```yaml theme={null}
dependencies:
  didit_sdk_core: ^4.13.2
```위 패키지 중 정확히 하나에만 의존하고, 선택한 패키지에서 가져오십시오. 예를 들어 `package:didit_sdk_core/sdk_flutter.dart`처럼 작성합니다.### iOS 설정이 플러그인은 Flutter의 두 가지 iOS 종속성 관리자인 Swift Package Manager와 CocoaPods를 모두 지원합니다.#### Swift Package ManagerFlutter 버전에서 Swift Package Manager 지원이 활성화되어 있으면(Flutter 3.24+에서 `flutter config --enable-swift-package-manager`로 옵트인), 플러그인은 자동으로 Swift 패키지로 사용됩니다.
Podfile 설정은 필요하지 않습니다. 네이티브 DiditSDK 종속성은 GitHub에서 자체적으로 해결됩니다.
Xcode에서 앱의 iOS 배포 대상(Runner 타깃 및 프로젝트)을 선택한 패키지의 최소 버전으로 설정하십시오. `didit_sdk`와 `didit_sdk_nfc`는 15.0, `didit_sdk_core`와 `didit_sdk_autodetection`은 13.0입니다.#### CocoaPods`didit_sdk`를 사용하면 플러그인은 앱의 `ios/Podfile`에서 설정하는 Ruby 전역 변수 `$DiditSdkIosVariant`로 네이티브 iOS SDK 변형을 선택합니다. DiditSDK pod는 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
```변형 패키지(`didit_sdk_core`, `didit_sdk_autodetection`, `didit_sdk_nfc`)를 사용하는 경우, 해당 패키지는 네이티브 서브스펙을 직접 고정하고 `$DiditSdkIosVariant`는 무시합니다. Podfile의 `pod` 줄과 배포 대상은 해당 변형에 맞게 유지하십시오. 예를 들어 `platform :ios, '13.0'`과 함께 `pod 'DiditSDK/Core', :podspec => ...`를 사용합니다.> [!NOTE]
>
> `all` 및 `nfc` 변형은 배포 대상이 iOS 15.0+여야 하며, `core` 및 `autodetection`은 iOS 13.0을 대상으로 할 수 있습니다. 변형을 전환할 때는 이전 SDK 변형이 재사용되지 않도록 먼저 CocoaPods를 정리하십시오(`rm -rf Pods Podfile.lock`).> [!WARNING]
>
> podspec URL의 태그는 `didit_sdk` 릴리스가 고정하는 네이티브 iOS SDK 버전과 일치해야 합니다(`4.13.2`는 네이티브 `4.13.2`를 고정합니다). URL이 이전 태그를 가리키면 `pod install`이 종속성을 해결하지 못합니다. CocoaPods는 이전 버전을 선언한 podspec을 찾는 반면, 플러그인은 정확한 일치를 요구하기 때문입니다.
> `didit_sdk`를 업그레이드할 때는 이 URL의 태그도 같은 버전으로 변경하십시오.### Android 설정기본적으로 이 플러그인은 NFC를 포함한 전체 Android SDK에 의존합니다. NFC 없이 빌드하려면 `android/gradle.properties`에 다음을 추가하십시오:```properties theme={null}
diditSdkAndroidNfcEnabled=false
```이렇게 하면 종속성이 `me.didit:didit-sdk`에서 `me.didit:didit-sdk-core`로 바뀌며, NFC 리더 모듈과 JMRTD/SCUBA/BouncyCastle 종속성이 제거됩니다.NFC가 활성화된 경우(기본값), BouncyCastle에서 발생하는 중복 메타데이터 파일을 해결하려면 `android/app/build.gradle.kts`에 다음 패키징 규칙을 추가하십시오:```kotlin theme={null}
android {
    packaging {
        resources {
            pickFirsts += "META-INF/versions/9/OSGI-INF/MANIFEST.MF"
        }
    }
}
```이 규칙이 없으면 빌드가 `mergeDebugJavaResource` 오류와 함께 실패합니다. `diditSdkAndroidNfcEnabled=false`인 경우에는 이 규칙이 필요하지 않습니다.> [!NOTE]
>
> Android 네이티브 SDK는 원격 GitHub Maven 저장소에서 해결됩니다(플러그인 3.4.0부터). 앱에서 별도의 Maven 설정은 필요하지 않습니다.***## Permissions### iOS앱의 `Info.plist`에 다음 키를 추가하십시오:| 권한 | Info.plist 키 | 설명 | 필수 여부 |
| - | - | - | - |
| 카메라 | `NSCameraUsageDescription` | 문서 스캔 및 얼굴 인증 | 예 |
| 마이크 | `NSMicrophoneUsageDescription` | 라이브니스 확인을 위한 영상 녹화 | 예 |
| 사진 보관함 | `NSPhotoLibraryUsageDescription` | 기기 갤러리에서 문서 업로드 | 예 |
| NFC | `NFCReaderUsageDescription` | 여권 및 신분증의 NFC 칩 읽기 | NFC 사용 시 |
| 위치 | `NSLocationWhenInUseUsageDescription` | 사기 방지를 위한 위치 정보 | 선택 사항 |> [!WARNING]
>
> 필수 iOS 개인정보 보호 키가 없으면, SDK가 해당 보호된 리소스에 접근하는 순간 iOS가 앱을 종료합니다. 예를 들어 `NSCameraUsageDescription`이 없으면 사용자가 문서 카메라의 사진 촬영 버튼을 누를 때 충돌이 발생합니다.#### NFC 설정칩이 있는 여권 및 신분증의 NFC 읽기를 활성화하려면 다음을 수행하십시오:1) Xcode에서 **NFC 기능을 추가합니다**:
   * 대상을 선택한 후 **Signing & Capabilities** > **+ Capability** > **Near Field Communication Tag Reading**을 선택합니다.

2) `Info.plist`에 **ISO7816 식별자**를 추가합니다:```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`을 설정하면 SDK가 [JPKI](https://docs.didit.me/ko/core-technology/nfc-verification/overview.md#일본-마이넘버카드-jpki)를 통해 일본 마이넘버 카드를 읽을 수 있습니다. 이 식별자가 없으면 iOS가 해당 카드를 SDK에 전달하지 않습니다. 마이넘버 카드 읽기 기능은 아직 프로덕션에서 활성화되어 있지 않으며, 이 식별자는 다른 문서에는 영향을 주지 않으므로 지금 추가해도 됩니다.3) NFC 태그 읽기가 활성화된 **entitlement 파일을 추가합니다**:```xml theme={null}
<key>com.apple.developer.nfc.readersession.formats</key>
<array>
    <string>TAG</string>
</array>
```앱의 프로비저닝 프로파일에 NFC Tag Reading 기능이 포함되어 있는지 확인하십시오. Apple Developer 계정에서 번들 ID에 대해 NFC가 설정되어 있지 않으면, Xcode는 `com.apple.developer.nfc.readersession.formats` 권한이 없다는 이유로 서명에 실패합니다.이 NFC 설정은 `core` 또는 `autodetection` 변형(`$DiditSdkIosVariant`)이나 `didit_sdk_core` / `didit_sdk_autodetection` 패키지에는 필요하지 않습니다. 이들은 모두 칩을 읽지 않기 때문입니다.### Android다음 권한은 SDK의 `AndroidManifest.xml`에 선언되어 있으며 자동으로 병합됩니다:| 권한 | 설명 | 필수 여부 |
| - | - | - |
| `INTERNET` | API 통신을 위한 네트워크 액세스 | 예 |
| `ACCESS_NETWORK_STATE` | 네트워크 사용 가능 여부 확인 | 예 |
| `CAMERA` | 문서 스캔 및 얼굴 인증 | 예 |
| `NFC` | 여권 및 신분증의 NFC 칩 읽기 | NFC 사용 시 |카메라 및 NFC 하드웨어 기능은 선택 사항(`android:required="false"`)으로 선언되어 있으므로, 이러한 기능이 없는 기기에도 앱을 설치할 수 있습니다. `diditSdkAndroidNfcEnabled=false`로 설정하면 SDK는 Android NFC 권한과 기능을 추가하지 않습니다.#### 런타임 권한SDK는 Android 런타임 권한 요청을 자동으로 처리합니다. 사용자가 카메라 액세스가 필요한 단계에 도달하면 다음과 같이 동작합니다:1) 카메라 권한이 아직 부여되지 않은 경우 SDK가 권한을 요청합니다.
2) 사용자가 권한을 **거부**하면 **다시 시도** 버튼이 있는 오류 메시지가 표시됩니다.
3) 사용자가 권한을 **허용**하면 인증 흐름이 계속 진행됩니다.`startVerification()`을 호출하기 전에 앱 코드에서 카메라 권한을 따로 요청할 필요가 없습니다. SDK가 이를 내부적으로 관리합니다.***## 빠른 시작```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}');
}
```***## 통합 방식SDK는 두 가지 통합 방식을 지원합니다:### 방법 1: 세션 토큰(프로덕션 권장)백엔드에서 [인증 세션 생성 API](https://docs.didit.me/ko/sessions-api/create-session.md)를 사용해 세션을 생성한 다음, 토큰을 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);
```이 방식을 사용하면 다음 항목을 완전히 제어할 수 있습니다:- 세션을 사용자와 연결(`vendor_data`)
- 사용자 지정 메타데이터 설정
- 세션별 콜백 구성### 방법 2: 워크플로 ID(간편한 통합)간단한 통합의 경우 SDK가 워크플로 ID를 사용해 세션을 직접 생성할 수 있으며, 이때 백엔드가 필요하지 않습니다:```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]
>
> 고급 세션 파라미터(`contact_details`, `expected_details`, `metadata`)는 **세션 토큰 방식에서만** 지원됩니다. 이 방식에서는 백엔드가 [세션 생성 API](https://docs.didit.me/ko/sessions-api/create-session.md)를 모든 파라미터와 함께 호출합니다. 반환된 `session_token`을 `DiditSdk.startVerification()`에 전달하십시오.***## 구성`DiditConfig` 객체를 전달하여 SDK 동작을 맞춤 설정합니다:```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
  ),
);
````startVerificationWithWorkflow`의 경우 `config`를 명명된 파라미터로 전달합니다:```dart theme={null}
final result = await DiditSdk.startVerificationWithWorkflow(
  'your-workflow-id',
  vendorData: 'user-123',
  config: const DiditConfig(
    languageCode: 'es',
    showLanguageSelector: true,
    loggingEnabled: true,
  ),
);
```### 구성 옵션| 속성 | 타입 | 기본값 | 설명 |
| - | - | - | - |
| `languageCode` | `String?` | 기기 로캘 | ISO 639-1 언어 코드 (예: `"en"`, `"fr"`, `"ar"`) |
| `showLanguageSelector` | `bool` | `false` | 환영 화면에 웹 스타일 언어 선택기를 표시합니다 |
| `fontFamily` | `String?` | 시스템 글꼴 | 사용자 지정 글꼴 패밀리 이름 (네이티브에 등록되어 있어야 합니다) |
| `loggingEnabled` | `bool` | `false` | SDK 디버그 로깅을 활성화합니다 |
| `showCloseButton` | `bool` | `true` | 인증 단계 화면에 닫기(X) 버튼을 표시합니다 |
| `showExitConfirmation` | `bool` | `true` | 사용자가 종료를 시도할 때 확인 대화상자를 표시합니다 |
| `closeOnComplete` | `bool` | `false` | 완료되면 인증 UI를 자동으로 닫습니다 |
| `defaultDocumentCamera` | `CameraLens?` | 네이티브 기본값 (`CameraLens.back`) | 문서 촬영 화면이 열릴 때 사용하는 렌즈 |
| `defaultLivenessCamera` | `CameraLens?` | 네이티브 기본값 (`CameraLens.front`) | 얼굴(라이브니스) 촬영 화면이 열릴 때 사용하는 렌즈 |
| `showDocumentCameraSwitchButton` | `bool` | `true` | 문서 촬영 화면에 촬영 중 카메라 전환 버튼을 표시합니다 |
| `showLivenessCameraSwitchButton` | `bool` | `true` | 얼굴 촬영 화면에 촬영 중 카메라 전환 버튼을 표시합니다 |### 카메라 옵션카메라 옵션은 [웹 SDK 카메라 옵션](https://docs.didit.me/ko/integration/web-sdks/javascript-sdk.md#카메라-옵션)과 일치합니다.
각 렌즈는 `CameraLens.front` 또는 `CameraLens.back`입니다.
기기에 요청한 렌즈가 없으면 SDK는 기기가 가진 첫 번째 카메라를 사용합니다.
카메라 전환 버튼은 카메라가 하나뿐인 기기에서도 숨겨집니다.
전환 버튼 옵션을 `false`로 설정하면 해당 단계의 기본 렌즈로 사용자를 고정합니다.```dart theme={null}
config: const DiditConfig(
  defaultLivenessCamera: CameraLens.back,
  showLivenessCameraSwitchButton: false,
),
```> [!NOTE]
>
> **테마 및 색상**: 색상, 배경, 인트로 화면 설정은 SDK 구성이 아니라 Didit Console의 [화이트 라벨 설정](https://business.didit.me)에서 구성합니다. 이렇게 하면 모든 플랫폼에서 브랜딩이 일관되게 유지됩니다.***## 언어 지원SDK는 **54개 언어**를 지원합니다. 언어를 지정하지 않으면 SDK는 기기 로캘을 사용하고, 대체 언어로 영어를 사용합니다.```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),
);
```**[지원되는 모든 언어 보기 →](https://docs.didit.me/ko/integration/supported-languages.md)*****## 고급 세션 파라미터`contact_details`, `expected_details`, `metadata` 같은 파라미터는 **세션 토큰 방식에서만** 지원됩니다. 백엔드에서 모든 파라미터를 지원하는 [`POST /v3/session/`](https://docs.didit.me/ko/sessions-api/create-session)을 호출한 다음, 반환된 `session_token`을 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]
>
> Dart의 `startVerificationWithWorkflow` 메서드는 `workflowId`, `vendorData`, `config`만 허용합니다. 3.4.0 이전 버전에서는 `contactDetails`, `expectedDetails`, `metadata` 파라미터를 제공했지만, Unilink endpoint가 이 값들을 지원하지 않기 때문에 제거되었습니다.***## 결과 처리`startVerification`과 `startVerificationWithWorkflow`는 모두 `Future<VerificationResult>`를 반환합니다. 결과는 sealed 클래스이며, 패턴 매칭을 사용하여 결과를 판단합니다.### 결과 케이스| 케이스 | 설명 |
| - | - |
| `VerificationCompleted` | 인증 흐름이 완료되었습니다 (결과는 `session.status`로 확인) |
| `VerificationCancelled` | 사용자가 인증 흐름을 취소했습니다 |
| `VerificationFailed` | 인증 중 오류가 발생했습니다 |### SessionData 속성| 속성 | 타입 | 설명 |
| - | - | - |
| `sessionId` | `String` | 고유 세션 식별자 |
| `status` | `VerificationStatus` | `approved`, `pending`, 또는 `declined` |### 오류 유형`VerificationErrorType` 열거형은 다음 케이스를 제공합니다:| 오류 | 설명 |
| - | - |
| `sessionExpired` | 세션이 만료되었습니다 |
| `networkError` | 네트워크 연결 문제 |
| `cameraAccessDenied` | 카메라 권한이 허용되지 않았습니다 |
| `notInitialized` | SDK가 초기화되지 않았습니다 (Android 전용) |
| `apiError` | API 요청이 실패했습니다 |
| `retryBlocked` | 현재 단계의 최대 재시도 횟수를 초과했습니다 |
| `unknown` | 기타 오류입니다. 자세한 내용은 `error.message`를 확인하세요 |### 연결 끊김인증 도중 기기의 연결이 끊어지면, SDK는 단계가 계속 로딩 상태로 남지 않도록 "인터넷 연결이 없습니다" 화면을 표시합니다.
SDK는 백그라운드에서 계속 확인하고, Didit에 다시 연결할 수 있게 되면 화면을 스스로 닫습니다. 사용자는 중단되었던 단계에서 이어서 진행합니다.
이 화면에는 **다시 시도** 버튼이 있으며, 닫기 버튼을 끄지 않았다면 **인증 종료** 버튼도 표시됩니다.
종료하면 오류 유형이 `networkError`인 `VerificationFailed` 결과로 인증이 끝나므로, 앱은 사용자가 연결 문제로 떠났음을 알 수 있습니다.### 전체 결과 처리 예제```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
  }
}
```***## 엔드 투 엔드 예제 (백엔드 세션 → Flutter SDK → webhook)프로덕션에 적용할 수 있는 연동 방식입니다. 백엔드가 세션을 생성하고, Flutter 앱이 `session_token`을 받아 SDK로 흐름을 실행합니다. 최종 결정은 webhook을 통해 백엔드로 전달됩니다. 클라이언트 측 결과만으로는 절대 신뢰하지 마세요.### 1. 백엔드: 세션 생성```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: 토큰 교환 후 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. 백엔드: 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);
});
```HMAC 서명 검증에 대한 자세한 내용은 [webhook 가이드](https://docs.didit.me/ko/integration/webhooks.md)를 참고하세요.***## 전체 예제```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.