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

Universal OID4VP

The Universal OID4VP module provides a backend-focused API for web applications and services that need to verify credentials without managing the low-level protocol details.

Overview​

While the standard Verifier API gives you full control over the OID4VP protocol, the Universal API simplifies common use cases:

  • Session Management - Handles session creation and lifecycle automatically
  • QR Code Generation - Creates QR codes with customizable styling
  • Status Polling - Exposes a single endpoint that returns the current verification state
  • Webhook Callbacks - Posts to a URL you configure when verification completes

Use Cases​

The Universal API is ideal for:

ScenarioDescription
Web ApplicationsDisplay QR code, poll for result, redirect on success
Kiosk SystemsStateless verification with QR display
Backend ServicesWebhook-based verification for async workflows
Mobile WebSame-device flows with deep links

Architecture​

Universal OID4VP Architecture

REST Endpoints​

The Universal API exposes three endpoints that map to the lifecycle of a verification session: create one, check its status, and clean it up when you're done.

Create Authorization Request​

This is the starting point for any verification flow. You post a DCQL query (either by referencing a pre-configured query_id or by inlining the query directly) and get back everything you need to present a QR code to the user and track the session.

POST /oid4vp/backend/auth/requests
Content-Type: application/json

{
"query_id": "age-verification",
"client_id": "https://verifier.example.com",
"callback": {
"url": "https://my-app.example.com/webhook",
"statuses": ["AUTHORIZATION_RESPONSE_VERIFIED"],
"include_verified_data": true
},
"ttl_seconds": 600,
"qr_code": {
"size": 400,
"color_dark": "#000000",
"color_light": "#ffffff"
}
}

Request Fields:

FieldTypeRequiredDescription
query_idstringEither query_id or dcql_queryReference to pre-configured DCQL query
dcql_queryobjectEither query_id or dcql_queryInline DCQL query
client_idstringNoOverride default client ID
callbackobjectNoWebhook configuration
ttl_secondsnumberNoSession TTL (default: 600)
qr_codeobjectNoQR code styling options

Response:

{
"correlation_id": "sess-abc123",
"query_id": "age-verification",
"request_uri": "openid4vp://authorize?request_uri=https%3A%2F%2Fverifier.example.com%2Foid4vp%2Frequests%2Fsess-abc123",
"qr_code_content": "openid4vp://authorize?request_uri=...",
"qr_code_data_uri": "data:image/png;base64,iVBORw0KGgo...",
"status_uri": "https://verifier.example.com/oid4vp/backend/auth/requests/sess-abc123",
"expires_at": 1704200400000,
"status": "CREATED"
}

Get Authorization Request Status​

After creating a session, your frontend (or backend) needs to know when the user has scanned the QR code and whether verification succeeded. Poll this endpoint on an interval, or use it as a one-off check if you're primarily relying on webhooks.

GET /oid4vp/backend/auth/requests/{correlationId}

Response:

{
"correlation_id": "sess-abc123",
"query_id": "age-verification",
"status": "AUTHORIZATION_RESPONSE_VERIFIED",
"created_at": 1704199800000,
"last_updated": 1704199850000,
"expires_at": 1704200400000,
"verified_data": {
"credentials": [
{
"id": "age_over_18",
"format": "dc+sd-jwt",
"type": "VerifiedPerson",
"claims": {
"age_over_18": true,
"given_name": "John"
}
}
]
}
}

Delete Authorization Request​

Sessions expire automatically based on ttl_seconds, but you can also remove them explicitly. This is useful if a user cancels a flow or navigates away, and you want to free up the session immediately rather than waiting for the TTL.

DELETE /oid4vp/backend/auth/requests/{correlationId}

Session Status Values​

A session moves through these states in order. Your polling logic or webhook handler should look for the terminal states (AUTHORIZATION_RESPONSE_VERIFIED, ERROR, EXPIRED) to decide what to do next.

