Skip to main content
Version: v0.25.0 (Latest)

HTTP/WebSocket Transport

HTTP and WebSocket transports enable remote mDoc presentations over the network. These are used with TO_APP (reverse) engagement for online verification scenarios.

Use Cases

Remote transports are used for:

  • Online identity verification during account registration
  • Remote document verification for digital services
  • Integration with OID4VP presentation flows
  • Server-based verification without physical proximity

TO_APP Engagement with HTTP

The TO_APP engagement type uses HTTP-based retrieval:

val engagementManager = session.graph.mdocEngagementManager

// Handle incoming mdoc:// URI from verifier
fun handleIncomingUri(uri: String) {
lifecycleScope.launch {
val result = engagementManager.toApp(
mdocUri = uri,
autoStart = true
)

if (result.isOk) {
val engagement = result.value
handleEngagement(engagement)
} else {
showError(result.error)
}
}
}

URI Scheme Patterns

The toApp() method supports three URI patterns:

SchemeTransportDescription
mdoc: (opaque)BLE/NFCClassic reverse engagement
mdoc:// (hierarchical)REST APIWebsite/server retrieval
mdoc-openid4vp://OID4VPOAuth 2.0 integration

Protocol Flow

REST API Mode

HTTP REST API Flow

REST API Protocol (ISO 18013-7 Annex A)

The REST API transport implements the online presentation flow defined in ISO 18013-7 Annex A. The protocol proceeds as follows:

  1. Holder POSTs DeviceEngagement: The holder sends a Tag 24-wrapped DeviceEngagement CBOR structure to the reader's URI.
  2. Reader responds with SessionEstablishment: The response contains a SessionEstablishment message, which includes the encrypted DeviceRequest.
  3. SessionTranscript derivation: Both parties derive the SessionTranscript using RestApiHandover. This handover includes the SHA-256 hash of the Tag 24-wrapped ReaderEngagement bytes, binding the session to the specific reader.
  4. Session encryption: The session uses ECDH key agreement followed by HKDF key derivation with AES-256-GCM encryption (Cipher Suite 1).
  5. Holder processes request: The holder decrypts the DeviceRequest, evaluates the requested data elements, and encrypts the DeviceResponse.
  6. Holder POSTs SessionData: The holder sends the encrypted SessionData (containing the DeviceResponse) back to the reader's endpoint.

HTTPS is required for all REST API transport connections. The transport validates this at construction time and rejects plain HTTP URIs.

OID4VP Protocol (ISO 18013-7 Annex B)

The OID4VP transport implements the OpenID for Verifiable Presentations flow for mDoc credentials, as defined in ISO 18013-7 Annex B.

The protocol proceeds as follows:

  1. URI reception: The holder receives an mdoc-openid4vp:// URI containing client_id and request_uri parameters.
  2. Authorization Request fetch: The holder fetches the JWT Authorization Request from the request_uri endpoint.
  3. Query format: The request supports both DCQL queries (the default format) and Presentation Definition (legacy format) for specifying which credentials and data elements are requested.
  4. SessionTranscript derivation: The SessionTranscript uses OID4VPHandover, which incorporates clientIdHash, responseUriHash, and a nonce to bind the session cryptographically.
  5. Response format: The response uses JARM (JWT-Secured Authorization Response Mode) for encryption.
  6. DeviceResponse encoding: The vp_token in the authorization response contains a base64url-encoded CBOR DeviceResponse.

Handling TO_APP Flow

Complete TO_APP flow with HTTP retrieval:

class ToAppHandler(
private val engagementManager: MdocEngagementManager,
private val documentProvider: DocumentProvider
) {
suspend fun handleUri(uri: String) {
// Create engagement from URI
val result = engagementManager.toApp(
mdocUri = uri,
autoStart = false
)

if (result.isOk) {
val engagement = result.value

// Monitor engagement events
engagement.events.collect { event ->
when (event.state) {
is MdocEngagementState.Connected -> {
// Start transfer
val transferManager = engagement.start()

// Handle the transfer
val deviceRequest = transferManager.receiveDeviceRequest()
val approved = showConsentDialog(deviceRequest)

if (approved) {
val response = transferManager.createResponse(
deviceRequest = deviceRequest,
documentProvider = documentProvider
)
transferManager.sendDeviceResponse(response)
}
}
}
}
} else {
handleError(result.error)
}
}
}

OID4VP Integration

For mdoc-openid4vp:// URIs, the IDK integrates with OID4VP:

// OID4VP URIs are handled the same way
val uri = "mdoc-openid4vp://authorize?..."

val result = engagementManager.toApp(
mdocUri = uri,
autoStart = true
)

// The IDK handles the OID4VP protocol internally

Android

Register to handle mDoc URIs in AndroidManifest.xml:

<activity
android:name=".MdocHandlerActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="mdoc" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="mdoc-openid4vp" />
</intent-filter>
</activity>

iOS

Register URL schemes in Info.plist:

<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>mdoc</string>
<string>mdoc-openid4vp</string>
</array>
</dict>
</array>

Monitoring Transfer State

Monitor HTTP-based transfer progress:

engagementManager.eventHub.allEvents.collect { event ->
when (event) {
is MdocEngagementEvent -> {
when (event.state) {
is MdocEngagementState.Connecting -> {
showMessage("Connecting to verifier...")
}
is MdocEngagementState.Connected -> {
showMessage("Connected")
}
is MdocEngagementState.Transferring -> {
showMessage("Transferring credentials...")
}
is MdocEngagementState.Completed -> {
showMessage("Transfer complete")
}
is MdocEngagementState.Error -> {
val error = (event.state as MdocEngagementState.Error).error
showError("Error: ${error.message}")
}
}
}
}
}

Error Handling

Handle network errors gracefully:

if (result.isErr) {
val error = result.error
val message = when {
error.message.contains("timeout", ignoreCase = true) ->
"Connection timed out. Please try again."
error.message.contains("network", ignoreCase = true) ->
"Network error. Check your connection."
error.message.contains("ssl", ignoreCase = true) ->
"Security error. The connection is not secure."
else -> error.message
}
showError(message)
}

Tips

When implementing HTTP/WebSocket transport:

  • Use HTTPS: Never use plain HTTP for credential exchange
  • Handle errors gracefully: Network requests can fail for many reasons
  • Set appropriate timeouts: Balance user experience with reliability
  • Validate server certificates: Ensure secure connections
  • Clean up on completion: Close engagements when transfer completes