Isolated Flow

The full-flow endpoint runs the whole OID4VCI exchange in a single call. When you're building a wallet UI and need to show intermediate state, prompt for a PIN, support the authorization-code grant, or handle deferred issuance, drive the isolated endpoints explained on this page yourself.

OID4VCI: The protocol used to deliver the credential from the issuer to the wallet.

Resolve the offer returns a consent-oriented preview — issuer branding and per-offer credential details — so UIs can show what will be issued before accepting.

All endpoints on this page are rooted at /wallet/{walletId}/credentials/receive. The examples use http://localhost:7006 as the base URL (Issuer2 defaults to 7005).

Prerequisites

Before you begin, ensure you have:

  • Wallet API v2 running — Follow the Setup guide.
  • A wallet holding a key — Create a wallet, then generate a key into it. You'll need the wallet's walletId.
  • A credential offer — Use an openid-credential-offer:// URL or inline offer JSON from an issuer such as Issuer2.

When to Use the Isolated Flow

Generally speaking, you should use the isolated flow when building any form of wallet user interface leveraging the wallet API v2. This allows you to properly step through the credential receiving process and build the necessary UI elements accordingly.

You want to…Use
Claim a credential in one callFull flow
Preview the offer before claimingResolve the offer first
Issuer requires user auth before claimingAuthorization-code flow
Poll for a credential issued laterDeferred credentials
Manage key material outside the walletInline keys

Pre-Authorized Code Flow

The isolated pre-authorized flow is:

1. Resolve Offer  →  2. Request Token  →  2b. Get Nonce  →  3. Sign Proof  →  4. Fetch Credential

Resolve the Offer

Parse a credential offer and fetch the issuer metadata needed for a consent preview and the later steps.

CURL

Endpoint: POST /wallet/{walletId}/credentials/receive/resolve-offer | API Reference

Example Request
curl -X POST http://localhost:7006/wallet/{walletId}/credentials/receive/resolve-offer \
  -H 'Content-Type: application/json' \
  -d '{
    "offerUrl": "openid-credential-offer://?credential_offer_uri=http%3A%2F%2Flocalhost%3A7005%2Fopenid4vci%2Fcredential-offer%3Fid%3D48f1dc54-134c-4e10-b889-17f3c0a595bf"
  }'

Path Parameters

  • walletId: String (required) - The wallet handling the exchange. See how to obtain it.

Body Parameters

  • Credential offer (required) — provide exactly one of:
    • offerUrl: String - The OID4VCI credential offer URL.
    • offerJson: Object - The credential offer as an inline JSON object.

Example Response
{
  "credentialIssuer": "http://localhost:7005/openid4vci",
  "credentialConfigurationIds": ["identity_credential"],
  "grantType": "pre-authorized_code",
  "preAuthorizedCode": "SplxlOBeZQQYbYS6WxSbIA",
  "txCodeRequired": true,
  "credentialEndpoint": "http://localhost:7005/openid4vci/credential",
  "tokenEndpoint": "http://localhost:7005/openid4vci/token",
  "nonceEndpoint": "http://localhost:7005/openid4vci/nonce",
  "issuer": {
    "credentialIssuer": "http://localhost:7005/openid4vci",
    "display": {
      "name": "Example Issuer",
      "locale": "en-US",
      "logoUri": "https://issuer.example.com/logo.png",
      "logoAltText": "Example Issuer"
    }
  },
  "offeredCredentials": [
    {
      "configurationId": "identity_credential",
      "format": "dc+sd-jwt",
      "scope": null,
      "vct": "https://issuer.example.com/identity_credential",
      "doctype": null,
      "display": {
        "name": "Identity Credential",
        "locale": "en-US",
        "description": "Personal identity claims"
      },
      "claims": [
        {
          "path": ["given_name"],
          "mandatory": true,
          "displayName": "Given name"
        },
        {
          "path": ["family_name"],
          "mandatory": false,
          "displayName": "Family name"
        }
      ]
    }
  ],
  "transactionCode": {
    "inputMode": "numeric",
    "length": 6,
    "description": "Enter the PIN you received from the issuer"
  }
}

