Skip to main content

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.

Prerequisites
  • 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.

note

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 -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-jwt specifies the SD-JWT credential format and urn:eudi:pid:1 identifies 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, the FULFILLED callback includes a responseCode and the wallet response contains a redirect_uri with 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—an openid4vp:// URI for same-device flows. Pass the full URI to the wallet as a deep link.
  • cross_device_request_uri—an openid4vp:// 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.

# 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.

# 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 credentials map. Evaluate the verification flags (signatureIsValid, isTrusted, kbSignatureIsValid) before accepting the credential—a FULFILLED status 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 errorDetails for 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 errorDetails for the reason.

For the complete list of statuses and payload fields, see the callback events reference.

Data handling responsibility

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, and cross_device_request_uri
  • QR code renders from cross_device_request_uri
  • Deep link opens the wallet on mobile
  • Callback receives the verification event with FULFILLED status
  • Callback correctly correlates state with the original session
  • Callback handles all five statuses (FULFILLED, REJECTED, EXPIRED, PROCESSING_ERROR, VERIFICATION_FAILED)
  • Same-device flow correctly uses responseCode for 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

Further reading