Skip to main content

Android SDK Integration Guide

Documented versions. Android SDK 1.0.11. This page follows the WIDTH integration guide dated 11 September 2026. Confirm the selected SDK build and its target environment before deployment.

Overview

The Width Identity SDK provides a drop-in identity verification flow for Android applications. It supports KYC and KYB workflows including document capture, liveness detection, and review. The SDK runs in its own Activity; you add a single dependency and call one entry point, and the SDK returns a single result.

Requirements

Requirement Minimum
Android SDK API 26 (Android 8.0)+
compileSdk 36 — the published SDK artifacts declare minCompileSdk=36 in their AAR metadata, and the build tooling enforces it, so a lower value fails the build
Kotlin 1.9+
Gradle / AGP 8.0+
Java 11 source/target
Host app Jetpack Compose-based or View-based (the SDK runs in its own Activity)

Installation

Step 1: Add the Maven Repository

In settings.gradle.kts. The Width repo serves the SDK plus its Width-hosted transitive dependencies (com.width.risksec, com.width.camera); everything else (AndroidX / Kotlin / Ktor / CameraX) resolves from Google + Maven Central.

The document-capture engines bundled inside the SDK are vendor-hosted, not on Width's Maven, so add their repositories as well — they are required at build time even if a capture provider goes unused. Paste the two provider blocks below exactly as shown.

dependencyResolutionManagement {
    repositories {
        maven { url = uri("https://maven.width.com/repository/sdk-releases/") }
        google()
        mavenCentral()

        // Document-capture provider repository (bundled transitively; required to resolve)
        maven {
            url = uri("https://cdn.veriff.me/android/")
            content { includeGroup("com.veriff") }
        }
        // Document-reader provider repository (bundled transitively; required to resolve)
        maven {
            url = uri("https://maven-master.regulaforensics.com/RegulaDocumentReader/")
            content {
                includeGroup("com.regula.documentreader")
                includeGroup("com.regula.documentreader.core")
                includeGroup("com.regula.common")
            }
        }
    }
}

Step 2: Add the Dependency

In app-level build.gradle.kts. A single coordinate transitively pulls the camera, liveness, and supporting libraries — no BOM, no extra coordinates.

dependencies {
    implementation("com.width.identity:sdk-activity:1.0.11")
}
minSdk requirement: the SDK requires minSdk 26.

Step 3: Required Build Settings

Two settings belong to your app module, not to the SDK. The build fails without them — neither touches your source code.

Core library desugaring is required. The bundled AWS Rekognition liveness engine ships Amplify AARs whose metadata declares requires core library desugaring, and AGP enforces that on the app side (checkReleaseAarMetadata / checkDebugAarMetadata) regardless of your minSdk. Add to your app module:

android {
    compileOptions {
        isCoreLibraryDesugaringEnabled = true
    }
}

dependencies {
    coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5")
}

android.uniquePackageNames=false is required on AGP 9. Two artifacts pulled in transitively by the AWS liveness engine (com.google.ai.edge.litert:litert-support and litert-support-api, both 1.4.1) declare the same library namespace org.tensorflow.lite.support — an upstream packaging bug. AGP 9's manifest merger validates namespace uniqueness and fails the build with Validation failed, exiting. Add to your project's gradle.properties:

# gradle.properties
android.uniquePackageNames=false
No more BOM or UI coordinate Earlier integrations used a sdk-bom platform plus a separate sdk-ui-width-modern coordinate. These are no longer needed — sdk-activity aggregates everything.
SDK token moved The SDK token is no longer registered via an AndroidManifest.xml <meta-data> entry / strings.xml. It is now passed at runtime as the sdkToken field inside CredentialsConfig (see Quick Start).

Quick Start

The SDK exposes two equivalent entry styles — use whichever fits your call site. Both drive the identical verification UI and return a single result.

Direct input (ActivityResultContract)

import com.width.identity.activity.CredentialsConfig
import com.width.identity.activity.CustomerConfig
import com.width.identity.activity.ThemeConfig
import com.width.identity.activity.UILanguage
import com.width.identity.activity.Verification
import com.width.identity.activity.VerificationInput
import com.width.identity.activity.VerificationResult

