Skip to main content
A lightweight, server-driven iOS SDK for identity verification with minimal configuration required. Latest version: 3.6.0.

GitHub Repository

View source code, releases, and the complete changelog on GitHub

Requirements

RequirementMinimum Version
iOS13.0+
Xcode15.0+
Swift5.9+

iOS Version Compatibility

iOS VersionFeatures Available
iOS 13.0 - 14.xAll features except NFC passport reading
iOS 15.0+All features including NFC passport reading

Installation

The SDK ships in two flavors:
ProductCocoaPodsSwiftPM productIncludesMinimum iOS
FullDiditSDKDiditSDKAll features, including NFC passport readingiOS 15.0+
CoreDiditSDK/CoreDiditSDKCoreAll features except NFC. No OpenSSL.xcframework, no CoreNFC runtime requirementiOS 13.0+
Pick Full if your workflow scans the NFC chip on passports/eIDs. Pick Core if you only need document + face + liveness and want to avoid the NFC binary, CoreNFC runtime, and the App Store NFC demo-video review request. Add the package to your project using Xcode:
  1. Go to File > Add Package Dependencies
  2. Enter the repository URL:
https://github.com/didit-protocol/sdk-ios.git
  1. Select the version (or Up to Next Major from 3.6.0) and click Add Package
  2. When prompted to choose a product, pick DiditSDK (Full, NFC) or DiditSDKCore (no NFC)
Or in your Package.swift:
dependencies: [
    .package(url: "https://github.com/didit-protocol/sdk-ios.git", from: "3.6.0")
]
Then add one of these products to your target:
// Full SDK (includes NFC passport reading)
.product(name: "DiditSDK", package: "DiditSDK")

// Core SDK (no NFC dependencies)
.product(name: "DiditSDKCore", package: "DiditSDK")

CocoaPods

DiditSDK is distributed as a binary podspec hosted in the repo (it is not on the public CocoaPods Trunk), so you must reference the podspec URL. Full SDK (with NFC, iOS 15.0+):
platform :ios, '15.0'

pod 'DiditSDK', :podspec => 'https://raw.githubusercontent.com/didit-protocol/sdk-ios/main/DiditSDK.podspec'
Core SDK (no NFC, iOS 13.0+):
platform :ios, '13.0'

pod 'DiditSDK/Core', :podspec => 'https://raw.githubusercontent.com/didit-protocol/sdk-ios/main/DiditSDK.podspec'
Then run:
pod install
After pod install, open the generated .xcworkspace (not .xcodeproj).
Xcode 15+ rsync errors: If you see Operation not permitted rsync errors during build, set Build Settings → User Script Sandboxing (ENABLE_USER_SCRIPT_SANDBOXING) to No on the project for both Debug and Release.

Manual (XCFramework)

Download the frameworks from the GitHub Releases page, then drag the .xcframework folders into Xcode and set Embed & Sign.
  • Full SDK: DiditSDK.xcframework.zip + OpenSSL.xcframework.zip
  • Core SDK: DiditSDK-Core.xcframework.zip

Permissions

The SDK requires the following permissions. Add these to your app’s Info.plist:
PermissionInfo.plist KeyDescriptionRequired
CameraNSCameraUsageDescriptionDocument scanning and face verification✅ Yes
MicrophoneNSMicrophoneUsageDescriptionVideo recording for liveness checks✅ Yes
Photo LibraryNSPhotoLibraryUsageDescriptionUpload documents from device gallery✅ Yes
NFCNFCReaderUsageDescriptionRead NFC chips in passports/ID cards⚠️ Full SDK only
LocationNSLocationWhenInUseUsageDescriptionGeolocation for fraud prevention❌ Optional

Example Info.plist Entries

<key>NSCameraUsageDescription</key>
<string>Camera access is required to scan your identity documents for verification.</string>

<key>NSMicrophoneUsageDescription</key>
<string>Microphone access is required to record video for liveness verification.</string>

<key>NSPhotoLibraryUsageDescription</key>
<string>Photo library access is required to upload document images.</string>

