A lightweight Android SDK for identity verification. Server-driven, minimal configuration required.
- Android API level 23+ (Android 6.0 Marshmallow and above)
- Kotlin 1.9+
- Jetpack Compose (included as a transitive dependency)
| Feature | API 23 | API 24+ |
|---|---|---|
| Document auto-detection (ML) | No (falls back to manual capture) | Yes |
| Face auto-detection (ML) | No (falls back to manual capture) | Yes |
| Camera permissions | Runtime | Runtime |
Note: The SDK gracefully degrades on older devices. ML-based document and face auto-detection require API 24+ (MediaPipe) and fall back to manual capture with a timed button on API 23. All other features work across the full supported range.
The SDK requires the following permissions. These are declared in the SDK's AndroidManifest.xml and will be merged automatically into your app's manifest:
| Permission | Description |
|---|---|
INTERNET |
Network access for API communication |
ACCESS_NETWORK_STATE |
Detect network availability |
CAMERA |
Document scanning and face verification |
NFC |
Read NFC chips in passports/ID cards |
The SDK declares android.hardware.camera and android.hardware.nfc as optional features (android:required="false"). This ensures your app can be installed on devices without a camera or NFC hardware — the SDK will gracefully handle missing hardware at runtime.
Add the Didit Maven repository to the repositories block in your settings.gradle.kts:
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven { url = uri("https://raw.githubusercontent.com/didit-protocol/sdk-android/main/repository") }
}
}Or if using settings.gradle (Groovy):
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven { url "https://raw.githubusercontent.com/didit-protocol/sdk-android/main/repository" }
}
}Add the SDK dependency to your app's build.gradle.kts:
dependencies {
implementation("me.didit:didit-sdk:4.9.1")
}Or if using build.gradle (Groovy):
dependencies {
implementation "me.didit:didit-sdk:4.9.1"
}Starting with 4.0.0 the SDK ships as separate artifacts (
didit-sdk-core,didit-sdk-autodetection,didit-sdk-nfc,didit-sdk, plus the optionaldidit-sdk-walletadd-on since 4.6.0) so apps only pay the binary cost of features they use. See SDK Variants and App Size for what each variant costs and how to keep the download small.
Add this to your app's android block to avoid build conflicts:
android {
packaging {
resources {
excludes += "META-INF/versions/9/OSGI-INF/MANIFEST.MF"
}
}
}That's it! Gradle will automatically resolve all transitive dependencies.
Pick the smallest artifact that covers the features you use:
| Artifact | Auto-capture | NFC | What it adds |
|---|---|---|---|
didit-sdk-core |
– | – | UI, camera, manual capture (~5 MB of code before R8) |
didit-sdk-autodetection |
✓ | – | MediaPipe runtime for document/face auto-detection |
didit-sdk-nfc |
– | ✓ | JMRTD passport chip reading |
didit-sdk-wallet |
– | – | Optional add-on: native WalletConnect wallet-signing (since 4.6.0) |
didit-sdk |
✓ | ✓ | Everything above, wallet add-on included |
didit-auth |
– | – | End-user authentication (DiditAuth), on its own or next to any artifact above |
Since 4.6.0, native WalletConnect wallet-signing lives in the optional me.didit:didit-sdk-wallet add-on instead of didit-sdk-core, which drops the Reown/WalletConnect dependencies (several MB of transitive jars) from every build that does not use them.
The full me.didit:didit-sdk bundle includes the add-on, so its behavior is unchanged.
If you depend on a slimmer artifact and want wallet-ownership signing to run natively in your app, add me.didit:didit-sdk-wallet alongside it; without it the SDK completes wallet ownership through the browser-based flow instead, so the feature keeps working either way.
The ML models used for auto-detection are downloaded at runtime and cached — they are never inside your APK.
The install cost of auto-capture is the MediaPipe native runtime, and it is per CPU architecture: ~10.4 MB for arm64-v8a, ~6.9 MB for armeabi-v7a, ~12.6 MB for x86_64, ~13.9 MB for x86.
To keep that from multiplying, follow these rules:
-
Publish an Android App Bundle (AAB). Google Play then delivers only the device's own architecture (~10 MB installed, roughly 4 MB of compressed download on an arm64 phone) and only the device's language resources. A universal APK instead carries all four architectures — over 40 MB of native libraries — which is the most common cause of "the SDK is huge" measurements.
-
If you must distribute a single APK and your audience is ARM phones and tablets, restrict architectures in your
build.gradle.kts:android { defaultConfig { ndk { abiFilters += listOf("arm64-v8a", "armeabi-v7a") } } }Keep every ABI your users actually run: add
x86_64if you target Chromebooks or other x86 devices, or publish per-ABI APK splits so no device loses support. -
Enable R8 —
isMinifyEnabled = trueandisShrinkResources = truein your release build type (minifyEnabled true/shrinkResources truein Groovybuild.gradle). The SDK ships consumer rules, so no extra configuration is needed and unused SDK code is stripped from your build. -
Limit bundled languages if you do not ship all 48 SDK locales, e.g.
resConfigs("en", "sw")indefaultConfig.
Initialize the SDK in your Application.onCreate():
import me.didit.sdk.DiditSdk
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
DiditSdk.initialize(this)
}
}import me.didit.sdk.DiditSdk
import me.didit.sdk.DiditSdkState
import me.didit.sdk.VerificationResult
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Observe SDK state to launch UI when ready
lifecycleScope.launch {
DiditSdk.state.collect { state ->
when (state) {
is DiditSdkState.Ready -> DiditSdk.launchVerificationUI(this@MainActivity)
is DiditSdkState.Error -> Log.e("Didit", "Error: ${state.message}")
else -> { /* Loading, Idle, CreatingSession */ }
}
}
}
}
private fun startVerification() {
DiditSdk.startVerification(
token = "your-session-token"
) { result ->
when (result) {
is VerificationResult.Completed -> {
Log.d("Didit", "Session: ${result.session.sessionId}")
Log.d("Didit", "Status: ${result.session.status.rawValue}")
}
is VerificationResult.Cancelled -> {
Log.d("Didit", "User cancelled")
}
is VerificationResult.Failed -> {
Log.e("Didit", "Failed: ${result.error.message}")
}
}
}
}
}There are two ways to start the verification flow:
// Your backend creates the session and returns a token
DiditSdk.startVerification(
token = "your-session-token"
) { result ->
handleResult(result)
}// SDK creates the session using your workflow ID
DiditSdk.startVerification(
workflowId = "your-workflow-id",
vendorData = "user-123" // Optional: your user identifier
) { result ->
handleResult(result)
}You can customize the SDK behavior using Configuration:
import me.didit.sdk.Configuration
import me.didit.sdk.core.localization.SupportedLanguage
val configuration = Configuration(
languageLocale = SupportedLanguage.SPANISH, // Force Spanish language
fontFamily = "my_custom_font", // Custom font (must be in res/font/)
loggingEnabled = true // Enable debug logging
)
DiditSdk.startVerification(
token = "your-session-token",
configuration = configuration
) { result ->
handleResult(result)
}| Option | Type | Default | Description |
|---|---|---|---|
languageLocale |
SupportedLanguage? |
Device locale | Force a specific language |
fontFamily |
String? |
System font | Custom font resource name (from res/font/) |
loggingEnabled |
Boolean |
false |
Enable SDK debug logging |
The SDK supports 54 languages. If no language is specified, the SDK uses the device locale with English fallback.
// Use device locale (default)
val config = Configuration()
// Force specific language
val config = Configuration(languageLocale = SupportedLanguage.FRENCH)| 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 |
Mongolian | MONGOLIAN |
| Chinese (Simplified) | CHINESE_SIMPLIFIED |
Montenegrin | MONTENEGRIN |
| Chinese (Traditional) | CHINESE_TRADITIONAL |
Norwegian | NORWEGIAN |
| Croatian | CROATIAN |
Persian | PERSIAN |
| Czech | CZECH |
Polish | POLISH |
| Danish | DANISH |
Portuguese | PORTUGUESE |
| Dutch | DUTCH |
Portuguese (Brazil) | PORTUGUESE_BRAZIL |
| English | ENGLISH |
Romanian | ROMANIAN |
| Estonian | ESTONIAN |
Russian | RUSSIAN |
| Finnish | FINNISH |
Serbian | SERBIAN |
| French | FRENCH |
Slovak | SLOVAK |
| Georgian | GEORGIAN |
Slovenian | SLOVENIAN |
| German | GERMAN |
Somali | SOMALI |
| Greek | GREEK |
Spanish | SPANISH |
| Hebrew | HEBREW |
Swedish | SWEDISH |
| Hindi | HINDI |
Thai | THAI |
| Hungarian | HUNGARIAN |
Turkish | TURKISH |
| Indonesian | INDONESIAN |
Ukrainian | UKRAINIAN |
| Italian | ITALIAN |
Uzbek | UZBEK |
| Japanese | JAPANESE |
Vietnamese | VIETNAMESE |
Provide contact details to prefill verification forms and enable notifications:
import me.didit.sdk.models.ContactDetails
val contactDetails = ContactDetails(
email = "user@example.com",
sendNotificationEmails = true, // Send status update emails
emailLang = "en", // Email language
phone = "+14155552671" // E.164 format
)
DiditSdk.startVerification(
workflowId = "your-workflow-id",
contactDetails = contactDetails
) { result ->
handleResult(result)
}Provide expected user details for cross-validation with extracted data:
import me.didit.sdk.models.ExpectedDetails
val expectedDetails = ExpectedDetails(
firstName = "John",
lastName = "Doe",
dateOfBirth = "1990-05-15", // YYYY-MM-DD format
nationality = "USA", // ISO 3166-1 alpha-3
country = "USA"
)
DiditSdk.startVerification(
workflowId = "your-workflow-id",
expectedDetails = expectedDetails
) { result ->
handleResult(result)
}Store custom JSON metadata with the session (not displayed to user):
DiditSdk.startVerification(
workflowId = "your-workflow-id",
vendorData = "user-123",
metadata = "{\"internalId\": \"abc123\", \"source\": \"mobile\"}"
) { result ->
handleResult(result)
}The VerificationResult sealed class 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 |
| Error | Description |
|---|---|
SessionExpired |
The session has expired |
NetworkError |
Network connectivity issue |
CameraAccessDenied |
Camera permission not granted |
NotInitialized |
SDK not initialized |
Unknown(message) |
Other error with message |
DiditSdk.startVerification(token = "your-token") { result ->
when (result) {
is VerificationResult.Completed -> {
when (result.session.status) {
VerificationStatus.APPROVED -> println("Approved!")
VerificationStatus.PENDING -> println("Under review")
VerificationStatus.DECLINED -> println("Declined")
}
}
is VerificationResult.Cancelled -> {
println("Cancelled: ${result.session?.sessionId ?: "unknown"}")
}
is VerificationResult.Failed -> {
println("Error: ${result.error.message}")
}
}
}The SDK includes its own consumer ProGuard rules. No additional configuration is needed.
If you use R8 full mode, you may need to add this to your gradle.properties:
android.enableR8.fullMode=falseCopyright (c) 2026 Didit. All rights reserved.