Tutorial (30 min)
Please note: You need to be an Enterprise Stack customer & have access to the private enterprise stack images, to go through this tutorial.
In this tutorial, we will provide a comprehensive guide on setting up the enterprise API on your local machine. We will demonstrate the complete process of issuing and verifying a W3C credential that includes a revocation status property. Additionally, you can watch the accompanying video guide below.
1. Setup
For the setup, we will be using the walt.id enterprise quickstart repository. It can be used to explore enterprise features via a CLI tool and/or to bring up the whole stack using Docker Compose. We will be using the latter for this tutorial.
Clone the repo
git clone https://github.com/walt-id/waltid-enterprise-quickstart.git
Change Working Directory
cd waltid-enterprise-quickstart
Add Docker Token
Save the docker-access token provided by walt.id to get access to the private enterprise stack img.
echo "DOCKER_TOKEN_PROVIDED_BY_WALT_ID_HERE" > .docker-token
Run the stack via docker-compose
./waltid-enterprise run
When running the docker-compose a mongoDB database is created (used to store data for the Enterprise Stack) and the Enterprise API is started.
Visit the API
Visit enterprise.localhost:3000/swagger to view the API.
Learn more about the base domain (enterprise.localhost) configuration here and in general about the configurations files available for the Enterprise Stack here.
2. Super Admin - Auth
The first step, after the Enterprise Stack API is up and running is to register a super-admin user. One or multiple
super admin accounts can be specified via
the superadmin-registration.conf
file.
This file can be found in the config
folder of the quickstart-repo in your local
setup. (waltid-enterprise-quickstart/config
)
Activate Super Admin
Endpoint:/v1/superadmin/create-by-token
| API Reference
Example Request
curl -X 'POST' \
'http://waltid.enterprise.localhost:3000/v1/superadmin/create-by-token' \
-H 'accept: */*' \
-H 'Content-Type: application/json' \
-d '{
<super-admin-token-specified-in-super-admin-conf-file>
}'
Body
{
<super-admin-token>
}
Body Content
Please provide the super-admin-token in the body without any quotation marks.
Response Codes
200
- Super admin account activated successfully
Now that we have activated the super-admin user, we can login with the super-admin credentials also specified in
the superadmin-registration.conf
using the
regular account/user login endpoint.
Login as Super Admin
Endpoint:/auth/account/emailpass
| API Reference
Example Request
curl -X 'POST' \
'http://waltid.enterprise.localhost:3000/auth/account/emailpass' \
-H 'accept: */*' \
-H 'Content-Type: application/json' \
-d '{
"email": "superadmin@walt.id",
"password": "super123456"
}'
Body
{
"email": "superadmin@walt.id",
"password": "super123456"
}
Body Parameters
email
: String - Email of the super admin account. Defined insuperadmin-registration.conf
.password
: String - Password of the super admin account. Defined insuperadmin-registration.conf
Response
{
"session_id": "39ed1ecd-aa61-4401-bf35-ca0a14a39c8e",
"status": "OK",
"token": "eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiJmZmZmZmZmZi1mZmZmLWZmZmYtZmZmZi1mZmZmZmZmZmZmZmYiLCJzZXNzaW9uIjoiMzllZDFlY2QtYWE2MS00NDAxLWJmMzUtY2EwYTE0YTM5YzhlIn0.NEKj737AqG7T7SAfQ2RovhWSjBsXCWCLPmkLtr_O5i8q_42BFX7pSGZbIslRy1PEQ3HKrnXib6D06D1kZugvCw"
}
The token we receive can be used in subsequent requests as a Bearer token.
Authenticate as Super Admin via API
To access all protected endpoints of the Enterprise API, we will use the token obtained from the response of the previous request. This token should be included in the header of each subsequent request.
Example Request
curl -X 'POST' \
'http://waltid.enterprise.localhost:3000/v1/...' \
-H 'Authorization: Bearer {tokenReceived}'
-d '{
...
}'
To learn more about the Super Admin Account in general, click here.
3. Organization
With the super-admin account active and the auth session token in our Authorization header, we can create an Organization.
The organization is at the top of the enterprise stack resource hierarchy, as shown in the diagram below. Under the Organization you can group customers as tenants, sub-tenants, or create a tenant of your own. This way services and their data can be kept virtually separate, enabling you to easily manage different customers or services of your own under one roof. Also, this structure allows you to build and sell B2B2C products (e.g., you can resell to business clients who offer consumer products).

