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:

  1. User may receive a direct link, which opens the wallet and prompts credential sharing.
  2. A website may display a QR code for the user to scan and share the credential.
  3. 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:

  1. Find credentials matching the presentation definition in the linked credential store of the wallet.
  2. 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.

CURL

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 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 + 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 each constraints.fields entry in all input_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

JWT
SD-JWT
CURL

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 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 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 credential
    • disclosures 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, 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

  • 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.

Last updated on April 17, 2025