Custom 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.

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

When to Use the Isolated Flow

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 the later steps.

CURL

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

Example Request
curl -X POST http://localhost:7005/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",
  "txCodeRequired": true,
  "credentialEndpoint": "http://localhost:7005/openid4vci/credential",
  "offeredCredentials": ["identity_credential"]
}

Response Fields

  • credentialIssuer: String - Issuer identifier from the offer.
  • credentialConfigurationIds: String - Credential configuration IDs listed in the offer.
  • grantType: String - pre-authorized_code or authorization_code.
  • txCodeRequired: Boolean - Whether the pre-authorized flow requires a transaction code (PIN).
  • credentialEndpoint: String - The issuer's credential endpoint, from its metadata.
  • offeredCredentials: String - Resolved offered credential configuration IDs.

Request Token

Exchange the pre-authorized code for an access token.

resolve-offer does not return the preAuthorizedCode or tokenEndpoint this step needs — source them yourself. The pre-authorized code is carried inside the credential offer at grants."urn:ietf:params:oauth:grant-type:pre-authorized_code"."pre-authorized_code". The token endpoint is the issuer's token_endpoint from its authorization-server metadata — for Issuer2 that is {credentialIssuer}/token (the credentialIssuer returned by resolve-offer).

CURL

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

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

Body Parameters

  • tokenEndpoint: String (required) - The issuer's token endpoint.
  • preAuthorizedCode: String (required) - The pre-authorized code from the credential offer.
  • txCode: String (optional) - Transaction code (PIN), if the offer requires one.
  • clientId: String (optional) - See OAuth client identity. Defaults to wallet-client.
  • redirectUri: String (optional) - Defaults to openid://.

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

Response Fields

  • accessToken: String - Use it in the fetch-credential request.
  • cNonce: String (optional) - Proof nonce, only if the issuer returns one in the token response. Under OID4VCI 1.0 the issuer (including Issuer2) serves the nonce from a dedicated Nonce Endpoint instead, so this is usually absent — fetch it in Get a Nonce below.
  • 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 — so request-token typically returns no cNonce. Get it via Get a Nonce before signing the proof.

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 the nonce_endpoint published in the issuer's credential-issuer metadata; for Issuer2 it is {credentialIssuer}/nonce (the credentialIssuer returned by resolve-offer).

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:7005/wallet/{walletId}/credentials/receive/sign-proof \
  -H 'Content-Type: application/json' \
  -d '{
    "issuerUrl": "http://localhost:7005/openid4vci",
    "nonce": "wKI4LT5Sadf6a0dyih5b0Ws"
  }'

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).
  • 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. Takes precedence over 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:7005/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..."
  }'

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.
  • proofJwt: String (optional) - The proof JWT from the sign-proof step. Required if the issuer requires proof.
  • clientId: String (optional) - See OAuth client identity. Defaults to wallet-client.

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

The isolated fetch endpoint returns raw credential strings — it does not store them. To keep a credential, import it with POST /wallet/{walletId}/credentials/import, or use the full flow, which stores automatically.


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

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:7005/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.
  • 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 wallet-client.

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

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.

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:7005/wallet/{walletId}/credentials/receive/exchange-code \
  -H 'Content-Type: application/json' \
  -d '{
    "tokenEndpoint": "http://localhost:7005/openid4vci/token",
    "code": "SplxlOBeZQQYbYS6WxSbIA",
    "codeVerifier": "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM",
    "redirectUri": "https://wallet.example.org/callback"
  }'

Body Parameters

  • tokenEndpoint: String (required) - The issuer's token endpoint.
  • code: String (required) - The authorization code returned to your redirect URI.
  • 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 wallet-client.

Example Response

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


Deferred Credentials

If the issuer isn't ready to issue immediately, the receive result returns a transaction_id in deferredTransactionIds instead of a credential. Poll the deferred endpoint with that ID until the credential is ready.

CURL

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

Example Request
curl -X POST http://localhost:7005/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.

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

If the issuer is still not ready, credentialIds comes back empty — retry later.


Advanced Options

OAuth Client Identity

Several endpoints accept a clientId. It is the OAuth 2.0 client_id 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, wallet-client, works with issuers that don't require client registration — including the walt.id Issuer2 in its default configuration. Set it explicitly when the issuer's authorization server requires a registered or attested client identity.

Inline Keys

The sign-proof step (and the full-flow receive) accept an inline key object instead of a keyId. An inline key takes precedence over keyId and the wallet's default key. Use it only when the caller manages key material outside the wallet store — most deployments should prefer keyId or the default key.

{
  "key": {
    "type": "jwk",
    "jwk": { "kty": "OKP", "crv": "Ed25519", "x": "...", "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 current token request builder does not forward these per-request headers. If tokenRequestHeaders are supplied, the service logs a warning and continues without sending them. Do not rely on this field for attestation-based client authentication until the underlying token builder supports additional headers.


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 July 13, 2026