Present a W3C Verifiable Credentials (JWT/SD-JWT signature) via OID4VP
To follow this guide, you need a wallet (wallet-service) configured with an additional service like the credential store for storing received credentials. For more information on different setups, please go here.
There are several methods by which a user (wallet) can present a credential to a verifier, and all are supported by the wallet API:
- User may receive a direct link, which opens the wallet and prompts credential sharing.
- A website may display a QR code for the user to scan and share the credential.
- The user may manually input a credential presentation request URL string into a text field.
Irrespective of the chosen method, these are just different ways of receiving, parsing and fulfilling a credential presenting request.
Credential Authorization Request URL
The authorization URL is a standardised method, as per the OID4VP specification, to communicate the needed
credentials and claims from verifier to the wallet. The URL can take various forms, such as QR code or a link, and
generally begins with openid4vp://
or haip://
.
Example Presentation Request
openid4vp://authorize
?response_type=vp_token
&client_id=<client_id>
&response_mode=direct_post
&state=<state>
&presentation_definition=<presentation_definition>
&client_id_scheme=redirect_uri
&response_uri=<response_uri>
The most important parameter for us will be the presentation_definition
. The others are more important for internal
behaviour of the wallet, response types, formats and secure communication between wallet and verifier, which we don't
need to worry about for the moment as the wallet API will manage all of this for us.
The Presentation Definition
The presentation definition specifies the criteria for the wallet to know what credentials and claims should be requested from the user and shard with the verifier.
Example
{
"input_descriptors": [
{
"id": "VerifiableId",
"format": {
"jwt_vc_json": {
"alg": [
"EdDSA"
]
}
},
"constraints": {
"fields": [
{
"path": [
"$.type"
],
"filter": {
"type": "string",
"pattern": "VerifiableId"
}
}
]
}
}
]
}
The definition says that one credential is requested in the JWT format, specifically with the algorithm "EdDSA" and it must be of type "VerifiableId", as indicated by the constraint that filters for this specific string pattern in the credential's type field. You can find more info about definition documents here.
Steps To Fulfill Presentation Request
To fulfill the presentation request received by the verifier, we will need to perform the following steps:
- Find credentials matching the presentation definition in the linked credential store of the wallet.
- Present matched credentials
1. Find Credentials To Present
To locate the credentials that align with the presentation definition provided by the verifier, we will utilize the credential store API. When accessing the credential store API, it is essential to ensure that we are using the correct store associated with our wallet.
Endpoint: /v1/{target}/credential-store-service-api/credentials/query/document/schema
| API Reference
Example Request
curl -X 'POST' \
'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/credential-store-service-api/credentials/query/document/schema' \
-H 'accept: */*' \
-H 'Authorization: Bearer {yourToken}' \
-H 'Content-Type: application/json' \
-d '[
{
"path": [
"$.vc.type"
],
"filter": {
"type": "string",
"pattern": "BankId"
}
}
]'
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.dev
when using the sandbox environment.target
: resourceIdentifier - The target indicates the organization + tenant + credential store which should be used to query for credentials ({organizationID}.{tenantID}.[credentialStoreID]
), e.g.waltid.tenant1.credential-store1
Body
[
{
"path": [
"$.vc.type"
],
"filter": {
"type": "string",
"pattern": "BankId"
}
}
]
Body Parameters
- Array of
contraints.fields
- In the body of the request, we provide an array containing the values for eachconstraints.fields
entry in allinput_descriptors
objects of the presentation definition provided in the Authorization Request. For example, the presentation definition for the body provided above looks as follows:
{
"id": "ANREjIRDBnb",
"input_descriptors": [
{
"id": "BankId",
"format": {
"jwt_vc_json": {
"alg": [
"EdDSA"
]
}
},
"constraints": {
"fields": [
// ⬇️ what we provide in the body ⬇️
{
"path": [
"$.vc.type"
],
"filter": {
"type": "string",
"pattern": "BankId"
}
}
// ⬆️ what we provide in the body ⬆️
]
}
}
],
"format": {
"jwt_vp_json": {
"alg": [
"ES256"
]
}
}
}
Response Codes
200
- Matching credentials
Body
[
{
"_id": "waltid.tenant22.credential-store-wallet1.3d629d4b-4111-461d-9233-b2224fd64486",
"credential": {
"type": "vc-w3c_1_1",
"disclosables": {},
"credentialData": {
"id": "urn:uuid:b14f7c2e-eed9-46bb-be20-a6e4313c098e",
"@context": [
"https://www.w3.org/2018/credentials/v1"
],
"credentialSubject": {
"name": "Test",
"id": "did:key:z6MkjJxfMvaqvHJePeqCfnEUsgQeYyCeVtuKAvJUAE48X4Eu"
},
"issuer": {
"id": "did:key:z6MkhPYWbZT1K7cbW5CmqCFRr2L44cMeL8EDSQNSxSh5G8Cc"
},
"type": [
"VerifiableCredential",
"BankId"
],
"issuanceDate": "2025-04-17T11:04:45.719883961Z"
},
"signature": {
"type": "signature-jwt",
"signature": "lOFanwzGBNYzOreDYpx9awQGTV3EjyIxcQphtictrbdQgxn9IRTWY7ev-m8fcdNYrOVN5U_mDUXxHJiC0K_SDg"
},
"signed": "eyJraWQiOiJkaWQ6a2V5Ono2TWtoUFlXYlpUMUs3Y2JXNUNtcUNGUnIyTDQ0Y01lTDhFRFNRTlN4U2g1RzhDYyN6Nk1raFBZV2JaVDFLN2NiVzVDbXFDRlJyMkw0NGNNZUw4RURTUU5TeFNoNUc4Q2MiLCJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtoUFlXYlpUMUs3Y2JXNUNtcUNGUnIyTDQ0Y01lTDhFRFNRTlN4U2g1RzhDYyIsInN1YiI6ImRpZDprZXk6ejZNa2pKeGZNdmFxdkhKZVBlcUNmbkVVc2dRZVl5Q2VWdHVLQXZKVUFFNDhYNEV1IiwidmMiOnsiaWQiOiJ1cm46dXVpZDpiMTRmN2MyZS1lZWQ5LTQ2YmItYmUyMC1hNmU0MzEzYzA5OGUiLCJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJjcmVkZW50aWFsU3ViamVjdCI6eyJuYW1lIjoiVGVzdCIsImlkIjoiZGlkOmtleTp6Nk1rakp4Zk12YXF2SEplUGVxQ2ZuRVVzZ1FlWXlDZVZ0dUtBdkpVQUU0OFg0RXUifSwiaXNzdWVyIjp7ImlkIjoiZGlkOmtleTp6Nk1raFBZV2JaVDFLN2NiVzVDbXFDRlJyMkw0NGNNZUw4RURTUU5TeFNoNUc4Q2MifSwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIkJhbmtJZCJdLCJpc3N1YW5jZURhdGUiOiIyMDI1LTA0LTE3VDExOjA0OjQ1LjcxOTg4Mzk2MVoifSwianRpIjoidXJuOnV1aWQ6YjE0ZjdjMmUtZWVkOS00NmJiLWJlMjAtYTZlNDMxM2MwOThlIiwiaWF0IjoxNzQ0ODg3ODg1LCJuYmYiOjE3NDQ4ODc4ODV9.lOFanwzGBNYzOreDYpx9awQGTV3EjyIxcQphtictrbdQgxn9IRTWY7ev-m8fcdNYrOVN5U_mDUXxHJiC0K_SDg"
},
"parent": "waltid.tenant22.credential-store-wallet1"
}
]
The received ID(s) of the returned credentials, we will need to save and then use in the next step.
2. Present Matched Credentials
Endpoint: /v1/{target}/wallet-service-api/credentials/present
| API Reference
Example Request
curl -X 'POST' \
'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/wallet-service-api/credentials/present' \
-H 'accept: */*' \
-H 'Authorization: Bearer {yourToken}' \
-H 'Content-Type: application/json' \
-d '{
"requestUrl": "openid4vp://authorize?response_type=vp_token&client_id=https%3A%2F%2Fverifier.example.org%2Fopenid4vc%2Fverify&response_mode=direct_post&state=AAA_BBB_CCC_DDD&presentation_definition_uri=https%3A%2F%2Fverifier.example.org%2Fopenid4vc%2Fpd%2FAAA_BBB_CCC_DDD&client_id_scheme=redirect_uri&client_metadata=%7B%22authorization_encrypted_response_alg%22%3A%22ECDH-ES%22%2C%22authorization_encrypted_response_enc%22%3A%22A256GCM%22%7D&nonce=aaaaaaaa-bbbb-cccc-dddd-ffffffffffff&response_uri=https%3A%2F%2Fverifier.example.org%2Fopenid4vc%2Fverify%2FAAA_BBB_CCC_DDD",
"keyReference": "example.kms.key1",
"didReference": "example.didstore.did1",
"credentials": [
{
"credential": "waltid.tenant22.credential-store-wallet1.3d629d4b-4111-461d-9233-b2224fd64486"
}
]
}'
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.dev
when using the sandbox environment.target
: resourceIdentifier - The target indicates the organization + tenant + wallet which should be used to present the credential ({organizationID}.{tenantID}.[walletID]
), e.g.waltid.tenant1.wallet1
Body
{
"requestUrl": "openid4vp://authorize?response_type=vp_token&client_id=https%3A%2F%2Fverifier.example.org%2Fopenid4vc%2Fverify&response_mode=direct_post&state=AAA_BBB_CCC_DDD&presentation_definition_uri=https%3A%2F%2Fverifier.example.org%2Fopenid4vc%2Fpd%2FAAA_BBB_CCC_DDD&client_id_scheme=redirect_uri&client_metadata=%7B%22authorization_encrypted_response_alg%22%3A%22ECDH-ES%22%2C%22authorization_encrypted_response_enc%22%3A%22A256GCM%22%7D&nonce=aaaaaaaa-bbbb-cccc-dddd-ffffffffffff&response_uri=https%3A%2F%2Fverifier.example.org%2Fopenid4vc%2Fverify%2FAAA_BBB_CCC_DDD",
"keyReference": "example.kms.key1",
"didReference": "example.didstore.did1",
"credentials": [
{
"credential": "waltid.tenant22.credential-store-wallet1.3d629d4b-4111-461d-9233-b2224fd64486"
}
]
}
Body Parameters
requestUrl
String - An OID4VCP Authorization URL.credentials
(optional) Array - You can optionally provide a list of credential IDs from the linked credential store that you want to present. If you do not provide a list, the wallet will attempt to find matching credentials based on the presentation definition specified in the request URL. If a match is found, those credentials will be presented.
Additionally, if the presentation definition requests a specific field in a credential and that field is selectively disclosable, the wallet will automatically share this field (but not any other selectively disclosable fields) when using the auto-matching mechanism.
If you prefer, you can also explicitly specify which fields to disclose next to the credential ID under the "disclosures" field in the credential object, as illustrated in the example below.Click to expand and see credential with selective disclosure example
Object in
credentials
{ "credential": "org1.users.user1.credentialstore.credential2", "disclosures": [ "$.name", "$.credentialSubject.achievement.description" ] }
credential
String - ID of the credentialdisclosures
Array - An array of paths to the attributes that can be selectively disclosed and should be shared.
keyReference
(optional) String - The resource ID (target) of the key which owns the credential. Key can be stored in a linked KMS store or provided as a static key as described in the setup section.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, which owns the credential.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-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
200
- Presentation was accepted
When the present
endpoint is executed, in the background, the Wallet encapsulated the credentials to be presented
within a Verifiable Presentation. It then
signed this presentation using the Holder's key, which serves as cryptographic proof to the verifier that the Holder (
and therefore the Wallet controlling the key) has possession of the credentials being presented.