Appearance
Usage Guide
Quick Start
1. Acquire an IsoDep
Acquire an IsoDep from Android. Refer to the NFC basics guide on how to configure your app and allow your Activity to receive the ACTION_TECH_DISCOVERED intent.
2. Connect to the DocVal Server
Create an EmrtdConnector instance and start the session:
kotlin
val clientId = "example_client"
val serverUrl = "wss://docval.kurzdigital.com/ws2/validate"
val emrtdConnector = EmrtdConnector(
clientId, serverUrl, ::closedListener, ::statusListener, ::emrtdPassportListener
)
fun connect(isoDep: IsoDep) {
// Unique transaction ID, usually from your server
val validationId = UUID.randomUUID().toString()
val options = ConnectionOptions.Builder()
.setValidationId(validationId)
.setChipAccessKeyFromMrz(
"123456789", // Document number
"970101", // Date of birth (yyMMdd)
"221212" // Date of expiry (yyMMdd)
)
.build()
emrtdConnector.connect(isoDep, options)
}
fun closedListener(code: Int, reason: String, remote: Boolean) {
if (code != 1000) {
println("Session closed with error. Reason: $reason")
} else {
println("Session closed successfully.")
}
}
fun statusListener(status: String) {
println("Status: $status")
}
fun emrtdPassportListener(emrtdPassport: EmrtdPassport?, exception: JSONException?) {
if (emrtdPassport != null) {
println("Result received: $emrtdPassport")
}
}Using With CAN
For documents that support PACE with a Card Access Number (a 6-digit number printed on the document):
kotlin
val options = ConnectionOptions.Builder()
.setValidationId(UUID.randomUUID().toString())
.setChipAccessKeyFromCan("123456")
.build()
emrtdConnector.connect(isoDep, options)INFO
CAN authentication only works with documents that support PACE (Password Authenticated Connection Establishment). It cannot be used with BAC (Basic Access Control).
EmrtdConnectorActivity
The SDK includes a pre-built Activity that handles the complete eMRTD reading flow with a UI, including NFC tag discovery, progress display, and status messages. This is the simplest way to integrate the SDK.
kotlin
val intent = Intent(context, EmrtdConnectorActivity::class.java).apply {
putExtra(EmrtdConnectorActivity.CLIENT_ID, "example_client")
putExtra(EmrtdConnectorActivity.VALIDATION_URI, "wss://docval.kurzdigital.com/ws2/validate")
putExtra(EmrtdConnectorActivity.VALIDATION_ID, UUID.randomUUID().toString())
// Use CAN:
putExtra(EmrtdConnectorActivity.CAN, "123456")
// Or use MRZ fields:
putExtra(EmrtdConnectorActivity.DOCUMENT_NUMBER, "123456789")
putExtra(EmrtdConnectorActivity.DATE_OF_BIRTH, "970101")
putExtra(EmrtdConnectorActivity.DATE_OF_EXPIRY, "221212")
}
startActivityForResult(intent, REQUEST_CODE)Handle the result in onActivityResult:
kotlin
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode != REQUEST_CODE) {
return
}
if (resultCode == RESULT_OK) {
val passport = data?.getParcelableExtra<EmrtdPassport>(EmrtdConnectorActivity.RETURN_DATA)
// Handle result
} else {
val error = data?.getStringExtra(EmrtdConnectorActivity.RETURN_ERROR)
// Handle error
}
}Custom HTTP Headers
If your server requires custom headers (e.g., for authentication), you can provide them via ConnectionOptions. These headers are sent in the WebSocket handshake and forwarded by the DocVal Server to your result server:
kotlin
val options = ConnectionOptions.Builder()
.setValidationId(UUID.randomUUID().toString())
.setChipAccessKeyFromMrz(documentNumber, dateOfBirth, dateOfExpiry)
.setHttpHeaders(mapOf(
"Authorization" to "Bearer your-token",
"X-Custom-Header" to "value"
))
.build()Diagnostics
Enable diagnostic data collection on the DocVal Server to help troubleshoot eMRTD reading problems:
kotlin
val options = ConnectionOptions.Builder()
.setValidationId(UUID.randomUUID().toString())
.setChipAccessKeyFromMrz(documentNumber, dateOfBirth, dateOfExpiry)
.setEnableDiagnostics(true)
.build()WARNING
When diagnostics are enabled, the DocVal Server collects additional debugging information that may contain personal data. Make sure to obtain user consent if required.
See Diagnostic Session for more information.
OpenTelemetry
The SDK supports OpenTelemetry for tracing the protocol communication. Configure a tracer provider before starting a session:
kotlin
// Set a global tracer provider
EmrtdConnector.setTracerProvider(yourTracerProvider)
// Access the current tracer (e.g., for custom spans)
val tracer = EmrtdConnector.getTracer()If no tracer provider is configured, the SDK uses a no-op implementation (no tracing overhead).
Error Handling
The SDK reports errors through the ClosedListener callback. Check the close code and reason to determine what happened:
kotlin
fun closedListener(code: Int, reason: String, remote: Boolean) {
when {
code == 1000 -> println("Session completed successfully")
reason == ClosedListener.NFC_CHIP_COMMUNICATION_FAILED ->
println("NFC connection lost - hold passport steady")
reason == ClosedListener.ACCESS_CONTROL_FAILED ->
println("Wrong MRZ/CAN values")
reason == ClosedListener.COMMUNICATION_FAILED ->
println("Network error: ${emrtdConnector.webSocketClientException}")
else -> println("Error: $reason (code $code)")
}
}Close Reasons
When the session closes, the ClosedListener receives a close code, a reason string, and whether the close was initiated by the server. A code of 1000 means success.
| Reason | Description |
|---|---|
CANCELLED_BY_USER | User called cancel() |
NFC_CHIP_COMMUNICATION_FAILED | NFC connection lost (passport moved away) |
INVALID_CLIENT_ID | Client ID is incorrect |
INVALID_ACCESS_KEY_VALUES | CAN, document number, or date formats are wrong |
ACCESS_CONTROL_FAILED | Access control failed (verify access key values) |
COMMUNICATION_FAILED | WebSocket connection failed (check network) |
FILE_READ_ERROR | Server could not read a file from the chip |
EMRTD_PASSPORT_READER_ERROR | Error in the eMRTD reader |
POST_TO_RESULT_SERVER_FAILED | Failed to post result to your server |
SERVER_ERROR | Unexpected server error |
TIMEOUT_WHILE_WAITING_FOR_START_MESSAGE | Server timeout waiting for start |
TIMEOUT_WHILE_WAITING_FOR_RESPONSE | Server timeout waiting for APDU response |
MAX_SESSION_TIME_EXCEEDED | Session exceeded maximum time |
PROTOCOL_ERROR | Protocol violation (implementation bug) |
Exception Details
For certain close reasons, you can get more details from the EmrtdConnector instance:
kotlin
fun closedListener(code: Int, reason: String, remote: Boolean) {
when (reason) {
ClosedListener.COMMUNICATION_FAILED -> {
val exception = emrtdConnector.webSocketClientException
println("WebSocket error: $exception")
}
ClosedListener.NFC_CHIP_COMMUNICATION_FAILED -> {
val exception = emrtdConnector.nfcException
println("NFC error: $exception")
}
}
}Status Updates
The StatusListener is called during the session to report progress. The following status values are defined:
| Status | Description |
|---|---|
CONNECTING_TO_SERVER | Connecting to the DocVal Server |
ACCESS_CONTROL | Performing Access Control (PACE or BAC) |
READ_ATR_INFO | Reading ATR/Info |
READ_SOD | Reading the Security Object (SOD) |
READ_DG14 | Reading Data Group 14 (Chip Authentication info) |
CHIP_AUTHENTICATION | Performing Chip Authentication |
READ_DG15 | Reading Data Group 15 (Active Authentication key) |
ACTIVE_AUTHENTICATION | Performing Active Authentication |
READ_DG1 | Reading Data Group 1 (MRZ info) |
READ_DG2 | Reading Data Group 2 (face photo) |
READ_DG7 | Reading Data Group 7 (signature photos) |
READ_DG11 | Reading Data Group 11 (additional personal details) |
READ_DG12 | Reading Data Group 12 (additional document details) |
PASSIVE_AUTHENTICATION | Performing Passive Authentication |
DONE | Session finished |
Session Management
Canceling a Session
Cancel a running session at any time:
kotlin
emrtdConnector.cancel()
// ClosedListener will be called with reason CANCELLED_BY_USERChecking Session State
kotlin
if (emrtdConnector.isOpen) {
// Session is currently active
}