Accept W3C Verifiable Credentials (JWT) via OID4VCI

In this guide, we take a look at how you can use the walt.id Enterprise wallet service API to accept a W3C Verifiable Credential from an issuer via OID4VCI.

Ensure you have set up a wallet (service) and linked the necessary enterprise services (e.g., Credential Store) before proceeding with this guide.


This guide refers specifically to W3C Verifiable Credential standard without selective disclosure. It is possible to wrap a W3C Verifiable Credential within an SD-JWT in order to support selective disclosure. To understand more, check out the Issue W3C Credentials (JWT / SD-JWT) via OID4VC guide.

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>

Within the URL, a query parameter credential_offer or credential_offer_uri provides either the actual offer as a JSON object or a URL that points to the JSON object.

Example of a Credential Offer Object for JWT VCs

{
  "credential_issuer": "https://issuer.demo.walt.id",
  "credentials": [
    {
      "format": "jwt_vc_json",
      "types": [
        "VerifiableCredential",
        "BankId"
      ],
      "credential_definition": {
        "@context": [
          "https://www.w3.org/2018/credentials/v1"
        ],
        "types": [
          "VerifiableCredential",
          "BankId"
        ]
      }
    }
  ],
  "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
    }
  }
}

Accepting Credential Offers

In the next step, we will provide the credential offer URL to a wallet service endpoint. This will initialize the exchange between wallet and issuer. The response of the call will contain the received credential, which will also be stored in the linked credential store. In case there is no linked the credential store, the credential won't be stored and must be saved otherwise if later retrieval is wanted.

If there is no credential stored linked with the wallet the received credential won't be persisted.


CURL

Endpoint: /v1/{target}/wallet-service-api/credentials/receive | API Reference

Example Request

curl -X 'POST' \
  'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/wallet-service-api/credentials/receive' \
  -H 'accept: */*' \
  -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": "example.kms.key1",
  "didReference": "example.didstore.did1"
}'

Path Parameters

  • orgID: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named test, your default Base URL will be test.enterprise-sandbox.walt.dev when using the sandbox environment.
  • target: resourceIdentifier - The target indicates the organization + tenant + wallet which should be used to receive the credential ({organizationID}.{tenantID}.[walletID]), e.g. waltid.tenant1.wallet1

Body

{
  "offerUrl": "openid-credential-offer://issuer.example.org/issue/?credential_offer_uri=https%3A%2F%2Fissuer.example.org%2FcredentialOffer%3Fabc123xyz789",
  "keyReference": "example.kms.key1",
  "didReference": "example.didstore.did1"
}

Body Parameters

  • offerUrl String - An OID4VCI offer URL.
  • keyReference (optional) String - The resource ID (target) of the key to which the received credential should be bound. Key can be stored in a linked KMS store, provided as a static key as described in the setup section, or just a raw Key object. See example by expanding.
  • didReference (optional) String - The DID reference of a DID stored in a linked DID Store.
  • key (optional) Object - A key object or reference key object, when using an external KMS, to which the credential should be bound to.

    Click to expand and see full key example

    Key Object

     {
     "type": "jwk",
     "jwk": {
       "kty": "OKP",
       "d": "ywmoRVTD9fexMtGW0lKE3o9_0ulfzGXr9xHGL0lPhhA",
       "crv": "Ed25519",
       "kid": "IQO7DILxtagpTLXkuHkRkJURb2GqcUIwXYZAcGwW1AU",
       "x": "JOsiIE7ME9UZ8y2H-P5RSuYAUiIfs1ywtdBjMGN7I5s"
     }
    }
    

    This key object can also be of the types tse, aws, or oci-rest-api when using external KMS providers. In this case, instead of providing the raw key, an object containing reference information that allows the walt.id Enterprise Stack to access keys stored externally is provided. Learn more about different keys here.

  • did (optional) String - DID as a string, e.g. did:key:z6MktjQeU9dZp8VfK8M9N8Y2u5wV6JzZ2v6s6Q1P7pRkFJ8H
  • metadata (optional) Object - Arbitrary JSON object that will be stored together with the credential if there is a credential store linked with the wallet.
  • runPolicies (optional) Boolean - If set to true, the wallet service will run any holder policies that are attached to the wallet service before storing the credential. If set to false, no holder policies will be run.

Response Codes

  • 201 - Credential received successfully.

Body