While the Enterprise Stack allows for multiple Organizations, on-prem Enterprise deployments should always have only one Organization.
Create a new Organization
**Endpoint:
** /v1/admin/organizations
| API Reference
Example Request
curl -X 'POST' \
'http://enterprise.localhost:3000/v1/admin/organizations' \
-H 'accept: */*' \
-H 'Authorization: Bearer {yourToken}' \
-H 'Content-Type: application/json' \
-d '{
"_id": "waltid",
"profile": {
"name": "Test GmbH"
},
"billing": {
"billingCountry": "AT",
"billingAddress": "test 111/115, 1090 Vienna",
"vatNr": "test"
}
}'
Body
{
"_id": "string",
"profile": {
"name": "string"
},
"billing": {
"billingCountry": "",
"billingAddress": "",
"vatNr": "",
"iban": "",
"swift": ""
}
}
Body Parameters
_id
type
: String - Unique identifier for the organization, e.g.waltid
profile
name
: String - Human readable name for the organization.
billing
: Billing information
Response Codes
201
- Organization created successfully.
When we create a new organization, an admin role is automatically generated in the background. This role is named using
the format [orgID].admin
. For example, if our organization ID is waltid
, the admin role will be
called waltid.admin
.
In the next step, we will use this role to create an API Key that grants admin rights exclusively for a specific organization. This is a common pratice to not always use the super-admin to do regular day-to-day operations.
If you want to learn more about how permissions and roles work, please go here.
Also, each organization is given a unique subdomain, which is utilized for carrying out operations specific to that
organization. Operations that are not tied to any particular organization mainly involve the global admin
endpoints,
as well as endpoints that do not require the target
path parameter.
The structure of the subdomain for each new organization is as follows: {orgID}.{baseUrl}. For example, in our case, this would be represented as http://waltid.enterprise.localhost:3000.
Learn more about the Organization in general here.
4. API Keys - Auth
In this section, we will create an API for the organization and grant it admin rights by assigning the waltid.admin
role to the API key, which was automatically created along with the waltid
organization in the last step.
Every operation from now on will be conducted within the scope of an organization, specifically waltid
. Therefore, we
need to update the base URL of our API to include waltid
. The new URL will
be http://waltid.enterprise.localhost:3000
, in contrast to the previous URL, which
was http://enterprise.localhost:3000
.
Create API-Key
Endpoint:/v1/{target}/apikeys-api/api-keys/create
| API Reference
Example Request
curl -X 'POST' \
'http://waltid.enterprise.localhost:3000/v1/waltid.myApiKey/apikeys-api/api-keys/create' \
-H 'accept: */*' \
-H 'Authorization: Bearer {yourToken}' \
-H 'Content-Type: application/json' \
-d '{
"name": "My API Key",
"expiration": "30d"
}'
Body
{
"name": "My API Key",
"expiration": "30d"
}
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 in which to create the new API Key and the API-Key's ID ({organizationID}.[YourID]
), e.g.waltid.myApiKey
Body Parameters
name
: String - Human readable name for API-Key.expiration
: (optional) String - Expiration time of the API-Key.- Set expiration times using the kotlinx.datetime Duration Syntax. Valid units are
days (d), hours (h), minutes (m), and seconds (s), with finer granularity using milliseconds (ms), microseconds (
us),
and nanoseconds (ns). Examples include
5h
,1d 12h
, and1h 0m 30.340s
. - Alternatively, you can use a simplified ISO-8601 duration format (PThHmMs.fS), such as P1DT2H3M4.058S. In this
format:
- The largest non-time designator is days (D). Years (Y), weeks (W), and months (M) are not parsed. Instead, use 365d for years, 7d for weeks, and 30d for months.
- A day is always assumed to be 24 hours (24-hour clock time scale).
- There is currently no alternative week-based representation like ["P"][number]"W".
- Set expiration times using the kotlinx.datetime Duration Syntax. Valid units are
days (d), hours (h), minutes (m), and seconds (s), with finer granularity using milliseconds (ms), microseconds (
us),
and nanoseconds (ns). Examples include
Response
{
"_id": "waltid.myApiKey",
"name": "My API Key",
"token": "eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiJhYjNhZjc0Zi0wNmNhLTRkY2EtYWJlZi1iNjA3MTJjOTM5YzEiLCJzZXNzaW9uIjoiYXBpLWFiM2FmNzRmLTA2Y2EtNGRjYS1hYmVmLWI2MDcxMmM5MzljMSJ9.FHEmayjhrfZMnOnEecNnOa6ADDwBm1jL_tue64PhxCTk1DZJeIdz-KPvuwWdSlLgw538fqAIoW8ulv6-B91ZAw"
}
The returned token can be used as a Bearer token in API requests.
If you want to check the expiration time of the API-Key you can use the /v1/{target}/apikeys-api/api-keys/view
endpoint.
By default, the API key we just created doesn't have any permissions. Therefore, we will assign the API Key
the admin role waltid.admin
in the next step.
Assigning Admin Role to API-Key
Endpoint:/v1/{target}/roles-api/roles/apikey/assign
Example Request
curl -X 'POST' \
'http://waltid.enterprise.localhost:3000/v1/waltid.admin/roles-api/roles/apikey/assign' \
-H 'accept: */*' \
-H 'Authorization: Bearer {yourToken}' \
-H 'Content-Type: application/json' \
-d '
{
"apikey": "waltid.myApiKey"
}'
Body
{
"apikey": "waltid.myApiKey"
}
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 ID of the role to assign to an API-Key. e.g.waltid.admin
Body Parameters
apikey
: resourceIdentifier - Specifies the ID of the API-Key to assign the role to.
Response Codes
200
- Role assigned successfully.
3. Tenant
In this section, we will move further down the Enterprise Stack hirachy (shown below) and create our first tenant to then be able to launch different types of services like Issuer, Verifier and others.
You can also create sub-tenants inside of tenants and sub-tenants. At this point, there is no limit to the level of nested sub-tenants. Services and their data inside tenants and sub-tenants are kept separate.

Create a New Tenant
Endpoint:/v1/{target}/resource-api/tenants/create
| API Reference
Example Request
curl -X 'POST' \
'http://waltid.enterprise.localhost:3000/v1/waltid.tenant1/resource-api/tenants/create' \
-H 'accept: */*' \
-H 'Authorization: Bearer {yourToken}' \
-H 'Content-Type: application/json' \
-d '{
"name": "My Tenant"
}'
Body
{
"name": "My Tenant"
}
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 in which to create the new tenant and the tenant's ID ({organizationID}.[YourID]
), e.g.waltid.tenant1
Body Parameters
name
: String - Human readable name for the tenant.
Response Codes
201
- Tenant created successfully.
4. Services
With the new tenant created, we will now establish various types of services within this tenant, progressing further down the hierarchy illustrated in the diagram below.

