Verifying Verifiable Credentials via Digital Credentials API
This guide shows you how to verify credentials using the browser-native Digital Credentials API (DC API). The DC API enables secure, native credential sharing directly through the user's browser or operating system, providing a seamless user experience without redirects or QR codes.
We recommend checking out our Digital Credential Button project, which provides a reference for how to invoke the Browser-based DC API. It contains up to date tutorials for setting up an end to end demo utilizing the DC API. This guide will just focus on how to configure the Verifier API for this use case.
Prerequisites
Before you begin, ensure you have:
- Verifier2 Service setup — Follow the Setup guide
- DC API-compatible browser — Chrome 121+, Safari 18+, or Edge 121+ (desktop or mobile)
- HTTPS enabled — The DC API only works over secure connections (required for production; localhost is allowed for development)
- Test wallet — Use a DC API-compatible wallet for testing
- X.509 certificate and key (for signed requests) — Required for HAIP profile and DC API using ISO 18013-7 flows
- Authentication token — Bearer token from your Enterprise Stack authentication flow
Understanding DC API Protocols
The Verifier2 API supports two DC API protocols:
- DC API using ISO 18013-7 using
flow_type = "dc_api_18013_7" - DC API using OpenID4VP using
flow_type = "dc_api_openid4vp"
For each of these, we support both same device and cross device flows. You can find many examples in the Digital Credential Button project as well as the swagger documentation for the Verifier API.
Step 1: Preparing the Verification Session
This example requests an mDL credential using DC API with ISO 18013-7 and signed requests.
Endpoint: POST /v2/{target}/verifier-service-api/verification-session/create | API Reference
Example Request
curl -X 'POST' \
'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{target}/verifier-service-api/verification-session/create' \
-H 'accept: */*' \
-H 'Authorization: Bearer {yourToken}' \
-H 'Content-Type: application/json' \
-d '{
"flow_type": "dc_api_18013_7",
"core_flow": {
"requestedElements": {
"org.iso.18013.5.1.mDL": {
"org.iso.18013.5.1": [
"family_name",
"given_name",
"age_over_21"
]
}
},
"signed_request": true,
"encrypted_response": true,
"clientId": "x509_hash:abc-xyz-base64url-sha256-hash-of-der-x509-leaf",
"key": {
"type": "jwk",
"jwk": {
"kty": "EC",
"d": "AEb4k1BeTR9xt2NxYZggdzkFLLUkhyyWvyUOq3qSiwA",
"crv": "P-256",
"kid": "_nd-T2YRYLSmuKkJZlRI641zrCIJLTpiHeqMwXuvdug",
"x": "G_TgBc0BkmMipiQ_6gkamIn3mmp7hcTrZuyrLTmknP0",
"y": "VkRMZdXYXSMff5AJLrnHiN0x5MV6u_8vrAcytGUe4z4"
}
},
"x5c": [
"BASE64_DER_ENCODED_LEAF_CERT"
]
},
"expectedOrigins": [
"https://{orgID}.enterprise-sandbox.waltid.dev"
]
}'
x5c is required whenever signed_request: true is used for dc_api_18013_7. Without it, session creation fails with x5c is required for signed Annex C requests. It must be an ordered chain in DER-encoded, Base64-encoded format where the first certificate corresponds to key, and its SHA-256 hash is what clientId's x509_hash: value must match.
Path Parameters
- orgID: String - Your organization ID (e.g.,
testfortest.enterprise-sandbox.waltid.dev) - target: String - The verifier service target path (e.g.,
waltid.verifier1)
Header Parameters
- Authorization: String - Bearer token obtained from your Enterprise Stack authentication flow. Format:
Bearer {token}.
Body Parameters
- flow_type: String (required) - Specifies how the credential request will be delivered to the holder's wallet. Options:
"cross_device"— User scans a QR code on a different device (e.g. desktop browser → phone wallet)"same_device"— User clicks a deep link on the same device (e.g. phone browser → wallet app on same phone)"dc_api_openid4vp"— Browser/OS-native Digital Credentials API (Chrome, Safari, iOS/Android native) using OpenID4VP."dc_api_18013_7"— Browser/OS-native Digital Credentials API (Chrome, Safari, iOS/Android native) using ISO 18013-7.
- core_flow: Object (required) - Defines what credentials to request and which policies to apply. Contains:
- dcql_query: Object (required for
dc_api_openid4vp) - Specifies the credential type, format, and claims to request using the DCQL query language. You can also use the DCQL builder here.- credentials: Array - List of credential queries. Each credential query contains:
- id: String - Unique identifier for this credential in the request (used in policy results)
- format: String - Credential format. For mDL, use
"mso_mdoc"(mobile security object document). - meta.doctype_value: String - The ISO mDL document type. For mobile driver's licenses, use
"org.iso.18013.5.1.mDL". - claims: Array (optional) - Specific claims to request from the credential. Each claim has a
patharray with namespace and claim name (e.g.["org.iso.18013.5.1", "family_name"]). If omitted, all claims that are non-selectively disclosable are provided by the wallet.
- credentials: Array - List of credential queries. Each credential query contains:
- requestedElements: Object (required for
dc_api_18013_7) - ISO 18013-7 requested elements, keyed by document type, then namespace, then element names. This is an alternative todcql_query; do not provide both. OpenID4VP flows (cross_device,same_device, anddc_api_openid4vp) must usedcql_queryand do not acceptrequestedElements. - policies: Object (optional) - Validation rules applied to the received credential. Defaults to signature verification only if omitted.
- vc_policies: Array - Policies applied to individual credentials. Each policy has a
policyfield specifying the policy name. Available policies:signature,expired,not-before,revoked-status-list,schema, and more. See Policies for the complete list. - vp_policies: Array (optional) - Policies applied to the entire presentation (not shown in this example)
- vc_policies: Array - Policies applied to individual credentials. Each policy has a
- expiration_duration: String (optional) - ISO-8601 duration specifying how long the verification session remains valid. Defaults to 10 minutes (
PT10M). Example:"PT1H"for 1 hour,"PT30M"for 30 minutes. - expiration_date: String (optional) - ISO-8601 datetime specifying the exact time when the session expires. Takes precedence over
expiration_durationif both are set. Example:"2026-05-21T15:00:00Z". Session expires if left unused (no presentation pushed). Once a presentation is received, the session is no longer eligible for expiry. - signed_request: Boolean (optional) - Whether to sign the authorization request. Defaults to
false. Required fordc_api_18013_7. - encrypted_response: Boolean (optional) - Whether the wallet should encrypt its response. Defaults to
false. - clientId: String (required when
signed_request: true) - The client ID presented to wallets. Fordc_api_18013_7, usex509_hash:<base64url-sha256-hash-of-der-x509-leaf>, matching the leaf certificate inx5c. - key: Object (required when
signed_request: trueorencrypted_response: trueand no service-wide default is configured) - Signing/encryption key for this session, in the format{"type":"jwk","jwk":{...}}. Overrides the service-wide default configured during verifier2 service creation. - x5c: Array of Strings (required when
signed_request: true) - X.509 certificate chain, ordered DER-encoded and Base64-encoded, first entry corresponding tokey. Without it, session creation fails withx5c is required for signed Annex C requests.
- dcql_query: Object (required for
- expectedOrigins: Array
(required for DC API flows) - Expected browser origins for DC API verification. For dc_api_18013_7, provide exactly one origin; that value is used for the ISO 18013-7 session transcript.Many parameters have defaults configured in the verifier service. Request-level parameters override these defaults for that specific session only.
Example Response
dc_api_18013_7 does not return bootstrapAuthorizationRequestUrl/fullAuthorizationRequestUrl. Unlike cross_device, same_device, and dc_api_openid4vp, this flow returns the browser-native DC API request payload directly in data — there's no authorization URL to build a QR code or deep link from.
{
"sessionId": "ff4f0c86-56ce-4e4a-a137-dd5569049ff3",
"creationTarget": "org.tenant.verifier.ff4f0c86-56ce-4e4a-a137-dd5569049ff3",
"data": {
"protocol": "org-iso-mdoc",
"data": {
"deviceRequest": "omd2ZXJzaW9uYzEuMGtkb2NSZXF1ZXN0c4GhbGl0ZW1zUmVxdWVzdNgY...",
"encryptionInfo": "gmVkY2FwaaJlbm9uY2VYJDgwNTRmMjU5LTcxMDEtNGZhNS05MzM2LTM2Mjk5OWYxN2VjYnJyZWNpcGllbnRQdWJsaWNLZXmk..."
}
}
}
- sessionId: String - The unique ID of this verification session
- creationTarget: String - The target path for this verification session in the Enterprise Stack
- data.protocol: String - The DC API protocol identifier to pass to the browser's Digital Credentials API. For
dc_api_18013_7, always"org-iso-mdoc". - data.data.deviceRequest: String - Base64url-encoded CBOR ISO 18013-7
DeviceRequest, to be passed as-is into the browser'snavigator.credentials.get()call. - data.data.encryptionInfo: String - Base64url-encoded CBOR encryption info for the wallet's response, to be passed as-is alongside
deviceRequest.
This same data object (identical protocol/deviceRequest/encryptionInfo) can also be re-fetched independently via Step 2 using the sessionId — useful if a different client needs it later, since you don't have to hold onto the create-session response.
Step 2: Generating the DC API Request
Take the sessionId from Step 1 and make the following request to (re-)fetch the DC API request payload. Note the endpoint's {target} is the verifier service path (not the session's creationTarget), and {sessionId} is appended directly — there's no verification-session/ segment in this URL, unlike the polling/info endpoint below.
Endpoint: GET /v2/{target}/verifier-service-api/{sessionId}/request
Example Request
curl -X 'GET' \
'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{target}/verifier-service-api/{sessionId}/request' \
-H 'accept: application/json' \
-H 'Authorization: Bearer {yourToken}'
Path Parameters
- orgID: String - Your organization ID (e.g.,
testfortest.enterprise-sandbox.waltid.dev) - target: String - The verifier service target path (e.g.,
waltid.tenant1.verifier1) - sessionId: String - The unique ID of the verification session returned from Step 1
Header Parameters
- Authorization: String - Bearer token obtained from your Enterprise Stack authentication flow. Format:
Bearer {token}.
Example Response
This returns the same protocol/data payload already included in Step 1's response, to pass into the browser's Digital Credentials API.
{
"protocol": "org-iso-mdoc",
"data": {
"deviceRequest": "omd2ZXJzaW9uYzEuMGtkb2NSZXF1ZXN0c4GhbGl0ZW1zUmVxdWVzdNgY...",
"encryptionInfo": "gmVkY2FwaaJlbm9uY2VYJDgwNTRmMjU5LTcxMDEtNGZhNS05MzM2LTM2Mjk5OWYxN2VjYnJyZWNpcGllbnRQdWJsaWNLZXmk..."
}
}
Step 3: Handling the Response
Once the wallet presents the credential via the DC API, the browser returns a response you pass to the verifier endpoint to verify the credential and run configured policies. As with Step 2, {target} is the verifier service path and {sessionId} is appended directly (no verification-session/ segment).
Endpoint: POST /v2/{target}/verifier-service-api/{sessionId}/response
Example Request
curl -X 'POST' \
'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{target}/verifier-service-api/{sessionId}/response' \
-H 'accept: application/json' \
-H 'Authorization: Bearer {yourToken}' \
-H 'Content-Type: application/json' \
-d '{
"protocol": "org-iso-mdoc",
"data": {
"response": "o2d2ZXJzaW9uYzEuMGlkb2N1bWVudH..."
}
}'
- protocol: String - Must match the
protocolvalue from Step 1/2 ("org-iso-mdoc"fordc_api_18013_7). - data.response: String - The base64url-encoded CBOR ISO 18013-7
DeviceResponsereturned by the browser's Digital Credentials API. A missingdata.responsefails withMissing 'response' attribute in Annex C JSON response from wallet.
Validating the Verification Session
You can query the verification session to check if the user has presented their credential and whether validation succeeded. Use the session's creationTarget (from Step 1's response) as {target} — it already includes the session ID, so there's no separate {sessionId} segment in this URL.
Endpoint: GET /v2/{target}/verifier-service-api/verification-session/info
Example Request
curl -X 'GET' \
'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{target}/verifier-service-api/verification-session/info' \
-H 'accept: application/json' \
-H 'Authorization: Bearer {yourToken}'
Path Parameters
- orgID: String - Your organization ID
- target: String - The verification session's
creationTarget(e.g.waltid.tenant1.verifier1.ff4f0c86-56ce-4e4a-a137-dd5569049ff3), as returned by Step 1 — this already encodes the session ID, not just the verifier service path.
Example Response
The response contains complete session information including setup, status, policy results, and presented credentials.
Instead of polling the session status, you can use callbacks or Server Sent Events (SSE) to be notified when the verification completes. See Callbacks & SSE for details.
Additional Resources
You can find more information on how to configure different DC API request flows and how to interact with the Browser-based DC API in our Digital Credential Button project.
Next Steps
- Apply custom policies — See Policies for validation rules beyond signature verification
- Set up notifications — Use Callbacks & SSE instead of polling for session status
- Verify other credential types — Try W3C VC or SD-JWT VC
