Isolated Flow
The full-flow endpoint (credentials/present) matches, signs, and submits in one call. The isolated flow lets you drive each step yourself when you need to:
- Preview which credentials (and claims) will be shared, and show a consent screen
- Let the user choose between multiple matching credentials and optional disclosures
- Reject a presentation request without submitting a VP token
- Present credentials that aren't stored in the wallet (supplied inline)
- Inspect intermediate results with the advanced resolve/match endpoints
For holder consent UIs, start with preview: one call resolves and validates the request, matches wallet credentials, and returns verifier metadata plus selectable credentials and claim options. After the user chooses, call build-vp-token, then send-response. To decline, call reject with the same requestUrl. Resolve and match remain available when you already have an authorization request or credentials outside the wallet.
All endpoints live under /v2/{target}/wallet-service-api/.
Consent UI flow
1. Preview Presentation → 2. Build VP Token → 3. Send Response
↘ Reject (with the same requestUrl)
Preview is stateless: the wallet does not retain a preview handle. Keep the original requestUrl and pass it to build-vp-token, send-response, and reject. The wallet re-resolves and revalidates that URL on each sensitive step. The preview authorizationRequest is for display / technical details only.
Any verifier trust configuration you've supplied to the wallet — X.509 trust anchors, verifier-attestation issuers, and pre-registered clients — is applied consistently across preview, build-vp-token, send-response, and reject, so the isolated flow validates the verifier the same way the full flow does.
Step 1 — Preview Presentation
Resolve and validate the OpenID4VP request, match the wallet's stored credentials, and return everything a consent UI needs.
Endpoint: POST /v2/{target}/wallet-service-api/credentials/present/preview
Example Request
curl -X POST \
'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials/present/preview' \
-H 'Authorization: Bearer {yourToken}' \
-H 'Content-Type: application/json' \
-d '{
"requestUrl": "openid4vp://authorize?client_id=did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5In0&request_uri=https://verifier.example.org/request/abc123"
}'
Body Parameters
requestUrlString — The OID4VP authorization request URL.keyReference(optional) String — Full resource path of the signing key used for wallet capability advertisement and request validation during preview. Defaults to the first key in the wallet's linked KMS (or the service's configured static key). The Ready response echoes back the effective librarykeyId— pass the same key selection to Step 2 — Build VP Token so preview and build agree on key capabilities.key(optional) Object — Inline serialized key ({ "type": "jwk", "jwk": { … } }). Takes precedence overkeyReference.
Example Response
{
"authorizationRequest": {
"nonce": "n-0S6_WzA2Mj",
"client_id": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5In0",
"response_uri": "https://verifier.example.org/response",
"dcql_query": {
"credentials": [
{
"id": "identity_credential",
"format": "dc+sd-jwt",
"claims": [
{ "path": ["given_name"] },
{ "path": ["family_name"] }
]
}
]
}
},
"valid": true,
"keyId": "waltid.tenant1.kms.wallet_key",
"clientId": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5In0",
"verifier": {
"display": {
"name": "Example Verifier",
"locale": "en-US",
"logoUri": "https://verifier.example.org/logo.png"
},
"clientUri": "https://verifier.example.org",
"policyUri": null,
"termsOfServiceUri": null
},
"responseUri": "https://verifier.example.org/response",
"state": "af0ifjsldkj",
"nonce": "n-0S6_WzA2Mj",
"responseEncryption": {
"required": false
},
"transactionData": [],
"credentialOptions": [
{
"queryId": "identity_credential",
"credentialId": "550e8400-e29b-41d4-a716-446655440000",
"multiple": false,
"format": "dc+sd-jwt",
"issuer": "https://issuer.example.org",
"subject": "did:jwk:…",
"label": null,
"credentialData": {
"given_name": "Ada",
"family_name": "Lovelace",
"vct": "https://issuer.example.org/identity_credential"
},
"disclosures": [
{
"path": "$.given_name",
"name": "given_name",
"value": "Ada",
"selectivelyDisclosable": true,
"required": true,
"selectable": false
},
{
"path": "$.family_name",
"name": "family_name",
"value": "Lovelace",
"selectivelyDisclosable": true,
"required": false,
"selectable": true
}
]
}
],
"credentialRequirements": [],
"error": null
}
authorizationRequest— Resolved authorization request for display / technical details only.valid— Whether the request passed wallet validation. Whenfalse, inspecterrorand optionally reject.keyId(Ready only) — Resolved signing key path the preview used for wallet capability advertisement and validation. Pass the same key selection back to Step 2 — Build VP Token viakeyReference(orkey) so preview and build stay consistent.clientId— The verifier's client identifier.verifier(optional) — Verifier display metadata (displaywithname/locale/logoUri, plusclientUri,policyUri,termsOfServiceUri). Supplied by the verifier — not evidence of trust on its own.responseUri/state/nonce— Request metadata projected for the UI.responseEncryption— Whether response encryption is required and related algorithms/keys when present.transactionData— Decodedtransaction_dataitems (type,credentialQueryIds,rawJson,details).credentialOptions— Wallet credentials that satisfy a DCQL credential query. Each entry includesqueryId,credentialId, format/issuer/subject/label,credentialData, anddisclosures(path,name,value,selectivelyDisclosable,required,selectable).credentialRequirements— When DCQL credential sets apply: each item hasoptions(query-id combinations); at least one combination must be satisfied.error(optional) — Whenvalidisfalse:codeand optionalmessage.
Response Codes
200— Preview completed (checkvalid).400— The request URL/object is malformed and cannot be parsed.
Step 2 — Build VP Token
After the user consents, build and sign a vp_token from the selected stored credentials and optional claim disclosures. The wallet re-resolves and revalidates requestUrl first, then uses that fresh authorization request for DCQL matching and signing. This step does not submit anything to the verifier.
Endpoint: POST /v2/{target}/wallet-service-api/credentials/present/build-vp-token
Example Request (after preview)
curl -X POST \
'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials/present/build-vp-token' \
-H 'Authorization: Bearer {yourToken}' \
-H 'Content-Type: application/json' \
-d '{
"requestUrl": "openid4vp://authorize?client_id=did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5In0&request_uri=https://verifier.example.org/request/abc123",
"selectedCredentialOptions": [
{
"queryId": "identity_credential",
"credentialId": "550e8400-e29b-41d4-a716-446655440000"
}
],
"selectedDisclosureOptions": [
{
"queryId": "identity_credential",
"credentialId": "550e8400-e29b-41d4-a716-446655440000",
"path": "$.family_name"
}
]
}'
Example Request (legacy IDs after match)
curl -X POST \
'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials/present/build-vp-token' \
-H 'Authorization: Bearer {yourToken}' \
-H 'Content-Type: application/json' \
-d '{
"requestUrl": "openid4vp://authorize?client_id=did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5In0&request_uri=https://verifier.example.org/request/abc123",
"selectedCredentialIds": {
"identity_credential": ["550e8400-e29b-41d4-a716-446655440000"]
}
}'
Body Parameters
requestUrlString — The same OID4VP authorization request URL used for Step 1 (or Resolve VP Request). Re-resolved and revalidated on every call.selectedCredentialOptions(optional) Array — Preferred for consent UIs. List of{ queryId, credentialId }fromcredentialOptions. When non-empty, takes precedence overselectedCredentialIds.selectedDisclosureOptions(optional) Array — Selectively disclosable claim paths to include:{ queryId, credentialId, path }. Omit to keep the default disclosure set for the selection. When provided as an empty list, optional selectively disclosable claims are not added beyond what the protocol still requires.selectedCredentialIds(optional) Object — Legacy map of DCQL query ID to wallet credential IDs (from match). Used whenselectedCredentialOptionsis empty.key(optional) Object — Inline key used to sign the VP token. Takes precedence overkeyReference.keyReference(optional) String — Full resource path of the key to sign the VP token. Defaults to the first key in the wallet's linked KMS or the service's configured static key. When you started the flow through Step 1 — Preview with a specifickeyReference, pass the same value here so preview and build use the same signing key. Referenced keys stay in the KMS, so PKCS#11 / HSM backends work in this flow.did(optional) String — DID for holder binding. Defaults to the first DID in the linked DID Store or the configured static DID.
Example Response
{
"vpToken": "{\"identity_credential\":\"eyJhbGciOiJFUzI1NiIs...\"}",
"idToken": null
}
vpToken— Serializedvp_tokenJSON string.idToken(optional) — Self-issued ID token for SIOPv2 flows, when one is required.
Response Codes
200— VP token built.400— Selected credentials do not satisfy the authorization request.
Step 3 — Send Response
Send the built VP token to the verifier. The wallet re-resolves and revalidates requestUrl first, then transmits according to the response mode / URI / encryption from that freshly resolved request.
Endpoint: POST /v2/{target}/wallet-service-api/credentials/present/send-response
Example Request
curl -X POST \
'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials/present/send-response' \
-H 'Authorization: Bearer {yourToken}' \
-H 'Content-Type: application/json' \
-d '{
"requestUrl": "openid4vp://authorize?client_id=did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5In0&request_uri=https://verifier.example.org/request/abc123",
"vpToken": "{\"identity_credential\":\"eyJhbGciOiJFUzI1NiIs...\"}",
"idToken": null
}'
Body Parameters
requestUrlString — The same OID4VP authorization request URL used for Step 1. Re-resolved and revalidated on every call.vpTokenString — The VP token returned by Step 2.idToken(optional) String — The ID token returned by Step 2, when present.
Example Response
Returns the same WalletPresentResult shape as the full-flow response.
Response Codes
200— Authorization response transmitted.400— The response could not be sent to the verifier.
Step 4 — Reject Presentation
Decline the presentation request. The wallet re-resolves the original requestUrl and returns an OpenID4VP error response to the verifier when a response channel is available. No prior preview is required.
Endpoint: POST /v2/{target}/wallet-service-api/credentials/present/reject
Example Request
curl -X POST \
'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials/present/reject' \
-H 'Authorization: Bearer {yourToken}' \
-H 'Content-Type: application/json' \
-d '{
"requestUrl": "openid4vp://authorize?client_id=did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5In0&request_uri=https://verifier.example.org/request/abc123",
"errorCode": "access_denied",
"errorDescription": "User declined"
}'
Body Parameters
requestUrlString — The same OID4VP authorization request URL used for preview.errorCode(optional) String — OpenID4VP error code. Defaults toaccess_denied.errorDescription(optional) String — Human-readable description sent to the verifier.
Example Response
Returns the same WalletPresentResult shape as Step 3 — Send Response.
Response Codes
200— Rejection transmitted (or redirect URL produced).400— The request could not be resolved or no response channel is available.
Advanced: Resolve VP Request
Parse the verifier's authorization request without matching credentials. Prefer Step 1 — Preview for consent UIs.
Endpoint: POST /v2/{target}/wallet-service-api/credentials/present/resolve-request
Example Request
curl -X POST \
'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials/present/resolve-request' \
-H 'Authorization: Bearer {yourToken}' \
-H 'Content-Type: application/json' \
-d '{
"requestUrl": "openid4vp://authorize?client_id=did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5In0&request_uri=https://verifier.example.org/request/abc123"
}'
Body Parameters
requestUrlString — The OID4VP authorization request URL.
Example Response
{
"authorizationRequest": {
"nonce": "n-0S6_WzA2Mj",
"client_id": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5In0",
"response_uri": "https://verifier.example.org/response",
"dcql_query": {
"credentials": [
{
"id": "identity_credential",
"format": "dc+sd-jwt"
}
]
}
},
"nonce": "n-0S6_WzA2Mj",
"clientId": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5In0",
"responseUri": "https://verifier.example.org/response",
"hasRequestUri": true,
"dcqlQuery": {
"credentials": [
{
"id": "identity_credential",
"format": "dc+sd-jwt"
}
]
}
}
authorizationRequest— Resolved authorization request for inspection / display.nonce— The verifier's nonce for replay protection.clientId— The verifier's client identifier (typically a DID).responseUri— The URL the presentation is submitted to.hasRequestUri— Whether the request was fetched from arequest_uri.dcqlQuery— The verifier's DCQL query. Pass this to Match Credentials from Store or Match Inline Credentials.
Response Codes
200— Request resolved.400— The request URL/object is malformed and cannot be parsed.500— Therequest_uricould not be fetched (e.g. the verifier's request endpoint returned 404).
Advanced: Match Credentials from Store
DCQL-match the dcqlQuery returned by Resolve against the wallet's own stored credentials. For a full consent preview (verifier display + claim options), use Step 1 — Preview instead.
Endpoint: POST /v2/{target}/wallet-service-api/credentials/present/match-credentials-from-store
Example Request
curl -X POST \
'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials/present/match-credentials-from-store' \
-H 'Authorization: Bearer {yourToken}' \
-H 'Content-Type: application/json' \
-d '{
"dcqlQuery": {
"credentials": [
{
"id": "identity_credential",
"format": "dc+sd-jwt",
"meta": { "vct_values": ["https://issuer.example.org/identity_credential"] },
"claims": [
{ "path": ["given_name"] },
{ "path": ["family_name"] }
]
}
]
}
}'
Body Parameters
dcqlQueryObject — The DCQL query returned by Resolve.
Example Response
{
"matchedQueryIds": ["identity_credential"],
"matchCount": 1,
"matchedCredentialIds": {
"identity_credential": ["550e8400-e29b-41d4-a716-446655440000"]
}
}
matchedQueryIds— DCQL query IDs for which at least one credential matched.matchCount— Total number of matches across all query IDs.matchedCredentialIds— For each matched query ID, the wallet-assigned credential IDs. Use these IDs in Step 2 viaselectedCredentialIds.
Response Codes
200— Matching completed.400— Invalid DCQL query.
Advanced: Match Inline Credentials
DCQL-match against credentials you supply inline — useful to preview matches for credentials that aren't stored in the wallet.
Endpoint: POST /v2/{target}/wallet-service-api/credentials/present/match-credentials
Example Request
curl -X POST \
'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials/present/match-credentials' \
-H 'Authorization: Bearer {yourToken}' \
-H 'Content-Type: application/json' \
-d '{
"dcqlQuery": {
"credentials": [
{
"id": "identity_credential",
"format": "dc+sd-jwt",
"meta": { "vct_values": ["https://issuer.example.org/identity_credential"] },
"claims": [ { "path": ["given_name"] } ]
}
]
},
"credentials": [
{
"id": "cred-1",
"credential": {
"type": "vc-sd_jwt",
"format": "dc+sd-jwt",
"credentialData": {
"given_name": "John",
"family_name": "Doe",
"vct": "https://issuer.example.org/identity_credential",
"cnf": { "jwk": { "kty": "OKP", "crv": "Ed25519", "x": "…" } }
},
"disclosures": [
{ "salt": "…", "name": "birthdate", "value": "1990-01-15", "location": ["birthdate"], "encoded": "…" }
],
"signed": "eyJraWQiOiJHMzdKaUdyM3B0bEQ…",
"signature": { "type": "signature-sd_jwt", "jwtHeader": { "typ": "dc+sd-jwt", "alg": "EdDSA" } }
}
}
]
}'
Body Parameters
dcqlQueryObject — The DCQL query from the verifier's authorization request.credentialsArray — Credentials to match against, each with anid(a caller-assigned label) and acredentialobject. Thecredential's shape depends on the credential format (itstypefield isvc-sd_jwt,vc-w3c_2, orvc_mdocs) — it is not a{ format, rawCredential }pair. The easiest way to obtain it is to copy thecredentialfield from the wallet'sGET /v2/{target}/wallet-service-api/credentials/{credentialId}response (abbreviated with…above).
Example Response
Same structure as Match Credentials from Store.
Response Codes
200— Matching completed.400— Invalid DCQL query or credentials.
Present Inline Credentials
Use the isolated endpoint below when the credentials are supplied inline rather than loaded from the wallet's stores.
Endpoint: POST /v2/{target}/wallet-service-api/credentials/present/isolated
Example Request
curl -X POST \
'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials/present/isolated' \
-H 'Authorization: Bearer {yourToken}' \
-H 'Content-Type: application/json' \
-d '{
"requestUrl": "openid4vp://authorize?client_id=did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5In0&request_uri=https://verifier.example.org/request/abc123",
"credentials": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"credential": {
"type": "vc-sd_jwt",
"format": "dc+sd-jwt",
"credentialData": {
"given_name": "John",
"family_name": "Doe",
"vct": "https://issuer.example.org/identity_credential",
"cnf": { "jwk": { "kty": "OKP", "crv": "Ed25519", "x": "…" } }
},
"disclosures": [
{ "salt": "…", "name": "birthdate", "value": "1990-01-15", "location": ["birthdate"], "encoded": "…" }
],
"signed": "eyJraWQiOiJHMzdKaUdyM3B0bEQ…",
"signature": { "type": "signature-sd_jwt", "jwtHeader": { "typ": "dc+sd-jwt", "alg": "EdDSA" } }
}
}
]
}'
Body Parameters
requestUrlString — The OID4VP authorization request URL.credentialsArray — The credentials to present, each with anid(a caller-assigned label) and acredentialobject. Thecredential's shape depends on the credential format (itstypefield isvc-sd_jwt,vc-w3c_2, orvc_mdocs) — it is not a{ format, rawCredential }pair. Copy it from thecredentialfield of the wallet'sGET /v2/{target}/wallet-service-api/credentials/{credentialId}response (abbreviated with…above).keyReference(optional) String — Full resource path of the key to sign with. Defaults to the first key in the wallet's linked KMS (or the service's configured static key if the KMS has none).key(optional) Object — Inline serialized key ({ "type": "jwk", "jwk": { … } }). Takes precedence overkeyReference.did(optional) String — DID to present as. Defaults to the first DID in the wallet's linked DID Store (or the configured static DID).
Example Response
{
"transmission_success": true,
"verifier_response": {
"status": "received",
"message": "Presentation received and is being processed."
}
}
WalletPresentResult fields are the same as the full-flow response — transmission_success, verifier_response, and (only when the verifier redirects) redirect_to / get_url / form_post_html.
Response Codes
200— Presentation built and submitted. If no supplied credential satisfied the DCQL query, this still returns200with"transmission_success": falseand the error inverifier_response.400— The presentation request could not be resolved.
Next Steps
- Present with the full flow — the per-format guides (SD-JWT VC, W3C VC, mDL) use the single-call
credentials/presentendpoint. - Receive a credential — Credential Receiving.
