Verify a credential
This guide shows you how to verify a credential of any type using the Truvity EUDIW Connector management API. You create a presentation request, display it to the user, and process the verification callback in your backend.
The examples use a generic DCQL query. Substitute your own DCQL query for your use case.
- A running connector instance with its public base URL configured and a callback URL pointing to your backend endpoint (for example,
http://localhost:3000/callback). See connector architecture for the deployment model and how callbacks are delivered. - Access to the internal management API (port 8081)
- A callback endpoint reachable from the connector over internal networking
- An X.509 access certificate configured in the connector
Overview
Create a presentation request via the management API, display the request URI to the user as a QR code or deep link, and handle the verification callback when the flow completes.
Time to implement: 1–2 hours.
Step 1: Create a presentation request
Call POST /oidc4vp on the management API with a DCQL query specifying the credentials and attributes you need.
The VCT values, requested claims, and credential values in this guide are examples. In production, request the attributes required by your use case and available in the target attestation scheme. Available attributes vary by issuer and member state.
- cURL
curl -X POST http://connector:8081/oidc4vp \
-H "Content-Type: application/json" \
-d '{
"dcql_query": {
"credentials": [
{
"id": "identity_credential",
"format": "dc+sd-jwt",
"meta": {
"vct_values": ["urn:eudi:pid:1"]
},
"claims": [
{ "path": ["given_name"] },
{ "path": ["family_name"] },
{ "path": ["birthdate"] }
]
}
]
}
}'
Example response:
{
"state": "abc123",
"same_device_request_uri": "openid4vp://?client_id=...&request_uri=https%3A%2F%2Fconnector.example.com%2Foidc4vp%2Fabc123%2Frequest%3Fflow_type%3Dsame-device",
"cross_device_request_uri": "openid4vp://?client_id=...&request_uri=https%3A%2F%2Fconnector.example.com%2Foidc4vp%2Fabc123%2Frequest%3Fflow_type%3Dcross-device"
}
Store the state value to correlate with the callback later.
The request body contains:
dcql_query—a DCQL query specifying the credentials and claims you need. In this example,dc+sd-jwtspecifies the SD-JWT credential format andurn:eudi:pid:1identifies the PID credential type.expires_in(optional)—session TTL in seconds. Overrides the default (ten minutes). Omit to use the default.transaction_data(optional)—an array of transaction data items to cryptographically bind to the request. See Use transactional data for details.redirect_uri(optional)—a URI to redirect the user to after same-device flow. When provided, theFULFILLEDcallback includes aresponseCodeand the wallet response contains aredirect_uriwith the code appended. Omit for cross-device flows.
The response contains:
state—a correlation token to match the callback with this request. Store this value in your session or database.same_device_request_uri—anopenid4vp://URI for same-device flows. Pass the full URI to the wallet as a deep link.cross_device_request_uri—anopenid4vp://URI to render as a QR code for cross-device flows.
Step 2: Display the request URI to the user
Use cross_device_request_uri to generate a QR code when the user is on a desktop. Use same_device_request_uri as a deep link when the user is on a mobile device.
This step is client-side. Use any QR code library to encode the cross_device_request_uri into a QR code image. For same-device flows, redirect the user's browser to the same_device_request_uri deep link.
- Shell
# Cross-device: generate a QR code PNG from the URI (requires qrencode)
qrencode -o qrcode.png "$cross_device_request_uri"
# Same-device: open the deep link directly (macOS)
open "$same_device_request_uri"
Step 3: Handle the verification callback
Implement a callback endpoint that receives the PresentedCredentialsEvent from the connector. The state field correlates the event with your original request. Use it to look up the session you stored in Step 1.
- cURL
# Simulate a FULFILLED callback for testing
curl -X POST https://example.com:3000/callback \
-H "Content-Type: application/json" \
-d '{
"status": "FULFILLED",
"state": "abc123",
"responseCode": "8k5CwzAseoGVK_bHQQJWMw",
"credentials": {
"identity_credential": [
{
"issuer": "https://issuer.example.com",
"claims": {
"given_name": "Erika",
"family_name": "Mustermann",
"birthdate": "1964-08-12"
},
"signatureIsValid": true,
"supportRevocation": false,
"supportTrustAnchor": true,
"isTrusted": false,
"kbKeyId": "KrXxHnYsf-Inp0hW1M6r...",
"kbSignatureIsValid": true
}
]
},
"credentialsRaw": {
"identity_credential": [
{
"claims": "eyJiaXJ0aGRhdGUiOiIx...",
"issuer": "https://issuer.example.com",
"kbKeyId": "KrXxHnYsf-Inp0hW1M6r..."
}
]
}
}'
The credentials and credentialsRaw fields are absent for REJECTED, EXPIRED, PROCESSING_ERROR, and VERIFICATION_FAILED statuses. The errorDetails field is present for REJECTED, PROCESSING_ERROR, and VERIFICATION_FAILED statuses. For same-device FULFILLED callbacks, the event includes a responseCode field to correlate the browser redirect with the callback.
Handle each status:
- FULFILLED—the wallet presented credentials matching your DCQL query. Extract the verified claims from the
credentialsmap. Evaluate the verification flags (signatureIsValid,isTrusted,kbSignatureIsValid) before accepting the credential—aFULFILLEDstatus means the protocol completed, not that all trust checks passed. - REJECTED—the user declined the presentation request in their wallet.
- EXPIRED—the session time-to-live (TTL) expired before the wallet responded. Create a new presentation request if the user wants to try again.
- PROCESSING_ERROR—the connector encountered an error processing the wallet's response. Check
errorDetailsfor the reason. - VERIFICATION_FAILED—the connector could not verify the presented credentials (for example, invalid signature, failed key binding, unsatisfied DCQL requirements, or X.509 trust chain validation failure). Check
errorDetailsfor the reason.
For the complete list of statuses and payload fields, see the callback events reference.
You are responsible for applying your own data retention and access control policies to credential data received through callbacks. The connector does not persist credential data after delivering the callback.
Testing
Test checklist
- Presentation request creates and returns
state,same_device_request_uri, andcross_device_request_uri - QR code renders from
cross_device_request_uri - Deep link opens the wallet on mobile
- Callback receives the verification event with
FULFILLEDstatus - Callback correctly correlates
statewith the original session - Callback handles all five statuses (
FULFILLED,REJECTED,EXPIRED,PROCESSING_ERROR,VERIFICATION_FAILED) - Same-device flow correctly uses
responseCodefor browser redirect correlation - Expired sessions are handled gracefully
Troubleshooting
Callback not receiving events
The connector delivers callbacks synchronously. Verify that your callback endpoint is reachable from the connector and responds within the timeout.
Session correlation failures
Verify that you store the state value from the POST /oidc4vp response and look it up when the callback arrives. The state value is a cryptographic random string generated by the connector.
Request expired before wallet responds
The default session time-to-live (TTL) is ten minutes. If users consistently time out, consider adjusting the TTL via the expires_in request parameter or guiding users to scan the QR code promptly.
Next steps
- Implement KYC verification—verify customer identity using PID credentials
- Implement passwordless authentication—replace passwords with key binding proof
Further reading
- OID4VP protocol—how the verification protocol works
- Callback events—Presented Credentials Event statuses and payload fields
- Error codes—wallet-facing HTTP error responses
- DCQL query language—how to specify credential requirements