Response Fields

  • credentialIssuer: String - Issuer identifier from the offer.
  • credentialConfigurationIds: String - Flat list of credential configuration IDs listed in the offer.
  • grantType: String - pre-authorized_code or authorization_code.
  • preAuthorizedCode: String (optional) - The pre-authorized code from the credential offer, returned when the offer uses the pre-authorized code grant.
  • txCodeRequired: Boolean - Whether the pre-authorized flow requires a transaction code (PIN).
  • credentialEndpoint: String - The issuer's credential endpoint, from its metadata.
  • tokenEndpoint: String (optional) - The token endpoint from the issuer's authorization-server metadata.
  • nonceEndpoint: String (optional) - The issuer's nonce endpoint for c_nonce flows (OID4VCI 1.0).
  • issuer: Object - Issuer metadata projection for consent UIs (credentialIssuer, optional localized display with name, locale, logoUri, logoAltText, and related branding fields when present).
  • offeredCredentials: Object - Per-configuration offer details for the UI (not strings). Each entry includes:
    • configurationId / format — configuration id and credential format.
    • scope / vct / doctype — format-specific identifiers when present.
    • display — localized credential display (name, description, colors, logo, …).
    • claims — declared claim metadata (path, mandatory, displayName).
  • transactionCode: Object (optional) - UI hints for collecting a transaction code when required (inputMode numeric or text, optional length, optional description). Distinct from the PIN value you send later as txCode.

Request Token

Exchange the pre-authorized code for an access token.

CURL

Endpoint: POST /wallet/{walletId}/credentials/receive/request-token | API Reference

Example Request
curl -X POST http://localhost:7006/wallet/{walletId}/credentials/receive/request-token \
  -H 'Content-Type: application/json' \
  -d '{
    "tokenEndpoint": "http://localhost:7005/openid4vci/token",
    "preAuthorizedCode": "SplxlOBeZQQYbYS6WxSbIA",
    "credentialIssuer": "http://localhost:7005/openid4vci",
    "txCode": "123456"
  }'

Body Parameters

  • tokenEndpoint: String (required) - The issuer's token endpoint.
  • preAuthorizedCode: String (required) - The pre-authorized code from the credential offer.
  • credentialIssuer: String (optional) - Credential issuer base URL. When supplied, the wallet resolves authorization-server metadata and can apply client-attestation headers if configured.
  • txCode: String (optional) - Transaction code (PIN), if the offer requires one.
  • clientId: String (optional) - See OAuth client identity. Defaults to eudiw-abca.
  • redirectUri: String (optional) - Defaults to openid://.
  • tokenRequestHeaders: Object (optional) - Additional headers to attach to the token request, such as OAuth-Client-Attestation and OAuth-Client-Attestation-PoP.
  • anonymousPreAuthorizedCode: Boolean (optional) - Request an anonymous pre-authorized-code token exchange when the issuer supports it. Defaults to false.

Example Response
{
  "accessToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expiresIn": 300
}

Response Fields

  • accessToken: String - Use it in the fetch-credential request.
  • tokenType: String (optional) — OAuth token type; preserve it for subsequent credential requests.
  • expiresIn: Number - Token lifetime in seconds.

Under OID4VCI 1.0 the proof nonce (c_nonce) comes from the issuer's Nonce Endpoint, not the token response — the wallet token response contains no cNonce. Get it via Get a Nonce before signing the proof.

Batch offer capability

POST /wallet/{walletId}/credentials/receive/resolve-offer/batch accepts the same offer source and returns { "offer": { ... }, "batchSize": 2 }. The nested offer has the same fields as the original resolve endpoint. batchSize is the issuer-advertised maximum number of holder bindings per credential request; an absent limit permits one. The original endpoint retains its released response shape.

Isolated batch token responses

Use POST /wallet/{walletId}/credentials/receive/request-token/batch for a pre-authorized grant, or POST /wallet/{walletId}/credentials/receive/exchange-code/batch for an authorization-code grant. Each accepts the same request body as its original endpoint and additionally returns the authorization server's authorizationDetails and scope when present. Existing endpoints retain accessToken, expiresIn, and tokenType only.

For pre-authorized selection, supply credentialIssuer and credentialConfigurationIds to negotiate authorization details or scopes from metadata. In the batch response, use each granted credential_identifiers entry as a credentialIdentifier in the subsequent fetch request; do not treat dataset identifiers as the number of copies. Request copies through holder bindings and plural proofs. Redeem each code once using the response contract your flow needs.

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 it — call the issuer's nonce endpoint directly. Its URL is returned as nonceEndpoint from 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).

CURL

