Mobile

Android

Run the verification flow in a hardened WebView from Kotlin (minSdk 24) and get a typed OkycResult.

View as Markdown

Version0.1.0

Install

The Android library (namespace in.okyc.sdk) ships as an AAR, okyc-release.aar; ask us at [email protected] for the current build. Put it in app/libs and add it with the AndroidX libraries it uses:

install.gradle.kts
// app/build.gradle.kts — the OKYC library (okyc-release.aar in app/libs) and the AndroidX libraries it uses
dependencies {
    implementation(files("libs/okyc-release.aar"))
    implementation("androidx.core:core-ktx:1.18.0")
    implementation("androidx.appcompat:appcompat:1.8.0")
    implementation("androidx.webkit:webkit:1.17.1")
    implementation("com.google.android.material:material:1.14.0")
}

Consumer R8/ProGuard rules ship inside the AAR, so you don't need your own keep rules.

Permissions

The SDK's manifest merges INTERNET and CAMERA (with android.hardware.camera not required), a FileProvider for camera capture from file inputs, and <queries> for image capture and https links. The camera is requested at runtime only when a step needs it; the microphone is never requested. The sample's manifest:

AndroidManifest.xml
<!-- The SDK's manifest already merges both; declare them yourself only to make them explicit.
     CAMERA is requested at runtime, and only when the flow needs it (selfie, document capture). -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="false" />

Start the flow

Create the session on your backend and pass its url to the app.

MainActivity.kt
val config = OkycConfig(
    sessionUrl = sessionUrl, // the `url` your backend got from POST /v1/sessions
    locale = locale, // "en" or "hi"; null keeps the session's language
    theme = theme, // LIGHT, DARK or SYSTEM (default)
    blockScreenshots = blockScreenshots, // FLAG_SECURE (default true)
    autoCloseDelay = 2000, // ms the result screen stays visible after `complete`; null = stay open
)
MainActivity.kt
Okyc.start(
    this, // any Activity
    config,
    onResult = { result -> handleResult(result) },
    onEvent = { event -> handleEvent(event) },
)

Okyc.start keeps callbacks in memory. To survive process death, launch with the Activity Result contract instead (results only, no progress events): registerForActivityResult(OkycContract()) { result -> … } and launch(OkycConfig(sessionUrl)).

Options

OptionTypeDefaultDescription
sessionUrlstringrequiredThe url returned by POST /v1/sessions. Never embed a secret API key in an app.
localestringnullFlow language (en, hi); must be one of the workflow's languages. Sent as ?locale=.
themelight | dark | system"system"Colour scheme of the flow. Sent as ?theme=.
allowedHostsstring[][]Extra hosts the WebView may load (self-hosting).
blockScreenshotsbooleantrueAndroid: FLAG_SECURE. iOS: cover the flow in the app switcher and while the screen is recorded.
autoCloseDelayduration_ms2000 msHow long the result screen stays visible after complete before the SDK closes and returns completed. 0 or null = return the result immediately (no result screen).

Android also has: title (see the SDK README).

Results and events

MainActivity.kt
private fun handleResult(result: OkycResult) {
    when (result) {
        // The applicant finished. Confirm the decision on your backend (webhook or GET /v1/sessions/:id).
        is OkycResult.Completed -> showResult("completed · ${result.sessionId} · ${result.status}")
        // Closed before finishing; the same session URL can be opened again.
        is OkycResult.Cancelled -> showResult("cancelled · ${result.sessionId ?: "—"}")
        // result.code is one of OkycResult.FailureCode (network_error, load_failed, …).
        is OkycResult.Failed -> showResult("failed · ${result.code} · ${result.message}")
    }
}
MainActivity.kt
private fun handleEvent(event: OkycEvent) {
    // Progress only (ids and statuses, never personal data). The result arrives in handleResult.
    when (event.type) {
        OkycEvent.Type.STEP_STARTED, OkycEvent.Type.STEP_COMPLETED -> logEvent("${event.type.wire} ${event.step}")
        OkycEvent.Type.ERROR -> logEvent("error ${event.payload["code"]}") // recoverable; the flow keeps running
        else -> logEvent(event.type.wire)
    }
}

OkycResult is Completed(sessionId, status), Cancelled(sessionId?) or Failed(code, message). Completed comes after the flow's complete event and the auto-close delay; back or ✕ asks "Exit verification?" before returning Cancelled. OkycEvent has type (.wire is the event name), sessionId and payload.

Failure codes on Android:

CodeMeaning
invalid_configMissing or malformed session URL or configuration.
load_failedThe flow page failed to load (HTTP error or blank page).
network_errorNo connection or DNS/TLS failure while loading.
navigation_blockedRepeated navigation to non-allowlisted hosts.
permission_deniedThe camera permission was permanently denied and the flow cannot continue.
flow_errorThe flow reported an unrecoverable error.
webview_unavailableThe WebView renderer crashed or is unavailable.

Theming and locale

theme (LIGHT, DARK, SYSTEM) is sent to the flow as ?theme= and also styles the native toolbar and dialog. locale (en, hi) is sent as ?locale= and sets the toolbar and dialog language; it must be one of the workflow's languages. Brand colours and logo come from your dashboard branding.

Security defaults

  • Only the session origin, DigiLocker and allowedHosts load in the WebView; other https links open in the browser on tap; intent:, file:, javascript: and plain http elsewhere are blocked.
  • JavaScript, DOM storage and Safe Browsing on; file/content access, mixed content and third-party cookies off. Remote debugging only when your app is debuggable.
  • Camera capture is granted only to the flow origin, after the OS permission; audio never.
  • OkycAndroid.postMessage(json) is the only method exposed to JavaScript, and messages from other origins or sessions are dropped.
  • FLAG_SECURE by default (blockScreenshots): blank card in Recents, black screen capture.
  • On finish, cookies and storage for the flow and DigiLocker origins, WebView history, form data and cache, and the SDK's capture directory are cleared. clearCache is app-wide for WebView.
  • The session URL is never logged; OkycConfig.toString() hides it.

Troubleshooting

SymptomFix
invalid_configThe URL must be the session url (https://…/s/ses_…#t=st_…).
permission_deniedThe user permanently denied the camera; ask them to enable it in Settings.
webview_unavailableThe WebView renderer crashed or was killed; start again.
navigation_blockedThe flow tried to load a host that isn't allowlisted; add self-hosted domains to allowedHosts.

Going live

  • Create sessions with a sk_live_… key on your backend; nothing changes in the app.
  • Keep blockScreenshots on and don't log the session URL.
  • Decide on the webhook, not on Completed.
  • Remove any cleartext or debug network config from release builds.

On this page