A lightweight iOS SDK for identity verification. Server-driven, minimal configuration required.
- iOS 13.0+ (NFC passport reading requires iOS 15.0+)
- Xcode 15.0+
- Swift 5.9+
| iOS Version | Features Available |
|---|---|
| iOS 13.0 - 14.x | All features except NFC passport reading |
| iOS 15.0+ | All features including NFC passport reading |
The SDK ships in installable variants so apps only pay for the features they use. Since 4.10.0 every variant includes auto capture.
| Variant | NFC passport reading | Auto capture | Location verification | Use when |
|---|---|---|---|---|
Core |
– | ✓ | – | smallest footprint |
NFC |
✓ | ✓ | – | passport chip reading |
AutoDetection |
– | ✓ | – | deprecated, identical to Core; switch to Core |
All |
✓ | ✓ | – | every feature (default subspec) |
Core-Location |
– | ✓ | ✓ | Core with the Location verification step |
NFC-Location |
✓ | ✓ | ✓ | NFC with the Location verification step |
All-Location |
✓ | ✓ | ✓ | All with the Location verification step |
What each variant adds to an app, measured per release: iOS SDK installation.
The detection models are downloaded at runtime and cached, so they do not contribute to the app bundle size.
Only the Location variants link Core Location; each carries the privacy manifest for the location it collects.
NFC and All additionally embed OpenSSL.xcframework.
App Store delivery compresses these binaries; use App Store Connect's app size report for the exact per-device download size.
Each subspec and library product is one complete prebuilt framework: pick exactly one subspec in your Podfile and exactly one library product in your Package.swift.
The SDK requires the following permissions. Add these to your app's Info.plist:
| Permission | Info.plist Key | Description |
|---|---|---|
| Camera | NSCameraUsageDescription |
Document scanning and face verification |
| Microphone | NSMicrophoneUsageDescription |
Video recording for liveness checks |
| Photo Library | NSPhotoLibraryUsageDescription |
Upload documents from device gallery |
| NFC | NFCReaderUsageDescription |
Read NFC chips in passports/ID cards (only if using NFC) |
<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>To enable NFC reading for passports and ID cards with chips:
-
Add NFC Capability in Xcode:
- Select your target → Signing & Capabilities → + Capability → Near Field Communication Tag Reading
-
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>
-
Add Entitlements File (*.entitlements) with TAG format:
<key>com.apple.developer.nfc.readersession.formats</key> <array> <string>TAG</string> </array>
Skip this section unless you install the NFC or All variant.
Simulator Limitation: The
NFCandAllvariants link CoreNFC. Since Xcode 12, there is a bug wherelibnfshared.dylibis missing from simulators. Refer to this Stack Overflow thread for a solution to this problem. This does not apply to theCoreorAutoDetectionvariants.
App Store Review: If you install the
NFCorAllvariant, Apple may ask you to provide a video demonstrating NFC usage because NFC-related code is part of the SDK binary. Request our NFC demo video to submit to Apple from support@didit.me.
Add the package to your project using Xcode:
- Go to File > Add Package Dependencies
- Enter the repository URL:
https://github.com/didit-protocol/sdk-ios.git - Select the version and click Add Package
Or add it to your Package.swift:
dependencies: [
.package(url: "https://github.com/didit-protocol/sdk-ios.git", from: "4.9.1")
],
targets: [
.target(
name: "YourApp",
dependencies: [
.product(name: "DiditSDK", package: "sdk-ios")
]
)
]Pick exactly one library product to match your chosen variant:
| Variant | SPM library product |
|---|---|
Core |
DiditSDKCore |
AutoDetection |
DiditSDKAutoDetection |
NFC |
DiditSDKNFC |
All (default) |
DiditSDK |
Core-Location |
DiditSDKCoreLocation |
NFC-Location |
DiditSDKNFCLocation |
All-Location |
DiditSDKLocation |
Signals (device signals and event submission only) |
DiditSDKSignals |
Auth (authentication only, iOS 15+) |
DiditAuth |
Every product except DiditAuth is imported as import DiditSDK. The verification variants include signals and authentication; Auth on its own is imported as import DiditAuth.
Swift Package Manager does not enforce the iOS 15 floor for you.
Package.swiftdeclaresplatforms: [.iOS(.v13)]for the whole package — SwiftPM has no per-product platform declaration — and the shipped binaries are built withminos 13.0. So Xcode will let you addDiditSDKorDiditSDKNFCto an iOS 13 or 14 target with no manifest error and no link error, even though chip reading needs iOS 15+ (NFCPassportReader). Set your own target's deployment target to 15.0 when using theAllorNFCproduct. CocoaPods enforces this for you through the subspec'sdeployment_target; SPM cannot.
podspec_url = 'https://raw.githubusercontent.com/didit-protocol/sdk-ios/main/DiditSDK.podspec'
platform :ios, '15.0'
pod 'DiditSDK/All', :podspec => podspec_url
# pod 'DiditSDK/Core', :podspec => podspec_url # platform :ios, '13.0'
# pod 'DiditSDK/AutoDetection', :podspec => podspec_url # platform :ios, '13.0'
# pod 'DiditSDK/NFC', :podspec => podspec_url # platform :ios, '15.0'
# pod 'DiditSDK/Core-Location', :podspec => podspec_url # platform :ios, '13.0', Core with Location verification
# pod 'DiditSDK/NFC-Location', :podspec => podspec_url # platform :ios, '15.0', NFC with Location verification
# pod 'DiditSDK/All-Location', :podspec => podspec_url # platform :ios, '15.0', All with Location verification
# pod 'DiditSDK/Signals', :podspec => podspec_url # platform :ios, '13.0', device signals and events only
# pod 'DiditSDK/Auth', :podspec => podspec_url # platform :ios, '15.0', authentication onlyPick exactly one subspec. Core and AutoDetection deploy down to iOS 13; NFC and All require iOS 15 because of NFCPassportReader.
Then run:
pod installNote: Open the
.xcworkspacefile (not.xcodeproj) after runningpod install.
If you encounter rsync permission errors like Operation not permitted when building with CocoaPods on Xcode 15+, you need to disable User Script Sandboxing:
- Open your project in Xcode
- Select your project (not target) in the navigator
- Go to Build Settings
- Search for "User Script Sandboxing"
- Set
ENABLE_USER_SCRIPT_SANDBOXINGto No for both Debug and Release
Each release publishes one XCFramework per variant. Pick the variant you want, download the zips from the Releases page, and drag them into your Xcode project:
| Variant | Download |
|---|---|
Core |
DiditSDK-Core.xcframework.zip |
AutoDetection |
DiditSDK-AutoDetection.xcframework.zip |
NFC |
DiditSDK-NFC.xcframework.zip + OpenSSL.xcframework.zip |
All |
DiditSDK.xcframework.zip + OpenSSL.xcframework.zip |
Core-Location |
DiditSDK-Core-Location.xcframework.zip |
NFC-Location |
DiditSDK-NFC-Location.xcframework.zip + OpenSSL.xcframework.zip |
All-Location |
DiditSDK-Location.xcframework.zip + OpenSSL.xcframework.zip |
Signals |
DiditSDK-Signals.xcframework.zip |
Auth |
DiditAuth.xcframework.zip |
Extract each zip and set the embed option to Embed & Sign under Frameworks, Libraries, and Embedded Content.
import SwiftUI
import DiditSDK
struct ContentView: View {
var body: some View {
YourMainView()
.diditVerification { result in
switch result {
case .completed(let session):
print("Session: \(session.sessionId)")
print("Status: \(session.status.rawValue)")
print("Country: \(session.country ?? "N/A")")
print("Document: \(session.documentType ?? "N/A")")
case .cancelled(let session):
print("User cancelled - Session: \(session?.sessionId ?? "unknown")")
case .failed(let error, let session):
print("Failed: \(error.localizedDescription)")
print("Session: \(session?.sessionId ?? "unknown")")
}
}
}
}There are two ways to start the verification flow:
// Your backend creates the session and returns a token
DiditSdk.shared.startVerification(token: "your-session-token")// SDK creates the session using your workflow ID
DiditSdk.shared.startVerification(
workflowId: "your-workflow-id",
vendorData: "user-123" // Optional: your user identifier
)You can customize the SDK behavior using 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
)| Option | Type | Default | Description |
|---|---|---|---|
languageLocale |
SupportedLanguage? |
Device locale | Force a specific language |
fontFamily |
String? |
System font | Custom font family name (must be registered in Info.plist) |
loggingEnabled |
Bool |
false |
Enable SDK debug logging |
showCloseButton |
Bool |
true |
Show close (X) button on verification step screens |
showExitConfirmation |
Bool |
true |
Show confirmation dialog when user attempts to exit |
closeOnComplete |
Bool |
false |
Automatically dismiss verification UI when complete |
The SDK supports 54 languages. If no language is specified, the SDK uses the device locale with English 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()| Language | Value | Language | Value |
|---|---|---|---|
| Albanian | .albanian |
Kazakh | .kazakh |
| Arabic | .arabic |
Korean | .korean |
| Armenian | .armenian |
Kyrgyz | .kyrgyz |
| Bengali | .bengali |
Latvian | .latvian |
| Bosnian | .bosnian |
Lithuanian | .lithuanian |
| Bulgarian | .bulgarian |
Macedonian | .macedonian |
| Catalan | .catalan |
Malay | .malay |
| Chinese | .chinese |
Montenegrin | .montenegrin |
| Chinese (Simplified) | .chineseSimplified |
Norwegian | .norwegian |
| Chinese (Traditional) | .chineseTraditional |
Persian | .persian |
| Croatian | .croatian |
Polish | .polish |
| Czech | .czech |
Portuguese | .portuguese |
| Danish | .danish |
Portuguese (Brazil) | .portugueseBrazil |
| Dutch | .dutch |
Romanian | .romanian |
| English | .english |
Russian | .russian |
| Estonian | .estonian |
Serbian | .serbian |
| Finnish | .finnish |
Slovak | .slovak |
| French | .french |
Slovenian | .slovenian |
| Georgian | .georgian |
Somali | .somali |
| German | .german |
Spanish | .spanish |
| Greek | .greek |
Swedish | .swedish |
| Hebrew | .hebrew |
Thai | .thai |
| Hindi | .hindi |
Turkish | .turkish |
| Hungarian | .hungarian |
Ukrainian | .ukrainian |
| Indonesian | .indonesian |
Uzbek | .uzbek |
| Italian | .italian |
Vietnamese | .vietnamese |
| Japanese | .japanese |
Mongolian | .mongolian |
For advanced session parameters such as contact_details, expected_details, metadata, and callback, use the Backend Session method. Your backend calls POST /v3/session/ with full parameters, then passes the session_token to the SDK:
// Your backend creates the session with all advanced parameters:
// POST /v3/session/ with contact_details, expected_details, metadata, etc.
// Then passes the session_token to the SDK:
DiditSdk.shared.startVerification(token: sessionTokenFromBackend)Note: The UniLink method (
startVerification(workflowId:)) only supportsvendorData. For any other session parameters, use the Backend Session method.
The host app can end an active verification programmatically via 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 is going to background.
DiditSdk.shared.dismiss()dismiss() goes through the SDK's normal completion pipeline: it dismisses the presented UI, resets internal state, and invokes the onComplete callback with .cancelled(session:) carrying the current sessionId if a session was created. It is a no-op when no verification is active.
NotificationCenter.default.addObserver(
forName: UIApplication.didEnterBackgroundNotification,
object: nil,
queue: .main
) { _ in
DiditSdk.shared.dismiss()
}Do not set
DiditSdk.shared.isPresented = falseto dismiss — the flag only triggers presentation on its rising edge and setting it tofalseis a no-op. Likewise, callingdismiss(animated:)on the topmost view controller is not supported: it bypasses the SDK's completion pipeline soonCompleteis never fired.
The VerificationResult enum provides the outcome of the verification:
| Case | Description |
|---|---|
.completed(session:) |
Verification flow completed (approved, pending, or declined) |
.cancelled(session:) |
User cancelled the verification flow |
.failed(error:session:) |
An error occurred during verification |
Each result includes a SessionData object:
| Property | Type | Description |
|---|---|---|
sessionId |
String |
The unique session identifier |
status |
VerificationStatus |
Status: .approved, .pending, .declined |
country |
String? |
Country code (ISO 3166-1 alpha-3) |
documentType |
String? |
Document type used for verification |
| Error | Description |
|---|---|
.sessionExpired |
The session has expired |
.networkError |
Network connectivity issue |
.cameraAccessDenied |
Camera permission not granted |
.unknown(String) |
Other error with message |
.diditVerification { result in
switch result {
case .completed(let session):
switch session.status {
case .approved:
print("Approved! Session: \(session.sessionId)")
case .pending:
print("Under review. Session: \(session.sessionId)")
case .declined:
print("Declined. Session: \(session.sessionId)")
}
case .cancelled(let session):
if let session = session {
print("Cancelled session: \(session.sessionId)")
}
case .failed(let error, let session):
print("Error: \(error.localizedDescription)")
}
}import SwiftUI
import DiditSDK
struct HomeView: View {
@State private var resultMessage: String?
var body: some View {
VStack(spacing: 24) {
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)
}
}
.diditVerification { result in
handleResult(result)
}
}
private func startVerification() {
let config = DiditSdk.Configuration(
languageLocale: .english,
loggingEnabled: true,
showCloseButton: true,
showExitConfirmation: true,
closeOnComplete: false
)
// Option A: With session token from your backend (recommended)
DiditSdk.shared.startVerification(
token: "your-session-token",
configuration: config
)
// Option B: With workflow ID (SDK creates session)
// DiditSdk.shared.startVerification(
// workflowId: "your-workflow-id",
// vendorData: "user-123",
// configuration: config
// )
}
private func handleResult(_ result: VerificationResult) {
switch result {
case .completed(let session):
resultMessage = """
Completed
Session: \(session.sessionId)
Status: \(session.status.rawValue)
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)"
}
}
}You can observe the SDK state for custom 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)")
}
}
}
}The shipped DiditSDK.framework binary is stripped, so crash reports inside the SDK arrive as raw addresses unless you supply its debug symbols.
Every release after 4.5.3 publishes a dSYM.zip asset next to the XCFramework for exactly this purpose.
4.5.3 and earlier have none, so if that is your pinned version there is nothing to download yet - see the note at the end of this section.
Download the one matching the variant you install, from the same release as your pinned version:
| Variant | SPM product | dSYM asset |
|---|---|---|
All |
DiditSDK |
DiditSDK.dSYM.zip |
Core |
DiditSDKCore |
DiditSDK-Core.dSYM.zip |
AutoDetection |
DiditSDKAutoDetection |
DiditSDK-AutoDetection.dSYM.zip |
NFC |
DiditSDKNFC |
DiditSDK-NFC.dSYM.zip |
Each archive contains ios-device/DiditSDK.framework.dSYM and ios-simulator/DiditSDK.framework.dSYM.
Crash reports from real devices and from the App Store need the ios-device bundle.
Confirm the symbols match the binary you shipped before uploading them. The UUID printed here must equal the one Xcode reports as missing:
dwarfdump --uuid ios-device/DiditSDK.framework.dSYMCrashlytics. Upload the device dSYM alongside your own:
/path/to/Pods/FirebaseCrashlytics/upload-symbols \
-gsp GoogleService-Info.plist -p ios ios-device/DiditSDK.framework.dSYMApp Store Connect. Copy the device dSYM into your .xcarchive's dSYMs/ directory before uploading the build:
cp -R ios-device/DiditSDK.framework.dSYM \
~/Library/Developer/Xcode/Archives/<date>/<YourApp>.xcarchive/dSYMs/This has to happen pre-upload. App Store Connect symbolicates using the dSYMs that shipped with the build, and there is no supported way to attach a third-party dSYM to a build that is already uploaded. If you shipped without it, symbolicate the crash locally with atos against the matching dSYM.zip instead.
This also clears the "missing dSYM" warning Xcode's archive validation raises for DiditSDK.framework.
That warning never blocked the upload; it only meant crashes inside the SDK could not be symbolicated.
Releases 4.5.3 and earlier predate these assets and have no dSYM published.
If you need symbols for an older pinned version, open an issue with the UUID from the validation warning.
The install surface is split into four variants. Most integrations keep working without code changes; one path has a meaningful behavior change.
| Old (3.x) | New (4.x) | Action required |
|---|---|---|
pod 'DiditSDK' |
pod 'DiditSDK' (resolves to DiditSDK/All) |
none — same feature set as 3.x DiditSDK/Full |
pod 'DiditSDK/Full' |
pod 'DiditSDK/All' |
rename the subspec |
pod 'DiditSDK/Core' (relying on auto capture) |
pod 'DiditSDK/AutoDetection' |
rename the subspec — the 4.x Core variant drops MediaPipe and silently falls back to manual capture |
pod 'DiditSDK/Core' (manual capture only) |
pod 'DiditSDK/Core' |
none — smaller binary, same behavior you had |
SPM .product(name: "DiditSDK", …) |
SPM .product(name: "DiditSDK", …) |
none — still resolves to the All variant |
SPM .product(name: "DiditSDKCore", …) (relying on auto capture) |
SPM .product(name: "DiditSDKAutoDetection", …) |
rename the product for the same reason as CocoaPods |
SPM .product(name: "DiditSDKCore", …) (manual capture only) |
SPM .product(name: "DiditSDKCore", …) |
none |
import DiditSDK is unchanged in every case. Capture views still expose the same public API; the only observable difference in Core is that document/face screens skip the auto-capture countdown and require the shutter button.
- Native wallet-ownership verification flow (method picker, message signing, screenshot upload, self-declaration, satoshi/micro-deposit test, completion/expired) styled like the verification process, launched natively for the
wallet_ownershipaction via the publicDiditSdk.presentWalletOwnership(widgetToken:). - WalletConnect message signing behind an optional
DiditSDK/WalletConnectsubspec (reown-swift / ReownAppKit, iOS 15+): EVMpersonal_signand Solanasolana_signMessage, enabled withDiditSdk.enableWalletConnect(projectId:appGroupIdentifier:). The core SDK stays iOS 13 and ships a no-op signer by default, so apps that don't need signing are unaffected. Correction: the subspec builds from the mobile-sdks source only; this podspec and the Swift Package Manager products have never included it. Since 4.10.0, without a signer the flow does not offer message signing, and a transaction opens the web flow when a proof allows only message signing. - Transaction submission: native
submitTransaction/getTransaction. Awallet_ownershiprequired-action auto-launches the native flow; averification_sessionaction is returned onresult.actionRequired(type, url, sessionId, sessionToken, status) for your existingstartVerification/.diditVerificationintegration. Concurrent submissions are de-duplicated so a second call can't clobber an in-flight verification. - New document quality-check screen validates the captured document (framing/crop) before it is uploaded.
- Full-color Didit logo on the verification welcome and wallet-ownership entry screens (the muted mark stays in the "Secured by" footer), and white-label logos now support SVG sources.
- Requests now send an
X-Didit-Device-Modelheader (device model identifier) used for backend IP-analysis / device fingerprinting. - Passive-liveness video is now recorded correctly on iPhone 17 / 17 Pro. These devices ship a portrait-mounted Center Stage front sensor that
AVCaptureMovieFileOutputrecords in the wrong orientation; on iPhone 17-class hardware (iPhone17,xand newer) the SDK now records from the capture sample buffers with a matching sample-buffer preview, so the preview and the uploaded video are upright and correctly proportioned. Every earlier iPhone keeps the original recording path unchanged. - Session-driven audio recording: document and passive-liveness capture now record audio only when the current session step requires it (a new per-step flag), and the microphone permission is requested on demand — steps that don't need audio never prompt.
- KYB company search now uses two separate fields, company name and registration number, instead of one unified query. Either field alone can search, both are sent for a combined lookup, and both are prefilled from the session's expected details; results, the US-state selector, and manual entry were updated to match.
- Backend-processing loader is fully synchronized with the verification WebView and stays on screen during the WebSocket → polling fallback, removing the flicker/desync between the web and native processing UIs.
- Document front-camera flow hardened: the anti-spoofing selfie is captured before the document camera starts (fixing a single-capture-session race that could silently skip the front-camera upload), a stalled capture can no longer hang the flow (resume-once timeout + guaranteed camera release), and repeated empty video segments fall back to a photo-only upload instead of looping the capture screen.
- KYB
kyb_key_people_ownership_requiredis now honored on iOS, consent/start-screen legal links are tappable, and translations were refreshed. - Fixes: passive liveness no longer gets stuck on the loading spinner after tapping "Continue"; front-camera document images are no longer mirrored on upload (with an instant un-mirrored confirmation frame and a capability-gated torch); the backend loader no longer flickers on passive-liveness success; the quality-check upload no longer drops recorded video segments; and the WalletConnect modal no longer crashes on device.
- Questionnaire: fields are now validated against their expected format before the step can be submitted, with a localized inline error message.
- Questionnaire: improved the inline validation error UI/UX for text, number, and text-area fields.
- Added internal front-camera orientation diagnostics (
LOGtelemetry) to investigate the iPhone 17 / Center Stage preview and recorded-video rotation. No public API or user-facing behavior change.
- KYB key people: the ownership percentage is now optional unless the session marks it required.
- Fix passive-liveness video recorded in landscape on iPhone 17 / 17 Pro / 17 Pro Max — the recording connection is now pinned to portrait (no-op on earlier devices).
- Fix the success/failure status texts shown after a face (liveness) upload.
- Fix the active-liveness face intro screen briefly appearing behind the processing/result overlay when the liveness WebView finishes.
- Document capture: detection corner guides are no longer drawn when auto-detection is unavailable (the Core/NFC variants, or when the detector fails to load).
- Add the Document AI verification step: prompts the user to upload the requested document(s) for AI-based data extraction, localized across all supported languages.
- KYB document upload now lists the fields the uploaded document must clearly show.
- Fix the KYB key-people close (X) button not opening the exit-confirmation modal.
- This is the first release that actually ships OpenSSL 3.x. The build is aligned to
NFCPassportReader 2.3.0/OpenSSL-Universal 3.3.3001(upstreamOpenSSL 3.3.3, 11 Feb 2025), and the shippedOpenSSL.frameworkcarries its ownPrivacyInfo.xcprivacyand code signature. This is what closes App Store rejections ITMS-91061 (missing privacy manifest) and ITMS-91065 (missing signature) forFrameworks/OpenSSL.framework/OpenSSL. NFC chip reading was verified end-to-end on device. No public API change. If you are pinned below4.0.7, you are still on OpenSSL 1.1.1s — upgrade to 4.0.7 or later.
Correction (2026-08-04). This entry previously claimed that 4.0.6 bumped
NFCPassportReader2.1.2 → 2.3.0, shippedOpenSSL-Universal 3.3.3001, and fixed App Store rejections ITMS-91061 / ITMS-91065. It did not. TheOpenSSL.xcframework.zippublished for 4.0.6 still contains OpenSSL 1.1.1s (CFBundleShortVersionString1.1.1900) with noPrivacyInfo.xcprivacy— matching 4.0.5, not 4.0.7. OpenSSL 3.x and the privacy manifest first appear in the 4.0.7 release asset. Check any release yourself with:unzip -p OpenSSL.xcframework.zip 'OpenSSL.xcframework/ios-arm64/OpenSSL.framework/Info.plist' | plutil -p -
- KYB: company card, key people and associated parties, with key-people reuse across steps.
- KYB: US state selector in company search and confirmation.
- KYB: editable company country in the confirmation view; prefill key people when the preceding step is backend-only; email no longer required for key people when KYC isn't required for them.
- Fix KYB company select failing to advance on partial registry data.
- Fix the backend-processing loader getting stuck on the AML screening step.
- Fix the country flag not updating when changing the selected country, document preview rotation on the back-side scan, and the country/state picker search-icon visibility.
- Fix some countries showing the alpha-3 code instead of the country name; align start-screen feature icons with Android and web; UI polish in KYB views (button spacing/separator, phone country picker).
- Russian ID & passport document-override translations, plus a general translations refresh.
- Fix App Store rejection ITMS-90338: the
AllandAutoDetectionxcframeworks no longer export MediaPipe's bundled native symbols (RE2, ICU, TensorFlow Lite, …) in their dyld export trie. The build now restricts exports to DiditSDK's own Swift/Objective-C API at link time (-exported_symbols_list) and verifies the export trie itself, so uploads are clear of the private-symbol collision (e.g.re2::*) that flagged 4.0.4. No source or API changes — same features and runtime behavior. - Align every input and selector to the white-label tokens (background, text, placeholder, border-radius) so they match the document selector: text fields, OTP entry, dropdowns, country/phone pickers, and all questionnaire fields. Unselected radio buttons now use the selected color at reduced opacity instead of a hardcoded gray, so they stay visible on every theme.
- Align the KYB screens to the white-label tokens: the documents upload view, the key-people cards and editor, the awaiting-users view, and company search/results — fixing low-contrast text/icons, the status badges, and the "enter company data manually" link color.
- Fix passive-liveness uploads that could exceed the backend size limit: resized images are now rendered at scale 1 (they were silently inflated to the device's screen scale), and face-image compression now matches the web frontend (resize to a max dimension + binary-search JPEG to the target size).
- Add Algeria-specific NFC intro videos: when the document country is Algeria (
DZA), the NFC chip-reading screen now plays the Algeria-specific instructional clip (nfc-id-algeriafor ID-type documents,nfc-passport-algeriafor passports). All other countries keep the standardnfc-id/nfc-passportvideos. - Resolve the NFC instructional video from the document's actual country: the SDK now reads the backend-detected
countryfrom the OCR response (falling back to the user-selected country, then the expected country), so the correct country video shows even when the country picked in the selector differs from the scanned document. - Suppress the camera shutter sound on still capture (document capture, the anti-spoofing front-camera selfie, and passive liveness) on iOS 18+ where the device and local regulations allow it, via Apple's official
AVCapturePhotoSettings.isShutterSoundSuppressionEnabled. Capture resolution and quality are unchanged; iOS 17 and earlier, and regions such as Japan/Korea, keep the system shutter (Apple provides no suppression there). - Fix a stretched / incorrectly rotated front-camera preview and capture on newer devices (e.g. iPhone 17 Pro) by applying iOS 17's
RotationCoordinatorhorizon-level rotation angle; scoped to the front camera so the rear document camera is unaffected. - Fix the document capture screen tearing down a camera owned by a newer screen instance when the view is recreated, by tracking the active camera owner.
- Improve the post-active-liveness loader: on success the SDK now dismisses the liveness WebView immediately and shows the native processing/success sheet over an opaque background, then cross-fades into the next step — removing the residual blank/flash that could appear between active liveness and the next step.
- Add an active-liveness backend-processing loader: after the liveness step, whenever the session is still processing on the backend (
current_step_v2!=current_step), the SDK shows a full-screen "Verifying your details" overlay with rotating status messages, the feature step bar, and the "Secured by" footer, then transitions smoothly into the next step. Removes the brief black screen that could appear between active liveness and the next step. Messages are translated across all supported locales. - Add support for the
Social Security Card(SSC) OCR document type, so iOS shows the same document set as the web frontend (uses the ID-card capture aspect ratio), translated across all supported locales. - Fix front-camera document capture being horizontally mirrored: the captured/uploaded document image is now un-mirrored on the front lens, while the live preview and selfie/liveness capture stay mirrored as before.
- Add proper retry error messaging for liveness and face-match failures, translated across all supported locales.
- Fix an active-liveness failure that could occur when the network (Wi-Fi) dropped during the liveness WebView flow.
- Remove the unused Location permission: the SDK no longer links
CoreLocationor requests location, so integrators can dropNSLocationWhenInUseUsageDescriptionfrom their Info.plist. - Emit a Tier-B device-class
composite_hashfingerprint (X-Didit-FP-Hashheader) for parity with the web SDK and backend device-identification contract. - Build: strengthen third-party symbol hiding in
build_xcframework.shto keep App Store submissions clear of ITMS-90338.
- Add
Configuration.defaultDocumentCameraandConfiguration.defaultLivenessCamera(new publicCameraLensenum:.front/.back) so apps can choose which physical lens opens first on each capture screen. Defaults: document.back, liveness.front. Falls back to the first available camera when the requested lens isn't present on the device. - Add
Configuration.showDocumentCameraSwitchButtonandConfiguration.showLivenessCameraSwitchButton(defaulttrue) to hide the in-capture camera switcher and lock the user to the chosen lens. The switcher is also automatically hidden on devices with only one camera, regardless of this flag. - Add support for the
Tax Card(TC) OCR document type so iOS now displays the same set of document cards as the web frontend. Includes the IndiaIND.taxCardcountry override ("PAN Card (Permanent Account Number)") translated across all 54 supported locales, and a newdocument-tax-cardvector asset. - Honor the session-level
expected_document_typeswhitelist (P / ID / DL / RP / HIC / TC) so a session that constrains the allowed document types now applies the same filter on iOS that the web frontend applies. - Fix the selected-country / selected-document badges on the upload screen to use the white-label
bc-pill-texttoken for the title text (was incorrectly usingbc-primary). Brings the badge title in line with both the icon next to it and the document-selection card on the previous screen. - Fix the manual capture button disappearing after the user switches the camera mid-session on the liveness (passive face) and document capture screens. The same delayed re-show timer used on first appear now also fires after each lens swap, matching the Android
LaunchedEffect(cameraManager)behavior.
Breaking change — the install surface is now split into four variants. Existing CocoaPods integrations of pod 'DiditSDK' keep working (default subspec is All, which is identical in features to 3.x DiditSDK/Full); SPM consumers of DiditSDK also keep working (library product DiditSDK is the All variant). Existing integrations of pod 'DiditSDK/Core' or .product(name: "DiditSDKCore", …) keep working — but note that the 4.0 Core variant no longer ships MediaPipe, so apps that rely on auto capture must switch to AutoDetection (no NFC) or stay on the default All. See the Migrating from 3.x to 4.x section above.
- Split the SDK into four installable variants:
Core(no MediaPipe, no NFC, ~6 MB device-slice Mach-O),AutoDetection(MediaPipe only, ~37 MB),NFC(NFC reader only, ~7 MB + OpenSSL),All(everything, default, ~38 MB + OpenSSL). This responds to client feedback that the 3.x ~38 MB binary triggered Apple's WiFi-only download warning on cellular; consumers that do not need auto capture can drop ~84% of the SDK's binary footprint by switching toCore. - Auto-detection sources (
DocumentDetector,FaceDetector,ModelDownloader, and the manager classes) now self-gate with#if canImport(MediaPipeTasksVision). Variants without MediaPipe still compile and fall back to manual shutter capture without functional regression beyond losing the auto-capture timer overlay. - Add Mongolian (
mn) localization inSupportedLanguage.mongolianwith full coverage of every SDK string. - Includes all fixes from 3.6.1/3.6.2 (iPhone X EXC_BAD_ACCESS on Continue, Mongolian localization).
- Add Mongolian (
mn) localization. Available viaSupportedLanguage.mongolianor device localemn-MN. Coverage is sourced from the web verification frontend; iOS-only strings (NFC reader UI, native upload sheet, exit-confirmation alert) fall back to English and can be hand-translated later. - Includes all fixes from 3.6.1 (iOS 13-17 EXC_BAD_ACCESS on Continue, built without the Swift 6.0+ runtime symbol
swift_getExtendedFunctionTypeMetadata).
- Fix
EXC_BAD_ACCESScrash on iPhone X and other devices stuck on iOS 13-17 immediately after tapping "Continue" on the start screen (3.6.0 binary importedswift_getExtendedFunctionTypeMetadata, a Swift 6.0+ runtime symbol unavailable before iOS 18) - Build with
SWIFT_APPROACHABLE_CONCURRENCY=NOand explicit-disable-upcoming-featureflags so Xcode 26.x toolchains stop emitting Swift 6.x metadata calls; binary is now back-deploy safe to iOS 13 - Add hard-fail back-deploy verification step (
Step 9) tobuild_xcframework.shso a regression cannot be published again - No source/API changes, no behavior changes: same release as 3.6.0 with corrected build configuration
- Add public
DiditSdk.shared.dismiss()to programmatically end an active verification from the host app (e.g. on app backgrounding). Funnels through the SDK's completion pipeline and invokesonCompletewith.cancelled(session:) - Fix questionnaire whitelabel colors in checkbox, radio, and dropdown to use
panel/pillTexttokens (matches webbc-panel/bc-pill-textinstead ofbutton1/primary)
- Fix CocoaPods binary podspec license metadata by inlining the MIT license text
- Uses the same SDK binaries as 3.4.0
- Add optional no-NFC install variants for apps that do not need NFC dependencies: CocoaPods
DiditSDK/Coreand Swift Package ManagerDiditSDKCore - Keep the default
DiditSDKinstallation NFC-enabled throughDiditSDK/Full - Fix NFC step routing in mixed CocoaPods integrations by detecting the
NFCPassportReadermodule at compile time - Document NFC capability, entitlement, and provisioning requirements
- Improve step recovery after interrupted or failed backend responses
- Fix verification flow getting stuck after upload fallback recovery
- Improve document camera capture quality and upload image readability
- Fix document scan video segments by finalizing recorder state and exporting playable MP4/H.264 uploads
- Add iOS SDK version/integration metadata and camera/video error LOG events
- Improve passive liveness recommendation text color consistency
- Fix active liveness WebView language: pass SDK locale in URL path
- Add KYB translations for all 52 supported languages (synced from web locales)
- Add native KYB flow on iOS, including company search, manual entry, associated parties, document upload, and processing screens
- Update verification UI components and step/progress styling
- Add Kazakh language support and expand KYB/session translations
- Add KYB/session models, API fields, and status handling for the new flow
- Fix PDF file upload in questionnaire on iOS 26 (use modern UTType document picker API with iOS 13 fallback)
- Fix document picker silently skipping files when security-scoped resource access returns false
- Apply same UTType fix to proof-of-address document picker
- Pin dependency versions to prevent breaking upgrades (MediaPipeTasksVision 0.10.21, NFCPassportReader 2.1.2)
- Face area threshold adjusted to 9% for optimal distance balance
- Tighten face detection thresholds for better capture quality
- Require tight stability only for auto-capture (no more loose threshold)
- Stricter frontal face check (nose offset 0.25, eye level 0.30)
- Tighter position bands (32% horizontal, 48% vertical)
- Higher ML confidence threshold (0.85)
- Longer hold time (1.0s, 25 consecutive frames)
- Fix keyboard overlapping questionnaire inputs (use safeAreaInset for footer)
- Fix tap-to-focus requiring double tap on inputs (replace simultaneousGesture with UIKit tap recognizer)
- Add scroll-to-dismiss keyboard on questionnaire (scrollDismissesKeyboard)
- Hide internal C++ symbols (OpenCV, MediaPipe, TFLite) to prevent linker collisions with other SDKs
- Per-architecture symbol hiding using nmedit for fat binary compatibility
- Fix SPM/Xcode 26 Archive failure: use plain zip instead of ditto to avoid __MACOSX metadata that breaks signature seal on extraction
- Verified:
codesign --verify --deep --strictpasses after bothunzipanddittoextraction
- Fix XCFramework signature seal consistency (resolves Xcode 26 Archive failure with
signature-collection failed) - Remove stale code signatures before re-signing after bitcode stripping
- Use
dittofor zip archives to preserve code signature metadata - Add
--deepsigning and post-sign verification to build script
- Sign inner .framework binaries within each XCFramework slice (fixes Xcode 26 Archive failure and ITMS-91065 for ios-arm64)
- Add missing exit confirmation dialog translations for all 51 languages
- Signed XCFrameworks with Apple Distribution certificate (fixes ITMS-91065: Missing signature on App Store submission)
- Force portrait orientation within the SDK regardless of host app settings
- Fix consecutive same-type verification steps (e.g. liveness -> liveness) getting stuck
- Fix "Continue" button becoming unresponsive on step screens due to coordinator re-initialization
- Add missing language translations (Albanian, Bosnian, Kyrgyz)
- Fix Configuration documentation (add showCloseButton, showExitConfirmation, closeOnComplete; remove incorrect parameters)
- Previous release
Copyright © 2026 Didit. All rights reserved.