Receive & Store W3C Verifiable Credentials via OID4VCI

In this guide, we take a look at how you can use the walt.id Enterprise wallet service API to receive & store 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.

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

{
  "credential_issuer": "https://issuer.portal.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

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

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.eyJpc3MiOiJkaWQ6a2V5Ono2TWtqb1JocTFqU05KZExpcnVTWHJGRnhhZ3FyenRaYVhIcUhHVVRLSmJjTnl3cCIsInN1YiI6IiIsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIiwiaHR0cHM6Ly9wdXJsLmltc2dsb2JhbC5vcmcvc3BlYy9vYi92M3AwL2NvbnRleHQuanNvbiJdLCJpZCI6InVybjp1dWlkOjIyNGJjYmM0LTc0MzYtNDg1NC04ZWYxLWNjNmUzYjgyMzZiZCIsInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJPcGVuQmFkZ2VDcmVkZW50aWFsIl0sIm5hbWUiOiJKRkYgeCB2Yy1lZHUgUGx1Z0Zlc3QgMyBJbnRlcm9wZXJhYmlsaXR5IiwiaXNzdWVyIjp7InR5cGUiOlsiUHJvZmlsZSJdLCJuYW1lIjoiSm9icyBmb3IgdGhlIEZ1dHVyZSAoSkZGKSIsInVybCI6Imh0dHBzOi8vd3d3LmpmZi5vcmcvIiwiaW1hZ2UiOiJodHRwczovL3czYy1jY2cuZ2l0aHViLmlvL3ZjLWVkL3BsdWdmZXN0LTEtMjAyMi9pbWFnZXMvSkZGX0xvZ29Mb2NrdXAucG5nIiwiaWQiOiJkaWQ6a2V5Ono2TWtqb1JocTFqU05KZExpcnVTWHJGRnhhZ3FyenRaYVhIcUhHVVRLSmJjTnl3cCJ9LCJjcmVkZW50aWFsU3ViamVjdCI6eyJ0eXBlIjpbIkFjaGlldmVtZW50U3ViamVjdCJdLCJhY2hpZXZlbWVudCI6eyJpZCI6InVybjp1dWlkOmFjMjU0YmQ1LThmYWQtNGJiMS05ZDI5LWVmZDkzODUzNjkyNiIsInR5cGUiOlsiQWNoaWV2ZW1lbnQiXSwibmFtZSI6IkpGRiB4IHZjLWVkdSBQbHVnRmVzdCAzIEludGVyb3BlcmFiaWxpdHkiLCJkZXNjcmlwdGlvbiI6IlRoaXMgd2FsbGV0IHN1cHBvcnRzIHRoZSB1c2Ugb2YgVzNDIFZlcmlmaWFibGUgQ3JlZGVudGlhbHMgYW5kIGhhcyBkZW1vbnN0cmF0ZWQgaW50ZXJvcGVyYWJpbGl0eSBkdXJpbmcgdGhlIHByZXNlbnRhdGlvbiByZXF1ZXN0IHdvcmtmbG93IGR1cmluZyBKRkYgeCBWQy1FRFUgUGx1Z0Zlc3QgMy4iLCJjcml0ZXJpYSI6eyJ0eXBlIjoiQ3JpdGVyaWEiLCJuYXJyYXRpdmUiOiJXYWxsZXQgc29sdXRpb25zIHByb3ZpZGVycyBlYXJuZWQgdGhpcyBiYWRnZSBieSBkZW1vbnN0cmF0aW5nIGludGVyb3BlcmFiaWxpdHkgZHVyaW5nIHRoZSBwcmVzZW50YXRpb24gcmVxdWVzdCB3b3JrZmxvdy4gVGhpcyBpbmNsdWRlcyBzdWNjZXNzZnVsbHkgcmVjZWl2aW5nIGEgcHJlc2VudGF0aW9uIHJlcXVlc3QsIGFsbG93aW5nIHRoZSBob2xkZXIgdG8gc2VsZWN0IGF0IGxlYXN0IHR3byB0eXBlcyBvZiB2ZXJpZmlhYmxlIGNyZWRlbnRpYWxzIHRvIGNyZWF0ZSBhIHZlcmlmaWFibGUgcHJlc2VudGF0aW9uLCByZXR1cm5pbmcgdGhlIHByZXNlbnRhdGlvbiB0byB0aGUgcmVxdWVzdG9yLCBhbmQgcGFzc2luZyB2ZXJpZmljYXRpb24gb2YgdGhlIHByZXNlbnRhdGlvbiBhbmQgdGhlIGluY2x1ZGVkIGNyZWRlbnRpYWxzLiJ9LCJpbWFnZSI6eyJpZCI6Imh0dHBzOi8vdzNjLWNjZy5naXRodWIuaW8vdmMtZWQvcGx1Z2Zlc3QtMy0yMDIzL2ltYWdlcy9KRkYtVkMtRURVLVBMVUdGRVNUMy1iYWRnZS1pbWFnZS5wbmciLCJ0eXBlIjoiSW1hZ2UifX19LCJpc3N1YW5jZURhdGUiOiIyMDI1LTA0LTAzVDA3OjM0OjEzLjUyMjQzMjc1M1oiLCJleHBpcmF0aW9uRGF0ZSI6IjIwMjYtMDQtMDNUMDc6MzQ6MTMuNTIyNTM1MjUzWiJ9LCJqdGkiOiJ1cm46dXVpZDoyMjRiY2JjNC03NDM2LTQ4NTQtOGVmMS1jYzZlM2I4MjM2YmQiLCJleHAiOjE3NzUyMDE2NTMsImlhdCI6MTc0MzY2NTY1MywibmJmIjoxNzQzNjY1NjUzfQ.eu7Owii99m9DGT_L9PPpdOxcjxpBN1CMsPoTvVtMD0WTsatn5c8MCIrr3NFM74CTbVrhe-NAOFseyC9QrFGdDw",
      "customParameters": {}
    }
}