Endpoint: POST {credentialIssuer}/nonce — the issuer's endpoint, not a wallet endpoint.

Example Request
curl -X POST http://localhost:7005/openid4vci/nonce

Example Response
{
  "c_nonce": "wKI4LT5Sadf6a0dyih5b0Ws"
}

Use the returned c_nonce as the nonce in Sign Proof.

Sign Proof

Sign a proof-of-possession JWT that proves the wallet controls the holder key.

CURL

Endpoint: POST /wallet/{walletId}/credentials/receive/sign-proof | API Reference

Example Request
curl -X POST http://localhost:7006/wallet/{walletId}/credentials/receive/sign-proof \
  -H 'Content-Type: application/json' \
  -d '{
    "issuerUrl": "http://localhost:7005/openid4vci",
    "nonce": "wKI4LT5Sadf6a0dyih5b0Ws",
    "credentialConfigurationId": "identity_credential"
  }'

Body Parameters

  • issuerUrl: String (required) - The credential issuer URL, used as the proof audience.
  • nonce: String (required) - The c_nonce from Get a Nonce (or from the token response, if the issuer returned one).
  • credentialConfigurationId: String (required) - The credential configuration ID being requested.
  • keyId: String (optional) - Key from the wallet's key store. Defaults to the wallet's default key.
  • did: String (optional) - DID to use as the proof key ID. If omitted, the proof carries the JWK directly.
  • key: Object (optional) - An inline key, supplied instead of keyId.

Example Response
{
  "proofJwt": "eyJhbGciOiJFUzI1NiIsInR5cCI6Im9wZW5pZDR2Y2ktcHJvb2Yrand0In0..."
}

Fetch Credential

Request the credential from the issuer's credential endpoint.

CURL

Endpoint: POST /wallet/{walletId}/credentials/receive/fetch-credential | API Reference

Example Request
curl -X POST http://localhost:7006/wallet/{walletId}/credentials/receive/fetch-credential \
  -H 'Content-Type: application/json' \
  -d '{
    "credentialEndpoint": "http://localhost:7005/openid4vci/credential",
    "accessToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
    "credentialConfigurationId": "identity_credential",
    "proofJwt": "eyJhbGciOiJFUzI1NiIsInR5cCI6Im9wZW5pZDR2Y2ktcHJvb2Yrand0In0...",
    "storeInWallet": true
  }'

Body Parameters

  • credentialEndpoint: String (required) - The issuer's credential endpoint.
  • accessToken: String (required) - The access token from the token response.
  • credentialConfigurationId: String (required) - The credential configuration ID to request.
  • proofs: Object (optional) - The JWT proof collection for multiple holders. For one proof, supply the returned proofJwt instead; do not combine both fields.
  • clientId: String (optional) - See OAuth client identity. Defaults to eudiw-abca.
  • storeInWallet: Boolean (optional) - Store the fetched credential(s) in the wallet credential store. Defaults to false.

Example Response
{
  "rawCredentials": [
    "eyJraWQiOiJkaWQ6a2V5Ono2TWtmcmlxMU1xTEJvUFdlY0dvRExqZ3VvMXNCOWJyajZ3VDNxWjVCeGtLcHVQNiJ9..."
  ]
}

The isolated fetch endpoint always returns raw credential strings. Set storeInWallet to true to store them during this call. If you leave it as false, store a returned credential later with POST /wallet/{walletId}/credentials/import, or use the full flow, which stores automatically.


For multiple targets or copies, follow Batch Issuance. Supply holderBindings to sign-proof and retain the same bindings for storage. Each fetch addresses one dataset; use the issuer's granted credentialIdentifier when present and provide credentialIssuerBaseUrl to validate a multi-proof request against the advertised limit. A single signed proof returns proofJwt; multiple proofs return proofs.jwt.

Authorization-Code Flow

When the issuer requires the holder to log in first, use the authorization-code grant. It replaces the "request token" step with a redirect through the issuer's authorization server.

1. Resolve Offer  →  2. Generate Authorization URL  →  (user logs in, issuer redirects back)
→  3. Exchange Code  →  4. Sign Proof  →  5. Fetch Credential

Steps 1, 4 and 5 are identical to the pre-authorized flow above.

Generate Authorization URL

This endpoint authorizes the first offered configuration. For multiple configurations or automatic scope negotiation, use the batch authorization endpoint.

