Skip to main content
A cross-platform Flutter plugin that wraps the native iOS and Android SDKs, providing a unified Dart API for identity verification.

GitHub Repository

View source code and examples on GitHub

pub.dev Package

didit_sdk

Requirements

RequirementMinimum Version
Flutter3.3+
Dart3.11+

Platform Requirements

PlatformMinimum VersionNotes
iOS13.0+NFC passport reading requires iOS 15.0+
AndroidAPI 23+ (6.0)Java 17+

Installation

flutter pub add didit_sdk
Or add it manually to your pubspec.yaml:
dependencies:
  didit_sdk: ^3.2.0
Then run:
flutter pub get

iOS Setup

Add the DiditSDK pod to your ios/Podfile (it’s not on CocoaPods trunk):
# In your ios/Podfile, inside the target block:
pod 'DiditSDK', :podspec => 'https://raw.githubusercontent.com/didit-protocol/sdk-ios/main/DiditSDK.podspec'
Then install dependencies:
cd ios
pod install

Android Setup

Add a packaging rule to your app’s android/app/build.gradle.kts to avoid build conflicts from the SDK’s cryptography dependencies:
android {
    packaging {
        resources {
            pickFirsts += "META-INF/versions/9/OSGI-INF/MANIFEST.MF"
        }
    }
}
The Android native SDK dependency is bundled inside the plugin — no Maven repository configuration is needed.

Permissions

iOS

Add the following keys to your app’s Info.plist:
PermissionInfo.plist KeyDescriptionRequired
CameraNSCameraUsageDescriptionDocument scanning and face verificationYes
NFCNFCReaderUsageDescriptionRead NFC chips in passports/ID cardsIf using NFC
LocationNSLocationWhenInUseUsageDescriptionGeolocation for fraud preventionOptional

NFC Configuration

To enable NFC reading for passports and ID cards with chips:
  1. Add NFC Capability in Xcode:
    • Select your target > Signing & Capabilities > + Capability > Near Field Communication Tag Reading
  2. Add ISO7816 Identifiers to Info.plist:
<key>com.apple.developer.nfc.readersession.iso7816.select-identifiers</key>
<array>
    <string>A0000002471001</string>
</array>

Android

The following permissions are declared in the SDK’s AndroidManifest.xml and merged automatically:
PermissionDescriptionRequired
INTERNETNetwork access for API communicationYes
ACCESS_NETWORK_STATEDetect network availabilityYes
CAMERADocument scanning and face verificationYes
NFCRead NFC chips in passports/ID cardsIf 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.

Quick Start

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}');
}

Integration Methods

The SDK supports two integration methods: Create a session on your backend using the Create Verification Session API, then pass the token to the SDK:
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);
This approach gives you full control over:
  • Associating sessions with your users (vendor_data)
  • Setting custom metadata
  • Configuring callbacks per session

Method 2: Workflow ID (Simpler Integration)

For simpler integrations, the SDK can create sessions directly using your workflow ID:
import 'package:didit_sdk/sdk_flutter.dart';

final result = await DiditSdk.startVerificationWithWorkflow(
  'your-workflow-id',
  vendorData: 'user-123',
  contactDetails: const ContactDetails(email: 'user@example.com'),
  config: const DiditConfig(loggingEnabled: true),
);

Configuration

Customize the SDK behavior by passing a DiditConfig object:
final result = await DiditSdk.startVerification(
  'your-session-token',
  config: const DiditConfig(
    languageCode: 'es',       // Force Spanish language
    fontFamily: 'Avenir',     // Custom font
    loggingEnabled: true,     // Enable debug logging
  ),
);
For startVerificationWithWorkflow, pass config as a named parameter:
final result = await DiditSdk.startVerificationWithWorkflow(
  'your-workflow-id',
  vendorData: 'user-123',
  config: const DiditConfig(
    languageCode: 'es',
    loggingEnabled: true,
  ),
);

Configuration Options

PropertyTypeDefaultDescription
languageCodeString?Device localeISO 639-1 language code (e.g. "en", "fr", "ar")
fontFamilyString?System fontCustom font family name (must be registered natively)
loggingEnabledboolfalseEnable 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.

Language Support

The SDK supports 40+ 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 language
await DiditSdk.startVerification(token,
  config: const DiditConfig(languageCode: 'fr'),
);
View All Supported Languages →

Advanced Options

These options are only available with startVerificationWithWorkflow, where the SDK creates the session on your behalf.

Contact Details (Prefill & Notifications)

Provide contact details to prefill verification forms and enable email notifications:
final result = await DiditSdk.startVerificationWithWorkflow(
  'your-workflow-id',
  contactDetails: const ContactDetails(
    email: 'user@example.com',
    sendNotificationEmails: true,  // Send status update emails
    emailLang: 'en',               // Email language (ISO 639-1)
    phone: '+14155552671',         // E.164 format
  ),
);

Expected Details (Cross-Validation)

Provide expected user details for automatic cross-validation with extracted document data:
final result = await DiditSdk.startVerificationWithWorkflow(
  'your-workflow-id',
  expectedDetails: const ExpectedDetails(
    firstName: 'John',
    lastName: 'Doe',
    dateOfBirth: '1990-05-15',     // YYYY-MM-DD format
    nationality: 'USA',            // ISO 3166-1 alpha-3
    country: 'USA',
  ),
);

Custom Metadata

Store custom JSON metadata with the session (not displayed to user):
final result = await DiditSdk.startVerificationWithWorkflow(
  'your-workflow-id',
  vendorData: 'user-123',
  metadata: '{"internalId": "abc123", "source": "mobile-app"}',
);

Handling Results

Both startVerification and startVerificationWithWorkflow return a Future<VerificationResult>. The result is a sealed class — use pattern matching to determine the outcome.

Result Cases

CaseDescription
VerificationCompletedVerification flow completed (check session.status for result)
VerificationCancelledUser cancelled the verification flow
VerificationFailedAn error occurred during verification

SessionData Properties

PropertyTypeDescription
sessionIdStringUnique session identifier
statusVerificationStatusapproved, pending, or declined

Error Types

ErrorDescription
sessionExpiredThe session has expired
networkErrorNetwork connectivity issue
cameraAccessDeniedCamera permission not granted
notInitializedSDK not initialized (Android only)
apiErrorAPI request failed
unknownOther error with message

Complete Result Handling Example

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

Complete Example

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),
                      ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}