class MainActivity : ComponentActivity() {

    private val launcher =
        registerForActivityResult(Verification()) { result ->
            when (result) {
                is VerificationResult.Completed ->   // .APPROVED / .REJECTED / .REVIEW
                    Log.i(TAG, "done: ${result.executionId} ${result.decision}")
                is VerificationResult.Waiting ->
                    Log.i(TAG, "waiting: ${result.executionId}")
                is VerificationResult.Dismissed ->
                    Log.i(TAG, "dismissed: ${result.executionId ?: "(no execution)"}")
                is VerificationResult.Cancelled ->
                    Log.i(TAG, "cancelled: ${result.executionId}")
                is VerificationResult.Failed ->
                    Log.w(TAG, "failed: ${result.code} ${result.message}")
            }
        }

    private fun startVerification() {
        val input = VerificationInput(
            credentials = CredentialsConfig(
                domain   = "YOUR_DOMAIN",
                sdkToken = "YOUR_SDK_TOKEN",
            ),
            customer = CustomerConfig(
                // Both customerId and email are required and must be non-blank.
                customerId = "user-123",
                email      = "user@example.com",
            ),
            theme = ThemeConfig(themeMode = "SYSTEM"),   // optional: "SYSTEM" / "LIGHT" / "DARK"
            eventType = "KYC_EVENT",   // optional: "KYC_EVENT" / "KYB_EVENT" to skip in-SDK selection; omit to let the user pick
            language = UILanguage.ZH,  // optional: UILanguage.EN (default) / .ZH; omit = English (does not follow device locale)
        )
        launcher.launch(input)
    }
}

Builder entry (Compose)

A fluent builder assembles the launch input in a single chain — supply the access token + credentials + applicant, set optional handlers, then build and launch. It delivers a single WIDTHCompletionResult. Register the launcher in composition with the WIDTHVerification contract, then build and launch at the call site.

import androidx.activity.compose.rememberLauncherForActivityResult
import com.width.identity.activity.WIDTHCompletionResult
import com.width.identity.activity.WIDTHDayNightMode
import com.width.identity.activity.WIDTHMobileSDK
import com.width.identity.activity.WIDTHVerification

@Composable
fun VerifyButton() {
    val launcher = rememberLauncherForActivityResult(WIDTHVerification()) { result ->
        when (result) {
            is WIDTHCompletionResult.SuccessTermination ->   // see status table
                Log.i(TAG, "done: ${result.status} ${result.executionId}")
            is WIDTHCompletionResult.AbnormalTermination ->
                Log.w(TAG, "failed: ${result.exception.code} ${result.exception.message}")
        }
    }

    Button(onClick = {
        WIDTHMobileSDK.Builder()
            .withAccessToken("YOUR_SDK_TOKEN")                 // RiskSec sdkToken (encodes appId/apiKey)
            .withCredentials(domain = "YOUR_DOMAIN")           // only domain is client-supplied
            .withApplicant(customerId = "user-123", email = "user@example.com")
            .withEventType("KYC_EVENT")                        // optional; "KYC_EVENT"/"KYB_EVENT" to skip in-SDK selection
            .withForcedDayNightMode(WIDTHDayNightMode.NIGHT)   // optional; omit to follow the system
            .withHandlers(onEvent = myLogger)                  // optional; see Event logging
            .withLauncher(launcher)
            .build()
            .launch()
    }) { Text("Verify") }
}

This is an optional entry style, not a separate launch path: the builder assembles a native VerificationInput and reuses the same flow underneath. The withAccessToken value is the sdkToken, which itself encodes the tenant appId / apiKey (decrypted on-device); only domain is client-supplied via withCredentials, and the applicant identity via withApplicant.

withEventType is optional ("KYC_EVENT" / "KYB_EVENT") — set it to launch straight into that flow and skip the in-SDK selection screen; omit it to let the user pick.

