> ## 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 KYC SDK。iOS・Android対応、生体認証ライブネス、NFCパスポート読み取り。フルKYC1件あたり$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.9.1/DiditSDK.podspec'
The tag in that URL must equal the native iOS SDK version the didit_sdk release pins (4.9.1 pins 4.9.1); do not use 'main', because the plugin requires that exact version and pod install fails when the URL resolves to a different one.
'all' and 'nfc' require platform :ios, '15.0'; 'core' and 'autodetection' run on iOS 13.0+.
Then: cd ios && pod install
Info.plist must include NSCameraUsageDescription, NSMicrophoneUsageDescription, NSPhotoLibraryUsageDescription (required) and NFCReaderUsageDescription if using NFC.

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

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

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

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

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

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

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

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

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

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

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

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

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

\</pre>



      
      

貼り付け先

ネイティブのiOS SDKとAndroid SDKをラップするクロスプラットフォームのFlutterプラグインです。本人確認のための統一されたDart APIを提供します。> [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 SDKとAndroid SDKをラップしており、最適なユーザー体験、最適化されたカメラ処理、完全なNFCサポートを実現します。***## 要件| 要件 | 最小バージョン |
| - | - |
| Flutter | 3.3+ |
| Dart | 3.11+ |### プラットフォーム要件| プラットフォーム | 最小バージョン | 備考 |
| - | - | - |
| iOS | 13.0+ | NFCによるパスポート読み取りにはiOS 15.0+が必要です |
| Android | API 23+ (6.0) | Java 17+ |> [!NOTE]
>
> **生体認証への同意にはネイティブSDK 4.10.0以降が必要です。** イリノイ州、テキサス州、コロラド州の法律が適用される人に対して、Diditは最初の顔撮影の前に明示的な[生体認証への同意](https://docs.didit.me/ja/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.9.1
```その後、次のコマンドを実行します。```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.9.1
```これらのパッケージのうち1つだけを依存関係として指定し、選択したパッケージからインポートしてください。例: `package:didit_sdk_core/sdk_flutter.dart`。### iOSのセットアップこのプラグインは、FlutterのiOS向け依存関係マネージャーであるSwift Package ManagerとCocoaPodsの両方に対応しています。#### Swift Package Managerお使いのFlutterで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.9.1/DiditSDK.podspec'

target 'Runner' do
  use_frameworks!

  pod didit_sdk_ios_pod, :podspec => didit_sdk_ios_podspec

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

post_install do |installer|
  installer.pods_project.targets.each do |target|
    flutter_additional_ios_build_settings(target)
    target.build_configurations.each do |config|
      config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = didit_sdk_ios_deployment_target
    end
  end
end
```バリアントパッケージ(`didit_sdk_core`、`didit_sdk_autodetection`、`didit_sdk_nfc`)を使用する場合、そのパッケージがネイティブのsubspecを直接固定指定し、`$DiditSdkIosVariant`は無視されます。Podfileの`pod`行とデプロイメントターゲットは、そのバリアントに合わせてください(例: `pod 'DiditSDK/Core', :podspec => ...`と`platform :ios, '13.0'`)。> [!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.9.1`はネイティブ`4.9.1`を固定指定します)。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を手動で設定する必要はありません。***## 権限### iOSアプリの`Info.plist`に次のキーを追加します。| 権限 | Info.plistキー | 説明 | 必須 |
| - | - | - | - |
| カメラ | `NSCameraUsageDescription` | 書類のスキャンと顔認証 | はい |
| マイク | `NSMicrophoneUsageDescription` | ライブネスチェック用の動画撮影 | はい |
| フォトライブラリ | `NSPhotoLibraryUsageDescription` | 端末のギャラリーからの書類アップロード | はい |
| NFC | `NFCReaderUsageDescription` | パスポートやIDカードのNFCチップの読み取り | NFCを使用する場合 |
| 位置情報 | `NSLocationWhenInUseUsageDescription` | 不正防止のための位置情報取得 | 任意 |> [!WARNING]
>
> 必須のiOSプライバシーキーが1つでも欠けていると、SDKがその保護されたリソースにアクセスしようとした時点でiOSはアプリを終了させます。たとえば`NSCameraUsageDescription`がない場合、ユーザーが書類撮影カメラの撮影ボタンをタップするとクラッシュします。#### NFCの設定チップ付きのパスポートやIDカードの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/ja/core-technology/nfc-verification/overview.md#日本のマイナンバーカード-jpki)を通じて日本のマイナンバーカードを読み取れるようになります。この識別子がないと、iOSはカードをSDKに通知しません。マイナンバーカードの読み取りは本番環境ではまだ有効になっていませんが、この識別子は他の書類には影響しないため、今のうちに追加しておけます。3) NFCタグ読み取りを有効にした**entitlementsファイルを追加**します。```xml theme={null}
<key>com.apple.developer.nfc.readersession.formats</key>
<array>
    <string>TAG</string>
</array>
```アプリのプロビジョニングプロファイルにNFC Tag Reading機能が含まれていることを確認してください。Apple Developerアカウントでバンドル IDがNFC用に設定されていない場合、`com.apple.developer.nfc.readersession.formats` entitlementがないというエラーでXcodeによる署名が失敗します。このNFC設定は、`core`または`autodetection`バリアント(`$DiditSdkIosVariant`)や、`didit_sdk_core` / `didit_sdk_autodetection`パッケージでは不要です。これらはいずれもチップを読み取りません。### Android次の権限はSDKの`AndroidManifest.xml`で宣言されており、自動的にマージされます。| 権限 | 説明 | 必須 |
| - | - | - |
| `INTERNET` | API通信のためのネットワークアクセス | はい |
| `ACCESS_NETWORK_STATE` | ネットワークの利用可否の検出 | はい |
| `CAMERA` | 書類のスキャンと顔認証 | はい |
| `NFC` | パスポートやIDカードのNFCチップの読み取り | NFCを使用する場合 |カメラとNFCのハードウェア機能は任意(`android:required="false"`)として宣言されているため、これらの機能を持たない端末にもアプリをインストールできます。`diditSdkAndroidNfcEnabled=false`の場合、SDKはAndroidのNFC権限と機能を追加しません。#### ランタイム権限SDKはAndroidのランタイム権限のリクエストを自動的に処理します。ユーザーがカメラへのアクセスを必要とするステップに到達すると、次のように動作します。1) カメラ権限がまだ付与されていない場合、SDKが権限を求めます
2) ユーザーが権限を**拒否**した場合、**Try Again**ボタン付きのエラーメッセージが表示されます
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は2つの連携方法をサポートしています。### 方法1: セッショントークン(本番環境で推奨)[検証セッション作成API](https://docs.didit.me/ja/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/ja/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` | ウェルカム画面にWeb版と同じ形式の言語セレクターを表示します |
| `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` | 顔の撮影画面に撮影中のカメラ切り替えボタンを表示します |### カメラオプションカメラオプションは[Web SDKのカメラオプション](https://docs.didit.me/ja/integration/web-sdks/javascript-sdk.md#カメラオプション)と同じです。
各レンズには`CameraLens.front`または`CameraLens.back`を指定できます。
指定したレンズがデバイスにない場合、SDKはデバイスにある最初のカメラを使用します。
カメラが1台しかないデバイスでも、カメラ切り替えボタンは非表示になります。
切り替えボタンのオプションを`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/ja/integration/supported-languages.md)*****## 高度なセッションパラメーター`contact_details`、`expected_details`、`metadata`などのパラメーターは、セッショントークン方式で**のみ**サポートされています。バックエンドがすべてのパラメーターに対応した[`POST /v3/session/`](https://docs.didit.me/ja/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エンドポイントがこれらのパラメーターを無視するため削除されました。***## 結果の処理`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はステップを読み込み中のままにせず、フローの上に「No internet connection」画面を表示します。
SDKはバックグラウンドで接続の確認を続け、Diditに再び接続できるようになるとすぐに自動的に画面を閉じます。ユーザーは中断したステップから再開できます。
この画面には**Try Again**と、閉じるボタンが無効になっていない限り**Exit Verification**が表示されます。
終了すると、エラーの種類が`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/ja/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.