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:
| Mode | When to use |
|---|---|
| Full-flow (single call) | You want the wallet to handle the entire OID4VCI exchange in one request. |
| Isolated step-by-step | You 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 IDtenant— the tenant the wallet belongs towallet-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.
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 oroffer.offer(optional*) Object — RawCredentialOfferJSON object. Alternative toofferUrl.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 tokeyReference.didReference(optional) String — Target reference of a DID stored in a linked DID Store.did(optional) String — Inline DID string. Alternative todidReference.txCode(optional) String — Transaction (PIN) code required by the issuer for pre-authorized flow.runPolicies(optional) Boolean — Whentrue, applies any configured holder policies before storing.nullmeans policies run only if a match exists;falseskips policies entirely.useClientAttestation(optional) Boolean — Whentrue, loads the storedWalletClientAttestationand sends theOAuth-Client-AttestationandOAuth-Client-Attestation-PoPheaders on the token request. Set this when the issuer’s authorization server metadata listsattest_jwt_client_authintoken_endpoint_auth_methods_supported(attestation is configured on the issuer viaclientAuthenticationConfig). Requires a Client Attestation Service to be linked to the wallet and an attestation to have been previously obtained. Omit or set tofalsewhen the issuer does not advertise attestation (for example anonymous pre-authorized code only, withpre_authorized_grant_anonymous_access_supportedand noattest_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, ornullif 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
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
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
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
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
offerString — The original OID4VCI offer URL.redirectUriString — 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
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
metadataObject — TheCredentialIssuerMetadataobtained in Step 2. This is required to determine the correct token endpoint and client authentication method for the token request.redirectUriString — Must exactly match theredirectUriused in Step 4.codeString — The authorization code received from the redirect callback.codeVerifierString — 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
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
credentialEndpointString — The issuer's credential endpoint URL, obtained from theCredentialIssuerMetadata.tokenResponseObject — The fullTokenResponseobtained in Step 5. The wallet needs theaccess_tokento request the credential and thec_nonceto generate the required proof of possession.credentialConfigurationIdString — The ID of the credential configuration being requested, as listed in theCredentialOfferandCredentialIssuerMetadata. 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, ornullif 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:
| Method | Fields | When to use |
|---|---|---|
| KMS reference | keyReference: "org.tenant.kms.key-id" | Key stored in a linked KMS Service |
| DID reference | didReference: "org.tenant.didstore.did-id" | DID stored in a linked DID Store Service |
| Inline | key: { "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.
