Receive Credentials via OID4VCI 1.0

The credential receive v2 endpoints are a modernized set of wallet APIs that support the full OID4VCI pre-authorized and authorization-code flows.

They live under wallet-service-api and offer two usage modes:

ModeWhen to use
Full-flow (single call)You want the wallet to handle the entire OID4VCI exchange in one request.
Isolated step-by-stepYou need visibility into each intermediate result — e.g., for a custom wallet UI, user-consent screens, or multi-step orchestration.

Ensure you have set up a wallet service with a linked KMS and (optionally) a Credential Store before proceeding. If no Credential Store is linked the received credential will not be persisted automatically.


Base URL

All v2 wallet receive endpoints follow this pattern:

/v2/{organization}.{tenant}.{wallet-id}/wallet-service-api/<path>
  • organization — your organization ID
  • tenant — the tenant the wallet belongs to
  • wallet-id — the ID of the wallet service instance

For example:

/v2/waltid.tenant1.wallet/wallet-service-api/credentials/receive/pre-authorized

Credential Offer URL

A credential offer URL is a standardized method, as per the OID4VCI specification, to communicate the issuance of credentials between issuer and wallet. This URL can take various forms, such as a QR code or a link, and generally begins with openid-credential-offer://.

Example Offer URL

openid-credential-offer://issuer.portal.walt.id/?credential_offer=<credential_offer>

Example of a Credential Offer Object

{
  "credential_issuer": "https://issuer.demo.walt.id",
  "credential_configuration_ids": [
    "identity_credential_vc+sd-jwt"
  ],
  "grants": {
    "authorization_code": {
      "issuer_state": "<issuer_state>"
    },
    "urn:ietf:params:oauth:grant-type:pre-authorized_code": {
      "pre-authorized_code": "<pre-authorized_code>",
      "user_pin_required": false
    }
  }
}

Pre-Authorized Flow

The pre-authorized flow is used when the issuer provides a pre-authorized_code in the credential offer. This flow is typically used for automated issuance or when the user has already authenticated with the issuer via another channel.

Full-Flow (Single Call)

Executes the entire pre-authorized code flow: resolve offer → fetch issuer metadata → request token → request credential → store credential.

CURL

Endpoint: /v2/{organization}.{tenant}.{wallet-id}/wallet-service-api/credentials/receive/pre-authorized | API Reference

Example Request

curl -X POST \
  'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials/receive/pre-authorized' \
  -H 'Authorization: Bearer {yourToken}' \
  -H 'Content-Type: application/json' \
  -d '{
  "offerUrl": "openid-credential-offer://issuer.example.org/issue/?credential_offer_uri=https%3A%2F%2Fissuer.example.org%2FcredentialOffer%3Fabc123xyz789",
  "keyReference": "org.tenant.wallet-id.key-id"
}'