<key>NFCReaderUsageDescription</key>
<string>NFC access is required to read the chip in your identity document.</string>

<key>NSLocationWhenInUseUsageDescription</key>
<string>Location access helps verify your identity and prevent fraud.</string>

NFC Configuration

Required only when installing the Full SDK. Skip this section if you installed DiditSDK/Core (CocoaPods) or DiditSDKCore (SwiftPM). To enable NFC reading for passports and ID cards with chips:
  1. Add NFC Capability in Xcode:
    • Select your target → Signing & Capabilities+ CapabilityNear Field Communication Tag Reading
  2. Add ISO7816 Identifiers to Info.plist:
<key>com.apple.developer.nfc.readersession.iso7816.select-identifiers</key>
<array>
    <string>D23300000045737445494420763335</string>
    <string>A0000002471001</string>
    <string>A0000002472001</string>
    <string>00000000000000</string>
</array>
  1. Add Entitlements (in your .entitlements file):
<key>com.apple.developer.nfc.readersession.formats</key>
<array>
    <string>TAG</string>
</array>
Simulator Limitation (Full SDK only): The Full SDK links CoreNFC, and since Xcode 12 libnfshared.dylib is missing from simulators. See this Stack Overflow thread for a workaround. This does not apply when installing the Core SDK (DiditSDK/Core or DiditSDKCore). Test NFC features on physical devices only.

Quick Start

SwiftUI Integration

import SwiftUI
import DiditSDK

struct ContentView: View {
    var body: some View {
        Button("Verify Identity") {
            // Method 1: UniLink — no backend required
            DiditSdk.shared.startVerification(workflowId: "your-workflow-id")
            
            // Method 2: Backend Session — full parameter control
            // DiditSdk.shared.startVerification(token: "your-session-token")
        }
        .diditVerification { result in
            handleResult(result)
        }
    }
    
    private func handleResult(_ result: VerificationResult) {
        switch result {
        case .completed(let session):
            print("Completed: \(session.status)")
        case .cancelled(let session):
            print("Cancelled")
        case .failed(let error, _):
            print("Failed: \(error.localizedDescription)")
        }
    }
}

UIKit Integration

import UIKit
import DiditSDK

class VerificationViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Start verification when ready
        DiditSdk.shared.startVerification(token: "your-session-token")
    }
}

Integration Methods

The SDK supports two integration methods: No backend required. The SDK creates the session directly using your workflow ID from the Didit Console. The UniLink method (startVerification(workflowId:)) only supports vendorData; for any other session parameters use Method 2.
For vendorData to be attached to the session via UniLink, enable the Vendor Data option in the Didit Console.
DiditSdk.shared.startVerification(
    workflowId: "your-workflow-id",
    vendorData: "user-123"
)
Your backend creates the session via the Create Verification Session API (POST /v3/session/) with full parameter support (contact_details, expected_details, metadata, callback, etc.), then passes the session_token to the SDK.
// Your backend creates a session and returns the token
let sessionToken = await yourBackend.createVerificationSession(userId: currentUser.id)

// Pass the token to the SDK
DiditSdk.shared.startVerification(token: sessionToken)
This approach gives you full control over:
  • Associating sessions with your users (vendor_data)
  • Setting contact details and expected details for cross-validation
  • Setting custom metadata
  • Configuring callbacks per session This data (contact details, expected details, metadata, callback) is sent to the Create Verification Session API.

Configuration

Customize the SDK behavior with DiditSdk.Configuration:
let configuration = DiditSdk.Configuration(
    languageLocale: .spanish,      // Force Spanish language
    fontFamily: "Avenir",          // Custom font (must be registered in your app)
    loggingEnabled: true,          // Enable debug logging
    showCloseButton: true,         // Show close (X) button on step screens
    showExitConfirmation: true,    // Show confirmation dialog when user taps close
    closeOnComplete: false         // Don't auto-dismiss on completion
)

DiditSdk.shared.startVerification(
    token: "your-session-token",
    configuration: configuration
)

Configuration Options