VerificationInput(...) throws IllegalArgumentException on a blank domain / sdkToken, a blank customerId / email, or an eventType that is a non-empty value other than "KYC_EVENT" / "KYB_EVENT"; build() throws if a required builder field was not set. Catch it at the call site.

Event logging (optional)

Register an SdkEventLogger to receive SDK lifecycle events (INFO / WARN / ERROR) in your own pipeline (Crashlytics, Timber, DataDog, etc.). Known PII is redacted before the callback fires. SdkEventLogger is a functional interface, so a lambda works anywhere one is expected.

import com.width.identity.activity.SdkLogLevel
import com.width.identity.activity.SdkLogging

SdkLogging.setLogger { level, tag, message, throwable ->
    when (level) {
        SdkLogLevel.INFO  -> Log.i(tag, message)
        SdkLogLevel.WARN  -> Log.w(tag, message, throwable)
        SdkLogLevel.ERROR -> Log.e(tag, message, throwable)
    }
}

The logger is global and optional; call SdkLogging.setLogger(null) to stop forwarding. You can also register the same logger inline on the builder via withHandlers(onEvent = ...).

Configuration Reference

VerificationInput

Field Type Required Description
credentials CredentialsConfig Yes SDK authentication
customer CustomerConfig Yes Customer identity
theme ThemeConfig No UI theme customization
eventType String No "KYC_EVENT" / "KYB_EVENT" to skip the in-SDK selection screen; omit to let the user pick
language UILanguage No UI language: UILanguage.EN (default) / .ZH

CredentialsConfig

Field Type Required Description
domain String Yes Tenant identifier for multi-tenant routing
sdkToken String Yes SDK token (previously registered via manifest <meta-data>)

CustomerConfig

Field Type Required Description
customerId String Yes Unique customer identifier (must be non-blank)
email String Yes Customer email address (must be non-blank)
Both fields now required Both customerId and email must be provided and non-blank. If either is blank, construction throws IllegalArgumentException. (This tightens the earlier rule, which only required at least one of the two.)

Public API

com.width.identity.activity is the only customer-facing package — everything else is internal and produces a compile error if referenced from your app.

Symbol Purpose
Verification ActivityResultContract entry point
VerificationInput / CredentialsConfig / CustomerConfig Launch parameters
VerificationResult / VerificationDecision Direct-input result
WIDTHMobileSDK.Builder + WIDTHVerification Builder entry point (Compose)
WIDTHCompletionResult / WIDTHStatus / WIDTHException Builder result types
ThemeConfig / WIDTHDayNightMode Optional theme / day-night override
UILanguage Optional UI language (EN default / ZH)
SdkLogging / SdkEventLogger / SdkLogLevel Optional event log callback

UILanguage

Value Description
EN English (default)
ZH Simplified Chinese

Omitting language keeps English — the SDK does not follow the device locale. Third-party capture screens have limits: Veriff shows Chinese only when the device is online and the tenant integration enables it (English otherwise); Regula supports Simplified Chinese only.

Handling Results

Direct-input VerificationResult

VerificationResult is a sealed class with five variants:

Result Fields Description
Completed executionId, decision Server returned a decision: APPROVED / REJECTED / REVIEW (under manual review out-of-band)
Waiting executionId Await server notification for the final decision
Dismissed executionId? User exited intending to return later (X → keep progress, or pre-workflow exit). The server execution is kept alive so the next launch resumes; executionId is null if the workflow had not started. Resumable.
Cancelled executionId User explicitly discarded the verification (X → discard). The SDK awaited workflow.execution.cancel on the server, so the execution is destroyed and cannot be resumed.
Failed code, message, executionId? Internal error
Dismissed vs Cancelled The single old Cancelled case is now split: Dismissed means the user paused and can resume; Cancelled means the user discarded and the server execution is destroyed (not resumable).

Builder WIDTHCompletionResult

Case When
SuccessTermination(status, executionId?) Flow ended normally; final state carried by status
AbnormalTermination(exception, executionId?) Technical failure; exception.code / exception.message carry detail

