---
title: "How to Accept W3C Verifiable Credentials via OID4VCI in a Wallet with walt.id"
description: "Accept W3C verifiable credentials in the walt.id wallet via OID4VCI—learn offer URLs, a simple API call, and code to review and store incoming credentials."
stack: "Community Stack (open source) — version 0.21.0"
stack_version: "0.21.0"
stack_comparison: https://docs.walt.id/community-vs-enterprise.md
canonical_url: https://docs.walt.id/community-stack/wallet/credential-exchange/guides/accept-w3c-vc-oid4vci
generated: 2026-07-20
---
# How to Accept W3C Verifiable Credentials (JWT / SD-JWT) via OID4VCI in a Wallet with walt.id

**TL;DR**

**What you:**

- Parse and process credential offer URLs in OID4VCI format
- Accept credential offers via the Wallet API endpoint
- Display credential details to users before acceptance
- Handle credential offers from QR codes, links, or manual input

**Relevant concepts:**

- [OpenID4VCI](https://docs.walt.id/concepts/data-exchange-protocols/openid4vci.md) – Protocol for verifiable credential issuance
- [Verifiable Credentials (W3C)](https://docs.walt.id/concepts/digital-credentials/verifiable-credentials-w3c.md) – W3C standard for digital credentials

Video: https://youtu.be/tyNv9uPn9HM

There are several methods by which a user can receive a credential into their wallet, and all are supported by the
walt.id wallet API:

1. User may receive a direct link, which opens the wallet and prompts credential acceptance.
2. A website may display a QR code for the user to scan and accept the credential.
3. The user may manually input an offer URL string into a text field.

Irrespective of the chosen method, these are just different ways of receiving, parsing and fulfilling a request URL.

## 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**

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

Within the URL, a query parameter `credential_offer` provides the actual offer as a JSON object.

**Example of a Credential Offer Object**

```json
{
  "credential_issuer": "https://issuer.demo.walt.id",
  // This is the URL of the issuer of the credential(s)
  "credentials": [
    // This array contains details about the types of credentials being offered
    {
      "format": "jwt_vc_json",
      // Specifies the format of the credential
      "types": [
        // An array indicating the credential types
        "VerifiableCredential",
        "BankId"
      ],
      "credential_definition": {
        "@context": [
          // Provides the contexts of the credential
          "https://www.w3.org/2018/credentials/v1"
        ],
        "types": [
          // Reiterates the type of credential being offered.
          "VerifiableCredential",
          "BankId"
        ]
      }
    }
  ],
  "grants": {
    // Specifies how the credentials can be obtained
    "authorization_code": {
      "issuer_state": "<issuer_state>"
      // Contains an issuer_state, a unique identifier for the grant
    },
    "urn:ietf:params:oauth:grant-type:pre-authorized_code": {
      // Provides details for a pre-authorized code grant type,
      //including the actual pre-authorized_code and a flag user_pin_required indicating whether a PIN is required
      // for user authentication.
      "pre-authorized_code": "<pre-authorized_code>",
      "user_pin_required": false
    }
  }
}
```

## Accepting Credential Offers

To accept a credential via the credential offer URL via the wallet API, please follow the steps below.

**Option: CURL**

**Endpoint:** `/wallet-api/wallet/{walletId}/exchange/useOfferRequest` | [API Reference](https://wallet.demo.walt.id/swagger/index.html#/Credential%20exchange/post_wallet_api_wallet__wallet__exchange_useOfferRequest)

**Example Request**

```bash
curl -X 'POST' \
  'http://0.0.0.0:7001/wallet-api/wallet/{walletId}/exchange/useOfferRequest?did={did}' \
  -H 'accept: application/json' \
  -H 'Content-Type: text/plain' \
  -H 'authorization: Bearer {token}' \
  -d 'openid-credential-offer://issuer.portal.walt.id/?credential_offer=<credential_offer>'
```

**Path Parameters**

- `walletId`: _String_ - The ID of the wallet to receive the credential into. See [Accounts & Wallets](https://docs.walt.id/community-stack/wallet/accounts-wallets.md#retrieve-wallet-id) for how to retrieve it.

**Query Parameters**

- `did` (optional): _String_ - The DID of the wallet holder to use when accepting the credential. If omitted, the wallet's default DID is used.

**Header Parameters**

- `authorization`: _String_ - Bearer token obtained from the [login endpoint](https://docs.walt.id/community-stack/wallet/authentication/email-password-auth.md#login). Format: `Bearer {token}`.

**Body**

The credential offer URL received from the issuer, provided as plain text.

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

**Note:**

  When using the terminal, the credential offer URL returned from the issuer may have a `%` at the end. Make sure to remove this before passing it to the wallet.

---

**Example Response**

On success, the API returns a list of the credentials that were received and stored in the wallet.

```json
[
  {
    "wallet": "6006b6f4-e651-46db-b6ae-e7bd6e9c40f2",
    "id": "urn:uuid:c2f7e988-a4cb-48c2-bbf6-ed97f6010abe",
    "document": "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaWFVcE5VelZpYTFwV1NXeHVZMlp4WDB4bVgxTjFlRW95U25SUk5VaDJZWG8zZEZkUWJrRnFWVlZrY3lJc0luZ2lPaUpHV21SMmQwTTRZVWRvVW5keGVsZHdkR1ZxTUU1YVozUjNXVUZKTVZONVJtY3hiVXRFUlZSUFpuRkZJbjAjaUpNUzVia1pWSWxuY2ZxX0xmX1N1eEoySnRRNUh2YXo3dFdQbkFqVVVkcyIsInR5cCI6IkpXVCIsImFsZyI6IkVkRFNBIn0.eyJpc3MiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaWFVcE5VelZpYTFwV1NXeHVZMlp4WDB4bVgxTjFlRW95U25SUk5VaDJZWG8zZEZkUWJrRnFWVlZrY3lJc0luZ2lPaUpHV21SMmQwTTRZVWRvVW5keGVsZHdkR1ZxTUU1YVozUjNXVUZKTVZONVJtY3hiVXRFUlZSUFpuRkZJbjAiLCJzdWIiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pYWxvMFdsRkhhbXBuTFhwWVJtWkdORnBtYmxkZlpEaFhZa2xqWjBWWVNXOXVOR2hKYlhoaFl6UmlaeUlzSW5naU9pSnVNVU5vUVROSlNVMVNka1ZOU201MVYwNTVkalUwYlRWclpVUmhheTFsZUhOME4zRjZlWGRQVG1aRklpd2llU0k2SWw5cVdWTjZVMTlEYjNSUVgyZE1NR3hpVlVOeVpXVk9UbVU0TVhJd2VIRXpNbGxsU2pKME5HMXhibGtpZlEiLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL2V4YW1wbGVzL3YxIl0sImlkIjoidXJuOnV1aWQ6YzJmN2U5ODgtYTRjYi00OGMyLWJiZjYtZWQ5N2Y2MDEwYWJlIiwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIlVuaXZlcnNpdHlEZWdyZWUiXSwiaXNzdWVyIjp7ImlkIjoiZGlkOmp3azpleUpyZEhraU9pSlBTMUFpTENKamNuWWlPaUpGWkRJMU5URTVJaXdpYTJsa0lqb2lhVXBOVXpWaWExcFdTV3h1WTJaeFgweG1YMU4xZUVveVNuUlJOVWgyWVhvM2RGZFFia0ZxVlZWa2N5SXNJbmdpT2lKR1dtUjJkME00WVVkb1VuZHhlbGR3ZEdWcU1FNWFaM1IzV1VGSk1WTjVSbWN4YlV0RVJWUlBabkZGSW4wIn0sImlzc3VhbmNlRGF0ZSI6IjIwMjYtMDQtMTZUMTU6MDg6MDEuNTczMzg0MTA1WiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmp3azpleUpyZEhraU9pSkZReUlzSW1OeWRpSTZJbEF0TWpVMklpd2lhMmxrSWpvaWFsbzBXbEZIYW1wbkxYcFlSbVpHTkZwbWJsZGZaRGhYWWtsalowVllTVzl1TkdoSmJYaGhZelJpWnlJc0luZ2lPaUp1TVVOb1FUTkpTVTFTZGtWTlNtNTFWMDU1ZGpVMGJUVnJaVVJoYXkxbGVITjBOM0Y2ZVhkUFRtWkZJaXdpZVNJNklsOXFXVk42VTE5RGIzUlFYMmRNTUd4aVZVTnlaV1ZPVG1VNE1YSXdlSEV6TWxsbFNqSjBORzF4YmxraWZRIiwiZGVncmVlIjp7InR5cGUiOiJCYWNoZWxvckRlZ3JlZSIsIm5hbWUiOiJCYWNoZWxvciBvZiBTY2llbmNlIGFuZCBBcnRzIn19LCJleHBpcmF0aW9uRGF0ZSI6IjIwMjctMDQtMTZUMTU6MDg6MDEuNTczNDA3ODA1WiJ9LCJqdGkiOiJ1cm46dXVpZDpjMmY3ZTk4OC1hNGNiLTQ4YzItYmJmNi1lZDk3ZjYwMTBhYmUiLCJleHAiOjE4MDc4ODgwODEsImlhdCI6MTc3NjM1MjA4MSwibmJmIjoxNzc2MzUyMDgxfQ.0pWxCrYF4LMMtzzFOnBx4kSDFTd7AZSYXfRnAA8IgJ1OSLnFXRjWsXnsL78VLOjxkEGmO8Paei3ewq2vDNn9CQ",
    "disclosures": "",
    "addedOn": "2026-04-16T15:08:01.604768543Z",
    "pending": false,
    "format": "jwt_vc_json",
    "parsedDocument": {
      "@context": [
        "https://www.w3.org/2018/credentials/v1",
        "https://www.w3.org/2018/credentials/examples/v1"
      ],
      "id": "urn:uuid:c2f7e988-a4cb-48c2-bbf6-ed97f6010abe",
      "type": [
        "VerifiableCredential",
        "UniversityDegree"
      ],
      "issuer": {
        "id": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiaUpNUzVia1pWSWxuY2ZxX0xmX1N1eEoySnRRNUh2YXo3dFdQbkFqVVVkcyIsIngiOiJGWmR2d0M4YUdoUndxeldwdGVqME5aZ3R3WUFJMVN5RmcxbUtERVRPZnFFIn0"
      },
      "issuanceDate": "2026-04-16T15:08:01.573384105Z",
      "credentialSubject": {
        "id": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoialo0WlFHampnLXpYRmZGNFpmbldfZDhXYkljZ0VYSW9uNGhJbXhhYzRiZyIsIngiOiJuMUNoQTNJSU1SdkVNSm51V055djU0bTVrZURhay1leHN0N3F6eXdPTmZFIiwieSI6Il9qWVN6U19Db3RQX2dMMGxiVUNyZWVOTmU4MXIweHEzMlllSjJ0NG1xblkifQ",
        "degree": {
          "type": "BachelorDegree",
          "name": "Bachelor of Science and Arts"
        }
      },
      "expirationDate": "2027-04-16T15:08:01.573407805Z"
    }
  }
]
```

## Example: Display Credentials to User Before Acceptance

Before calling the API, you may want to show the user which credentials they are about to accept. In JavaScript,
assuming you have the offer URL and a `decodeOfferURL(offerURL)` function that returns the parsed offer object:

```js
var offerURL = "openid-credential-offer://...";

// Extract the offer object from the URL
var offerObject = decodeOfferURL(offerURL);

// Map credentials to a display-friendly format
var credentialDetails = offerObject["credentials"].map((credential) => {
  var lastType = credential.types[credential.types.length - 1];
  return {
    format: credential.format,
    type: lastType,
  };
});

// Display credential details to the user
credentialDetails.forEach((credential) => {
  console.log(`Format: ${credential.format}`);
  console.log(`Type: ${credential.type}`);
});
```

The user would then see a list of credentials that will be received and can confirm whether they want to proceed
with accepting the offer.

**Note:**

The above example assumes `decodeOfferURL(offerURL)` is implemented. In a real application, use a URL parsing
library, handle JSON decoding, and add appropriate error handling.