Body Parameters — WalletReceiveRequest

  • offerUrl (optional*) String — OID4VCI credential offer URL (openid-credential-offer://...). Use this or offer.
  • offer (optional*) Object — Raw CredentialOffer JSON object. Alternative to offerUrl.
  • keyReference (optional*) String — Target reference (org.tenant.wallet.key-id) of a key stored in a linked KMS.
  • key (optional*) Object — Inline key object (JWK, TSE, AWS, OCI). Alternative to keyReference.
  • didReference (optional) String — Target reference of a DID stored in a linked DID Store.
  • did (optional) String — Inline DID string. Alternative to didReference.
  • txCode (optional) String — Transaction (PIN) code required by the issuer for pre-authorized flow.
  • runPolicies (optional) Boolean — When true, applies any configured holder policies before storing. null means policies run only if a match exists; false skips policies entirely.
  • useClientAttestation (optional) Boolean — When true, loads the stored WalletClientAttestation and sends it in the token request. Requires a client attestation to have been previously obtained.
  • metadata (optional) Object — Arbitrary JSON stored alongside the credential in the Credential Store.
  • trustedVicals (optional) Array — Whitelist of trusted VICAL artifacts for mDoc IACA certificate chain validation.

Response Codes

  • 201 — Credential(s) received successfully.

Body — List<ReceiveResponse>

[
  {
    "stored": "urn:uuid:224bcbc4-7436-4854-8ef1-cc6e3b8236bd",
    "credential": "eyJraWQiOiJ..."
  }
]
  • stored — The credential ID assigned in the Credential Store, or null if no store is linked.
  • credential — The raw issued credential string (JWT, SD-JWT, or base64-encoded mDoc).

Authorized Flow (Authorization Code)

The authorized flow is used when the user must authenticate with the issuer via an external authorization server (e.g., via a browser redirect). Because it involves a user redirect, the exchange is performed in steps.

Flow Overview

1. Resolve Offer          →  parse offer URL → CredentialOffer
2. Fetch Metadata         →  CredentialOffer → CredentialIssuerMetadata
3. View Offered Creds     →  ResolvedOfferWithMetadata → List<OfferedCredential>
4. Generate Auth URL      →  build authorization URL → redirect user to browser
   ── user authenticates in browser ──
5. Exchange Code          →  authorization_code → TokenResponse (access_token)
6. Receive Credential     →  access_token + key → stored credential

Step 1 — Resolve Credential Offer

Parse the raw offer URL into a structured CredentialOffer object.

Required permission: ISOLATED_RESOLVE_OFFER

CURL

Endpoint: POST /v2/{org}.{tenant}.{wallet-id}/wallet-service-api/isolated/offer/resolve | API Reference

Example Request

curl -X POST \
  'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/isolated/offer/resolve' \
  -H 'Authorization: Bearer {yourToken}' \
  -H 'Content-Type: text/plain' \
  -d 'openid-credential-offer://issuer.example.org/issue/?credential_offer_uri=https%3A%2F%2Fissuer.example.org%2FcredentialOffer%3Fabc123xyz789'

The request body is the raw offer URL as a plain string.

Response — CredentialOffer

{
  "credential_issuer": "https://issuer.example.org",
  "credential_configuration_ids": [
    "identity_credential_vc+sd-jwt"
  ],
  "grants": {
    "authorization_code": {
      "issuer_state": "eyJhbGciOiJSU..."
    }
  }
}

Save the full CredentialOffer object — it is used as input for Steps 2, 3, and 4.


Step 2 — Fetch Issuer Metadata

Retrieve the issuer's CredentialIssuerMetadata, which describes supported credential formats, cryptographic binding methods, and display information.

Required permission: ISOLATED_PROVIDER_METADATA

CURL

Endpoint: POST /v2/{org}.{tenant}.{wallet-id}/wallet-service-api/isolated/metadata/view | API Reference

Example Request

curl -X POST \
  'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/isolated/metadata/view' \
  -H 'Authorization: Bearer {yourToken}' \
  -H 'Content-Type: application/json' \
  -d '{
  "credential_issuer": "https://issuer.example.org",
  "credential_configuration_ids": ["identity_credential_vc+sd-jwt"],
  "grants": {
    "authorization_code": {
      "issuer_state": "eyJhbGciOiJSU..."
    }
  }
}'

The request body is the CredentialOffer object returned from Step 1.

Response — CredentialIssuerMetadata

{
  "credential_issuer": "https://issuer.example.org",
  "authorization_servers": ["https://auth.example.org"],
  "credential_endpoint": "https://issuer.example.org/credential",
  "credential_configurations_supported": {
    "identity_credential_vc+sd-jwt": {
      "format": "vc+sd-jwt",
      "vct": "https://issuer.example.org/identity_credential",
      "cryptographic_binding_methods_supported": ["jwk"],
      "credential_signing_alg_values_supported": ["ES256", "EdDSA"],
      "display": [
        {
          "name": "Identity Credential",
          "locale": "en-US"
        }
      ]
    }
  }
}

Step 3 — View Offered Credentials

List the credentials being offered, with full display and format details. Use this step to show the user a consent screen.

Required permission: ISOLATED_OFFER_CREDENTIALS

CURL

Endpoint: POST /v2/{org}.{tenant}.{wallet-id}/wallet-service-api/isolated/offer/credentials/view | API Reference

Example Request