status is a WIDTHStatus: Approved, FinallyRejected, Pending (manual review), Incomplete (resumable / dismissed), Cancelled (destroyed). exception is a WIDTHException: one of Init, Network, Api, Permission, Workflow, Unknown — each exposing code and message.

Theme Customization

ThemeMode

ThemeMode Behavior
"SYSTEM" Follows device setting
"LIGHT" Forces light theme
"DARK" Forces dark theme

Set the mode via ThemeConfig, or override day/night on the Builder entry point. Omit either to follow the system setting.

ThemeConfig(themeMode = "SYSTEM")   // "SYSTEM" / "LIGHT" / "DARK"

// On the Builder entry point — omit to follow the system:
.withForcedDayNightMode(WIDTHDayNightMode.NIGHT)

Resume & Lifecycle

User Choice Behavior Result Resumable?
Pause & Exit (keep progress) Server execution kept alive Dismissed Yes
End Verification (discard) Server cancel, execution destroyed Cancelled No

To resume, call launcher.launch(...) with the same customerId. The SDK auto-detects in-progress workflows.

Permissions

The SDK and its bundled capture engines declare every permission they need, and they are merged into your app automatically — you do not declare or request them:

<!-- WIDTH identity SDK core -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<!-- Added transitively by the capture / liveness engine -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.VIBRATE" />

<!-- Added transitively by the document-reader engine -->
<uses-permission android:name="android.permission.FLASHLIGHT" />
Permission Type Purpose / Source
CAMERA Runtime Document capture, liveness (SDK core)
INTERNET Install-time API communication (SDK core)
ACCESS_NETWORK_STATE Install-time Connectivity checks (SDK core)
RECORD_AUDIO Runtime Merged by the capture / liveness engine
NFC Install-time Merged by the capture / liveness engine
WAKE_LOCK Install-time Merged by the capture / liveness engine
VIBRATE Install-time Merged by the capture / liveness engine
FLASHLIGHT Install-time Merged by the document-reader engine
The runtime (dangerous) permissions are CAMERA and RECORD_AUDIO; the SDK and its capture engines request them from inside their own Activities, so your app does not prompt for them. Every permission above appears in your app's permission listing and must be reflected in your Google Play Data Safety disclosure. The SDK requires no storage / media permission — picking a document image uses the system photo picker.

ProGuard / R8: The SDK ships with embedded consumer rules. No additional configuration needed.

Troubleshooting

Issue Solution
Failed to resolve com.width.identity:... Ensure the Width Maven repo is in settings.gradle.kts (not build.gradle.kts), and that you use the single coordinate com.width.identity:sdk-activity:1.0.11 (no BOM). If the artifacts that fail to resolve are com.veriff or com.regula.*, the two provider repositories from Step 1 are missing.
IllegalArgumentException on launch Check that domain / sdkToken and both customerId and email are non-blank. Also check eventType: if you pass it, the value must be exactly "KYC_EVENT" or "KYB_EVENT".
Camera not working Check the merged manifest for permission conflicts; the SDK auto-merges CAMERA from its library manifest.
Theme colors not applied Verify hex values (no # prefix), case-sensitive token names
Build fails on checkReleaseAarMetadata / checkDebugAarMetadata with requires core library desugaring Enable core library desugaring in your app module and add the desugar_jdk_libs dependency — see Installation, Step 3. This is enforced regardless of your minSdk.
Build fails on the same AAR metadata check because the module's compileSdk is lower than the SDK requires The SDK's AARs declare minCompileSdk=36 in their AAR metadata and the build tooling enforces it, so a module compiling against anything lower stops the build. Set compileSdk = 36 in your app module — see Requirements. This is separate from minSdk, which stays 26.
Manifest merge fails with Validation failed, exiting on AGP 9 Add android.uniquePackageNames=false to your project's gradle.properties — see Installation, Step 3.
Upgrading: bump the version string in your build.gradle.kts; Gradle fetches the new artifacts from maven.width.com on the next sync.

Integration notes

Use the package access and installation steps above. Keep the SDK build and issued credentials aligned with the intended WIDTH environment.