Android
Run the verification flow in a hardened WebView from Kotlin (minSdk 24) and get a typed OkycResult.
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:
// 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:
<!-- 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.
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
)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
| Option | Type | Default | Description |
|---|---|---|---|
sessionUrl | string | required | The url returned by POST /v1/sessions. Never embed a secret API key in an app. |
locale | string | null | Flow language (en, hi); must be one of the workflow's languages. Sent as ?locale=. |
theme | light | dark | system | "system" | Colour scheme of the flow. Sent as ?theme=. |
allowedHosts | string[] | [] | Extra hosts the WebView may load (self-hosting). |
blockScreenshots | boolean | true | Android: FLAG_SECURE. iOS: cover the flow in the app switcher and while the screen is recorded. |
autoCloseDelay | duration_ms | 2000 ms | How 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
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}")
}
}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:
| Code | Meaning |
|---|---|
invalid_config | Missing or malformed session URL or configuration. |
load_failed | The flow page failed to load (HTTP error or blank page). |
network_error | No connection or DNS/TLS failure while loading. |
navigation_blocked | Repeated navigation to non-allowlisted hosts. |
permission_denied | The camera permission was permanently denied and the flow cannot continue. |
flow_error | The flow reported an unrecoverable error. |
webview_unavailable | The 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
allowedHostsload 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_SECUREby 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.
clearCacheis app-wide for WebView. - The session URL is never logged;
OkycConfig.toString()hides it.
Troubleshooting
| Symptom | Fix |
|---|---|
invalid_config | The URL must be the session url (https://…/s/ses_…#t=st_…). |
permission_denied | The user permanently denied the camera; ask them to enable it in Settings. |
webview_unavailable | The WebView renderer crashed or was killed; start again. |
navigation_blocked | The 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
blockScreenshotson and don't log the session URL. - Decide on the webhook, not on
Completed. - Remove any cleartext or debug network config from release builds.
Mobile SDKs
Android, iOS, React Native and Flutter wrappers around the hosted flow — one contract, the same behaviour everywhere.
iOS
Present the verification flow from UIKit or SwiftUI (iOS 15+) and await a typed OkycResult.
© 2026 Aiistic Technologies Private Limited. OKYC is a product of Aiistic Technologies Private Limited. Privacy notice