DC API (Browser Verification)
Verify credentials directly in the browser using the Digital Credentials API
The Digital Credentials (DC) API lets you verify credentials directly in the browser without QR codes or deep links. The browser handles the wallet interaction natively, similar to how WebAuthn works for authentication.
DC API supports four request protocols:
| Protocol | Value | Use case |
|---|---|---|
| OpenID4VP (unsigned) | openid4vp | DCQL queries for mDoc and SD-JWT credentials |
| OpenID4VP (HAIP, signed) | haip | Signed OpenID4VP request per HAIP |
| ISO mDoc | iso-mdoc | Direct mdoc presentation (ISO 18013-7 Annex C, HPKE-encrypted) |
| ISO mDoc (signed) | iso-mdoc-signed | Same as iso-mdoc with reader authentication |
These values are what you send in the request body. The request.protocol string returned by the API (and passed to the browser) differs: openid4vp returns openid4vp-v1-unsigned, haip returns openid4vp-v1-signed, and both iso-mdoc variants return org-iso-mdoc.
Session flow
- Your backend creates a DC API session with the desired protocol and query.
- Your frontend calls
navigator.credentials.get()with the session data. - The browser prompts the user to select a credential from their wallet.
- Your frontend submits the wallet response to your backend for verification.
1. Create a session
POST /verifier/dc-api/request
Authentication: API key (publishable or secret)
Request body (OpenID4VP)
{
"protocol": "openid4vp",
"origin": "https://your-site.example.com",
"dcql_query": {
"credentials": [{
"id": "pid",
"format": "mso_mdoc",
"meta": { "doctype_value": "eu.europa.ec.eudi.pid.1" },
"claims": [
{ "path": ["eu.europa.ec.eudi.pid.1", "given_name"] },
{ "path": ["eu.europa.ec.eudi.pid.1", "family_name"] }
]
}]
}
}Request body (ISO mDoc)
{
"protocol": "iso-mdoc",
"origin": "https://your-site.example.com",
"namespace": "eu.europa.ec.eudi.pid.1",
"attributes": ["given_name", "family_name", "birth_date"]
}| Field | Type | Required | Description |
|---|---|---|---|
protocol | "openid4vp" | "haip" | "iso-mdoc" | "iso-mdoc-signed" | Yes | Which DC API protocol to use |
origin | string (URL) | Yes | Origin of the page that will call navigator.credentials.get() |
dcql_query | object | No | DCQL query |
namespace | string | No | mDoc namespace for simple queries |
attributes | string[] | No | Attributes to request (with namespace) |
claims | array | No | Per-attribute namespace claims: { namespace, id, intent_to_retain } |
skip_trusted_authorities | boolean | No | Skip trusted authority enrichment |
Response 200
{
"sessionId": "550e8400-e29b-41d4-a716-446655440000",
"request": {
"protocol": "openid4vp-v1-unsigned",
"data": { ... }
}
}The nested request object (request.protocol and request.data) is what you pass to navigator.credentials.get().
2. Call the browser API
Use the session response to invoke the Digital Credentials API:
async function requestCredential(session: {
sessionId: string
request: { protocol: string; data: object }
}) {
// Check browser support
if (!('digital' in navigator.credentials)) {
throw new Error('Digital Credentials API not supported in this browser')
}
const credential = await navigator.credentials.get({
digital: {
requests: [{
protocol: session.request.protocol,
data: session.request.data
}]
}
})
return credential
}The browser displays a native credential picker. The user selects which credential (and which claims) to share.
3. Submit the response
POST /verifier/dc-api/response
Send the wallet response to your backend for cryptographic verification.
Authentication: API key (publishable or secret)
Request body
{
"session_id": "550e8400-e29b-41d4-a716-446655440000",
"response": { ... }
}| Field | Type | Required | Description |
|---|---|---|---|
session_id | string (UUID) | Yes | The sessionId from the create-session response (note the snake_case) |
response | object | No | The credential response from navigator.credentials.get() (may be omitted on cancel) |
Response 200
{
"status": "valid",
"verification": {
"ISSUER_AUTH": [
{ "status": "✅", "check": "Issuer certificate must be valid" }
],
"DEVICE_AUTH": [
{ "status": "✅", "check": "Device signature must be valid" }
]
},
"presentation": {
"eu.europa.ec.eudi.pid.1": {
"given_name": "Marie",
"family_name": "Dupont"
}
}
}4. Poll for status (alternative)
If you prefer a polling approach instead of submitting the response directly:
GET /verifier/dc-api/status/:session_id
Authentication: API key (publishable or secret)
Response 200
{
"status": "valid",
"presentation": { ... }
}| Status | Meaning |
|---|---|
pending | Session exists, waiting for wallet response |
valid | Credential verified successfully |
failed | Verification failed |
expired | Session is gone (no response within the 300 second session TTL, or the result's 60 second retention elapsed) |
Full integration example
<button id="verify-btn">Verify Identity</button>
<script type="module">
const API_BASE = 'https://api.verifiables.com'
const API_KEY = 'oat_your_publishable_key'
document.getElementById('verify-btn').addEventListener('click', async () => {
// 1. Create session
const sessionRes = await fetch(`${API_BASE}/verifier/dc-api/request`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
protocol: 'openid4vp',
origin: window.location.origin,
dcql_query: {
credentials: [{
id: 'pid',
format: 'mso_mdoc',
meta: { doctype_value: 'eu.europa.ec.eudi.pid.1' },
claims: [
{ path: ['eu.europa.ec.eudi.pid.1', 'given_name'] },
{ path: ['eu.europa.ec.eudi.pid.1', 'family_name'] }
]
}]
}
})
})
const session = await sessionRes.json()
// 2. Invoke browser API
const credential = await navigator.credentials.get({
digital: {
requests: [{
protocol: session.request.protocol,
data: session.request.data
}]
}
})
// 3. Submit for verification
const verifyRes = await fetch(`${API_BASE}/verifier/dc-api/response`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
session_id: session.sessionId,
response: credential
})
})
const result = await verifyRes.json()
console.log('Verified:', result.presentation)
})
</script>Browser support
The Digital Credentials API is available in Chrome 128+ on Android and desktop (behind a flag in earlier versions). Safari and Firefox support is in development. For browsers without DC API support, fall back to QR code verification.