PropertyTypeDefaultDescription
languageLocaleSupportedLanguage?Device localeForce a specific language
fontFamilyString?System fontCustom font family name (must be registered in your app via UIAppFonts in Info.plist)
loggingEnabledBoolfalseEnable SDK debug logging
showCloseButtonBooltrueShow close (X) button on verification step screens
showExitConfirmationBooltrueShow confirmation dialog when user attempts to exit
closeOnCompleteBoolfalseAuto-dismiss verification UI when complete (Web SDK equivalent: closeModalOnComplete)
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.
Options showCloseButton, showExitConfirmation, and closeOnComplete match the Web SDK’s DiditSdkConfiguration. Mobile-specific options languageLocale and fontFamily exist because the mobile SDK renders the full verification UI natively (unlike the Web SDK which delegates to the hosted frontend inside an iframe).

Language Support

The SDK supports 53 languages. If no language is specified, the SDK uses the device locale with English as fallback.
// Use device locale (default)
let config = DiditSdk.Configuration()

// Force specific language
let config = DiditSdk.Configuration(languageLocale: .french)

// Detect device locale programmatically
let deviceLanguage = SupportedLanguage.fromDeviceLocale()
View All Supported Languages →

Advanced Session Parameters

For advanced session parameters (contact_details, expected_details, metadata, callback), use the Backend Session method. Your backend calls the Create Verification Session API with full parameters, then passes the session_token to the SDK.

Handling Results

The VerificationResult enum provides the outcome of the verification:

Result Cases

CaseDescription
.completed(session:)Verification flow completed (check session.status for result)
.cancelled(session:)User cancelled the verification flow
.failed(error:session:)An error occurred during verification

SessionData Properties

PropertyTypeDescription
sessionIdStringUnique session identifier
statusVerificationStatus.approved, .pending, or .declined
countryString?Country code (ISO 3166-1 alpha-3)
documentTypeString?Document type used for verification

Error Types

ErrorDescription
.sessionExpiredThe session has expired
.networkErrorNetwork connectivity issue
.cameraAccessDeniedCamera permission not granted
.unknown(String)Other error with message

Complete Result Handling Example

.diditVerification { result in
    switch result {
    case .completed(let session):
        switch session.status {
        case .approved:
            print("✅ Approved! Session: \(session.sessionId)")
            // User is verified - grant access
            
        case .pending:
            print("⏳ Under review. Session: \(session.sessionId)")
            // Show "verification in progress" UI
            
        case .declined:
            print("❌ Declined. Session: \(session.sessionId)")
            // Handle declined verification
        }
        
    case .cancelled(let session):
        if let session = session {
            print("🚫 Cancelled session: \(session.sessionId)")
        }
        // User chose to cancel - maybe show retry option
        
    case .failed(let error, let session):
        print("⚠️ Error: \(error.localizedDescription)")
        // Handle error - show retry or contact support
    }
}

Dismissing the Verification Programmatically

Available in DiditSDK 3.6.0+.
The host app can end an active verification programmatically with DiditSdk.shared.dismiss(). This is the recommended way to tear down the verification when the host needs to take over the screen — for example, when the app moves to the background.
DiditSdk.shared.dismiss()
dismiss() goes through the SDK’s normal completion pipeline: it dismisses the presented UI, resets internal state, and invokes the .diditVerification handler with .cancelled(session:) carrying the current sessionId if a session was created. It is a no-op when no verification is active.

Example: dismiss when the app backgrounds

NotificationCenter.default.addObserver(
    forName: UIApplication.didEnterBackgroundNotification,
    object: nil,
    queue: .main
) { _ in
    DiditSdk.shared.dismiss()
}
Do not set DiditSdk.shared.isPresented = false to dismiss — the flag only triggers presentation on its rising edge and setting it to false is a no-op. Likewise, calling UIKit’s dismiss(animated:) on the topmost view controller is not supported: it bypasses the SDK’s completion pipeline so your .diditVerification handler is never fired.

Observing SDK State

You can observe the SDK state for custom loading UI:
struct CustomView: View {
    @ObservedObject private var sdk = DiditSdk.shared
    
