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.
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 namedtest, your default Base URL will betest.enterprise-sandbox.walt.devwhen 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
offerUrlString - 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, oroci-rest-apiwhen 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:z6MktjQeU9dZp8VfK8M9N8Y2u5wV6JzZ2v6s6Q1P7pRkFJ8Hmetadata(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 totrue, the wallet service will run any holder policies that are attached to the wallet service before storing the credential. If set tofalse, 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": {}
}
}
