Custom Flow
The full-flow endpoint (credentials/present) matches, signs, and submits in one call. The custom 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
- Present credentials that aren't stored in the wallet (supplied inline)
- Inspect intermediate results
All endpoints live under /v2/{target}/wallet-service-api/.
Flow Overview
1. Resolve VP Request → parse request URL → nonce, clientId, responseUri
2. Match Credentials → DCQL match (from store or inline) → matched credential IDs
── show user consent screen ──
3. Present Credentials → sign and submit → WalletPresentResult
Step 1 — Resolve VP Request
Parse the verifier's authorization request to extract its metadata.
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
- Presentation request (required) — provide exactly one of:
requestUrlString — The OID4VP authorization request URL.requestObjectObject — The authorization request as JSON, instead of a URL.
Example Response
{
"nonce": "n-0S6_WzA2Mj",
"clientId": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5In0",
"responseUri": "https://verifier.example.org/response",
"hasRequestUri": true
}
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.
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).
resolve-request returns the request's metadata but not the DCQL query itself. To match credentials in the next step you need the query — see Where the DCQL Query Comes From below.
Where the DCQL Query Comes From
The next step matches credentials against a DCQL query — the machine-readable statement of what the verifier wants. The DCQL query travels inside the verifier's authorization request as the dcql_query parameter, and the wallet reads it out of that request.
Given only the openid4vp:// request URL, extract the query from the request object:
- Inline request — the query is the
dcql_queryparameter in the URL itself. Read it directly from the query string. request_urirequest (the common case) — the URL carries arequest_uripointing to the request object rather than the query. Fetch that URL and read thedcql_queryclaim out of what it returns.- By default, Verifier2 returns a plain JSON object (
Content-Type: application/json) — readdcql_querydirectly from it. This is what you get unless the verification session was created withsigned_request: true. - If the session set
signed_request: true, the request object is instead a signed JWT (Content-Type: application/oauth-authz-req+jwt, fetched viaPOST) —dcql_queryis in its payload, so decode the JWT payload to read it.
- By default, Verifier2 returns a plain JSON object (
The wallet has no endpoint that hands you the query directly — resolve-request deliberately returns only the request metadata (nonce, client, response URI), so you extract dcql_query from the request object yourself as above.
Step 2a — Match Credentials from Store
DCQL-match against the wallet's own stored credentials. This is the recommended matching step when credentials live in the wallet's Credential Store.
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 from the verifier's authorization request. See DCQL Query Structure.
Example Response
{
"matchedQueryIds": ["identity_credential"],
"matchCount": 1,
"matchedCredentialIds": {
"identity_credential": ["0"]
}
}
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 identifiers of the matching credentials.
For the from-store variant these identifiers are positional indices ("0", "1", …) into the wallet's matched-credential set for this call — not the stored credential UUIDs. They currently cannot be used directly to fetch or select a specific stored credential. (The inline match-credentials variant instead echoes back the id you supplied.) If you need to present a specific stored credential, drive the full flow with a DCQL query narrow enough to match only that credential.
Response Codes
200— Matching completed.400— Invalid DCQL query.
Step 2b — 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 Step 2a.
Response Codes
200— Matching completed.400— Invalid DCQL query or credentials.
Step 3 — Present
After the user consents, complete the presentation. Use the full-flow endpoint (credentials/present) to present from the wallet's stores, or the isolated endpoint below to present credentials supplied inline.
Present Inline Credentials
Present credentials that are supplied inline rather than matched 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
- Presentation request (required) — provide exactly one of:
requestUrlString — The OID4VP authorization request URL.requestObjectObject — The authorization request as JSON, instead of a 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).keyId(optional) String — ID 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).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.
