Documentation menu
Android
Drop-in identity verification for Android: CameraX capture screens for the document and the selfie. Your backend creates the session; the SDK runs the document and liveness steps.
Prepare the environment
Add the Blink Maven repository and the dependency:
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven("https://blink-pay.net/maven/releases") // Blink SDK repository
}
}android {
defaultConfig { minSdk = 24 }
}
dependencies {
implementation("com.blink.kyc:blink-kyc-android:1.0.13")
}The SDK declares the camera permission and its capture screens; they are merged into your manifest and the drop-in screens request the runtime permission themselves.
Verification flow
Get a session token from your backend (Session API), build a
BlinkConfig, then present the capture screens:
import com.blink.kyc.BlinkConfig
import com.blink.kyc.BlinkError
import com.blink.kyc.BlinkKyc
import com.blink.kyc.BlinkStepError
import com.blink.kyc.DocumentType
val config = BlinkConfig.builder(sessionToken)
.baseUrl("https://kyc-api.blink-pay.net")
.issuingCountry("SDN") // SDN | ARE
.documentType(DocumentType.PASSPORT) // PASSPORT | NATIONAL_ID
.build() // throws BlinkError (BLINK_CONFIG) on a bad value
BlinkKyc(config)
.onProgress { step, state -> Log.d("kyc", "$step $state") }
.present(this) // the SDK owns the camera UI
.whenComplete { result, error ->
runOnUiThread {
when (val e = (error as? java.util.concurrent.CompletionException)?.cause ?: error) {
null -> Log.i("kyc", "${result.result}") // VERIFIED | REJECTED | REVIEW: a device copy
is BlinkStepError -> Log.w("kyc", "${e.step}: ${e.code}") // e.g. DOCUMENT_UNREADABLE
is BlinkError -> Log.e("kyc", "${e.code} (${e.httpStatus})") // switch on e.code
}
}
}Ask your backend for the authoritative result before acting on the device copy.
| Call | Effect |
|---|---|
BlinkConfig.builder(sessionToken) … .build() | The session settings:
baseUrl, issuingCountry (SDN or ARE, default SDN),
documentType (default PASSPORT), profile, locale
("ar" for Arabic), theme, strings, rejectExpiredDocuments,
timeoutMs. |
BlinkKyc(config) | Create the client. |
.document(type), .face() | Limit the journey to these steps. With neither, the
session's purpose decides. type must match the config. |
.onProgress { step, state -> } | Progress events, e.g. DOCUMENT_FRONT UPLOADING. |
.present(activity) | Run the journey on the built-in capture screens; returns a
CompletableFuture<SessionResult>. |
.capture(hooks).runAsync() | Supply the media yourself (headless); same future. |
.status() | Read-only session progress from Blink. Blocking: call it off the main thread. |
SessionResult carries result (VERIFIED, REJECTED or
REVIEW) and detail.
Headless capture
Bring your own camera: return the document image for each side and the liveness frames, and the SDK handles challenges, uploads and the verdict. The hooks are called on a worker thread, so they may block.
BlinkKyc(config)
.capture(object : CaptureHooks {
override fun document(type: DocumentType, side: DocumentSide) =
DocumentCapture(grabDocumentJpeg(side)) // FRONT, then BACK for an ID
override fun liveness(actions: List<String>) =
LivenessCapture(recordFrames(actions), null) // JPEG frames for the server's actions
})
.runAsync()
.whenComplete { result, error -> /* as above */ }UI customisation
val config = BlinkConfig.builder(sessionToken)
.theme(BlinkTheme.builder()
.accent(0xFF15803D.toInt())
.introColor(0xFF143A68.toInt())
.logo(BlinkImage.resource(R.drawable.bank_logo))
.build())
.locale("ar") // Blink's built-in Arabic, right to left
.build()Every field and the copy you can replace are in Customisation.
Next
Handle the device outcome and errors (SDK result), then read the authoritative decision on your backend (Result API).