Services To Be Created
- KMS Service - Create and manage keys based on different algorithms.
- DID Service - Create DID based on various methods (did:key, did: web, ...).
- Issuer Service - Sign & Issue various credentials (W3C, SD-JWT VC) via OID4VC.
- Credential Status Service - Create, update, and manage status credentials.
- Verifier Service - Request & verify various types of digital credentials via OID4VP.
Create A KMS Service
First, we will set up a Key Management Service (KMS) to create and store the keys necessary for signing the verifiable credential and the status credential. In this tutorial, we will use the Enterprise Stack database to store these keys. However, for production scenarios, it is recommended to store the keys in an external KMS that the Enterprise Stack can access through integrations. For more information about the supported KMS providers, please go here.
Setup Key Service
Endpoint: /v1/{target}/resource-api/services/create
| API Reference
Example Request
curl -X 'POST' \
'http://waltid.enterprise.localhost:3000/v1/waltid.tenant1.kms1/resource-api/services/create' \
-H 'accept: */*' \
-H 'Authorization: Bearer {yourToken}' \
-H 'Content-Type: application/json' \
-d '{
"type": "kms"
}'
Body
{
"type": "kms"
}
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 in which to create the new KMS service and the service's ID ({organizationID}.{tenantID}.[NewKmsServiceID]
), e.g.waltid.tenant1.kms1
Body Parameters
type
: serviceType - Specifies the type of service to create. In our casekms
Response Codes
201
- Service created successfully.
Create Key
Endpoint: /v1/{target}/kms-service-api/keys/generate
| API Reference
Example Request
curl -X 'POST' \
'http://waltid.enterprise.localhost:3000/v1/waltid.tenant1.kms1.key1/kms-service-api/keys/generate' \
-H 'accept: */*' \
-H 'Authorization: Bearer {yourToken}' \
-H 'Content-Type: application/json' \
-d '{
"backend": "jwk",
"keyType": "Ed25519"
}'
Body
{
"backend": "jwk",
"keyType": "Ed25519"
}
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 + kmsService in which to create the new key and the key's ID ({organizationID}.{tenantID}.{kmsServiceID}.[newKeyID]
), e.g.waltid.tenant1.kms1.key1
Body Parameters
backend
: String - Specifies the storage type of key.jwk
means the local storage option is used and the key is stored in the Enterprise Stack backend.keyType
: String - the algorithm used to generate the key. For local, it can be ed25519, secp256k1, secp256r1, or RSA.
Response
201
- Key created successfully.
{
"_id": "waltid.tenant1.kms1.key1",
"key": {
"type": "jwk",
"jwk": {
"kty": "OKP",
"d": "ywmoRVTD9fexMtGW0lKE3o9_0ulfzGXr9xHGL0lPhhA",
"crv": "Ed25519",
"kid": "IQO7DILxtagpTLXkuHkRkJURb2GqcUIwXYZAcGwW1AU",
"x": "JOsiIE7ME9UZ8y2H-P5RSuYAUiIfs1ywtdBjMGN7I5s"
}
},
"parent": "waltid.tenant1.kms1"
}
Now, we created one key that can be used for both the issuer and the credential status service in this tutorial. In practice, however, different keys should be created for different purposes. You can also use the example above to create another key if desired.
In the next step, we will create a DID service and create a did:key for our key.
Create DID Service
Endpoint: /v1/{target}/resource-api/services/create
| API Reference
Example Request
curl -X 'POST' \
'http://waltid.enterprise.localhost:3000/v1/waltid.did-service-1/resource-api/services/create' \
-H 'accept: */*' \
-H 'Authorization: Bearer {yourToken}' \
-H 'Content-Type: application/json' \
-d '{
"type": "did"
}'
Body
{
"type": "did"
}
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 in which to create the new DID service and the service's ID ({organizationID}.{tenantID}.[NewDIDServiceID]
), e.g.waltid.tenant1.did1
Body Parameters
type
: serviceType - Specifies the type of service to create. In our casedid
Response Codes
201
- Service created successfully.
Create did:key
Endpoint: /v1/{target}/did-service-api/dids/create/key
| API Reference
Example Request
curl -X 'POST' \
'http://waltid.enterprise.localhost:3000/v1/waltid.tenant1.did-service-1/did-service-api/dids/create/key' \
-H 'accept: */*' \
-H 'Authorization: Bearer {yourToken}' \
-H 'Content-Type: application/json' \
-d '{
"keyId": "waltid.tenant1.kms1.key1",
"useJwkJcsPub": false
}'
Body
{
"keyId": "waltid.tenant1.kms1.key1",
"useJwkJcsPub": false
}
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 + DID service in which to execute the DID creation ({organizationID}.{tenantID}.{didServiceID}
), e.g.waltid.tenant1.did1
Body Parameters
keyId
: resourceIdentifier - Specifies the key for which to generate a DID + DID Document. Please make sure that the key is stored in a KMS service under the same tenant as the DID service.useJwkJcsPub
: boolean - If set totrue
it generates an EBSI compliant DID:key + DID Document. This is only important if you are operating within the EBSI ecosystem.
Response
201
- DID created successfully.
{
"did": "did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt",
"document": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/jws-2020/v1"
],
"id": "did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt",
"verificationMethod": [
{
"id": "did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt#z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt",
"type": "JsonWebKey2020",
"controller": "did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt",
"publicKeyJwk": {
"kty": "EC",
"crv": "P-256",
"kid": "zY9AzWFTMKveeXIdHWSsvX16Cso1JXMEVKGFUuw_LkQ",
"x": "PMzrrJU6Gh5JZK_6aIpiWWHcGoWymGFkYptM10a1sec",
"y": "6-hp5Y7Hpb9nFfovyeycXSPubEJ7LWdaGNoCcKZCIyA"
}
}
],
"assertionMethod": [
"did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt#z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt"
],
"authentication": [
"did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt#z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt"
],
"capabilityInvocation": [
"did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt#z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt"
],
"capabilityDelegation": [
"did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt#z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt"
],
"keyAgreement": [
"did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt#z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt"
]
}
}
Please make sure to save the generated DID (Decentralized Identifier) along with the key ID for future reference. This information will be necessary, when issuing credentials through the Issuer Service. We are working on a solution to have this referenced stored automatically for easier access across all services.
Create Credential Status Service (optional)
In this section, we will set up a credential status service and create a configuration for it. To proceed with this section, you need to have an account with one of the following cloud providers: AWS, Azure, or Google Cloud. This is necessary because the credential status must be hosted publicly, and currently, we only support these three providers for hosting.
If you prefer not to create a cloud account (AWS, Azure, or Google Cloud) which is required for this step or wish to skip the credential status setup altogether, you can move directly to the example for creating an issuer.
Credential Status Service Setup
The Credential Status service enables users to create, update, and manage status credentials. Status credentials can have different purposes depending on the use-case. The most common purpose is revocation.
Currently, status credentials can be stored and made available via three types of external services:
Endpoint: /v1/{target}/resource-api/services/create
| API Reference
Example Request
curl -X 'POST' \
'http://waltid.enterprise.localhost:3000/v1/waltid.tenant1.credential-status-service-1/resource-api/services/create' \
-H 'accept: */*' \
-H 'Authorization: Bearer {yourToken}' \
-H 'Content-Type: application/json' \
-d '{
"type": "credential-status",
"config": {
"registry": {
"type": "aws",
"bucketName": "bucket-name",
"region": "region",
"accessKeyId": "s3-access-key-id",
"secretKey": "s3-secret-key"
},
"bucketUrl": "https://bucket-name.s3.amazonaws.com"
}
}'
Body
{
"type": "credential-status",
"config": {
"registry": {
"type": "aws",
"bucketName": "bucket-name",
"region": "region",
"accessKeyId": "s3-access-key-id",
"secretKey": "s3-secret-key"
},
"bucketUrl": "https://bucket-name.s3.amazonaws.com"
}
}
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 in which to create the new credential status service and the service's ID ({organizationID}.{tenantID}.[NewCredentialStatusServiceID]
), e.g.waltid.tenant1.credential-status-service-id
Body Parameters
type
: credential-status - Specifies the type of service to create. In this case, it iscredential-status
.config
: object - Storage configurations for status credential service.registry
: object - Defines registry & access credentials for chosen registry.type
: aws - Indicates the type of registry, which isaws
in this context.bucketName
: string - The name of the AWS S3 bucket.region
: string - The AWS region where the bucket is located.accessKeyId
: string - The access key ID for accessing the AWS S3 bucket.secretKey
: string - The secret access key for accessing the AWS S3 bucket.
bucketUrl
: string - The URL of the chosen registry bucket, e.g. "https://bucket-name.s3.amazonaws.com" for AWS.
Response Codes
201
- Service created successfully.401
- Invalid authentication.
Create A Status List Credential Configuration
Before we can create a status for a specific credential, we first need to establish a status credential configuration. This configuration serves as the blueprint for creating a status credential. The status credential itself is responsible for holding the status entries of one or more credentials. Each time we issue a new status for a credential that we want to provide to a holder, the status credential get updated, re-issued, and made publicly available through the configured registry.
When defining the configuration, we specify which key will sign the status credential, the Decentralized Identifier (
DID) of the issuer, and the configuration details of the status itself. This configuration includes the purpose
of the
status (for example, revocation), the statusSize
, and additional parameters.
In the example below, we will create a configuration specifically for a revocation status credential.
One StatusListCredential can host 32.000 status list entries.
Endpoint: /v1/{target}/credential-status-service-api/status-credential/create
| API Reference
Example Request
curl -X 'POST' \
'http://waltid.enterprise.localhost:3000/v1/waltid.tenant1.credential-status-service-1.config1/credential-status-service-api/status-credential/create' \
-H 'accept: */*' \
-H 'Authorization: Bearer {yourToken}' \
-H 'Content-Type: application/json' \
-d '{
"kid": "waltid.tenant1.kms1.key1",
"did": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiMW1rQnBEYWZqVk9ONm9XVmlIZGREZXNEYXpKaXk1R0NTYko3VDU4QkJ3cyIsIngiOiJzZ1BiZWRPQjl1WEMtVG54LUVhV1IxRmg1Y25JLUxRLU43NUV1UE8wV2VFIn0",
"config": {
"purpose": "revocation",
"type": "StatusList2021",
"statusSize": 1,
"statuses": [
{
"status": "0x0",
"message": "unset"
},
{
"status": "0x1",
"message": "set"
}
]
}
}'
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 status service in which to execute the status credential creation ({organizationID}.{tenantID}.{credentialStatusServiceID}
), e.g.waltid.tenant1.status-service1
Body
{
"kid": "waltid.tenant1.kms1.key1",
"did": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiMW1rQnBEYWZqVk9ONm9XVmlIZGREZXNEYXpKaXk1R0NTYko3VDU4QkJ3cyIsIngiOiJzZ1BiZWRPQjl1WEMtVG54LUVhV1IxRmg1Y25JLUxRLU43NUV1UE8wV2VFIn0",
"config": {
"purpose": "revocation",
"type": "StatusList2021",
"statusSize": 1,
"statuses": [
{
"status": "0x0",
"message": "unset"
},
{
"status": "0x1",
"message": "set"
}
]
}
}
Body Parameters
kid
: resourceIdentifier - Specifies a key used for singing the status credential. The key must come from a KMS service that lives under the same tenant as the credential status service.did
: String - Specifies that DID related to thekid
.config
: Object - Status credential configuration.purpose
: String - Describes the type of status e.g.revocation
,suspension
or any other custom type.type
: String - Describes the status credential standard. It should beStatusList2021
.statusSize
: String - Describes the size of each status entry in bits. The default value is 1, meaning each status is represented by a single bit. If a value greater than 1 is used, it allows for more complex status messages, and the number of possible status messages must equal the number of possible values (e.g., 2^statusSize).statuses
: Array - An array of objects, each representing a possible status value and its associated message. Each object contains:status
: A string representing the hexadecimal value of the status, prefixed with 0x.message
: A descriptive string used by developers for debugging purposes, indicating what the status value represents. It is not intended for end-user display.
Response
201
- Credential Status config created successfully.
Create Issuer Service
In this section, we will create an issuer service to issue credentials via OID4VCI. We will configure the issuer
service to support the issuance of W3C UniversityDegree
type credentials using JWT signatures. You can learn more
about how to configure other and custom
credentials here.
Endpoint: /v1/{target}/resource-api/services/create
| API Reference
Example Request
curl -X 'POST' \
'http://waltid.enterprise.localhost:3000/v1/waltid.tenant1.issuer1/resource-api/services/create' \
-H 'accept: */*' \
-H 'Authorization: Bearer {yourToken}' \
-H 'Content-Type: application/json' \
-d '{
"type": "issuer",
"baseUrl": "http://waltid.enterprise.localhost:3000",
"kms": "waltid.tenant1.kms1",
"tokenKeyId": "waltid.tenant1.kms1.key1",
"supportedCredentialTypes": {
"UniversityDegree_jwt_vc_json": {
"format": "jwt_vc_json",
"cryptographic_binding_methods_supported": [
"did"
],
"credential_signing_alg_values_supported": [
"ES256"
],
"credential_definition": {
"type": [
"VerifiableCredential",
"UniversityDegree"
]
}
}
}
}'
Body
{
"type": "issuer",
"baseUrl": "http://waltid.enterprise.localhost:3000",
"kms": "waltid.tenant1.kms1",
"tokenKeyId": "waltid.tenant1.kms1.key1",
"supportedCredentialTypes": {
"UniversityDegree_jwt_vc_json": {
"format": "jwt_vc_json",
"cryptographic_binding_methods_supported": [
"did"
],
"credential_signing_alg_values_supported": [
"ES256"
],
"credential_definition": {
"type": [
"VerifiableCredential",
"UniversityDegree"
]
}
}
}
}
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 in which to create the new Issuer service and the service's ID ({organizationID}.{tenantID}.[NewIssuerServiceID]
), e.g.waltid.tenant1.issuer1
Body Parameters
type
: serviceType - Specifies the type of service to create. In our caseissuer
baseUrl
: String - This URL will be included in the generated OIDC4VC offer, allowing the wallet to know how to reach the issuer. It should reflect your organization's base URL. The general format for this URL ishttps://{orgID}.yourEnterpriseStackUrl.com
. For example, if your organization is named myorg and the Enterprise Stack is hosted at the domain enterprise-stack.com, your base URL would be: https://myorg.enterprise-stack.com.kms
: - resourceIdentifier - A KMS service you want to connect with the to be created Issuer service. It should be setup under the same tenant.tokenKeyId
: - resourceIdentifier - A key in the connected KMS service used to sign the access token, which is used to get the credential from the credential endpointsupportedCredentialTypes
: Object - A map of credentials the issuer supports. This list will be used to generate the issuer metadata. This list of supported credentials can be updated also after the initial setup.Expand For W3C JWT & SD-JWT Credentials
To specify the support of a W3C credential the object will look as follows:"OpenBadgeCredential_jwt_vc_json": { "format": "jwt_vc_json", "cryptographic_binding_methods_supported": [ "did" ], "credential_signing_alg_values_supported": [ "ES256" ], "credential_definition": { "type": [ "VerifiableCredential", "OpenBadgeCredential" ] } }
The key will have the following structure:
[CustomCredentialType]_jwt_vc_json
, e.g.OpenBadgeCredential_jwt_vc_json
Inside the object:
format
- will bejwt_vc_json
for W3C JWT & SD-JWT credentialscryptographic_binding_methods_supported
- will bedid
credential_signing_alg_values_supported
- will beES256
credential_definition
type
- specifies a list of credential types. E.g.[VerifiableCredential, MyCustomCredential]
- First Entry: Your list must always start with
VerifiableCredential
. - Subsequent Entries: After
VerifiableCredential
, you have two options:
- You can add your custom type, such as
CustomCredential
. - If your credential is based on another credential (for example,
VerifiableAttestation
), first list all the credentials it builds upon, and then add your custom type at the end.
- First Entry: Your list must always start with
Expand For SD-JWT VC Credentials
To specify the support of a SD-JWT VC credential the object will look as follows:"identity_credential_vc+sd-jwt": { "format": "vc+sd-jwt", "vct": "{vctBaseURL}/identity_credential", "cryptographic_binding_methods_supported": [ "jwk" ], "credential_signing_alg_values_supported": [ "ES256" ], "sdJwtVcTypeMetadata": { "name": "Identity Credential", "description": "The Identity Verifiable Credential", "vct": "{vctBaseURL}/identity_credential" } }
The key will have the following structure:
[custom_credential_type]_vc+sd-jwt
, e.g.identity_credential_vc+sd-jwt
Inside the object:
format
- will bevc+sd-jwt
for SD-JWT VC credentials.vct
- will follow the structure{vctBaseURL}/[custom_credential_type]
, e.g.{vctBaseURL}/custom_credential_type
. The vctBaseURL will be replaced during issuance by the issuer API.cryptographic_binding_methods_supported
- will bejwk
credential_signing_alg_values_supported
- will beES256
sdJwtVcTypeMetadata
name
- Name of the credentialdescription
- Description of the credentialvct
- Holding the same value as thevct
above.
displayConfigurations
(optional) - an optional list of objects, where each object contains specific display
information for different languages. Including the local option is not mandatory; you can simply provide one object if
localization is not required. To learn more about the display config in general
go here, for object property details
expand below.Expand to learn more about the display config object properties
Display Config Object
{
"name": "walt.id Enterprise Issuer Service",
"locale": "en-US",
"logo": {
"uri": "http://cdn.walt.id/issuer/logo.png",
"alt_text": "Logo of walt.id Enterprise Issuer Service"
}
}
name
(optional) String: String value of a display name for the Credential Issuer.locale
(optional) String: String value that identifies the language of this object represented as a language tag taken from values defined in BCP47 RFC5646. There MUST be only one object for each language identifier. Example values are:en-US
,de-DE
, orfr-FR
.logo
(optional) Object: Object with information about the logo of the Credential Issuer.uri
String: String value that contains a URI where the Wallet can obtain the logo of the Credential Issuer.alt_text
(optional) String: String value of the alternative text for the logo image.
Response Codes
201
- Service created successfully.
Create Verifier Service
In this section, we will create a verifier service to verify credentials via OID4VCP.
Endpoint: /v1/{target}/resource-api/services/create
| API Reference
Example Request
curl -X 'POST' \
'http://waltid.enterprise.localhost:3000/v1/waltid.tenant1.verifier1/resource-api/services/create' \
-H 'accept: */*' \
-H 'Authorization: Bearer {yourToken}' \
-H 'Content-Type: application/json' \
-d '{
"type": "verifier",
"baseUrl": "http://localhost:3000"
}'
Body
{
"type": "verifier",
"baseUrl": "http://localhost:3000",
"metadataConfig": {
"name": "walt.id Enterprise Verifier",
"logoUri": "https://cdn.walt.id/verifier/logo.png"
}
}
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 in which to create the new Verifier service and the service's ID ({organizationID}.{tenantID}.[NewVerifierServiceID]
), e.g.waltid.tenant1.verifier1
Body Parameters
type
: serviceType - Specifies the type of service to create. In our caseverifier
baseUrl
: String - This URL will be included in the generated OIDC4VC offer, allowing the wallet to know how to reach the verifier. It should reflect your organization's base URL. The general format for this URL ishttps://{orgID}.yourEnterpriseStackUrl.com
. For example, if your organization is named myorg and the Enterprise Stack is hosted at the domain enterprise-stack.com, your base URL would be: https://myorg.enterprise-stack.com.metadataConfig
: Object - Verifier metadata config. Can be used to edit e.g. externally exposed human-readable verifier name and logo. Learn more in general here or expand below to see object property options.
Expand to learn more about metadata config object properties
Metadata Config Object
"metadataConfig": { "name": "walt.id Enterprise Verifier", "logoUri": "https://cdn.walt.id/verifier/logo.png" }
name
(optional) String: Human-readable string name of the verifier.logoUri
(optional) String: URL string that references a logo for the verifier.
Response Codes
201
- Service created successfully.
Issue Credential
Now, we'll issue a verifiable credential using the key and DID created earlier. You can choose to either issue the W3C credential without or with a status (revocation). To issue the credential with a status, you must have completed the optional setup of a Credential Status Service.
To facilitate the issuance of the credential from us (the issuer) to the holder, we will utilise the OID4VCI protocol. In particular, we will be generating an OID4VC offer URL that can be accepted by any OID compliant wallet to receive credential(s).
The credential offer URL specifies the credentials to be issued. This includes details such as the URL of the issuer, the authorisation and token endpoints, and information about the credential's format, type, and scope.
After calling the endpoint, an OID4VCI offer URL is generated. Initially, this URL does not include the actual signed credentials; instead, it contains a list of credentials that we are offering for the wallet to claim. Once the wallet processes this offer and chooses to accept the credential, it will request those credentials through a series of exchanges between the issuer and the wallet. Only at this point, when the wallet requests the credential, will the issuer sign it. Additionally, just before the signing occurs, the provided data functions are executed.
Endpoint: /v1/{target}/issuer-service-api/credentials/issue
| API Reference
Example Request
curl -X 'POST' \
'http://waltid.enterprise.localhost:3000/v1/waltid.tenant1.issuer1/issuer-service-api/credentials/issue' \
-H 'accept: */*' \
-H 'Authorization: Bearer {yourToken}' \
-H 'statusCallbackUri: https://example.com/$id' \
-H 'Content-Type: application/json' \
-d '{
"issuerKeyId": "waltid.tenant1.kms1.key1",
"issuerDid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp",
"authenticationMethod": "PRE_AUTHORIZED",
"credentialConfigurationId": "UniversityDegree_jwt_vc_json",
"credentialData": {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1"
],
"id": "http://example.gov/credentials/3732",
"type": [
"VerifiableCredential",
"UniversityDegree"
],
"issuer": {
"id": "did:web:vc.transmute.world"
},
"issuanceDate": "2020-03-10T04:24:12.164Z",
"credentialSubject": {
"id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
"degree": {
"type": "BachelorDegree",
"name": "Bachelor of Science and Arts"
}
}
},
"mapping": {
"id": "<uuid>",
"issuer": {
"id": "<issuerDid>"
},
"credentialSubject": {
"id": "<subjectDid>"
},
"issuanceDate": "<timestamp>",
"expirationDate": "<timestamp-in:365d>"
}
}'
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 + issuer service with which to execute the credential issuance ({organizationID}.{tenantID}.{issuerServiceID}
), e.g.waltid.tenant1.issuer1
Header Parameters
statusCallbackUri
: URL - Receive updates on the created issuance process, e.g. when a credential was successfully claimed. The parameter expects a URL which can accept a JSON POST request. The URL can also hold a$id
, which will be replaced by the issuance session id. For example:https://myurl.com/$id
,https://myurl.com
orhttps://myurl.com/test/$id
Expand To Learn More
Body
The data send to the provided URL will contain a JSON body:
id
: String - the issuance session idtype
: String - the event typedata
: JsonObject - the data for the event
Event Types
Possible events (event types) and their data are:
resolved_credential_offer
with the credential offer as JSON (in our Web Wallet: called when the issuance offer is entered into the wallet, but not processing / accepted yet)requested_token
with the issuance request for the token as json object (called for the token required to receive the credentials)
Credential issuance (called for every credential that's issued (= requested from wallet))
jwt_issue
withjwt
being the issued jwtsdjwt_issue
withsdjwt
being the issued sdjwtbatch_jwt_issue
withjwt
being the issued jwtbatch_sdjwt_issue
withsdjwt
being the issued sdjwtgenerated_mdoc
withmdoc
being the CBOR (HEX) of the signed mdoc
To allow for secure business logic flows, if a callback URL is set, and it cannot be reached, the issuance will not commence further (after that point). If no callback URL is set, the issuance logic does not change in any way.
Body
{
"issuerKeyId": "waltid.tenant1.kms1.key1",
"issuerDid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp",
"authenticationMethod": "PRE_AUTHORIZED",
"credentialConfigurationId": "UniversityDegree_jwt_vc_json",
"credentialData": {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1"
],
"id": "http://example.gov/credentials/3732",
"type": [
"VerifiableCredential",
"UniversityDegree"
],
"issuer": {
"id": "did:web:vc.transmute.world"
},
"issuanceDate": "2020-03-10T04:24:12.164Z",
"credentialSubject": {
"id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
"degree": {
"type": "BachelorDegree",
"name": "Bachelor of Science and Arts"
}
}
},
"mapping": {
"id": "<uuid>",
"issuer": {
"id": "<issuerDid>"
},
"credentialSubject": {
"id": "<subjectDid>"
},
"issuanceDate": "<timestamp>",
"expirationDate": "<timestamp-in:365d>"
}
}
Body Parameters
issuerKeyId
: resourceIdentifier - The keyID of a key stored in a KMS service under the same tenant. E.g.waltid.tenant1.kms1.key1
issuerDid
: String - The DID related to the provided key.credentialConfigurationId
: String - Reference to a specific credential configuration the issuer supports. Configured during issuer service setup.credentialData
: JSON - A credential data structure to sign (W3C compliant). A list of predefined valid structures can be found here.mapping
(optional): JSON - The mapping object that allows for dynamic value insertion via data functions, executed at the time when the credentials is claimed. This feature enables personalized credentials based on real-time data. Learn more about it and see a list of supported data functions here.authenticationMethod
: (optional) String - Defines which OIDC4VC exchange flow is used (pre-auth or full-auth). If no value is provided, it will default to pre-auth flow indicated asPRE_AUTHORIZED
. The parameter options are:
Expand To Learn More About The Options
PWD
- used for authorization code flow with username/password authentication with external auth serverID_TOKEN
- used for authorization code flow with id_token authentication. When this method is used an authorization request is sent to the wallet, which generates and issues the ID_TOKEN using its DID ( Decentralized Identifier). The issuer then verifies the ID_TOKEN to confirm the holder's identity and DID.VP_TOKEN
- used for authorization code flow with vp_token(OIDC4VP). With this method, the receiver of the credential must be authenticated by presenting the requested credential. The credential which must be presented can be configured using thevpRequestedValue
as explained below. The policies which are applied to the presented credential are 'signature,' 'expired,' and 'not-before.' Learn more about policies here.NONE
- used for authorization code flow with none authentication methodPRE_AUTHORIZED
- used for pre-authorizated code flow
Additional parameters required for selected auth method:
For "ID_TOKEN" and "VP_TOKEN":
useJar
: (Optional) Boolean - used for using JAR OAuth specification in the id/vp_token requests. If omitted, the default value is true.
For VP_TOKEN:
vpRequestedValue
: String - Specifies the requested credential type value for the VP token. E.g. "VerifiableId"vpProfile
: (Optional) String - Specifies the profile of the VP request. Available Profiles: DEFAULT: For W3C OpenID4VP, ISO_18013_7_MDOC: For MDOC OpenID4VP, EBSIV3: For EBSI V3 Compliant VP. If omitted, the default value is DEFAULT
Example Response
The issuer endpoint will respond with Credential Offer URL.
Plain Response
openid-credential-offer://issuer.portal.walt.id/?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Fissuer.portal.walt.id%22%2C%22credentials%22%3A%5B%7B%22format%22%3A%22jwt_vc_json%22%2C%22types%22%3A%5B%22VerifiableCredential%22%2C%22UniversityDegree%22%5D%2C%22credential_definition%22%3A%7B%22%40context%22%3A%5B%22https%3A%2F%2Fwww.w3.org%2F2018%2Fcredentials%2Fv1%22%2C%22https%3A%2F%2Fwww.w3.org%2F2018%2Fcredentials%2Fexamples%2Fv1%22%5D%2C%22types%22%3A%5B%22VerifiableCredential%22%2C%22UniversityDegree%22%5D%7D%7D%5D%2C%22grants%22%3A%7B%22authorization_code%22%3A%7B%22issuer_state%22%3A%220431b78c-cd94-4f50-bfdf-e24d436c0cf6%22%7D%2C%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%7B%22pre-authorized_code%22%3A%22eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiIwNDMxYjc4Yy1jZDk0LTRmNTAtYmZkZi1lMjRkNDM2YzBjZjYiLCJpc3MiOiJodHRwczovL2lzc3Vlci5wb3J0YWwud2FsdC5pZCIsImF1ZCI6IlRPS0VOIn0.NorG7GtjmA-HXMJfUzU9vfnshcIgFY0oYQb8qJjDfORPoNxuurgySSOIDKFi7Z4XJmC-oJZnM0Nbb0NUd57cDA%22%2C%22user_pin_required%22%3Afalse%7D%7D%7D
Decoded
openid-credential-offer://issuer.portal.walt.id/?
credential_offer=
{
"credential_issuer": "https://issuer.portal.walt.id",
"credentials": [
{
"format": "jwt_vc_json",
"types": [
"VerifiableCredential",
"UniversityDegree"
],
"credential_definition": {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1"
],
"types": [
"VerifiableCredential",
"UniversityDegree"
]
}
}
],
"grants": {
"authorization_code": {
"issuer_state": "0431b78c-cd94-4f50-bfdf-e24d436c0cf6"
},
"urn:ietf:params:oauth:grant-type:pre-authorized_code": {
"pre-authorized_code": "eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiIwNDMxYjc4Yy1jZDk0LTRmNTAtYmZkZi1lMjRkNDM2YzBjZjYiLCJpc3MiOiJodHRwczovL2lzc3Vlci5wb3J0YWwud2FsdC5pZCIsImF1ZCI6IlRPS0VOIn0.NorG7GtjmA-HXMJfUzU9vfnshcIgFY0oYQb8qJjDfORPoNxuurgySSOIDKFi7Z4XJmC-oJZnM0Nbb0NUd57cDA",
"user_pin_required": false
}
}
}
If you want to dive deeper into the issuer service, you can go here.
Receive the Credential Offer
The created credential offer can now be embedded into a QR code for users to scan with their mobile wallet or pasted manually into the credential offer field of our web-wallet. If you want to run the web-wallet locally on your machine, you can checkout our guide here.
Verify A Credential
To request and verify the credential issued previously we will leverage the verifier service and make the following request.
curl -X 'POST' \
'http://waltid.enterprise.localhost:3000/v1/waltid.tenant1.verifier1/verifier-service-api/credentials/verify' \
-H 'accept: */*' \
-H 'Authorization: Bearer {yourToken}' \
-H 'authorizeBaseUrl: openid4vp://authorize' \
-H 'responseMode: direct_post' \
-H 'successRedirectUri: https://example.com/success/{id}' \
-H 'errorRedirectUri: https://example.com/error/{id}' \
-H 'statusCallbackUri: https://example.com/status_callback/{id}' \
-H 'statusCallbackApiKey: myAPIKey' \
-H 'stateId: myUniqueStateValue' \
-H 'Content-Type: application/json' \
-d '{
"request_credentials": [
{ "type": "UniversityDegree", "format": "jwt_vc_json" }
]
}'
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 + verifier service with which to execute the credential verification ({organizationID}.{tenantID}.{verifierServiceID}
), e.g.waltid.tenant1.verifier1
Header Parameters
- authorizeBaseUrl - is used to modify the start of the OID4VC request URL. If you are using the
cross-device
flow, where you will display the URL as a QR code, you can leave the value as
openid4vp://authorize
or if you don't know the wallet the user will be using to claim the credential. If you are using the same device flow, where you already know the user's wallet and want the user to be able to go directly to it, you can use the wallet URL path that is able to receive an OIDC request as a query parameter. Our wallet for example can receive OID4VC requests herehttps://wallet.walt.id//wallet-api/wallet/{wallet}/exchange/useOfferRequest
. - responseMode - should be
direct_post
as the other options are not yet supported. - successRedirectUri (optional) - is used to redirect the user if verification is successful. You can use the
$id
placeholder to get access to the id of verification session in your application in order to retrieve the verification results. E.g./success?id=$id
will be replaced with/success?id=1234567890
- errorRedirectUri (optional) - is used to redirect the user if verification is unsuccessful. You can use the
$id
placeholder to get access to the id of verification session in your application in order to retrieve the verification results. E.g./error?id=$id
will be replaced with/error?id=1234567890
- statusCallbackUri (optional) - URL that should be called when the presentation request has been fulfilled by a
wallet. The request
sent will be a
POST
including the whole presentation result. See Verification Status Policies Response - statusCallbackApiKey (optional) - If the endpoint you provide via
statusCallbackUri
is protected, you can use thestatusCallbackApiKey
to authenticate. The provided key will be appended as Authorization Header to the request. - stateId (optional) - overwrite the unique
state
value which gets created for each verification request with your own. - openId4VPProfile (optional) - Define profile for VP (Verifiable Presentation) request. The default is W3C
OpenID4VP, which can optionally provided as
DEFAULT
. Apart from that, you can choose from the following options:EBSIV3
: For EBSI V3 compliant VP. Learn more here.ISO_18013_7_MDOC
: For mdoc Openid4VP.
Body Parameters
- request_credentials - An array of objects detailing the credentials to be requested from the user. Each object
varies based on the type of credential being requested.
Expand To Learn More
Below are the possible credential types and their respective object structures:
- W3C JWT Credential
- type Specifies the type of credential (e.g.,
VerifiableID
,VerifiableDiploma
). This maps to thetype
attribute in W3C credentials. - format: Describes the format of the credential. For W3C JWT credentials, this would
be
jwt_vc_json
.{ "type": "ProofOfResidence", "format": "jwt_vc_json" }
- SD-JWT VC Credential (IETF Standard)
- vct: Specifies the type of credential (e.g., https://issuer.com/identity_credential). This maps to
the
vct
attribute in the SD-JWT VC credential. - format: Describes the format of the credential. For SD-JWT VC credentials, this would be
vc+sd-jwt.: Describes the format of the credential. For SD-JWT VC credentials, this would be
vc+sd-jwt
.
{ "vct": "test.com/identity_credential", "format": "vc+sd-jwt" }
- mDL Credential (ISO/IEC 18013-5)
- doc_type: Specifies the type of credential (e.g., org.iso.18013.5.1.mDL). This maps to the doc_type attribute of the mdoc document.
- format: Describes the format of the credential. For mDL credentials, this would be
mso_mdoc
.{ "doc_type": "org.iso.18013.5.1.mDL", "format": "mso_mdoc" }
Optional Parameters
Next to describing the type and format of the credential, the objects also take an optionalpolicies
andid
attribute- policies: An array of policies to apply to the specified credential. A list of all policies can be found here.
- id: Used to set a specific id for the generated Presentation Definition. If not set, the verifier API auto-assigns a generated one.
Full Examples
{ "type": "ProofOfResidence", "format": "jwt_vc_json", "policies": [ "schema", { "policy": "webhook", "args": "https://example.org/abc/xyz" } ], "id": "test123" }
The OID4VP URL from the verifier can be rendered as a QR code for the wallet to scan or entered in the credential request field of the walt.id web wallet. To run the web wallet locally, check our guide here.
Inspect Verification Result
After the user presents the credential(s), you can verify the status by performing the following call with the
state
value obtained from the URL query params you shared with the user previously.
Example
openid4vp://authorize?...state=waltid.tenant1.verifier1.fb94470b-bcee-4ed7-93f5-906a4f5f585d...
Making the call to receive the verification result
curl -X 'GET' \
'http://waltid.enterprise.localhost:3000/v1/waltid.tenant1.verifier1.{state}/verifier-service-api/credentials/sessions/view' \
-H 'accept: */*'
-H 'Authorization: Bearer {yourToken}'
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 + verifier service + sessionID to request ({organizationID}.{tenantID}.{verifierServiceID}.{sessionID}
), e.g.waltid.tenant1.verifier1.fb94470b-bcee-4ed7-93f5-906a4f5f585d
Verification Status Policies Response
The response of the verification status call will contain the status of the verification policies applied to the credential(s) presented by the user. The policy results will be in the following format:
{
"verificationResult": true,
"policyResults": {
"results": [
{
"credential": "VerifiableDiploma",
"policies": [
{
"policy": "signature",
"is_success": true
}
]
}
]
}
}
The verificationResult
field will be true
if all policies were successful, otherwise it will be false
.
The policyResults
field will contain the results of the policies applied to each credential. The credential
field
will contain the name of the credential, and the policies
field will contain the results of the policies applied to
the credential. The policy
field will contain the name of the policy, and the is_success
field will contain the
result of the policy.
Want to learn more about the verifier service in detail? Please go here.