SmartComply Android SDK
The SmartComply Android SDK delivers a fully self-contained identity verification flow for Android apps. Launch one Activity and the SDK handles session management, country and ID-type selection, document capture, identity verification, and liveness detection automatically.Features
- Single-Activity launch — start verification with one Intent and receive a typed result back
- Two verification modes — document photo capture or ID number data entry, configured from your Dashboard
- Guide-box document capture — frames the ID card precisely so images are always clean and correctly cropped
- Liveness detection — a passive camera scan (blink plus natural head movement) runs automatically after identity verification, with no step-by-step prompts
- Dynamic ID types — channels and fields are fetched live from your Dashboard configuration
- Multi-country support — renders a country picker automatically when more than one country is configured
- Dark and light mode — theme adapts to the system setting; override via the launch intent
- Your brand, not ours: colour and logo come from the Dashboard, your name appears while the session is being set up, and the font is passed by your app. See Branding
- Any backend host: production, staging, or your own, without a rebuild
Requirements
- Android API 24 (Android 7.0) or later
- Kotlin 2.1 or later — the SDK is compiled with the Kotlin 2.1 toolchain, so an older consumer compiler rejects it
- Java 17 — the SDK targets JVM 17
- Jetpack Compose enabled in your module
Installation
1 — Add Maven Central
Insettings.gradle.kts (already present in most projects):
2 — Add the dependency
In your app or feature modulebuild.gradle.kts:
1.1.2 changes when the liveness selfie is taken. Up to 1.1.1 it fired on a
fixed timer half a second after the camera opened, before anything had looked
for a face, so the autoshot on the dashboard was routinely of nobody, of a
ceiling, or of half a face. It now waits for a frame with a whole face in it.
That adds up to 2.5 seconds to the start of the check for a user who is slow to
get into frame, and nothing at all for one who is already there. Read
Liveness before you upgrade.
Take 1.1.1 or later, not 1.1.0. In 1.1.0 every liveness selfie uploads
rotated 90 degrees, so a verification that passes can still report “Face
Detected: No” in the dashboard’s Biometrics panel. 1.1.1 is that one fix and
changes nothing else.
1.1.0 changes how documents are captured. The camera step now takes a
photograph rather than a still of the viewfinder, so it binds a second CameraX
use case in your process, uploads an image roughly five times larger in pixels,
and takes a moment on the shutter where it used to be instant. Read
Document images before you upgrade, and re-test your
document flow. It also fixes a genuine ID being rejected with “No face
detected” and a first run on a device that has never used ML Kit.
1.0.7 changed two behaviours and they still apply.
RESULT_STATUS holds the
real verdict, and the success screen does not mean a pass. Read
Result Extras before upgrading from 1.0.6 or below.3 — Enable Compose
Credentials
Both values come from your Adhere Dashboard and both are permanent. Reuse the same pair for every verification.Permissions
The SDK declares these permissions automatically via manifest merge. You do not need to add them manually unless your project uses a custom manifest merge strategy:SmartComplyActivity requests the CAMERA permission at runtime before it starts the flow, so if you launch through the Activity your app does not need to request it separately. If you embed SmartComplyFlowScreen directly, see Advanced: Custom Host Activity.
From 1.1.0 one meta-data entry merges in as well:
meta-data, keep this one.
Quick Start
1 — Register the result launcher
In yourActivity or Fragment:
2 — Launch verification
Jetpack Compose
If you are launching from a composable, userememberLauncherForActivityResult:
buildIntent Parameters
Environments
SANDBOX targets a server running on the handset itself, for local backend development. It is not a hosted test environment. Use PRODUCTION for all integration work.
A different host
Environment covers the two hosts you ship against. For anything else, a staging
deployment or a QA build that has to reach more than one backend without being
rebuilt, override it. Added in 1.0.7.
environment rather
than being treated as a host, so a build reading this off an empty field does not
post verifications to a relative URL.
SmartComplyActivity.buildIntent defaults to PRODUCTION, but the SDKConfig constructor used by the embedded flow defaults to SANDBOX. Set environment explicitly when you construct SDKConfig yourself.Result Extras
Read from theIntent returned to your activity result callback.
From 1.0.7 the Activity returns
TYPE_FAILURE when apiKey or clientId is blank, which
is the usual sign of an unset BuildConfig field or a missing CI value. That result carries no
other extras. Before 1.0.7 the same input crashed the host app, so no released version returned
TYPE_FAILURE for anything else.RESULT_VERIFIED_NAME and RESULT_ID_NUMBER are populated from the result poll as well as
from submit, from 1.0.7. In document mode they previously appeared only when OCR happened
to finish before the user completed liveness, so whether a user saw their own name came down
to timing.The
liveness.completed webhook is the authoritative record. The value here is the same verdict delivered earlier for convenience, and it is absent when the worker is slow; the webhook always arrives.Verification Flow
The SDK steps through these screens automatically.Document images
Document photos are capped at 5 MB server-side, in JPG or PNG. Both routes are held to 1600px on the long edge and re-encoded at JPEG quality 85, so a full-resolution phone photo does not fail withFile too large. Maximum size is 5MB.
A gallery pick is also rotated to match its EXIF orientation from 1.1.0. Before that it was uploaded as decoded, so a normal phone photo reached the backend sideways.
Before the review screen the SDK checks the front photo contains a face. A failure is a warning, not a block: the user can still confirm, because a false negative would otherwise trap someone holding a genuine but unusual ID. The backend OCR is the check that actually rejects. The back photo is not checked.
From 1.1.0 that check runs ML Kit’s accurate model. The fast one it used before does not find the portrait on a laser-engraved card, so genuine IDs of that kind, Kenya’s Maisha Namba among them, were told “No face detected”.
Branding
The flow carries your brand, not Adhere’s. Colour and logo come from the SDK Config on the dashboard, so they change without an app release, and your name is shown while the session is being set up. The font is the one exception and is passed by the host.
The welcome heading and subheading are fixed SDK copy: “Verify Your Identity”, then one of two
lines keyed off the verification mode.
Logo
Upload it under Settings → Integrations → SDK → Brand Details. PNG, JPEG or WebP, up to 512 KB and 1024x1024 pixels. Requires 1.0.7. It is drawn on its own, with no box or border behind it, and bounded by height rather than fitted into a square so a wordmark stays legible. Only the SDK’s fallback shield sits in a tinted frame. The logo belongs to the SDK Config, not the company. A branch running several configs for different products gives each its own mark, and a config with no logo shows the SDK’s own shield rather than falling back to the company logo on your profile page. If you want the same mark everywhere, upload it to each config.A logo never delays or blocks a verification. The SDK fetches it after the
welcome screen is already on screen, on a three second budget, and keeps its own
shield if the fetch fails. It is also decoded with a size cap, so an oversized
image cannot exhaust memory on the handset.
Font
Passed by your app, because the SDK neither ships nor downloads font files: your loading, caching and licensing stay yours. Requires 1.0.7. ThroughbuildIntent, with a font resource:
SmartComplyFlowScreen yourself, with a FontFamily directly,
which is the only way to control each weight:
fontResId at an XML font family rather than a single font file and the
platform resolves real faces per weight on API 28+. A single file is used for every
weight, which flattens the SDK’s type hierarchy.
A font resource that cannot be loaded, including one R8 has stripped, falls back
to the platform font. It does not fail the verification.
Liveness
Two actions are drawn fromBLINK and TURN_HEAD in random order and detected passively. Both are watched on every frame, neither is named to the user, and the HUD only reports how many are done: there is no prompted sequence.
The scan has a 20 second cap.
When the autoshot is taken
Two stills leave the device. The autoshot is taken before the scan starts and uploaded with the entry; the snapshot is taken once both actions are done and uploaded with the video. Both come off the analysis stream, so neither is a composed photograph: 720px on the long edge at JPEG quality 72. From 1.1.2 the autoshot waits for a face. Up to 1.1.1 it fired on a fixeddelay(500) after the camera bound, and nothing had looked for a face by then,
because detection did not start until recording did. The one frame of the session
that became the selfie was the only frame no detector had ever seen, so clients
reported autoshots of nobody, of ceilings, and of half a face.
It now runs face detection from the first frame and holds the shot until three
consecutive frames show a face that is wholly inside the frame with an 8% margin,
spans at least 18% of the frame width, and is within 30 degrees of facing the
camera. What changes for you:
- Timing. A 400ms settle, then up to 2.5 seconds waiting. On a TECNO KI7 a user already in frame cleared it in about 1 second. A user who never presents a face waits the full 2.5 seconds and then gets the old behaviour, so the fallback is never a worse image than 1.1.1 produced, only a later one.
- The challenge window is unaffected. The wait sits before
liveness.create, so the 20 second cap still starts with a full 20 seconds. - The HUD. “Position your face in the oval” now shows during that wait, where earlier versions went straight from camera-ready to detecting.
What the success screen means
Nothing about the verdict. Every submitted entry reaches it, whether the backend passed or failed the check. The submit call returnsprocessing, because the backend resolves face matching on a worker moments later. The SDK polls for up to about 15 seconds and puts whatever it learns in RESULT_STATUS, then shows the success screen either way. A user who failed is not told so and is not offered a retry.
This is a product decision, not an oversight: tenants asked for the end user to see a submitted verification as accepted, and to act on the real verdict themselves from the dashboard and the webhook. It means you have to close the loop with a user who failed.
Pre-submission failures are unaffected and still show the failure screen with Try Again: a denied camera, a configuration or network error, and reaching the 20 second liveness cap. Nothing has been billed at that point and the user can recover on the spot.
SDK Configuration
Error Handling
SmartComplyActivity handles and displays all error states inside the flow: an invalid key, an expired session, or an exhausted upload retry all render the SDK’s own failure screen with a Try Again button. From 1.0.7 the Activity also returns immediately, before any screen renders, when apiKey or clientId is blank. Otherwise it returns a result only when the user completes the flow or backs out.
Advanced: Custom Host Activity
If you need to embed the verification flow directly inside your ownComponentActivity instead of launching a separate screen, you can use SmartComplyFlowScreen as a Compose composable:
You do not need to request
CAMERA yourself. From 1.0.2 the composable routes through its own permission screen in both verification modes, before the ID-input or document-capture step, and skips it silently once the permission is granted.On 1.0.1 and earlier data mode never asked, so if the permission had not already been granted the camera never opened: the liveness preview stayed black and the flow ended with Recording failed: Recording finalized with error code 4. If you are pinned to an older version, request CAMERA yourself before entering the flow, or launch through SmartComplyActivity, which always asked for both modes.Dependencies
From 1.0.3 the SDK no longer depends on Ktor. It uses OkHttp directly, so your own Ktor version cannot conflict with ours.ProGuard / R8
There is nothing to do. The SDK ships consumer ProGuard rules inside the AAR, so R8 applies them to your app automatically. They keep the generated kotlinx.serialization serializers andSmartComplyActivity; OkHttp ships its own rules in its jar.