curl -X POST \
  'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/isolated/offer/credentials/view' \
  -H 'Authorization: Bearer {yourToken}' \
  -H 'Content-Type: application/json' \
  -d '{
  "offer": {
    "credential_issuer": "https://issuer.example.org",
    "credential_configuration_ids": ["identity_credential_vc+sd-jwt"],
    "grants": {
      "authorization_code": {
        "issuer_state": "eyJhbGciOiJSU..."
      }
    }
  },
  "metadata": {
    "credential_issuer": "https://issuer.example.org",
    "credential_configurations_supported": {
      "identity_credential_vc+sd-jwt": {
        "format": "vc+sd-jwt",
        "vct": "https://issuer.example.org/identity_credential",
        "cryptographic_binding_methods_supported": ["jwk"],
        "display": [{ "name": "Identity Credential", "locale": "en-US" }]
      }
    }
  }
}'

The request body is a ResolvedOfferWithMetadata object combining the CredentialOffer (Step 1) and the CredentialIssuerMetadata (Step 2).

Response — List<OfferedCredential>

[
  {
    "id": "identity_credential_vc+sd-jwt",
    "format": "vc+sd-jwt",
    "vct": "https://issuer.example.org/identity_credential",
    "cryptographic_binding_methods_supported": ["jwk"],
    "display": [
      {
        "name": "Identity Credential",
        "locale": "en-US",
        "background_color": "#12107c",
        "text_color": "#FFFFFF"
      }
    ]
  }
]

Step 4 — Generate Authorization URL

Build the URL to which the user must be redirected to authenticate with the issuer's authorization server.

Required permission: ISOLATED_TOKEN_SEND_REQUEST

CURL

Endpoint: POST /v2/{org}.{tenant}.{wallet-id}/wallet-service-api/isolated/authorization-url/generate | API Reference

Example Request

curl -X POST \
  'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/isolated/authorization-url/generate' \
  -H 'Authorization: Bearer {yourToken}' \
  -H 'Content-Type: application/json' \
  -d '{
  "offer": "openid-credential-offer://issuer.example.org/issue/?credential_offer_uri=https%3A%2F%2Fissuer.example.org%2FcredentialOffer%3Fabc123xyz789",
  "redirectUri": "https://your-wallet-app.example.com/callback"
  }'

Body Parameters — ResolveAuthorizationUrlRequestV2

  • offer String — The original OID4VCI offer URL.
  • redirectUri String — The URI to which the authorization server will redirect the user after authentication. Must match a registered redirect URI for your client.

Response — Authorization URL

{
  "url": "https://auth.example.org/authorize?response_type=code&client_id=your-wallet-client-id&redirect_uri=https%3A%2F%2Fyour-wallet-app.example.com%2Fcallback&scope=identity_credential_vc%2Bsd-jwt&issuer_state=eyJhbGciOiJSU...&code_challenge=abc123&code_challenge_method=S256",
  "state": "abc123",
  "pkceData": {
    "codeVerifier": "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM",
    "codeChallenge": "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM",
    "codeChallengeMethod": "S256"
  }
}
  • url — Redirect the user to this URL to begin authentication.
  • codeVerifier — PKCE code verifier. Store this securely — you need it in Step 5 to exchange the code.

After redirecting the user, your app receives an authorization code at redirectUri as a query parameter: ?code=<authorization_code>&state=<state>.


Step 5 — Exchange Authorization Code for Token

Exchange the authorization code received from the redirect for an access token.

Required permission: ISOLATED_TOKEN_SEND_REQUEST

CURL

Endpoint: POST /v2/{org}.{tenant}.{wallet-id}/wallet-service-api/isolated/token-request/exchange-code | API Reference

Example Request

curl -X POST \
  'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/isolated/token-request/exchange-code' \
  -H 'Authorization: Bearer {yourToken}' \
  -H 'Content-Type: application/json' \
  -d '{
  "metadata": {
    "credential_issuer": "https://issuer.example.org",
    "credential_endpoint": "https://issuer.example.org/credential",
    "credential_configurations_supported": {
      "NaturalPersonVerifiableID_jwt_vc": {
        "format": "jwt_vc_json",
        "credential_definition": {
          "type": [
            "VerifiableCredential",
            "VerifiableAttestation",
            "NaturalPersonVerifiableID"
          ]
        },
        "credential_signing_alg_values_supported": [
          "ES256",
          "ES256K"
        ],
        "cryptographic_binding_methods_supported": [
          "jwk"
        ],
        "proof_types_supported": {
          "jwt": {
            "proof_signing_alg_values_supported": [
              "ES256",
              "ES256K",
              "EdDSA"
            ]
          }
        }
      }
    }
  },
  "code": "SplxlOBeZQQYbYS6WxSbIA",
  "pkceVerifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk",
  "redirectUri": "http://localhost:3000/isolated/credentials/receive"
}'

