Accept 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 the OAuth-Client-Attestation and OAuth-Client-Attestation-PoP headers on the token request. Set this when the issuer’s authorization server metadata lists attest_jwt_client_auth in token_endpoint_auth_methods_supported (attestation is configured on the issuer via clientAuthenticationConfig). Requires a Client Attestation Service to be linked to the wallet and an attestation to have been previously obtained. Omit or set to false when the issuer does not advertise attestation (for example anonymous pre-authorized code only, with pre_authorized_grant_anonymous_access_supported and no attest_jwt_client_auth).
  • 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 14, 2026