StatusDescription
CREATEDSession created, waiting for wallet scan
REQUEST_RETRIEVEDWallet has retrieved the authorization request
AUTHORIZATION_RESPONSE_RECEIVEDWallet submitted response, verification in progress
AUTHORIZATION_RESPONSE_VERIFIEDCredentials verified successfully
ERRORVerification failed
EXPIREDSession timed out

Usage Examples​

Web Application Flow​

The most common pattern is a two-step flow: create a session to get a QR code, then poll for the result. The examples below show both the Kotlin SDK and a plain JavaScript approach using fetch against the REST API.

// 1. Create session and get QR code
val createCommand: CreateAuthRequestEndpointCommand = session.graph.createAuthRequestCommand

val result = createCommand.execute(
CreateAuthorizationRequestInput(
queryId = "age-verification",
qrCodeOptions = QrCodeOptions(size = 300)
),
sessionContext
)

val output = result.getOrThrow()
println("Display QR: ${output.qrCodeDataUri}")
println("Poll status at: ${output.statusUri}")

// 2. Poll for completion
val statusCommand: GetAuthRequestStatusEndpointCommand = session.graph.getAuthRequestStatusCommand

while (true) {
delay(2000)
val status = statusCommand.execute(
correlationId = output.correlationId,
sessionContext
).getOrThrow()

when (status.status) {
AuthorizationSessionStatus.AUTHORIZATION_RESPONSE_VERIFIED -> {
println("Verified: ${status.verifiedData}")
break
}
AuthorizationSessionStatus.ERROR -> {
println("Error: ${status.error}")
break
}
AuthorizationSessionStatus.EXPIRED -> {
println("Session expired")
break
}
else -> continue
}
}

Webhook Integration​

If you don't want to poll, you can register a callback URL when creating the session. The server will POST to that URL whenever the session reaches one of the statuses you specify. This is a better fit for backend services where you don't have a browser polling loop.

val result = createCommand.execute(
CreateAuthorizationRequestInput(
queryId = "kyc-verification",
callback = CallbackConfig(
url = "https://my-app.example.com/webhook/oid4vp",
statuses = listOf(
AuthorizationSessionStatus.AUTHORIZATION_RESPONSE_VERIFIED,
AuthorizationSessionStatus.ERROR
),
includeVerifiedData = true
)
),
sessionContext
)

Your webhook endpoint will receive a POST with a JSON body like this:

{
"correlation_id": "sess-abc123",
"status": "AUTHORIZATION_RESPONSE_VERIFIED",
"verified_data": {
"credentials": [...]
}
}

Dependencies​

You need the universal implementation module plus the underlying verifier and DCQL libraries. If you're already using the standard verifier API, you only need to add the universal module.

dependencies {
// Universal OID4VP
implementation("com.sphereon.idk:lib-openid-oid4vp-universal-impl:0.25.0")

// Required dependencies
implementation("com.sphereon.idk:lib-openid-oid4vp-verifier-impl:0.25.0")
implementation("com.sphereon.idk:lib-openid-oid4vp-dcql:0.25.0")
}

Configuration​

Instead of sending a full DCQL query with every request, you can register named queries in a registry. Then reference them by query_id when creating sessions. This keeps your request payloads small and your query definitions in one place.

@Inject
@SingleIn(AppScope::class)
class Oid4vpQueryRegistry {
val queries = mapOf(
"age-verification" to DcqlQuery(
credentials = listOf(
DcqlCredential(
id = "age_over_18",
format = "dc+sd-jwt",
claims = listOf(
DcqlClaim(path = listOf("age_over_18"))
)
)
)
),
"kyc-verification" to DcqlQuery(
credentials = listOf(
DcqlCredential(
id = "identity",
format = "dc+sd-jwt",
claims = listOf(
DcqlClaim(path = listOf("given_name")),
DcqlClaim(path = listOf("family_name")),
DcqlClaim(path = listOf("birth_date"))
)
)
)
)
)
}

Next Steps​