Native SDK: This is the recommended approach for Flutter apps. The SDK wraps the native iOS and Android SDKs via platform channels for the best user experience, optimized camera handling, and full NFC support.
The plugin selects the native iOS SDK variant from the DIDIT_SDK_IOS_NFC_ENABLED environment variable. Configure your ios/Podfile (the DiditSDK pod is not on CocoaPods trunk):
didit_sdk_ios_nfc_enabled = ENV.fetch('DIDIT_SDK_IOS_NFC_ENABLED', 'true').downcase != 'false'platform :ios, didit_sdk_ios_nfc_enabled ? '15.0' : '13.0'didit_sdk_ios_pod = didit_sdk_ios_nfc_enabled ? 'DiditSDK' : 'DiditSDK/Core'didit_sdk_ios_podspec = 'https://raw.githubusercontent.com/didit-protocol/sdk-ios/main/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__))endpost_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_nfc_enabled ? '15.0' : '13.0' end endend
Install the full SDK with NFC (default):
cd iospod install
Install the core SDK without NFC (removes NFCPassportReader, CoreNFC-linked code, and OpenSSL):
cd iosDIDIT_SDK_IOS_NFC_ENABLED=false pod install
NFC-enabled iOS builds require a deployment target of iOS 15.0+; core-only builds can target iOS 13.0. When switching variants, clean CocoaPods first (rm -rf Pods Podfile.lock) so the previous SDK variant is not reused.
By default the plugin depends on the full Android SDK including NFC. To build without NFC, add this to android/gradle.properties:
diditSdkAndroidNfcEnabled=false
This switches the dependency from me.didit:didit-sdk to me.didit:didit-sdk-core, removing the NFC reader module and its JMRTD/SCUBA/BouncyCastle dependencies.When NFC is enabled (default), add this packaging rule to android/app/build.gradle.kts to resolve a duplicate metadata file from BouncyCastle:
If any required iOS privacy key is missing, iOS terminates the app as soon as the SDK tries to access that protected resource. For example, missing NSCameraUsageDescription causes a crash when the user taps the document camera’s take photo button.
Make sure the app’s provisioning profile includes the NFC Tag Reading capability. If the bundle ID is not configured for NFC in your Apple Developer account, Xcode will fail signing with a missing com.apple.developer.nfc.readersession.formats entitlement.This NFC configuration is not needed when DIDIT_SDK_IOS_NFC_ENABLED=false.
The following permissions are declared in the SDK’s AndroidManifest.xml and merged automatically:
Permission
Description
Required
INTERNET
Network access for API communication
Yes
ACCESS_NETWORK_STATE
Detect network availability
Yes
CAMERA
Document scanning and face verification
Yes
NFC
Read NFC chips in passports/ID cards
If using NFC
Camera and NFC hardware features are declared as optional (android:required="false"), so your app can be installed on devices without these features. When diditSdkAndroidNfcEnabled=false, the Android NFC permission and feature are not added by the SDK.
import 'package:didit_sdk/sdk_flutter.dart';// Start verification with a session token from your backendfinal 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}');}
import 'package:didit_sdk/sdk_flutter.dart';// Your backend creates a session and returns the tokenfinal sessionToken = await yourBackend.createVerificationSession(userId);// Pass the token to the SDKfinal result = await DiditSdk.startVerification(sessionToken);
This approach gives you full control over:
Associating sessions with your users (vendor_data)
Advanced session parameters (contact_details, expected_details, metadata) are only supported through the Session Token method, where your backend calls the Create Session API with full parameter support. Pass the returned session_token to DiditSdk.startVerification().
Custom font family name (must be registered natively)
loggingEnabled
bool
false
Enable SDK debug logging
Theming & Colors: Colors, backgrounds, and intro screen settings are configured through your White Label settings in the Didit Console, not in the SDK configuration. This ensures consistent branding across all platforms.
The SDK supports 53 languages. If no language is specified, the SDK uses the device locale with English as fallback.
// Use device locale (default)await DiditSdk.startVerification(token);// Force specific languageawait DiditSdk.startVerification(token, config: const DiditConfig(languageCode: 'fr'),);
Parameters like contact_details, expected_details, and metadata are only supported through the Session Token method. Your backend calls POST /v3/session/ with full parameter support, then passes the returned session_token to the SDK.
// 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 tokenfinal result = await DiditSdk.startVerification(sessionToken);
The Dart startVerificationWithWorkflow method only accepts workflowId, vendorData, and config. Pre-3.4.0 versions exposed contactDetails, expectedDetails, and metadata parameters, but these were removed because the Unilink endpoint does not honour them.
Both startVerification and startVerificationWithWorkflow return a Future<VerificationResult>. The result is a sealed class — use pattern matching to determine the outcome.
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 }}
End-to-End Example (Backend Session → Flutter SDK → Webhook)
The production-ready integration: your backend creates the session, your Flutter app receives the session_token, and the SDK runs the flow. The final decision is delivered to your backend via webhook — never trust the client-side result alone.
3. Backend — receive the final decision via webhook
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);});
See the Webhooks guide for HMAC signature verification details.