Build the authorization URL to redirect the user to.

CURL

Endpoint: POST /wallet/{walletId}/credentials/receive/authorization-url | API Reference

Example Request
curl -X POST http://localhost:7006/wallet/{walletId}/credentials/receive/authorization-url \
  -H 'Content-Type: application/json' \
  -d '{
    "offerUrl": "openid-credential-offer://?credential_offer_uri=...",
    "redirectUri": "https://wallet.example.org/callback",
    "usePkce": true
  }'

Body Parameters

  • Credential offer (required) — provide exactly one of:
    • offerUrl: String - The OID4VCI credential offer URL.
    • offerJson: Object - The credential offer as an inline JSON object.
  • useScope: Boolean (optional) — Defaults to false (authorization details). Set true to request the first offered configuration by its advertised scope.
  • redirectUri: String (optional) - Redirect URI registered with the issuer. Defaults to openid://.
  • usePkce: Boolean (optional) - Whether to use PKCE. Defaults to true.
  • clientId: String (optional) - See OAuth client identity. Defaults to eudiw-abca.

Example Response
{
  "authorizationUrl": "http://localhost:7005/openid4vci/authorize?response_type=code&client_id=eudiw-abca&state=abc123&code_challenge=...",
  "state": "abc123",
  "codeVerifier": "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM",
  "credentialConfigurationId": "identity_credential",
  "credentialIssuerBaseUrl": "http://localhost:7005/openid4vci",
  "nonceEndpoint": "http://localhost:7005/openid4vci/nonce"
}

Response Fields

  • authorizationUrl: String - Redirect the user here.
  • state: String - Returned to your redirect URI; use it to correlate the response.
  • codeVerifier: String - Store it — you'll need it to exchange the code (PKCE).
  • credentialConfigurationId: String - The credential being requested.
  • credentialIssuerBaseUrl: String - Credential issuer base URL. Pass this to Exchange Code.
  • nonceEndpoint: String (optional) - The issuer's nonce endpoint for c_nonce flows (OID4VCI 1.0), when published. See Get a Nonce.

After the user authenticates, the issuer redirects to your redirectUri with code and state query parameters.

Exchange Code

Exchange the authorization code for an access token.

CURL

Endpoint: POST /wallet/{walletId}/credentials/receive/exchange-code | API Reference

Example Request
curl -X POST http://localhost:7006/wallet/{walletId}/credentials/receive/exchange-code \
  -H 'Content-Type: application/json' \
  -d '{
    "code": "SplxlOBeZQQYbYS6WxSbIA",
    "credentialIssuerBaseUrl": "http://localhost:7005/openid4vci",
    "codeVerifier": "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM",
    "redirectUri": "https://wallet.example.org/callback"
  }'

Body Parameters

  • code: String (required) - The authorization code returned to your redirect URI.
  • credentialIssuerBaseUrl: String (required) - Credential issuer base URL returned by Generate Authorization URL. The wallet uses it to resolve authorization-server metadata, including the token endpoint.
  • codeVerifier: String (optional) - The PKCE code verifier from the authorization-url step.
  • redirectUri: String (optional) - The redirect URI used in the authorization request. Defaults to openid://.
  • clientId: String (optional) - See OAuth client identity. Defaults to eudiw-abca.
  • tokenRequestHeaders: Object (optional) - Additional headers to attach to the token request.

Example Response

Returns the same shape as Request Token — an accessToken, optional expiresIn, and optional tokenType. Continue with Sign Proof and Fetch Credential.


Deferred Credentials

Batch receive flows return retained handles in deferredCredentials; resume them through the wallet continuation route. The original receive response keeps its non-null deferredTransactionIds map. Use explicit batch selections to receive the complete target list; a legacy response that cannot represent multiple deferred datasets returns 409 with that detailed progress instead of discarding a transaction.

Requests using only the released fields keep the original successful response bodies: rawCredentials for fetch, and credentialIds with deferredTransactionIds for poll. Fetch selects detailed progress when you provide proofs, non-default holderBindings, credentialIdentifier, or DPoP context. Poll selects it with proofRequired=true, non-default holderBindings, credentialIdentifier, or DPoP context. A legacy pending response returns 409; a legacy local-save failure returns 500. Both include the complete detailed progress instead of returning an incompatible success body. Detailed requests use 200 for pending and 207 for partial progress.

