Handle verification errors
This guide shows you how to handle the four non-success PresentedCredentialsEvent statuses in your callback endpoint. You implement a handler that maps each error outcome to a user-facing message and add retry logic for recoverable errors.
- 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
- Familiarity with the getting started guide
Overview
The connector delivers a PresentedCredentialsEvent to your callback endpoint at the end of each presentation flow. The getting started guide covers parsing the event and extracting credentials from a FULFILLED response. This guide focuses on the four non-success statuses: REJECTED, EXPIRED, PROCESSING_ERROR, and VERIFICATION_FAILED.
For the complete event schema including all payload fields, see the callback events reference.
Time to implement: 30 minutes to 1 hour.
Step 1: Handle each error status
Add a branch for each non-success status in your callback handler. The credentials and credentialsRaw fields are absent for all four statuses. The errorDetails field is present for REJECTED, PROCESSING_ERROR, and VERIFICATION_FAILED.
- cURL
# Simulate a REJECTED callback
curl -X POST http://localhost:3000/callback \
-H "Content-Type: application/json" \
-d '{"status": "REJECTED", "state": "abc123", "errorDetails": "access_denied"}'
# Simulate an EXPIRED callback
curl -X POST http://localhost:3000/callback \
-H "Content-Type: application/json" \
-d '{"status": "EXPIRED", "state": "abc123"}'
# Simulate a PROCESSING_ERROR callback
curl -X POST http://localhost:3000/callback \
-H "Content-Type: application/json" \
-d '{"status": "PROCESSING_ERROR", "state": "abc123", "errorDetails": "decryption_failed"}'
# Simulate a VERIFICATION_FAILED callback
curl -X POST http://localhost:3000/callback \
-H "Content-Type: application/json" \
-d '{"status": "VERIFICATION_FAILED", "state": "abc123", "errorDetails": "invalid_signature"}'
REJECTED
The user declined the presentation request in their wallet. Log the errorDetails field (which contains the wallet's OAuth 2.0 error code), inform the user, and offer to retry.
Example user message: "You declined the verification request. Select 'Verify' to try again."
EXPIRED
The session timed out before the wallet responded. Create a new presentation request with a fresh state value.
Example user message: "Your verification session timed out. Select 'Verify' to start a new session."
PROCESSING_ERROR
An internal processing failure occurred (for example, a decryption, state validation, or infrastructure error). Log the errorDetails field, alert your operations team, and offer the user a retry.
Example user message: "Something went wrong during verification. Try again, and contact support if the problem persists."
VERIFICATION_FAILED
Credential verification failed (for example, invalid signature, revoked credential, or DCQL mismatch). The credentials and credentialsRaw fields are absent. Use the errorDetails field to diagnose the specific failure. Do not grant access.
Example user message: "Your credential could not be verified. Contact your credential issuer if you believe this is an error."
Step 2: Implement retry logic
When a recoverable error occurs (REJECTED, EXPIRED, or PROCESSING_ERROR), create a new presentation request with a fresh state value. Do not retry the same session—expired and rejected sessions cannot be reused.
- cURL
# Create a new presentation request to retry verification
curl -X POST http://connector:8081/oidc4vp \
-H "Content-Type: application/json" \
-d '{
"dcql_query": {
"credentials": [
{
"id": "pid_identity",
"format": "dc+sd-jwt",
"meta": { "vct_values": ["urn:eudi:pid:1"] },
"claims": [
{ "path": ["given_name"] },
{ "path": ["family_name"] },
{ "path": ["birthdate"] }
]
}
]
}
}'
Example response:
{
"state": "def456",
"same_device_request_uri": "openid4vp://?client_id=...&request_uri=https%3A%2F%2Fconnector.example.com%2Foidc4vp%2Fdef456%2Frequest%3Fflow_type%3Dsame-device",
"cross_device_request_uri": "openid4vp://?client_id=...&request_uri=https%3A%2F%2Fconnector.example.com%2Foidc4vp%2Fdef456%2Frequest%3Fflow_type%3Dcross-device"
}
Store the new state value and display the new QR code or deep link to the user.
Step 3: Map statuses to user-facing messages
| Status | User-facing message | Action |
|---|---|---|
REJECTED | "You declined the verification request. Select 'Verify' to try again." | Log error, offer retry |
EXPIRED | "Your verification session timed out. Select 'Verify' to start a new session." | Create new request |
PROCESSING_ERROR | "Something went wrong during verification. Try again, and contact support if the problem persists." | Log error, offer retry |
VERIFICATION_FAILED | "Your credential could not be verified. Contact your credential issuer if you believe this is an error." | Log error, deny access |
Understanding errorDetails
The errorDetails field in REJECTED, PROCESSING_ERROR, and VERIFICATION_FAILED callbacks contains information about the underlying failure.
| Callback status | errorDetails contents |
|---|---|
REJECTED | The wallet's OAuth 2.0 error code and optional description, such as access_denied or access_denied: User canceled |
PROCESSING_ERROR | Internal failure details such as decryption errors or state validation failures |
VERIFICATION_FAILED | Credential verification failure details such as invalid signatures, revoked credentials, or DCQL mismatches |
The OID4VP specification defines error codes that wallets return when they cannot fulfill a presentation request. The connector maps these wallet-reported errors to the REJECTED status and includes the original error code in errorDetails. Log this field to diagnose integration issues at the protocol level.
For the complete list of wallet-facing error codes, see the error codes reference.
Testing
Test checklist
- Callback handler processes all four error statuses without errors
-
REJECTEDandEXPIREDstatuses trigger retry flow with a newstate -
REJECTED,PROCESSING_ERROR, andVERIFICATION_FAILEDstatuses logerrorDetails - User-facing messages display for each status
-
VERIFICATION_FAILEDstatus denies access
Troubleshooting
Callback receives unexpected status values
The connector delivers exactly one of the five documented statuses. If your handler receives an unrecognized value, log it and treat it as an error. Check that you are running a compatible connector version.
Wallet returns unsupported response mode or algorithm errors
The connector operates under the HAIP profile, which mandates the direct_post.jwt response mode and ES256 for signing. If the wallet does not support these requirements, it returns an error at the protocol level. Check that the wallet supports the HAIP profile. These errors typically appear as REJECTED callbacks with OID4VP error codes in the errorDetails field.
Retry creates duplicate sessions
Ensure you invalidate the old session before creating a new presentation request. Use the state value as the session key and overwrite it when retrying.
Next steps
- KYC verification—implement identity verification with full error handling
- Going to production—production readiness checklist
Further reading
- Callback events—complete event status and payload reference
- Error codes—wallet-facing HTTP error responses
- HAIP profile—required algorithms, response modes, and credential formats
- Connector architecture—how the connector processes requests and delivers results