{
    "offeredCredential": {
      "format": "jwt_vc_json",
      "credential_definition": {
        "type": [
          "VerifiableCredential",
          "OpenBadgeCredential"
        ]
      },
      "cryptographic_binding_methods_supported": [
        "did"
      ],
      "customParameters": {}
    },
    "credentialResponse": {
      "format": "jwt_vc_json",
      "credential": "eyJraWQiOiJkaWQ6a2V5Ono2TWtqb1JocTFqU05KZExpcnVTWHJGRnhhZ3FyenRaYVhIcUhHVVRLSmJjTnl3cCN6Nk1ram9SaHExalNOSmRMaXJ1U1hyRkZ4YWdxcnp0WmFYSHFIR1VUS0piY055d3AiLCJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtqb1JocTFqU05KZExpcnVTWHJGRnhhZ3FyenRaYVhIcUhHVVRLSmJjTnl3cCIsInN1YiI6IiIsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIiwiaHR0cHM6Ly9wdXJsLmltc2dsb2JhbC5vcmcvc3BlYy9vYi92M3AwL2NvbnRleHQuanNvbiJdLCJpZCI6InVybjp1dWlkOjIyNGJjYmM0LTc0MzYtNDg1NC04ZWYxLWNjNmUzYjgyMzZiZCIsInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJPcGVuQmFkZ2VDcmVkZW50aWFsIl0sIm5hbWUiOiJKRkYgeCB2Yy1lZHUgUGx1Z2Zlc3QgMyBJbnRlcm9wZXJhYmlsaXR5IiwiaXNzdWVyIjp7InR5cGUiOlsiUHJvZmlsZSJdLCJuYW1lIjoiSm9icyBmb3IgdGhlIEZ1dHVyZSAoSkZGKSIsInVybCI6Imh0dHBzOi8vd3d3LmpmZi5vcmcvIiwiaW1hZ2UiOiJodHRwczovL3czYy1jY2cuZ2l0aHViLmlvL3ZjLWVkL3BsdWdmZXN0LTEtMjAyMi9pbWFnZXMvSkZGX0xvZ29Mb2NrdXAucG5nIiwiaWQiOiJkaWQ6a2V5Ono2TWtqb1JocTFqU05KZExpcnVTWHJGRnhhZ3FyenRaYVhIcUhHVVRLSmJjTnl3cCJ9LCJjcmVkZW50aWFsU3ViamVjdCI6eyJ0eXBlIjpbIkFjaGlldmVtZW50U3ViamVjdCJdLCJhY2hpZXZlbWVudCI6eyJpZCI6InVybjp1dWlkOmFjMjU0YmQ1LThmYWQtNGJiMS05ZDI5LWVmZDkzODUzNjkyNiIsInR5cGUiOlsiQWNoaWV2ZW1lbnQiXSwibmFtZSI6IkpGRiB4IHZjLWVkdSBQbHVnRmVzdCAzIEludGVyb3BlcmFiaWxpdHkiLCJkZXNjcmlwdGlvbiI6IlRoaXMgd2FsbGV0IHN1cHBvcnRzIHRoZSB1c2Ugb2YgVzNDIFZlcmlmaWFibGUgQ3JlZGVudGlhbHMgYW5kIGhhcyBkZW1vbnN0cmF0ZWQgaW50ZXJvcGVyYWJpbGl0eSBkdXJpbmcgdGhlIHByZXNlbnRhdGlvbiByZXF1ZXN0IHdvcmtmbG93IGR1cmluZyBKRkYgeCBWQy1FRFUgUGx1Z2Zlc3QgMy4iLCJjcml0ZXJpYSI6eyJ0eXBlIjoiQ3JpdGVyaWEiLCJuYXJyYXRpdmUiOiJXYWxsZXQgc29sdXRpb25zIHByb3ZpZGVycyBlYXJuZWQgdGhpcyBiYWRnZSBieSBkZW1vbnN0cmF0aW5nIGludGVyb3BlcmFiaWxpdHkgZHVyaW5nIHRoZSBwcmVzZW50YXRpb24gcmVxdWVzdCB3b3JrZmxvdy4gVGhpcyBpbmNsdWRlcyBzdWNjZXNzZnVsbHkgcmVjZWl2aW5nIGEgcHJlc2VudGF0aW9uIHJlcXVlc3QsIGFsbG93aW5nIHRoZSBob2xkZXIgdG8gc2VsZWN0IGF0IGxlYXN0IHR3byB0eXBlcyBvZiB2ZXJpZmlhYmxlIGNyZWRlbnRpYWxzIHRvIGNyZWF0ZSBhIHZlcmlmaWFibGUgcHJlc2VudGF0aW9uLCByZXR1cm5pbmcgdGhlIHByZXNlbnRhdGlvbiB0byB0aGUgcmVxdWVzdG9yLCBhbmQgcGFzc2luZyB2ZXJpZmljYXRpb24gb2YgdGhlIHByZXNlbnRhdGlvbiBhbmQgdGhlIGluY2x1ZGVkIGNyZWRlbnRpYWxzLiJ9LCJpbWFnZSI6eyJpZCI6Imh0dHBzOi8vdzNjLWNjZy5naXRodWIuaW8vdmMtZWQvcGx1Z2Zlc3QtMy0yMDIzL2ltYWdlcy9KRkYtVkMtRURVLVBMVUdGRVNUMy1iYWRnZS1pbWFnZS5wbmciLCJ0eXBlIjoiSW1hZ2UifX19LCJpc3N1YW5jZURhdGUiOiIyMDI1LTA0LTAzVDA3OjM0OjEzLjUyMjQzMjc1M1oiLCJleHBpcmF0aW9uRGF0ZSI6IjIwMjYtMDQtMDNUMDc6MzQ6MTMuNTIyNTM1MjUzWiJ9LCJqdGkiOiJ1cm46dXVpZDoyMjRiY2JjNC03NDM2LTQ4NTQtOGVmMS1jYzZlM2I4MjM2YmQiLCJleHAiOjE3NzUyMDE2NTMsImlhdCI6MTc0MzY2NTY1MywibmJmIjoxNzQzNjY1NjUzfQ.eu7Owii99m9DGT_L9PPpdOxcjxpBN1CMsPoTvVtMD0WTsatn5c8MCIrr3NFM74CTbVrhe-NAOFseyC9QrFGdDw",
      "customParameters": {}
    }
}
Last updated on November 4, 2025