    var body: some View {
        VStack {
            switch sdk.state {
            case .idle:
                Text("Ready to verify")
            case .creatingSession:
                ProgressView("Creating session...")
            case .loading:
                ProgressView("Loading...")
            case .ready:
                Text("Verification in progress")
            case .error(let message):
                Text("Error: \(message)")
            }
        }
    }
}

End-to-End Example (Backend Session → iOS SDK → Result)

This pattern is the production-ready integration. Your backend creates the session, your iOS app receives the session_token, and the SDK runs the flow.

1. Backend — create the session

// 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. iOS — exchange and start the SDK

import SwiftUI
import DiditSDK

struct VerifyButton: View {
    let userId: String
    @State private var error: String?

    var body: some View {
        Button("Verify Identity") {
            Task { await start() }
        }
        .diditVerification { result in
            switch result {
            case .completed(let session) where session.status == .approved:
                // Optimistically reflect success in the UI; rely on the webhook
                // delivered to your backend as the source of truth.
                break
            case .completed(let session):
                print("Terminal status: \(session.status.rawValue)")
            case .cancelled:
                print("User cancelled")
            case .failed(let err, _):
                error = err.localizedDescription
            }
        }
    }

    private func start() async {
        do {
            var req = URLRequest(url: URL(string: "https://your-backend.example.com/api/verification/start")!)
            req.httpMethod = "POST"
            req.setValue("application/json", forHTTPHeaderField: "Content-Type")
            req.httpBody = try JSONEncoder().encode(["userId": userId])

            let (data, _) = try await URLSession.shared.data(for: req)
            struct Payload: Decodable { let session_token: String }
            let payload = try JSONDecoder().decode(Payload.self, from: data)

            await MainActor.run {
                DiditSdk.shared.startVerification(token: payload.session_token)
            }
        } catch {
            self.error = error.localizedDescription
        }
    }
}

3. Backend — receive the final decision via webhook

The SDK result is convenient for UI feedback, but the authoritative outcome arrives via webhook. See the Webhooks guide for HMAC verification.
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);
});

Complete SwiftUI Example

import SwiftUI
import DiditSDK

struct HomeView: View {
    @State private var resultMessage: String?
    @State private var isVerified = false
    
    var body: some View {
        VStack(spacing: 24) {
            if isVerified {
                Image(systemName: "checkmark.circle.fill")
                    .font(.system(size: 64))
                    .foregroundColor(.green)
                Text("Identity Verified")
                    .font(.title2)
            } else {
                Button("Verify Identity") {
                    startVerification()
                }
                .font(.headline)
                .padding(.horizontal, 32)
                .padding(.vertical, 16)
                .background(Color.blue)
                .foregroundColor(.white)
                .cornerRadius(12)
            }
            
            if let message = resultMessage {
                Text(message)
                    .foregroundColor(.secondary)
                    .multilineTextAlignment(.center)
                    .padding()
            }
        }
        .diditVerification { result in
            handleResult(result)
        }
    }
    
    private func startVerification() {
        let config = DiditSdk.Configuration(
            languageLocale: .english
        )
        
        // Method 1: UniLink — no backend required
        DiditSdk.shared.startVerification(
            workflowId: "your-workflow-id",
            vendorData: "user-123",
            configuration: config
        )
        
        // Method 2: Backend Session — full parameter control
        // DiditSdk.shared.startVerification(
        //     token: "your-session-token",
        //     configuration: config
        // )
    }
    
    private func handleResult(_ result: VerificationResult) {
        switch result {
        case .completed(let session):
            if session.status == .approved {
                isVerified = true
            }
            resultMessage = """
                Status: \(session.status.rawValue)
                Session: \(session.sessionId)
                Country: \(session.country ?? "N/A")
                Document: \(session.documentType ?? "N/A")
                """
                
        case .cancelled(let session):
            resultMessage = "Cancelled - Session: \(session?.sessionId ?? "unknown")"
            
        case .failed(let error, _):
            resultMessage = "Failed: \(error.localizedDescription)"
        }
    }
}