Skip to content

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.

ReasonDescription
CANCELLED_BY_USERUser called cancel()
NFC_CHIP_COMMUNICATION_FAILEDNFC connection lost (passport moved away)
INVALID_CLIENT_IDClient ID is incorrect
INVALID_ACCESS_KEY_VALUESCAN, document number, or date formats are wrong
ACCESS_CONTROL_FAILEDAccess control failed (verify access key values)
COMMUNICATION_FAILEDWebSocket connection failed (check network)
FILE_READ_ERRORServer could not read a file from the chip
EMRTD_PASSPORT_READER_ERRORError in the eMRTD reader
POST_TO_RESULT_SERVER_FAILEDFailed to post result to your server
SERVER_ERRORUnexpected server error
TIMEOUT_WHILE_WAITING_FOR_START_MESSAGEServer timeout waiting for start
TIMEOUT_WHILE_WAITING_FOR_RESPONSEServer timeout waiting for APDU response
MAX_SESSION_TIME_EXCEEDEDSession exceeded maximum time
PROTOCOL_ERRORProtocol 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:

StatusDescription
CONNECTING_TO_SERVERConnecting to the DocVal Server
ACCESS_CONTROLPerforming Access Control (PACE or BAC)
READ_ATR_INFOReading ATR/Info
READ_SODReading the Security Object (SOD)
READ_DG14Reading Data Group 14 (Chip Authentication info)
CHIP_AUTHENTICATIONPerforming Chip Authentication
READ_DG15Reading Data Group 15 (Active Authentication key)
ACTIVE_AUTHENTICATIONPerforming Active Authentication
READ_DG1Reading Data Group 1 (MRZ info)
READ_DG2Reading Data Group 2 (face photo)
READ_DG7Reading Data Group 7 (signature photos)
READ_DG11Reading Data Group 11 (additional personal details)
READ_DG12Reading Data Group 12 (additional document details)
PASSIVE_AUTHENTICATIONPerforming Passive Authentication
DONESession finished

Session Management

Canceling a Session

Cancel a running session at any time:

kotlin
emrtdConnector.cancel()
// ClosedListener will be called with reason CANCELLED_BY_USER

Checking Session State

kotlin
if (emrtdConnector.isOpen) {
    // Session is currently active
}