Isolated Flow
The full-flow endpoint (credentials/receive) runs the whole exchange in one call. The isolated flow lets you drive each step yourself when you need to:
- Build a custom wallet UI with consent screens and PIN prompts
- Use the authorization-code grant (browser redirect)
- Handle deferred issuance
- Inspect intermediate results, or manage keys/credentials externally
Resolve Offer returns a consent-oriented preview — issuer branding and per-offer credential details — so UIs can show what will be issued before accepting. Accepting still re-sends the offer URL (the resolve step is stateless).
All endpoints live under /v2/{target}/wallet-service-api/.
Holder Key & DID
Every receive flow signs a proof of possession to prove the wallet controls the holder key. You can let the wallet use its defaults, reference stored material, or pass material inline:
| Field | Type | Description |
|---|---|---|
keyReference | String | Full resource path of a key in a linked KMS, e.g. waltid.tenant1.kms.wallet_key. Referenced keys stay in the KMS, so backends whose keys never leave the KMS (PKCS#11, HSM) work in this flow. |
key | Object | Inline serialized key ({ "type": "jwk", "jwk": { … } }). Takes precedence over keyReference. |
did | String | DID to use as the holder identity, e.g. did:jwk:…. Resolved against a linked DID Store or used inline. |
All three are optional — if omitted, the wallet uses its configured default key and DID.
Pre-Authorized Code Flow
Drive the pre-authorized code grant one step at a time:
1. Resolve Offer → 2. Request Token → 2b. Get Nonce → 3. Sign Proof → 4. Fetch Credential
For the authorization-code grant (browser redirect, user login), see Authorization-Code Grant below — it reuses Get Nonce, Sign Proof, and Fetch Credential from this flow.
Step 1 — Resolve Offer
Parse the offer URL and resolve the issuer metadata in one call. Returns a consent-oriented preview (issuer branding and offered-credential details). No server-side offer handle is retained — complete issuance with the pre-authorized or authorization-code endpoints using the original offer (and any PIN).
Endpoint: POST /v2/{target}/wallet-service-api/credentials/receive/resolve-offer
Example Request
curl -X POST \
'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials/receive/resolve-offer' \
-H 'Authorization: Bearer {yourToken}' \
-H 'Content-Type: application/json' \
-d '{
"offerUrl": "openid-credential-offer://?credential_offer_uri=https%3A%2F%2Fissuer.example.org%2Fcredential-offer%3Fid%3Dabc123"
}'
Body Parameters
- Credential offer (required) — provide exactly one of:
offerUrlString — The OID4VCI offer URL.offerJsonObject — Raw credential offer JSON, instead of a URL.
Example Response
{
"credentialIssuer": "https://issuer.example.org",
"credentialConfigurationIds": ["UniversityDegree_jwt_vc_json"],
"grantType": "pre-authorized_code",
"preAuthorizedCode": "SplxlOBeZQQYbYS6WxSbIA",
"txCodeRequired": false,
"credentialEndpoint": "https://issuer.example.org/credential",
"tokenEndpoint": "https://auth.example.org/token",
"nonceEndpoint": "https://issuer.example.org/nonce",
"issuer": {
"credentialIssuer": "https://issuer.example.org",
"display": {
"name": "Example University",
"locale": "en-US",
"logoUri": "https://issuer.example.org/logo.png",
"logoAltText": "Example University"
}
},
"offeredCredentials": [
{
"configurationId": "UniversityDegree_jwt_vc_json",
"format": "jwt_vc_json",
"scope": null,
"vct": null,
"doctype": null,
"display": {
"name": "University Degree",
"locale": "en-US",
"description": "Bachelor of Science"
},
"claims": []
}
],
"transactionCode": null
}
Breaking change: offeredCredentials is now an array of offer-detail objects, not a list of configuration ID strings. Use credentialConfigurationIds (or offeredCredentials[].configurationId) whenever you need the flat ID list.
credentialIssuer— Issuer identifier from the offer.credentialConfigurationIds— Flat list of credential configuration IDs listed in the offer.grantType—pre-authorized_codeorauthorization_code.preAuthorizedCode(optional) — The pre-authorized code from the credential offer, returned when the offer uses the pre-authorized code grant.txCodeRequired— Whether a transaction code (PIN) is required for the pre-authorized grant.credentialEndpoint— Where to POST the credential request in Step 4.tokenEndpoint(optional) — The token endpoint from the issuer's authorization-server metadata.nonceEndpoint(optional) — The issuer's nonce endpoint forc_nonceflows (OID4VCI 1.0).issuer— Issuer metadata projection for consent UIs (credentialIssuer, optional localizeddisplay).offeredCredentials— Per-configuration offer details for the UI (not strings):configurationId,format, optionalscope/vct/doctype,display, andclaims(path,mandatory,displayName).transactionCode(optional) — UI hints for collecting a transaction code when required (inputMode, optionallength, optionaldescription). Distinct from the PIN value you send later astxCode.
Response Codes
200— Offer resolved.400— Offer could not be resolved.
Step 2 — Request Token
Exchange a pre-authorized code for an access token.
Endpoint: POST /v2/{target}/wallet-service-api/credentials/receive/request-token
Example Request
curl -X POST \
'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials/receive/request-token' \
-H 'Authorization: Bearer {yourToken}' \
-H 'Content-Type: application/json' \
-d '{
"tokenEndpoint": "https://auth.example.org/token",
"preAuthorizedCode": "SplxlOBeZQQYbYS6WxSbIA",
"credentialIssuer": "https://issuer.example.org",
"txCode": "493536"
}'
Body Parameters
tokenEndpointString — The authorization server's token endpoint.preAuthorizedCodeString — The pre-authorized code from the offer.credentialIssuer(optional) String — Credential issuer base URL. When supplied, Enterprise resolves authorization-server metadata and can apply client-attestation headers if a client attester is linked.txCode(optional) String — Transaction code (PIN), if the offer requires one.clientId(optional) String — Defaults towallet-client. If one client attester service is linked and the issuer supports attestation-based client authentication, Enterprise uses the wallet resource path as the effective client ID.redirectUri(optional) String — Defaults toopenid://.tokenRequestHeaders(optional) Object — Additional headers to attach to the token request, such asOAuth-Client-AttestationandOAuth-Client-Attestation-PoP.anonymousPreAuthorizedCode(optional) Boolean — Request an anonymous pre-authorized-code token exchange when the issuer supports it. Defaults tofalse.
Example Response
{
"accessToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"expiresIn": 300
}
accessToken— Bearer token for the credential request in Step 4.cNonce(optional) — Proof nonce, if the issuer returns one in the token response. Under OID4VCI 1.0 the issuer (including Issuer2) instead exposes a dedicated Nonce Endpoint, socNonceis usually absent here — fetch the nonce in Step 2b instead.
Response Codes
200— Token issued.400— Invalid or expired pre-authorized code.
Under OID4VCI 1.0 the proof nonce (c_nonce) comes from the issuer's Nonce Endpoint, not the token response, so request-token typically returns no cNonce. Get it via Step 2b before signing the proof.
Step 2b — Get a Nonce
Under OID4VCI 1.0 the proof c_nonce comes from the issuer's dedicated Nonce Endpoint, not the token response. The wallet does not proxy this — call the issuer's nonce endpoint directly. Its URL is returned as nonceEndpoint from Step 1 — Resolve Offer when the issuer publishes one (otherwise use the nonce_endpoint from the issuer's credential-issuer metadata; for Issuer2 it is {credentialIssuer}/nonce).
Endpoint: POST {credentialIssuer}/nonce — this is the issuer's endpoint, not a wallet endpoint.
Example Request
curl -X POST 'https://issuer.example.org/nonce'
Example Response
{
"c_nonce": "wKI4LT5Sadf6a0dyih5b0Ws"
}
Use the returned c_nonce as the nonce in Step 3.
Response Codes
200— Nonce issued.
Step 3 — Sign Proof
Sign a proof of possession over the issuer's c_nonce to prove control of the holder key.
Endpoint: POST /v2/{target}/wallet-service-api/credentials/receive/sign-proof
Example Request
curl -X POST \
'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials/receive/sign-proof' \
-H 'Authorization: Bearer {yourToken}' \
-H 'Content-Type: application/json' \
-d '{
"issuerUrl": "https://issuer.example.org",
"nonce": "wKI4LT5Sadf6a0dyih5b0Ws",
"credentialConfigurationId": "UniversityDegree_jwt_vc_json",
"keyReference": "waltid.tenant1.kms.wallet_key",
"did": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Ii..."
}'
Body Parameters
issuerUrlString — The credential issuer identifier (used as the proofaud).nonceString — Thec_noncefrom the Nonce Endpoint (Step 2b), or from the token response if the issuer returned one.credentialConfigurationIdString — The credential configuration the proof is for (required).keyReference/key(optional) — Holder key reference or inline key (see Holder Key & DID).did(optional) String — Holder DID. When set, the proof binds to the DID; otherwise it binds to the raw JWK.
Example Response
{
"proofJwt": "eyJ0eXAiOiJvcGVuaWQ0dmNpLXByb29mK2p3dC..."
}
Response Codes
200— Proof signed.400— No resolvable holder key.
Step 4 — Fetch Credential
Request the credential from the issuer's credential endpoint using the access token and the signed proof.
Endpoint: POST /v2/{target}/wallet-service-api/credentials/receive/fetch-credential
Example Request
curl -X POST \
'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials/receive/fetch-credential' \
-H 'Authorization: Bearer {yourToken}' \
-H 'Content-Type: application/json' \
-d '{
"credentialEndpoint": "https://issuer.example.org/credential",
"accessToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"credentialConfigurationId": "UniversityDegree_jwt_vc_json",
"proofJwt": "eyJ0eXAiOiJvcGVuaWQ0dmNpLXByb29mK2p3dC...",
"storeInWallet": true
}'
Body Parameters
credentialEndpointString — The issuer's credential endpoint (from Step 1 or issuer metadata).accessTokenString — The access token from Step 2.credentialConfigurationIdString — The credential configuration being requested.proofJwt(optional) String — The proof JWT from Step 3. Required when the issuer mandates key-proof.clientId(optional) String — Defaults towallet-client.storeInWallet(optional) Boolean — Store the fetched credential(s) in the wallet's linked Credential Store. Defaults tofalse.
Example Response
{
"rawCredentials": [
"eyJraWQiOiJkaWQ6a2V5Ono2TWtqb1Jo..."
]
}
rawCredentials— The issued credential string(s) (JWT, SD-JWT, or base64-encoded mDoc). SetstoreInWallettotrueto persist them during this call. If you leave it asfalse, store a returned credential later withPOST /v2/{target}/wallet-service-api/credentials/import, or use the full-flow endpoints.
Response Codes
200— Credential(s) fetched.400/500— The issuer rejected the credential request (e.g. missing or invalid proof).
Authorization-Code Grant
You don't know which grant an offer uses until you resolve it — Step 1 — Resolve Offer above returns grantType, and authorization_code is what routes you here instead of the pre-authorized flow. The authorization-code grant then requires the user to authenticate with the issuer's authorization server via a browser redirect, so it cannot be a single blocking call:
Step 1 (above) — Resolve Offer → grantType: "authorization_code"
Generate Authorization URL → redirect the user to the issuer
── user authenticates in the browser ──
Complete Receive → exchange the code, sign the proof, fetch & store the credential (single call)
— or drive it yourself: —
Exchange Code → Step 2b (above) — Get Nonce → Step 3 (above) — Sign Proof → Step 4 (above) — Fetch Credential
(Get Nonce, Sign Proof, and Fetch Credential are the same steps used by the pre-authorized flow above)
Generate Authorization URL
Resolve the offer and build the authorization URL the user must be redirected to.
Endpoint: POST /v2/{target}/wallet-service-api/credentials/receive/authorization-url
Example Request
curl -X POST \
'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials/receive/authorization-url' \
-H 'Authorization: Bearer {yourToken}' \
-H 'Content-Type: application/json' \
-d '{
"offerUrl": "openid-credential-offer://?credential_offer_uri=https%3A%2F%2Fissuer.example.org%2Fcredential-offer%3Fid%3Dabc123",
"redirectUri": "https://your-wallet-app.example.com/callback"
}'
Body Parameters
- Credential offer (required) — provide exactly one of:
offerUrlString — The OID4VCI offer URL.offerJsonObject — Raw credential offer JSON, instead of a URL.
clientId(optional) String — Defaults towallet-client.redirectUri(optional) String — Where the authorization server redirects the user after authentication. Defaults toopenid://.usePkce(optional) Boolean — Whether to use PKCE. Defaults totrue.
Example Response
{
"authorizationUrl": "https://auth.example.org/authorize?response_type=code&client_id=wallet-client&redirect_uri=https%3A%2F%2Fyour-wallet-app.example.com%2Fcallback&scope=UniversityDegree_jwt_vc_json&issuer_state=eyJhbGciOiJSU...&code_challenge=abc123&code_challenge_method=S256",
"state": "abc123",
"codeVerifier": "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM",
"credentialConfigurationId": "UniversityDegree_jwt_vc_json",
"credentialIssuerBaseUrl": "https://issuer.example.org",
"nonceEndpoint": "https://issuer.example.org/nonce"
}
authorizationUrl— Redirect the user here to begin authentication.state— Returned to yourredirectUrias a query parameter; use it to correlate the callback.codeVerifier— PKCE code verifier. Store this securely — the Exchange Code step below needs it.credentialConfigurationId— The credential configuration this authorization is for.credentialIssuerBaseUrl— Credential issuer base URL. Pass it to Exchange Code.nonceEndpoint(optional) — The issuer's nonce endpoint, for Step 2b — Get a Nonce once the user returns with an authorization code.
After redirecting the user, your app receives the authorization code at redirectUri: ?code={authorizationCode}&state={state}.
Response Codes
200— Authorization URL generated.400— Offer could not be resolved, or the issuer has no authorization endpoint.
Exchange Code (Isolated)
Exchange the authorization code obtained above for an access token. This is the isolated alternative to Complete Receive below — after this, continue with Step 2b — Get a Nonce, Step 3 — Sign Proof, and Step 4 — Fetch Credential from the pre-authorized flow above; they're identical for both grant types.
Endpoint: POST /v2/{target}/wallet-service-api/credentials/receive/exchange-code
Example Request
curl -X POST \
'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials/receive/exchange-code' \
-H 'Authorization: Bearer {yourToken}' \
-H 'Content-Type: application/json' \
-d '{
"code": "SplxlOBeZQQYbYS6WxSbIA",
"credentialIssuerBaseUrl": "https://issuer.example.org",
"codeVerifier": "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"
}'
Body Parameters
codeString — The authorization code from the redirect callback.credentialIssuerBaseUrlString — Credential issuer base URL returned by Generate Authorization URL. Enterprise uses it to resolve authorization-server metadata, including the token endpoint.codeVerifier(optional) String — The PKCE code verifier saved from the authorization-url step.clientId(optional) String — Defaults towallet-client. If one client attester service is linked and the issuer supports attestation-based client authentication, Enterprise uses the wallet resource path as the effective client ID.redirectUri(optional) String — Must match the redirect URI used earlier. Defaults toopenid://.tokenRequestHeaders(optional) Object — Additional headers to attach to the token request.
Example Response
Same structure as Step 2 — Request Token in the pre-authorized flow.
Response Codes
200— Token issued.400— Invalid authorization code or PKCE verifier.
Complete Receive (Combined Alternative)
The simpler alternative to driving Exchange Code, Get Nonce, Sign Proof, and Fetch Credential yourself once the user is back with an authorization code: exchange the code for a token, sign the proof, fetch the credential, and store it — in one call.
Endpoint: POST /v2/{target}/wallet-service-api/credentials/receive/authorized
Example Request
curl -X POST \
'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials/receive/authorized' \
-H 'Authorization: Bearer {yourToken}' \
-H 'Content-Type: application/json' \
-d '{
"code": "SplxlOBeZQQYbYS6WxSbIA",
"codeVerifier": "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM",
"credentialIssuer": "https://issuer.example.org",
"credentialEndpoint": "https://issuer.example.org/credential",
"nonceEndpoint": "https://issuer.example.org/nonce",
"credentialConfigurationId": "UniversityDegree_jwt_vc_json",
"keyReference": "waltid.tenant1.kms.wallet_key",
"did": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Ii..."
}'
Body Parameters
codeString — The authorization code received at yourredirectUri.codeVerifier(optional) String — The PKCE code verifier saved from Step 1. Required if PKCE was used.credentialIssuerString — The credential issuer identifier.credentialEndpointString — The issuer's credential endpoint (from its metadata).credentialConfigurationIdString — The credential configuration being requested.nonceEndpoint(optional in the request schema, but effectively required for Issuer2) String — The issuer's nonce endpoint — for Issuer2 this is{credentialIssuer}/nonce. Under OID4VCI 1.0 the token response typically carries noc_nonce(same as Step 2b), so if you omitnonceEndpointand the issuer doesn't return ac_nonce, this call fails with a500.keyReference(optional) String — Resource path of a holder key in one of the wallet's linked KMS stores (e.g.waltid.tenant1.kms.wallet_key). Referenced keys are used through their KMS-backed signer, so they work with KMS backends whose keys never leave the KMS (e.g. PKCS#11, HSM). Ignored whenkeyis provided.key(optional) Object — Inline serialized holder key. Takes precedence overkeyReference.did(optional) String — Holder DID.clientId(optional) String — Defaults towallet-client. If one client attester service is linked and the issuer supports attestation-based client authentication, Enterprise uses the wallet resource path as the effective client ID.redirectUri(optional) String — Must match theredirectUriused in Step 1. Defaults toopenid://.
Example Response
{
"credentialIds": [
"f58ac347-0728-45c6-ba59-bde9d3343aa6"
],
"deferredTransactionIds": {}
}
Response Codes
200— Credential(s) received and stored.400— Invalid authorization code, or the issuer rejected the request.500— The issuer provided noc_nonceand nononceEndpointwas supplied to fetch one.
Deferred Issuance
If the issuer defers a credential, the full-flow (or authorized) response returns its transaction_id under deferredTransactionIds. Poll the issuer's deferred credential endpoint until the credential becomes available.
Endpoint: POST /v2/{target}/wallet-service-api/credentials/receive/deferred
Example Request
curl -X POST \
'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials/receive/deferred' \
-H 'Authorization: Bearer {yourToken}' \
-H 'Content-Type: application/json' \
-d '{
"deferredCredentialEndpoint": "https://issuer.example.org/credential_deferred",
"transactionId": "8xLOxBtZp8",
"accessToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
}'
Body Parameters
deferredCredentialEndpointString — The deferred credential endpoint from the issuer's metadata.transactionIdString — Thetransaction_idreturned underdeferredTransactionIds.accessTokenString — The access token from the original token response.
Example Response
{
"credentialIds": [
"f58ac347-0728-45c6-ba59-bde9d3343aa6"
],
"deferredTransactionIds": {}
}
If the credential is not yet ready, credentialIds is empty — retry later.
Response Codes
200— Poll completed (credential stored, or not yet ready).400/500— The issuer rejected the deferred request.
Next Steps
- Receive with the full flow — the per-format guides (SD-JWT VC, W3C VC, mDL) use the single-call
credentials/receiveendpoint. - Manage stored credentials — list and retrieve credentials in the wallet.
- Present a credential — Credential Presenting.
