Handling verification results
Learn how to retrieve and interpret mDoc verification results in your web application
When a holder responds to a web verification request, that response includes structured data containing the verification outcome. This guide explains the different types of responses your web application can receive as a verifier and how to access them.
What gets verified
Before working through the response structure, it is worth being clear about what is actually being verified. The credential itself, the full mDoc as issued and stored in the holder's wallet, never leaves the wallet and is never shared with a verifier. What the holder shares is a credential presentation: a subset of the credential's data, accompanied by the issuer's signature over the released items and a device authentication that proves the holder's device authorized this specific presentation.
This model is grounded in ISO/IEC 18013-7, which defines remote mDL and mDoc verification and specifies the wire protocols used to exchange a presentation between holder and verifier: the OpenID for Verifiable Presentations (OID4VP) protocol (Annex B), and the W3C Digital Credentials (DC) API integration (Annex C and Annex D). Regardless of the wire protocol, what the holder shares is a presentation derived from the credential rather than the credential itself, and the presentation carries the same cryptographic proof and integrity guarantees as the credential it is drawn from.
When you verify a response, you are verifying the credential presentation. The cryptographic
trust checks (issuer signature, issuer trust, validity, revocation, device key binding) apply
to that presented slice, not to the credential as a whole on the holder's device. The MATTR VII
verificationResult.verified flag reflects whether this presented credential passed those
checks.
The rest of this guide uses "credential" as shorthand for the presented credential when the context makes clear we are talking about what was returned in the response.
Result delivery methods
The way your web application receives verification results depends on your MATTR VII verifier application configuration:
Front channel delivery (resultAvailableInFrontChannel: true): Results are returned directly to your web application through the Verifier Web SDK as a RequestCredentialsResponse object.
Back channel delivery (resultAvailableInFrontChannel: false): Your backend retrieves results by calling the retrieve presentation session result endpoint, receiving them as a 200 response.
For more details on how these delivery methods work within the complete verification flow, see the workflow guide.
Production implementations should use back channel delivery with a backend. This provides better security through challenge validation, protecting against session replay attacks.
Regardless of the delivery method, the same information is available in both response structures. The key difference is where and how you retrieve it.
Understanding response types
When a holder responds to a verification request, you receive one of two high-level result types, depending on whether the presentation workflow was completed successfully or not.
Presentation failed
In this case, the holder was unable to complete the presentation workflow. This can happen for various reasons, such as the user canceling the request, the wallet being unavailable, or an error occurring during presentation processing.
You will receive a PresentationFailureResult object containing:
sessionId: The unique identifier for this verification sessionchallenge: The challenge used to verify response authenticitycredentialQuery: The original request defining what credentials and claims were requestederror: Object withtypeandmessagefieldsstate(optional): The caller-supplied correlation reference, present only when astatevalue was provided when starting the session. See Correlating verification sessions.
The error.type indicates why the presentation failed:
WalletUnavailable: The wallet appears to be unavailable and unable to respond to the requestSessionAborted: User explicitly aborted the session before it timed outResponseError: Received an error presentation responseVerificationError: The submitted presentation response is invalidUnknown: Encountered an unknown error while processing the presentation response
Example: User cancels the mDL presentation request:
{
"sessionId": "550e8400-e29b-41d4-a716-446655440000",
"challenge": "c5a27e4c-85b6-4b3c-9f1a-2d8e5f3a4b7c",
"credentialQuery": [
{
"profile": "mobile",
"docType": "org.iso.18013.5.1.mDL",
"nameSpaces": {
"org.iso.18013.5.1": {
"family_name": { "intentToRetain": false },
"given_name": { "intentToRetain": false },
"birth_date": { "intentToRetain": false }
}
}
}
],
"error": {
"type": "SessionAborted",
"message": "User aborted the session"
}
}Presentation succeeded
In this case, the holder successfully completed the presentation workflow. You will receive a PresentationSuccessResult object containing:
sessionId: The unique identifier for this verification sessionchallenge: The challenge used to verify response authenticitycredentialQuery: The original request defining what credentials and claims were requestedcredentials(optional): Array of verified credentials with their claims and verification statuscredentialErrors(optional): Array of errors for credentials that couldn't be returnedstate(optional): The caller-supplied correlation reference, present only when astatevalue was provided when starting the session. See Correlating verification sessions.
A successful presentation can include several scenarios, often in combination. Each one is described along two independent axes: whether the requested credential itself verified, and whether the requested claims within it were provided.
- Requested credential not presented: The holder doesn't have the requested credential, so there is nothing to verify. The credential appears under
credentialErrorswith an error code ofnotReturned, and noverificationResultis produced. - Presented credential not verified: A credential was provided but the credential-level trust checks failed. The
credentialsarray contains the credential withverified: falseand areason.typeexplaining why. Even if claims were returned, they should not be relied on while the credential itself did not verify. - Presented credential verified, all claims provided: The credential-level checks pass (
verified: truewith noreason) and every requested claim is returned (noclaimErrors). Both layers are clean. - Presented credential verified, some claims missing: The credential-level checks pass (
verified: true), but one or more requested claims were not returned and appear underclaimErrors. The credential is trustworthy; the data payload is incomplete. How to handle the missing data is up to the relying party, depending on its use case. Options include failing the flow, falling back to collecting the required data through another channel, or proceeding if the missing claim is not essential.
Detailed result structure
When a presentation succeeds, the result includes detailed information at multiple levels:
Credential-level information
Each credential in the credentials array contains:
docType: The credential type (e.g.,org.iso.18013.5.1.mDLfor a mobile driver's license).claims: Verified claims organized by namespace.claimErrors: Errors for individual claims that couldn't be verified or were not returned.validityInfo: Credential validity period timestamps.verificationResult: Verification status containing:verified: Boolean indicating if verification succeeded (this is a high-level result and individual claim errors may still exist).reason(optional): Object withtypeandmessageexplaining verification failures (if such failures exist). The followingreason.typevalues are possible:DeviceKeyInvalid: Device key is not valid. This can occur if the credential was not properly bound to the device or if the device's secure element is compromised.InvalidSignerCertificate: Invalid signer certificate. This can occur if the credential's signing certificate is not valid or has been tampered with.IssuerNotTrusted: Credential was issued by a certificate that is not trusted by the verifier. This can occur if the issuer's certificate is not included in the verifier's trusted issuer list.MobileCredentialExpired: Credential expired. This can occur if the current date is after the credential'svalidUntildate.MobileCredentialInvalid: Credential is not valid. This can occur for various reasons such as failing signature verification, containing invalid data, or not conforming to expected formats.MobileCredentialNotYetValid: Credential not yet valid. This can occur if the current date is before the credential'svalidFromdate.StatusRevoked: Credential has been revoked. This can occur if the issuer has revoked the credential after it was issued, which is typically checked through a revocation mechanism provided by the issuer.StatusUnknown: Credential status could not be determined. This can occur if the verifier is unable to check the revocation status of the credential due to network issues or if the issuer does not provide a revocation mechanism.TrustedIssuerCertificateExpired: Trusted issuer certificate expired. This can occur if the certificate of the trusted issuer has passed its expiration date, which may affect the trustworthiness of credentials issued by that issuer.TrustedIssuerCertificateNotYetValid: Trusted issuer certificate not yet valid. This can occur if the certificate of the trusted issuer is not yet valid (i.e., the current date is before the certificate'svalidFromdate), which may affect the trustworthiness of credentials issued by that issuer.UnsupportedCurve: Credential object contains unsupported curve. This can occur if the credential uses cryptographic curves that are not supported by the verifier's cryptographic library, which may prevent successful verification of the credential's signatures.
issuerInfo: Issuer details includingcommonNameandtrustedIssuerId.branding(optional): Visual information for displaying the credential (name, description, colors, logos). You can use this information to create a rich user interface when showing the credential details in your application.
Claim-level information
Claims are organized by namespace within the claims object. For example, for an mDL, claims will appear under the org.iso.18013.5.1 namespace:
"claims": {
"org.iso.18013.5.1": {
"family_name": { "value": "Smith" },
"given_name": { "value": "Jane" },
"birth_date": { "value": "1990-05-15" },
"address": { "value": "123 Main Street, Springfield" }
}
}If specific claims couldn't be verified or weren't provided, they appear in the claimErrors object with the same namespace structure and an error code of notReturned:
"claimErrors": {
"org.iso.18013.5.1": {
"portrait": "notReturned"
}
}Credential errors
When a requested credential wasn't provided by the holder, it appears in the credentialErrors array with a docType and an errorCode of notReturned:
"credentialErrors": [
{
"docType": "org.iso.18013.5.1.mDL",
"errorCode": "notReturned"
}
]Understanding the verified flag
With the structure in mind, it is worth being explicit about what
verificationResult.verified does and does not tell you, because it is the single most important
field for deciding whether to trust a presentation.
verified is the pass/fail flag for the presented credential. It applies to the slice of
credential data the holder released in this session, together with its associated issuer
signature and device authentication, not to the credential as a whole as stored on the holder's
device. When verified: true, what the holder presented has passed MATTR VII's cryptographic
and trust checks: the issuer signature on the released items is intact, the credential was
issued by a trusted issuer, it is not expired or revoked, and the device key binding holds. In
plain terms, the presented mDoc is genuine, has not been tampered with, and is currently valid.
What verified: true does not mean is that every claim you requested came back. verified is a
high-level result on the credential as a whole, and individual claim errors may still exist. A
credential can return verified: true while a specific requested claim (for example, portrait)
is missing and surfaces under claimErrors with an error code of notReturned. The credential
is trustworthy; the data payload may still be incomplete.
The mirror image is verified: false. When the credential layer fails, MATTR VII surfaces a
reason.type explaining why (for example, expired, revoked, untrusted issuer, invalid signer
certificate, or broken device key binding). The reason field is typed as optional, so
defensive code should handle the case where it is absent. See the
credential-level information section above for the full list of
reason.type values.
Two layers of checks
A relying party's business logic needs to check two distinct things:
- Is the credential real and valid? Look at
verificationResult.verifiedon each credential in thecredentialsarray. This is the objective trust check, and it should not be overridden by business logic. - Did we get all the data we asked for? Look at
claimErrorson each credential, and atcredentialErrorson the top-level result. Whether a missing claim or credential is acceptable is a business-logic decision specific to your use case.
In other words, verified is the objective measure of what can or cannot be accepted at all.
Everything else, including claimErrors, credentialErrors, and the specific claim values
returned, feeds your business logic about whether to accept the presentation for your particular
use case.
Treating verified: true as a green light to proceed without inspecting claimErrors is a
common integration mistake. The credential may be cryptographically valid while still missing a
field your use case requires. For example, an age-restricted purchase flow needs birth_date,
but if only family_name came back the relying party cannot make an age decision even though
the credential itself returned verified: true. Conversely, bypassing a verified: false
result with business logic (for example, accepting an expired or revoked credential because the
claims look correct) defeats the trust model and should not be done.
verified is MATTR VII's API shape. The underlying trust evaluation for mDL and mDoc
credentials follows ISO/IEC 18013-5, but the verified boolean itself is not a standards-defined
field.
Complete examples
Requested credential not presented
Neither layer of the check produces a result here. The holder did not provide a matching mDL, so
the credential layer has nothing to verify (no verificationResult is returned) and the claims
layer is not applicable. The missing credential is reported under credentialErrors, and the
relying party's business logic must decide how to handle it.
{
"sessionId": "550e8400-e29b-41d4-a716-446655440000",
"challenge": "c5a27e4c-85b6-4b3c-9f1a-2d8e5f3a4b7c",
"credentialQuery": [
{
"profile": "mobile",
"docType": "org.iso.18013.5.1.mDL",
"nameSpaces": {
"org.iso.18013.5.1": {
"family_name": { "intentToRetain": false },
"given_name": { "intentToRetain": false }
}
}
}
],
"credentialErrors": [
{
"docType": "org.iso.18013.5.1.mDL",
"errorCode": "notReturned"
}
]
}Presented credential not verified
The credential layer fails. The holder provided an mDL but the credential-level trust checks
failed (in this example, because the credential has expired): verified: false, with
reason.type identifying why. Even if claims were returned, they should not be relied on while
the credential itself did not verify. The credential should not be accepted regardless of
business logic.
{
"sessionId": "550e8400-e29b-41d4-a716-446655440000",
"challenge": "c5a27e4c-85b6-4b3c-9f1a-2d8e5f3a4b7c",
"credentialQuery": [
{
"profile": "mobile",
"docType": "org.iso.18013.5.1.mDL",
"nameSpaces": {
"org.iso.18013.5.1": {
"family_name": { "intentToRetain": false },
"given_name": { "intentToRetain": false },
"birth_date": { "intentToRetain": false }
}
}
}
],
"credentials": [
{
"docType": "org.iso.18013.5.1.mDL",
"verificationResult": {
"verified": false,
"reason": {
"type": "MobileCredentialExpired",
"message": "Credential has expired"
}
},
"validityInfo": {
"signed": "2024-01-15T10:00:00Z",
"validFrom": "2024-01-15T10:00:00Z",
"validUntil": "2025-01-15T10:00:00Z",
"expectedUpdate": "2026-01-15T10:00:00Z"
}
}
]
}Presented credential verified, all claims provided
Both layers pass. The credential layer returns verified: true with no reason, confirming the
mDL is genuine, current, and from a trusted issuer. The claims layer returns every requested
claim with no claimErrors, so the relying party has the complete data payload it asked for.
{
"sessionId": "550e8400-e29b-41d4-a716-446655440000",
"challenge": "c5a27e4c-85b6-4b3c-9f1a-2d8e5f3a4b7c",
"credentialQuery": [
{
"profile": "mobile",
"docType": "org.iso.18013.5.1.mDL",
"nameSpaces": {
"org.iso.18013.5.1": {
"family_name": { "intentToRetain": false },
"given_name": { "intentToRetain": false },
"birth_date": { "intentToRetain": false },
"address": { "intentToRetain": false }
}
}
}
],
"credentials": [
{
"docType": "org.iso.18013.5.1.mDL",
"claims": {
"org.iso.18013.5.1": {
"family_name": { "value": "Smith" },
"given_name": { "value": "Jane" },
"birth_date": { "value": "1990-05-15" },
"address": { "value": "123 Main Street, Springfield" }
}
},
"validityInfo": {
"signed": "2024-01-15T10:00:00Z",
"validFrom": "2024-01-15T10:00:00Z",
"validUntil": "2027-01-15T10:00:00Z",
"expectedUpdate": "2028-01-15T10:00:00Z"
},
"verificationResult": {
"verified": true
},
"issuerInfo": {
"commonName": "State Department of Motor Vehicles",
"trustedIssuerId": "d4a6e9f2-3b1c-4d8e-a5f7-9c2b0e8d1a3f"
},
"branding": {
"name": "Driver License",
"description": "State-issued driver license",
"backgroundColor": "#1E3A8A",
"issuerLogo": {
"format": "svg",
"data": "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg=="
}
}
}
]
}Presented credential verified, some claims missing
The credential layer passes but the claims layer is incomplete. The mDL itself returns
verified: true and is trustworthy, while one requested claim (portrait) was not released and
appears under claimErrors as notReturned. What is missing here is data, not trust, and the
relying party's business logic must decide whether the missing claim is acceptable for its use
case.
{
"sessionId": "550e8400-e29b-41d4-a716-446655440000",
"challenge": "c5a27e4c-85b6-4b3c-9f1a-2d8e5f3a4b7c",
"credentialQuery": [
{
"profile": "mobile",
"docType": "org.iso.18013.5.1.mDL",
"nameSpaces": {
"org.iso.18013.5.1": {
"family_name": { "intentToRetain": false },
"given_name": { "intentToRetain": false },
"birth_date": { "intentToRetain": false },
"portrait": { "intentToRetain": false }
}
}
}
],
"credentials": [
{
"docType": "org.iso.18013.5.1.mDL",
"claims": {
"org.iso.18013.5.1": {
"family_name": { "value": "Smith" },
"given_name": { "value": "Jane" },
"birth_date": { "value": "1990-05-15" }
}
},
"claimErrors": {
"org.iso.18013.5.1": {
"portrait": "notReturned"
}
},
"validityInfo": {
"signed": "2024-01-15T10:00:00Z",
"validFrom": "2024-01-15T10:00:00Z",
"validUntil": "2027-01-15T10:00:00Z",
"expectedUpdate": "2028-01-15T10:00:00Z"
},
"verificationResult": {
"verified": true
},
"issuerInfo": {
"commonName": "State Department of Motor Vehicles",
"trustedIssuerId": "d4a6e9f2-3b1c-4d8e-a5f7-9c2b0e8d1a3f"
}
}
]
}Front channel vs back channel details
Front channel delivery
When using resultAvailableInFrontChannel: true, the Verifier Web SDK's requestCredentials function returns a RequestCredentialsResponse:
type RequestCredentialsResponse = {
sessionId: string;
state?: string;
result?: PresentationSessionResult;
sessionCompletedInRedirect?: boolean;
};Where result contains either a PresentationSuccessResult or PresentationFailureResult as detailed above.
For same-device flows, the result is delivered via handleRedirectCallback() after the wallet redirects the user back to your application:
type HandleRedirectCallbackResponse = {
sessionId: string;
state?: string;
result?: PresentationSessionResult;
};The optional state field is the caller-supplied correlation reference returned when a state value was provided when starting the session. See Correlating verification sessions for details.
Back channel delivery
When your backend retrieves results from the MATTR VII tenant:
GET /v2/presentations/sessions/{sessionId}/resultThe response is either a PresentationSuccessResult or PresentationFailureResult with the same structure as front channel results.
Backend implementation steps:
- Retrieve verification results using the session ID
- Validate the returned
challengematches the challenge generated before starting the session - Process the results according to your business logic
- Return relevant information to your web application
Challenge validation in the backend protects against session replay attacks. Generate a unique challenge when creating the session, and verify it matches when retrieving results.
Next steps
- Review the Verifier Web SDK API reference for complete type definitions
- See the presentation session result API reference for backend integration
- Explore the workflow guide for the complete end-to-end verification process
- Follow the tutorial for building a remote web verifier with the MATTR VII tenant and Verifier Web SDK for practical implementation examples
How would you rate this page?
Last updated on