Body Parameters — ExchangeTokenWithAuthorizationCodeRequest

  • metadata Object — The CredentialIssuerMetadata obtained in Step 2. This is required to determine the correct token endpoint and client authentication method for the token request.
  • redirectUri String — Must exactly match the redirectUri used in Step 4.
  • code String — The authorization code received from the redirect callback.
  • codeVerifier String — The PKCE code verifier saved from Step 4.

Response — TokenResponse

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 300,
  "c_nonce": "tZignsnFbp",
  "c_nonce_expires_in": 86400
}
  • access_token — Pass this to Step 6 to request the credential.
  • c_nonce — Issuer-provided nonce. The wallet must sign a proof of possession using this nonce when requesting the credential.

Step 6 — Receive Credential

Request the actual credential from the issuer using the access token obtained in Step 5.

Required permission: ISOLATED_RECEIVE_CREDENTIAL

CURL

Endpoint: POST /v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credential/receive/authorized | API Reference

Example Request

curl -X POST \
  'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credential/receive/authorized' \
  -H 'Authorization: Bearer {yourToken}' \
  -H 'Content-Type: application/json' \
  -d '{
  "credentialEndpoint": "https://issuer.example.org/credential",
  "tokenResponse": {
    "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
    "token_type": "Bearer",
    "c_nonce": "8xFQ5j9Q8L-3PUdnl4RWxXgB7oNc3mK2pL9vR6tY1zA="
  },
  "credentialConfigurationId": "NaturalPersonVerifiableID_jwt_vc",
  "didReference": "example.didstore.wallet_did",
  "keyReference": "example.kms.wallet_key",
  "metadata": {
    "credential_issuer": "https://issuer.example.org",
    "credential_endpoint": "https://issuer.example.org/credential",
    "credential_configurations_supported": {
      "NaturalPersonVerifiableID_jwt_vc": {
        "format": "jwt_vc_json",
        "credential_definition": {
          "type": [
            "VerifiableCredential",
            "VerifiableAttestation",
            "NaturalPersonVerifiableID"
          ]
        }
      }
    }
  }
}'

Body Parameters — ReceiveCredentialNewRequest

  • credentialEndpoint String — The issuer's credential endpoint URL, obtained from the CredentialIssuerMetadata.
  • tokenResponse Object — The full TokenResponse obtained in Step 5. The wallet needs the access_token to request the credential and the c_nonce to generate the required proof of possession.
  • credentialConfigurationId String — The ID of the credential configuration being requested, as listed in the CredentialOffer and CredentialIssuerMetadata. This tells the wallet which credential format to expect and how to request it.
  • keyReference / key — Key reference or inline key for the holder's proof of possession.
  • didReference / did — DID reference or inline DID for the holder's proof of possession. Required if the issuer's supported binding methods include a DID-based method.
  • metadata (optional) Object — Arbitrary JSON stored alongside the credential in the Credential Store.

Response Codes

  • 201 — Credential(s) received and stored successfully.

Body — List<ReceiveResponse>

[
  {
    "stored": "urn:uuid:224bcbc4-7436-4854-8ef1-cc6e3b8236bd",
    "credential": "eyJraWQiOiJ..."
  }
]
  • stored — The credential ID assigned in the Credential Store, or null if no store is linked.
  • credential — The raw issued credential string (JWT, SD-JWT, or base64-encoded mDoc).

Key Holder Reference

Both the full-flow and isolated credential-receive endpoints accept a holder key in one of three ways:

MethodFieldsWhen to use
KMS referencekeyReference: "org.tenant.kms.key-id"Key stored in a linked KMS Service
DID referencedidReference: "org.tenant.didstore.did-id"DID stored in a linked DID Store Service
Inlinekey: { "type": "jwk", "jwk": {...} } / did: "did:key:..."One-off testing or external key management

Learn more about key types (JWK, TSE, AWS KMS, HashiCorp Vault) in the KMS Service documentation.

Last updated on May 8, 2026