The isolated poll below is for callers managing the issuer request themselves. Preserve deferredCredential and its intervalSeconds from fetch-credential, wait the advertised interval, and pass the original token, binding context, and credentialIssuerBaseUrl. The wallet uses that issuer URL to negotiate encryption for the Deferred Credential Request. Do not poll the issuer again after a received response has failed only during local storage; resume its storageOutcome.deferredCredentials handle instead.

CURL

Endpoint: POST /wallet/{walletId}/credentials/receive/deferred | API Reference

Example Request
curl -X POST http://localhost:7006/wallet/{walletId}/credentials/receive/deferred \
  -H 'Content-Type: application/json' \
  -d '{
    "deferredCredentialEndpoint": "http://localhost:7005/openid4vci/credential_deferred",
    "transactionId": "8xFQ5j9Q8L-3PUdnl4RWxXgB7oNc3mK2pL9vR6tY1zA",
    "accessToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
  }'

Body Parameters

  • deferredCredentialEndpoint: String (required) - The deferred credential endpoint from the issuer metadata.
  • transactionId: String (required) - The transaction ID returned when issuance was deferred.
  • accessToken: String (required) - The access token from the original token response.
  • holderBindings / proofRequired: Array / Boolean - Copy both from the original deferredCredential so stored credentials are matched to the original proof keys.
  • credentialIdentifier: String (optional) - Copy the issuer-granted dataset identifier, when present.
  • credentialIssuerBaseUrl / credentialConfigurationId: String - Preserve the original issuer and configuration for metadata, response validation, and Deferred Credential Request encryption.
  • tokenType / dpopKeyId: String - Preserve the token type and, for DPoP, the original sender key independently of holder bindings.

Example Response
{
  "credentialIds": ["550e8400-e29b-41d4-a716-446655440000"],
  "deferredTransactionIds": {}
}

For requests with explicit batch fields, a pending response contains pending.transactionId and pending.intervalSeconds, with empty credentialIds. Released requests retain the successful response shown above and report pending progress with HTTP 409. Wait that interval before another isolated poll. A failed local save returns storageOutcome with committed IDs and a retained handle; use wallet continuation instead of repeating the issuer request.


Advanced Options

OAuth Client Identity

Several endpoints accept a clientId. It is the OAuth 2.0 client_id — an identifier in the OAuth 2.0 authorization protocol — the wallet presents to the issuer's authorization server in token and authorization requests (and it feeds client-attestation headers when those are used).

The default, eudiw-abca, works with issuers that don't require client registration — including the walt.id Issuer2 in its default configuration. Set clientId explicitly when the issuer's authorization server requires a registered or attested client identity.

Inline Keys

The sign-proof step accepts an inline key object instead of a keyId; do not supply both. Storage-producing receive, fetch, and poll operations require a stored wallet key. Use inline keys only for isolated signing when the caller manages key material outside the wallet store.

{
  "key": {
    "type": "jwk",
    "jwk": { "kty": "EC", "crv": "P-256", "x": "...", "y": "...", "d": "..." }
  }
}

The key uses the same serialized shape as the key import endpoint.

Token Request Headers

ReceiveCredentialRequest includes a tokenRequestHeaders map, intended for headers such as OAuth-Client-Attestation and OAuth-Client-Attestation-PoP.

The token request endpoints forward tokenRequestHeaders to the issuer's token endpoint. If credentialIssuer or credentialIssuerBaseUrl is supplied and client attestation is configured, the wallet can also build attestation headers from issuer metadata.


Endpoint Reference

EndpointMethodDescription
/wallet/{walletId}/credentials/receivePOSTFull pre-authorized code flow
/wallet/{walletId}/credentials/receive/resolve-offerPOSTResolve a credential offer
/wallet/{walletId}/credentials/receive/request-tokenPOSTExchange a pre-authorized code for a token
/wallet/{walletId}/credentials/receive/authorization-urlPOSTGenerate an authorization-code redirect URL
/wallet/{walletId}/credentials/receive/exchange-codePOSTExchange an authorization code for a token
/wallet/{walletId}/credentials/receive/sign-proofPOSTSign a proof-of-possession JWT
/wallet/{walletId}/credentials/receive/fetch-credentialPOSTFetch credential(s) from the issuer
/wallet/{walletId}/credentials/receive/deferredPOSTPoll deferred credential issuance
Last updated on November 2, 2026