# walt.id Documentation — Full Text
> Complete documentation of walt.id's digital identity infrastructure in one file: Part 1 covers stack-agnostic concepts, Part 2 the open-source Community Stack, Part 3 the commercial Enterprise Stack. The two stacks are distinct products with different APIs — always note which part a page belongs to.
Generated: 2026-07-20 · Community Stack version: 0.21.0 · Enterprise Stack version: 0.21.0
Page index: https://docs.walt.id/llms.txt · Stack comparison: https://docs.walt.id/community-vs-enterprise.md
OpenAPI 3.1 specifications (machine-readable):
- Issuer API — Community Stack: https://issuer.demo.walt.id/api.json
- Verifier API — Community Stack: https://verifier.demo.walt.id/api.json
- Wallet API — Community Stack: https://wallet.demo.walt.id/api.json
- Enterprise API — Enterprise Stack: https://enterprise.sandbox.walt.id/api.json
################################################################################
# PART 1 OF 3: CONCEPTS — STACK-AGNOSTIC (20 pages)
# Stack-agnostic background: DIDs, credential formats (W3C VC, SD-JWT VC, mDL/mdoc),
# selective disclosure, OID4VCI/OID4VP protocols, trust systems.
# These concepts apply to BOTH the Community Stack and the Enterprise Stack.
################################################################################
--------------------------------------------------------------------------------
PAGE: Concepts
URL: https://docs.walt.id/concepts
RAW: https://docs.walt.id/concepts.md
STACK: Concepts (stack-agnostic, applies to both Community and Enterprise Stack)
--------------------------------------------------------------------------------
# Concepts
Learn more about the general building blocks of decentralized ID solutions.
- [Decentralised Identity](https://docs.walt.id/concepts/decentralized-identity.md) - An introduction to decentralized identity.
- [ID Ecosystems](https://docs.walt.id/concepts/id-ecosystems.md) - The role of ecosystems in decentralized ID solutions.
- [Decentralised Identifiers (DIDs)](https://docs.walt.id/concepts/decentralised-identifiers.md) - The role of DIDs and how they work.
### Digital Credentials
Learn more about the different types of digital credentials
- [Verifiable Credentials (W3C)](https://docs.walt.id/concepts/digital-credentials/verifiable-credentials-w3c.md) - The W3C data model for tamper-evident digital credentials.
- [mDL / mdoc credentials](https://docs.walt.id/concepts/digital-credentials/mdoc-mdl-iso.md) - ISO/IEC 18013 mobile driver's license and mdoc credential format.
- [SD-JWT VC credentials](https://docs.walt.id/concepts/digital-credentials/sd-jwt-vc.md) - JWT-based credentials with selective disclosure for claim-level privacy.
- [Mono claim credentials](https://docs.walt.id/concepts/selective-disclosure/mono-claim-credentials) - Single-attribute credentials for minimal disclosure and simple verification.
### Credential Exchange Protocols
- [OpenID4VCI (Issuance)](https://docs.walt.id/concepts/data-exchange-protocols/openid4vci.md) - Protocol for issuing verifiable credentials to wallets.
- [OpenID4VP (Presentation)](https://docs.walt.id/concepts/data-exchange-protocols/openid4vp.md) - Protocol for presenting credentials from a wallet to a verifier.
- [Digital Credentials API (Browser-Based)](https://docs.walt.id/concepts/data-exchange-protocols/dc-api.md) - W3C browser API that mediates credential presentation and issuance natively in the browser also known as DC API.
- [HAIP (High Assurance Profile)](https://docs.walt.id/concepts/data-exchange-protocols/haip.md) - Security-hardened profile of OpenID4VCI and OpenID4VP for high-assurance interoperability. Required by eIDAS 2.
### Trust Systems
- [EU Trust Lists](https://docs.walt.id/concepts/trust-systems/eu-trust-lists.md) - Machine-readable registries of trusted entities for the EU digital identity ecosystem (eIDAS 2.0).
- [ISO mDL Trust / VICALs](https://docs.walt.id/concepts/trust-systems/iso-trust-vicals.md) - Certificate authority hierarchies for verifying ISO mobile document issuers.
- [Wallet Attestations](https://docs.walt.id/concepts/trust-systems/wallet-attestations.md) - Cryptographic proofs that establish the authenticity and integrity of digital wallets.
### Credential Lifecycle
- [Credential lifecycle](https://docs.walt.id/concepts/credential-lifecycle/credential-status-and-revocation.md) - How to manage the status (e.g. revocation) of credentials.
--------------------------------------------------------------------------------
PAGE: Decentralized Identity
URL: https://docs.walt.id/concepts/decentralized-identity
RAW: https://docs.walt.id/concepts/decentralized-identity.md
STACK: Concepts (stack-agnostic, applies to both Community and Enterprise Stack)
--------------------------------------------------------------------------------
# Decentralized Identity
## Definitions
Digital identity is the sum of all the (digital) information that exists about you. For example:
- core identity attributes (name, address or birthday),
- education and work history (diplomas, work records, certificates),
- health or insurance data (medical reports, prescriptions, vaccination passes),
- financial information (bank account information, transaction histories).
As a result, your digital identity describes who you are in every aspect of your (digital) life.
Decentralized identity is simply one (of many) approaches for making digital identity possible.
## **How decentralized identity works**
Decentralized identity enables governments and businesses to issue identity credentials to their citizens, users or other stakeholders. These credentials are stored in ID wallets and can be easily shared with others. It is like a 3-sided marketplace:
1. **Issuers** issue digital identity credentials (e.g. governments issue passports, universities issue diplomas).
2. **Holders** receive digital credentials (from Issuers) and store them in ID wallets. They control and share their digital credentials with third parties (Verifiers).
3. **Verifiers** rely on identity data to provide products and services, they verify and process credentials provided by Holders.
The so-called “**Trust Triangle**” illustrates these roles:

## Technology & Building Blocks
Decentralized identity is based on a set of complex technologies and protocols which can be thought of as building blocks that are available in different variations and can be put together in different ways. As a result, there are different “flavors” or ways to implement decentralized identity depending on the business requirements.
The following list outlines some of the most important ones:
- **Cryptographic keys** convey control over digital identities and enable core functionality such as encryption and authentication.
- **Decentralized Identifiers** (DIDs, W3C) establish a public key infrastructure by linking keys to identifiers allowing parties to find and interact with each other.
- **Digital Credentials** like Verifiable Credentials (VCs, W3C) and mobile driver’s license/mdoc (ISO/IEC 18013-5/-7) are the most common “types” of digital credentials. They can be easily and securely shared with and verified by others. (Note that they are never stored on a blockchain due to privacy and compliance reasons.)
- **Selective Disclosure** (SD) and **Zero Knowledge Proofs** (ZKPs) are ways to minimize data exposure. Selective disclosure using SD-JWTs (IETF) is a solid and reliable way to improve users’ privacy that is gaining traction. While ZKPs are a promising technology with a bright future, they are not yet broadly adopted due to concerns or lacking certifications of the underlying cryptography,
- **Non-Fungible Tokens** (NFTs) and **Soulbound Tokens** (SBTs) are used to tokenize proofs or assets on blockchains. They enable “ownership-based access” such as for holders of tickets or memberships. Also, they enable on-chain proofs that can be processed by smart contracts (without the use of oracles).
- **Data Exchange Protocols** enable the transfer of identity credentials between parties (like Issuer to Holder or Holder to Verifier). Currently, OpenID Connect, the standard that already enables federated identity, is evolving into the most used standard for the exchange of digital credentials. Protocols can enable data exchange online and in-person scenarios (even offline).
- **ID Wallets** store our keys (control) and credentials (identity data). They enable the management and sharing of our identity data via easy-to-use apps.
## **Walt.id**
We’re building open source decentralized identity and wallet infrastructure trusted by thousands of developers, governments and businesses. Our goal is to enable anyone to build use cases and applications based on decentralized identity fast and with ease.
Our infrastructure enables organizations to become Issuers, Wallet Providers and Verifiers.

You can think of our infrastructure as an abstraction layer that hides technical complexity and handles different building blocks (e.g., trust registries, credential types, data exchange protocols) while ensuring ongoing compliance with standards and regulations.

## **Further Readings**
You can learn more about decentralized identity in our ebooks:
- [Decentralized Identity Playbook](https://walt.id/white-paper/decentralized-identity-playbook) (eBook)
- [Introduction to Digital Identity](https://walt.id/white-paper/digital-identity) (eBook)
- [Introduction to Decentralized Identity / Self-Sovereign Identity](https://walt.id/white-paper/self-sovereign-identity-ssi) (eBook)
- [Introduction to non-fungible (NFTs) and soulbound tokens (SBTs) for identity](https://walt.id/white-paper/nfts-for-identity) (eBook)
- [NFTs/SBTs vs Self-Sovereign Identity](https://walt.id/white-paper/ssi-vs-nfts) (eBook)
- [Monetizing decentralized identity](https://walt.id/white-paper/ssi-data-monetization) (eBook)
- [The rise of identity ecosystems](https://walt.id/white-paper/the-rise-of-identity-ecosystems) (eBook)
- [The role of blockchain for decentralized identity](https://walt.id/white-paper/the-role-of-blockchains-for-decentralized-identity) (eBook)
- [eIDAS2 is here](https://walt.id/white-paper/eidas2) (eBook in collaboration with Trustscape)
- [Me, myself and SSI](https://mvtrosz5a6vgwqrp.public.blob.vercel-storage.com/me-myself-and-ssi-bcg-x-waltid.pdf) (eBook in collaboration with BCG)
- [A digital twin for everything](https://mvtrosz5a6vgwqrp.public.blob.vercel-storage.com/digital-twin-of-everything-bcg-x-waltid.pdf) (eBook in collaboration with BCG)
--------------------------------------------------------------------------------
PAGE: ID Ecosystems
URL: https://docs.walt.id/concepts/id-ecosystems
RAW: https://docs.walt.id/concepts/id-ecosystems.md
STACK: Concepts (stack-agnostic, applies to both Community and Enterprise Stack)
--------------------------------------------------------------------------------
# ID Ecosystems
ID ecosystems enable users to control and share their data with others. They are open, user-centric systems that enable
direct interactions and the free exchange of identity information.
Identity ecosystems create trust between people and organizations that typically don’t know each other. They have two
major components: "Trust Registries" and "Governance and Trust Frameworks".
## Trust Registries
Trust Registries enable the verification of identity data, serving as the single source of truth and can be implemented
with different technologies and levels of decentralization (e.g. Domain Name Service, traditional PKIs, blockchains).
There are different types of Trust Registries for different purposes. For example:
- Organization Registries enable the verification of information about organizations acting as Issuers or Verifiers.
- Schema Registries established standards for data models and semantics.
- Revocation Registries enable management and verification of credentials’ lifecycle.
Different technologies can be used to implement Registries. For example:
- **Blockchains** or **L2s**: Blockchains are a useful technology to create Trust Registries (e.g. Organization
Registries) due to their decentralization, immutability, transparency, security, and efficiency. They provide a
permanent, auditable record without intermediaries. Also, they can be used to enable NFTs or SBTs for diverse use
cases like tokenizing proofs or assets in a way that can be processed by smart contracts. Today, we see a growing
number of developers and organizations focusing on permissioned blockchains (i.e. only a selected group can “write”).
Permissionless blockchains, like Ethereum, are still less used than permissioned alternatives particularly due to
issues with scalability, costs or the lack of customizable governance frameworks.
- **Domain Name Service (DNS)**: Considering certain drawbacks of DLTs and their relatively slow adoption by the mass
market, DNS can also be used to serve as a registry. Though it is not fully decentralized (considering its underlying
governance framework), DNS has many advantages like its maturity and global adoption. Importantly, digital identity
can be implemented without registries, particularly without blockchains, because identity data (or at least personal
data of individuals) is never anchored due to privacy and compliance reasons. However, by combining digital identity
with blockchains (or other technologies), robust and trustworthy identity ecosystems that utilize transparent DPKIs
and reliable Trust Registries can emerge.
- **Traditional Public Key Infrastructure (PKI)**: Finally, traditional public key infrastructures, that already
underpin today’s identity and certificate infrastructure can be used. The main downside is the centralization,
information asymmetry and dependence on the PKI provider.
## Governance and Trust Frameworks
Governance and Trust Frameworks are like the constitution of an ID ecosystem, ensuring trust in identity data from
Issuers and other ecosystem participants. They regulate governing bodies and processes, onboarding and accreditation,
liability and enforceability, trust and assurance levels, privacy and data protection, security and interoperability
standards. Also, these frameworks align with regulations that impact the identity industry, such as GDPR or eIDAS2 in
Europe.
You can read more about this topic in
our [ID ecosystem ebook](https://walt.id/white-paper/the-rise-of-identity-ecosystems).
--------------------------------------------------------------------------------
PAGE: Decentralised Identifiers
URL: https://docs.walt.id/concepts/decentralised-identifiers
RAW: https://docs.walt.id/concepts/decentralised-identifiers.md
STACK: Concepts (stack-agnostic, applies to both Community and Enterprise Stack)
--------------------------------------------------------------------------------
# Decentralised Identifiers
DIDs give us the power of verifying information, for example credentials, anywhere, anytime, through the establishment
of a public key infrastructure.
DIDs are **unique identifiers** (URIs) which are standardised by the [W3C](https://www.w3.org/TR/did-core/). They can
refer to any subject - from a person, to
an organization, to a thing or basically anything else for that matter. Before we have a look at how a DID is structured
and the benefit it provides, let's understand the shortcomings of current identifiers first and then see how the DID
solves those.
Video: https://www.youtube.com/watch?v=XajpI6yWxwA
## The shortcomings of current IDs
In the digital economy, data exchange happens a lot, which makes it increasingly important to be able to identify
persons, concepts or anything else for that matter in a secure and verifiable way.
A person today can have multiple different identifiers, like:
- name@surname.com
- https://www.surname.com
- 0000-0000-0000-0000 (ORCID based identifier, used to identify authors of scholarly communication)
All of these identifiers work, but none fulfills to be decentralized, persistent, resolvable and cryptographically
verifiable when put into the following questions.
#### Is the identifier decentralized?
- `https://www.surname.comdepends` on a single point of failure. What happens if the hosting side disappears?
- `0000-0000-0000-0000 (ORCID)` depends on the ORCID database. What happens if it is discontinued, hacked, etc?
#### Is the identifier persistent?
- `https://www.surname.com` If I do no longer pay for that domain, the identifier will be gone or even bought by
somebody else.
#### Is the identifier resolvable?
- How can I get additional information about `0000-0000-0000-0000 (ORCID)` identifiers?
#### Is the identifier verifiable?
- How can I prove that I own the domain, `https://www.surname.com`?
- If I stopped paying for my domain, `https://www.surname.com` and somebody else would buy it. How would somebody know
that
the information provided on the side was actually mine?
All those problems make it hard to be 100% sure when exchanging information, that the party we are exchanging
information with, is actually the party and not some malicious actor pretending to be the party.
## DID to the rescue
The design of DIDs, which is a new form of unique identifier that has been standardized by the W3C, can now help us
address these problems of current identifiers, by being:
**Decentralized**
- The DIDs no longer depend on centralized registries, identity providers, authorities, etc.
**Persistent**
- Once created, the did is permanently assigned to the subject.
**Resolvable**
- It is possible to find a basic set of information when resolving the did. Typically, this will lead to a DID Document.
**Cryptographically verifiable**
- There is a mechanism to cryptographically prove identity and ownership via information provided by the DID Document.
#### DID format:
The DID is a simple text string built up of three parts:

| Schema | The did URL schema identifier |
| ------------------------------ | --------------------------------------------------------- |
| DID Method | The identifier for the did method |
| DID Method-Specific Identifier | An identifier based on the requirements of the did method |
A variety of **"DID methods"**, which are different implementations of the DID specification, exist. Considering that
DID
methods differ in terms of how they are created, registered and resolved, different methods come with different
advantages and disadvantages.
For example, while DIDs are often anchored on Registries, such as EBSI (did:ebsi) or the Domain Name Service (did:web),
new methods emerged that do not require Registries because their distribution is based on peer-to-peer interactions (
e.g. did:key).
Now we can take any did, look at the method and resolve it based on the framework around the method. The resolved
content will most of the time be **JSON** or **JSON-LD**, although other data formats might also be added in the future.
The
resolved content is called **DID Document**.
## DID Document
A container of information holding:
**The Subject**
- The owner of the DID Document
**The Controllers**
- The entities allowed to introduce changes to the DID Document. The controller may or may not be identical to the "
subject". For example, if the DID Document belonged to a DID of a book. The controller would be the author or another
related person, rather than the book itself.
**Cryptographic data**
- The DID document contains the public keys of the corresponding entity. The keys might be of any algorithm (typically
elliptical curve keys or RSA keys are used), and are mostly encoded in the JWK format. However, other encoding formats
such as PEM or Multibase are also supported. The keys can be classified to be used in certain use cases such as:
authentication, verification, etc.
**Service endpoints**
- Services that the subject wants to mention
## DID General Flow

#### Example (did:ebsi)
The identifier `did:ebsi:2A9RkiYZJsBHT1nSB3HZAwYMNfgM7Psveyodxrr8KgFvGD5y` of the method `did:ebsi` would resolve to the
following DID Document:
```json
{
"@context": ["https://w3id.org/did/v1"],
// keys used for the authentication of the contoller
"authentication": ["did:ebsi:2A9RkiYZJsBHT1nSB3HZAwYMNfgM7Psveyodxrr8KgFvGD5y#1a7514b2d58141c3982021a6323b99bf"],
// the subject
"id": "did:ebsi:2A9RkiYZJsBHT1nSB3HZAwYMNfgM7Psveyodxrr8KgFvGD5y",
// the cryptographic data used for authentication, verification or other use cases
// utilizing public keys
"verificationMethod": [
{
"controller": "did:ebsi:2A9RkiYZJsBHT1nSB3HZAwYMNfgM7Psveyodxrr8KgFvGD5y",
"id": "did:ebsi:2A9RkiYZJsBHT1nSB3HZAwYMNfgM7Psveyodxrr8KgFvGD5y#1a7514b2d58141c3982021a6323b99bf",
"publicKeyJwk": {
"alg": "EdDSA",
"crv": "Ed25519",
"kid": "1a7514b2d58141c3982021a6323b99bf",
"kty": "OKP",
"use": "sig",
"x": "tqJADByHRU3YxswewQD4wQYXU9tB43j3PfjofsYEvqs"
},
"type": "Ed25519VerificationKey2018"
}
]
}
```
## Working With DIDs
walt.id offers a wide range of open-source tools that help you create, resolve and verify DIDs based on different
ecosystems easily. To get started, checkout:
- [DID Lib](https://github.com/walt-id/waltid-identity/tree/main/waltid-libraries/waltid-did) - work with DIDs in Kotlin/Java and JavaScript environments.
- [Issuer API](https://issuer.demo.walt.id/swagger/index.html#/Issuer%20onboarding/post_onboard_issuer) - create a set of keys and DIDs via the onboard functionality.
- [Wallet API](https://docs.walt.id/community-stack/wallet/did-management/overview.md) - create & manage DIDs for credential holders.
--------------------------------------------------------------------------------
PAGE: Digital Credentials
URL: https://docs.walt.id/concepts/digital-credentials
RAW: https://docs.walt.id/concepts/digital-credentials.md
STACK: Concepts (stack-agnostic, applies to both Community and Enterprise Stack)
--------------------------------------------------------------------------------
# Digital Credentials
Explore different credential formats below:
- [W3C Verifiable Credential](https://docs.walt.id/concepts/digital-credentials/verifiable-credentials-w3c.md)
- [SD-JWT VC (IETF)](https://docs.walt.id/concepts/digital-credentials/sd-jwt-vc.md)
- [mDL (ISO/IEC 18013-5)](https://docs.walt.id/concepts/digital-credentials/mdoc-mdl-iso.md)
--------------------------------------------------------------------------------
PAGE: Verifiable Credentials: A Simple Guide to How They Work
URL: https://docs.walt.id/concepts/digital-credentials/verifiable-credentials-w3c
RAW: https://docs.walt.id/concepts/digital-credentials/verifiable-credentials-w3c.md
STACK: Concepts (stack-agnostic, applies to both Community and Enterprise Stack)
--------------------------------------------------------------------------------
# What Are Verifiable Credentials? The Complete Guide
## The Problem with Digital Trust Today
In the modern digital ecosystem, identity is fragmented and insecure. The systems used to manage and verify who a person is online are largely built on outdated models that expose individuals and organizations to significant risk. Traditional digital identity relies on centralized and federated approaches, both of which present fundamental flaws.
In a **centralized identity** model, each service a person uses—from social media to online banking—maintains its own separate database of user credentials. This approach creates countless data silos, each a potential target for attack. A breach at any single service provider can expose the sensitive personal information of millions of users.
The **federated identity** model, exemplified by "Sign in with Google" or "Log in with Facebook," was developed to simplify this process. While more convenient, it introduces a different set of problems. It concentrates immense power in the hands of a few large identity providers, who can track user activity across different websites and services. This creates significant privacy concerns, as personal data can be correlated and used in ways the individual never intended or explicitly consented to. Both models suffer from a core weakness: the user is never truly in control of their own data.
### A Comparison of Digital Identity Models
## Table 1: A Comparison of Digital Identity Models
| Category | Centralized Identity | Federated Identity | Self-Sovereign Identity (SSI) |
|---|---|---|---|
| Data Control | Controlled by a single service provider (e.g., a website). | Controlled by a third-party Identity Provider (e.g., Google, Facebook). | Controlled by the individual user. |
| User Privacy | Low, as the provider controls data collection and use. | Medium. Data is shared between services, creating potential for tracking. | High. User shares only necessary data (selective disclosure). |
| Single Point of Failure | High. A breach at the provider compromises all user data. | High. A breach at the Identity Provider compromises access to many services. | Low. Decentralized nature eliminates single targets for large-scale breaches. |
| Portability | Low. Identity is locked to a single service. | Medium. Identity is portable across services that support the provider. | High. Credentials are held by the user and are interoperable by design. |
To solve these deep-seated issues of security, privacy, and control, a new technical paradigm is required. This is where Verifiable Credentials (VCs) emerge as a foundational technology for a more secure and user-centric internet. In essence, Verifiable Credentials are tamper-evident, cryptographically secure digital versions of the credentials we use every day, giving individuals control over their own data and allowing them to share proof of their achievements and attributes without revealing unnecessary personal information.
## Verifiable Credentials: The Digital Wallet for Your Achievements
The easiest way to understand Verifiable Credentials is to compare them to a physical wallet. A physical wallet holds various items that prove things about a person: a driver's license proves the ability to drive, a university ID proves student status, and an insurance card proves coverage. Each card was issued by a different trusted authority (the DMV, a university, an insurance company) and now resides in the individual's possession, under their direct control.
A **digital wallet** for **Verifiable Credentials** functions in precisely the same way, but for the digital world. It is a secure application, typically on a smartphone, where an individual can store and manage their digital credentials. Just as a person decides whether to pull out their driver's license or just state their name, the holder of a digital wallet has complete control over when, where, and with whom they share their digital credentials.
This analogy reveals a crucial shift in thinking away from a single, monolithic "digital identity." A wallet does not contain a person's "identity"; it holds a collection of distinct, verifiable attributes or claims. One credential makes a claim about driving privileges, another about educational attainment. The power of VCs lies in this granularity. They are not about creating one universal online ID but about empowering users to manage a collection of discrete, context-specific claims about themselves. "Verifying credentials," in this context, is the process where a third party can request claims about you and then cryptographically check the authenticity of these requested claims instantly, without needing to contact the original issuer every time.
## The Core Components: The Triangle of Trust
The **Verifiable Credentials** ecosystem is built on the interaction between three core roles. This relationship is often referred to as the "triangle of trust" and is essential for the system's function.
### The Issuer: The Source of Truth
The **Issuer** is an organization that attests to a claim about a subject and creates a cryptographically signed credential containing that claim.
- **Examples**: A government agency issuing a digital passport, a university issuing a digital diploma, a bank issuing proof of income, or an employer issuing an employee badge.
### The Holder: The User in Control
The **Holder** is the individual or entity that receives the credential from the **Issuer**. The Holder stores the credential in their personal digital wallet and has sole authority over how and when it is shared.
### The Verifier: The Trusting Party
The **Verifier** is the person or organization that needs to confirm a claim about the **Holder**. The Verifier requests proof from the Holder, who then presents their **Verifiable Credential**. The Verifier can then check the credential's authenticity and integrity instantly.
- **Examples**: An employer verifying a job applicant's degree, a website confirming a user is over 18, a border agent checking a digital passport, or a hospital confirming a doctor's medical license.
## How Verifiable Credentials Work: A Step-by-Step Process
The lifecycle of a **Verifiable Credential** follows a clear four-step process:
### Step 1: Issuance (Creating the Credential)
The process begins when an **Issuer** validates information about a potential **Holder** through a standard process, such as a Know Your Customer (KYC) check or student enrollment confirmation. Once the information is confirmed, the **Issuer** generates a digital credential containing specific claims (e.g., "Date of Birth: 1990-05-15"). This credential is then cryptographically signed with the **Issuer**'s private key, creating a tamper-evident seal, and is securely transmitted to the **Holder**.
### Step 2: Storage (The Holder's Digital Wallet)
The **Holder** receives the credential and accepts it into their digital wallet. This wallet can be an app or web application on their phone or laptop. Once stored, the credential resides entirely under the **Holder**'s control, independent of the **Issuer**'s systems. This is a critical distinction from traditional models where data remains on a company's servers.
### Step 3: Presentation (Sharing Proof, Not Data)
When a **Verifier** needs to confirm a claim, they request it from the **Holder**. The **Holder** uses their wallet to generate a **Verifiable Presentation**. This is a bundle that contains the relevant credential (or parts of it) and is also cryptographically signed by the **Holder** using their private key. This second signature proves to the **Verifier** that the **Holder** is the rightful owner of the credential and consents to sharing it.
Crucially, this step enables selective disclosure. The Holder can choose to reveal only the specific claims necessary for a given transaction. For example, to prove they are of legal drinking age, they can present only the claim "is over 21" without revealing their name, address, or exact date of birth, dramatically enhancing privacy.
### Step 4: Verification (The Cryptographic Handshake)
The **Verifier** receives the **Verifiable Presentation** and performs a series of automated cryptographic checks. First, it verifies the **Holder**'s signature to confirm ownership. Second, it verifies the **Issuer**'s original signature on the credential. To do this, the **Verifier** retrieves the **Issuer**'s public key from a trusted data registry. If both signatures are valid, the **Verifier** can be certain that the credential is authentic, was issued by the claimed **Issuer**, has not been altered, and is being presented by its rightful owner. This entire verification happens in seconds, without the **Verifier** ever needing to contact the **Issuer** directly.
## What Are Examples of Verifiable Information?
The applications for **Verifiable Credentials** span nearly every industry, transforming processes that currently rely on slow, insecure, and paper-based verification.
### Example 1: The University Diploma
A university (**Issuer**) issues a digital diploma as a VC to a graduating student (**Holder**). When applying for a job, the student can instantly present this credential to a potential employer (**Verifier**). The employer's system can cryptographically verify its authenticity in seconds. This eliminates the need for expensive and time-consuming manual background checks and calls to the university registrar, streamlining the hiring process for both parties.
### Example 2: The Proof of Residence
A Public Utility Company (**Issuer**) issues a monthly digital bill as a Verifiable Credential (VC) to a resident (**Holder**). When signing up for a new internet service, the **Holder** needs to prove they live at the service address. The **Holder** can present a Verifiable Presentation to the Internet Service Provider (**Verifier**) that proves only two claims: "Full Name" and "Residential Address." The ISP's system can cryptographically confirm the validity of the address and that it is associated with the **Holder**'s name, without ever seeing the **Holder**'s utility account number, energy consumption, or other information. This fulfills the ISP's business needs while protecting the resident's other data.
### Example 3: The Professional License
A medical board (**Issuer**) issues a license-to-practice VC to a qualified doctor (**Holder**). When the doctor joins a new hospital, the hospital's administration (**Verifier**) can instantly confirm that the doctor's license is authentic and has not been revoked. This ensures patient safety, satisfies regulatory compliance, and dramatically speeds up the onboarding and credentialing process for medical professionals.
## DIDs vs. Verifiable Credentials: What's the Difference?
Within the world of **Self-Sovereign Identity (SSI)**, two foundational technologies work together: **Decentralized Identifiers (DIDs)** and **Verifiable Credentials (VCs)**. They are distinct but complementary.
A simple analogy helps clarify their roles: a DID is like your physical street address. It is a unique, persistent identifier for your home. Knowing the address `123 Main Street` doesn't tell someone anything personal about who lives there, their credit score, or their profession. It's simply a stable endpoint where information can be sent and received. A VC is like an Official Letter delivered to that mailbox, such as a diploma or a utility bill. Each letter is a specific claim from a specific issuer (a university, a power company).
More technically, **DID**s are a new type of identifier that individuals can create for themselves, which they own and control completely. A DID can be the anchor of a digital identity (some identity ecosystems only use private and public keys without DIDs). It contains no personal data itself; instead, it resolves to a public "DID Document" that contains the cryptographic public keys and potentially service endpoints needed to interact with the **DID**'s owner securely.
**Verifiable Credentials** are the statements or claims that are cryptographically bound to a **DID**. The **Issuer** signs a credential and addresses it to the **Holder**'s **DID**. From a technical standpoint, this relationship is the critical component that unlocks true data mobility and independence from any single issuer. Because the credential is tied to the user-controlled **DID** rather than an account on the **Issuer**'s system, the **Holder** can truly own and manage it for life.
## Understanding the W3C Verifiable Credentials Data Model
**Verifiable Credentials (VCs)** are based on open standards from the World Wide Web Consortium (W3C), ensuring interoperability and avoiding vendor lock-in. The **W3C Verifiable Credentials Data Model (VCDM)** defines a standard way to express credentials (the data model) and leaves room for multiple cryptographic encodings to make them tamper-evident and verifiable. Most commonly, VCs are secured using Data Integrity proofs (JSON-LD), JOSE/JWT (JWS), or SD-JWT (selective disclosure with JWT). The last one enables users to share only a subset of the claims in a credential.
### A quick note on VC Data Model versions
W3C has published two versions of the Verifiable Credentials Data Model: v1.1 (W3C Recommendation on March 3, 2022) and v2.0 (W3C Recommendation on May 15, 2025). Both define the same fundamental concepts (issuer, credentialSubject/claims, and a verifiable signature), while v2.0 refines terminology and alignment with modern security mechanisms (e.g., JOSE/COSE and Data Integrity) and improves extensibility. Many ecosystems still run v1.1, with adoption of v2.0 growing.
### The three core parts of a VC
### Claims (the pieces of information)
The `credentialSubject` block contains the claims about the subject (**Holder**). For a university diploma, claims could include degree type, major, and graduation date.
### Metadata (Who issued it and when)
Includes information like the issuer’s `DID`, the `issuanceDate`, and a unique credential `id`. This helps a verifier understand origin and lifecycle.
### Cryptographic proof or signature (the tamper-proof seal)
This is what makes the credential verifiable. Depending on the chosen approach, you’ll see either a proof block (Data Integrity / JSON-LD) or a JWT/JWS/SD-JWT signature that covers the credential payload. W3C’s VC-JOSE-COSE spec defines how to secure VCDM payloads with JOSE (JWT), COSE (CBOR), and SD-JWT.
### Example 1 — Data Integrity (JSON-LD) VC
```json
{
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1"
],
"id": "http://example.edu/credentials/3732",
"type": ["VerifiableCredential", "UniversityDegreeCredential"],
"issuer": "did:example:76e12ec712ebc6f1c221ebFEB1f",
"issuanceDate": "2024-01-01T19:23:24Z",
"credentialSubject": {
"id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
"degree": {
"type": "Bachelor of Science",
"name": "Computer Science"
}
},
"proof": {
"type": "Ed25519Signature2018",
"created": "2024-01-01T19:23:24Z",
"proofPurpose": "assertionMethod",
"verificationMethod": "https://example.edu/issuers/14#keys-1",
"jws": "eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..l9d_264a..."
}
}
```
Here, the VCDM payload is secured with a **Data Integrity** proof (a JSON-LD proof block).
### Example 2 — VC-JWT (JOSE/JWS) representation (simplified)
In a JWT representation defined by VC-JOSE-COSE, the same VCDM fields appear inside the JWT payload (e.g., under `vc`), and the credential is signed with JWS. Instead of a JSON-LD proof block, verification uses the JWT header+signature.
```json
// (header)
{ "alg": "EdDSA", "typ": "vc+jwt" }
// (payload) — key VCDM fields embedded as JSON
{
"iss": "did:example:76e12ec712ebc6f1c221ebFEB1f",
"nbf": 1704137004,
"vc": {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1"
],
"type": ["VerifiableCredential", "UniversityDegreeCredential"],
"id": "http://example.edu/credentials/3732",
"issuanceDate": "2024-01-01T19:23:24Z",
"issuer": "did:example:76e12ec712ebc6f1c221ebFEB1f",
"credentialSubject": {
"id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
"degree": { "type": "Bachelor of Science", "name": "Computer Science" }
}
}
}
// The compact JWS string is what gets transported/presented.
```
## The Future is Verifiable: Key Benefits
The shift toward **Verifiable Credentials** and **Self-Sovereign Identity** offers great benefits for individuals, organizations, and the digital economy as a whole.
### Enhanced Security & Fraud Prevention
Because VCs are **cryptographically** signed and tamper-evident, they are exponentially more difficult to forge than physical documents or simple digital files. This provides strong guarantees against fraud. Furthermore, by moving data from centralized, high-value targets to individual **digital wallets**, the SSI model drastically reduces the risk and impact of large-scale data breaches.
### Better User Privacy & Control
This is perhaps the most significant benefit. Through **selective disclosure**, users can prove a specific claim without over-sharing personal data. This principle of data minimization is a cornerstone of modern privacy regulations and is built into VCs.
### Interoperability and Efficiency
Built on open W3C standards, VCs are designed to work across different services, industries, and international borders, creating a digital ID model that works globally. This interoperability eliminates redundant and inefficient verification processes. Instead of every new service conducting its own costly identity check, it can simply verify a credential that has already been issued by a trusted source. This speeds up everything from customer onboarding and employee credentialing to loan applications and supply chain management.
## Become a Verifiable Credential Issuer, Verifier, or Wallet Provider with walt.id
Learn how to issue, verify, and store W3C VCs quickly and easily. Choose the free open-source Community Stack (Apache-2.0) or the production-ready Enterprise Stack from walt.id, trusted by 25,000+ developers and organizations worldwide.
### Product Editions
- [The Community Stack](https://docs.walt.id/community-stack/home/the-community-stack.md) (Apache-2.0) — trusted open source multi-platform libs, powerful APIs and easy-to-use white label apps for digital identity and wallets.
- [The Enterprise Stack](https://docs.walt.id/enterprise-stack/home/the-enterprise-stack.md) — a solution for building secure, compliant and robust identity and wallet solutions or platforms at scale.
### Guides
- [How to issue a W3C Verifiable Credential](https://docs.walt.id/community-stack/issuer/credential-issuance/vc-oid4vc.md)
- [How to verify a W3C Verifiable Credential](https://docs.walt.id/community-stack/verifier/credential-verification/vc-oid4vc.md)
- [How to Store a W3C Verifiable Credential](https://docs.walt.id/community-stack/wallet/credential-exchange/guides/accept-w3c-vc-oid4vci.md)
- [How to Present a W3C Verifiable Credential](https://docs.walt.id/community-stack/wallet/credential-exchange/guides/present-digital-credentials-oid4vp.md)
--------------------------------------------------------------------------------
PAGE: SD-JWT VC Explained: A Practical Guide for 2026
URL: https://docs.walt.id/concepts/digital-credentials/sd-jwt-vc
RAW: https://docs.walt.id/concepts/digital-credentials/sd-jwt-vc.md
STACK: Concepts (stack-agnostic, applies to both Community and Enterprise Stack)
--------------------------------------------------------------------------------
# SD-JWT VC: The Ultimate Guide to Verifiable Credentials with Selective Disclosure
A Selective Disclosure JSON Web Token Verifiable Credential (SD-JWT VC) is a new type of digital credential format, based on Internet Engineering Task Force (IETF) standards, designed for privacy-preserving information sharing.
In this guide we learn how SD-JWT VCs work, look at runnable code examples and do a deep dive into more advanced topics like revocation and type metadata that are often buried in dense specifications.
## What is an SD-JWT VC? A Plain English Introduction
To understand the importance of SD-JWT VC, it is essential to first grasp the problem it is designed to solve.
### The Privacy Problem: Why Sharing Your Whole ID is a Bad Idea
Traditional identity documents, from a physical driver's license to standard digital certificates, operate on an "all-or-nothing" principle. To prove a single fact—for instance, that a person is over 21 years of age—an individual is often forced to reveal a full set of personal data, including their name, home address, and exact date of birth. This over-sharing of information is a significant privacy risk.
### The Solution: How SD-JWTs Allow You to Share Only What's Necessary
The SD-JWT VC provides an elegant solution to this long-standing privacy dilemma. It is a verifiable credential format that enables selective disclosure, a mechanism that gives individuals granular control to reveal only specific pieces of information (claims) while keeping the rest private and cryptographically hidden.
This is achieved through a clever architectural design. Instead of including all personal data directly in the credential, an SD-JWT VC only contains cryptographic “placeholders” (hashes) for sensitive claims. The actual values, called **Disclosures**, are stored separately. When a user (**Holder**) needs to prove something, they share the main token plus only the specific **Disclosures** the **Verifier** requires. The **Verifier** then checks that these **Disclosures** match the hashes in the token, ensuring they are genuine and were issued by the original **Issuer**.
This shift from a monolithic data-sharing model to an "opt-in per claim" model is profound. It fundamentally alters the balance of power, moving control from the verifier (who previously received everything) to the holder (who now curates the flow of each individual attribute). Which is why SD-JWT VCs are a mandatory standard for **Issuers**, **Verifiers** and **Wallets** in regulations such as eIDAS2 in the EU.
## Core Concepts: The Building Blocks of an SD-JWT VC
An SD-JWT VC presentation is composed of up to three distinct components that work together.
### The SD-JWT: The Main Credential with Hidden Claims (Hashes)
The core of the credential is the SD-JWT itself, which is a standard Issuer-signed JWT. Its structure, however, is unique. Instead of a payload containing all user claims in plaintext, it holds a mixture of two types of information:
1. Claims that are always visible and are not considered sensitive.
2. A special `_sd` (selective disclosure) JSON array that contains digests—cryptographic hashes (e.g., SHA-256) of the claims that are meant to be selectively disclosable.
The issuer's digital signature covers the entire JWT payload, including the plaintext claims and the `_sd` array of hashes. This signature provides a guarantee of integrity; it ensures that the set of cryptographic commitments cannot be altered, and no claims can be added or removed after the credential has been issued. The SD-JWT serves as the immutable root of trust for the entire credential.
### The Disclosures: The Individual Pieces of Information
**Disclosures** are the individual, plaintext pieces of information that correspond to the hashes stored in the SD-JWT's `_sd` array. They are the actual data attributes that the holder can choose to reveal. Each **Disclosure** is a separate, Base64URL-encoded string that, when decoded, represents a JSON array with three specific elements:
1. A unique, randomly generated salt to prevent pre-computation attacks (e.g., rainbow tables).
2. The claim name (e.g., "family_name").
3. The claim value (e.g., "Doe").
During the issuance process, the issuer generates these **Disclosures**, hashes them to create the digests for the SD-JWT, and then provides both the signed SD-JWT and the full set of plaintext **Disclosures** to the **Holder**'s digital wallet. The **Disclosures** are never part of the signed JWT itself but are unavoidably linked to it through their corresponding hashes.
### The Holder Binding JWT: Proving You Own the Credential
A standard JWT is a "bearer token," meaning anyone who possesses the token can use it. This presents a significant security risk if the token is stolen.
**Holder Binding**, also known as **Key Binding**, is a critical mechanism within the SD-JWT VC framework that mitigates this risk by cryptographically tying the credential to its legitimate owner.
This is achieved by having the issuer embed the holder's public key (or a reference to it) within the SD-JWT, typically in a cnf (confirmation) claim. When the holder needs to present the credential, the verifier provides a unique challenge, such as a random string called a "nonce." The holder must then use their corresponding private key to sign a new, short-lived token containing this nonce. This new token is the Holder Binding JWT. The verifier can then use the public key from the main SD-JWT to validate the signature on the **Holder Binding JWT**. A successful validation proves that the person presenting the credential is in possession of the correct private key and is, therefore, the legitimate owner.
These three components—the **SD-JWT**, the **Disclosures**, and the **Holder Binding JWT**—represent a clear separation of concerns that mirrors the roles in the classic identity triangle. The SD-JWT is the **Issuer**'s attestation, a signed, tamper-evident statement of facts. The **Disclosures** represent the **Holder**'s data, a collection of granular attributes under their direct control. The **Holder Binding JWT** proves that the Holder controls the credential and agrees to its use, created only for the specific moment of presentation.
## How It Works: The 3-Step Flow from Issuance to Verification
The lifecycle of an SD-JWT VC involves a coordinated interaction between three parties: the **Issuer**, the **Holder**, and the **Verifier**. This flow is often governed by standardized protocols like [**OpenID for Verifiable Credential Issuance (OID4VCI)**](https://docs.walt.id/concepts/data-exchange-protocols/openid4vci.md) and [**OpenID for Verifiable Presentations (OID4VP)**](https://docs.walt.id/concepts/data-exchange-protocols/openid4vp.md) to ensure interoperability.
### Step 1: An Issuer Creates the Credential for the Holder
The process begins when an entity, such as a university, government agency, or employer, wants to issue a credential to an individual.
1. The Issuer collects the Holder's claims (e.g., name, date of birth, degree earned).
2. The Issuer determines which of these claims should be selectively disclosable based on their sensitivity.
3. For each disclosable claim, the Issuer generates a unique salt, constructs a Disclosure array in the format [salt, claim_name, claim_value], and calculates its cryptographic hash (e.g., using SHA-256).
4. The Issuer then assembles the JWT payload. This payload includes any non-disclosable claims in plaintext, along with the calculated hashes of the disclosable claims, which are placed in the _sd array.
5. The Issuer digitally signs this entire JWT payload using its private key, creating the final SD-JWT.
6. Finally, the Issuer securely transmits both the signed SD-JWT and the full set of corresponding plaintext Disclosures to the Holder's digital wallet, often using the official [OID4VCI exchange protocol](https://docs.walt.id/concepts/data-exchange-protocols/openid4vci.md).
### Step 2: The Holder Selects Disclosures to Present
At a later time, the Holder needs to prove a piece of information to a third party.
1. A Verifier (e.g., a website, a merchant) requests specific information from the Holder, such as proof of age or academic qualifications. This request is often formatted according to the [OID4VP protocol](https://docs.walt.id/concepts/data-exchange-protocols/openid4vp.md).
2. The Holder's wallet, acting on their behalf, identifies the required Disclosures from its secure storage to satisfy the Verifier's request. For an age check, it might select only the "is_over_21" Disclosure, leaving all other information private.
3. The wallet combines the original SD-JWT with only the selected Disclosures.
4. If the Verifier requires proof of ownership (which is best practice), the wallet also prompts the Holder to create and sign the Holder Binding JWT using their private key.
5. This combined package—containing the SD-JWT, the selected Disclosures, and optionally the Holder Binding JWT—is known as a Verifiable Presentation. This presentation is then sent to the Verifier.
### Step 3: A Verifier Checks the Presented Information
Upon receiving the Verifiable Presentation, the Verifier performs a series of checks to validate its authenticity and the claims within it.
1. The Verifier first checks the Issuer's signature on the main SD-JWT. This confirms that the credential is authentic and has not been tampered with since issuance. This step typically involves retrieving the Issuer's public key, which may be done via a URL in the token or from a trusted registry.
2. Next, for each Disclosure it received, the Verifier independently re-calculates the hash using the exact same method as the Issuer (e.g., hashing the Base64URL representation of the [salt, claim_name, claim_value] array).
3. The Verifier then checks if these newly calculated hashes are present in the _sd array within the SD-JWT's payload. A match confirms that the disclosed claim is legitimate and was part of the original set of claims signed by the Issuer.
4. Finally, if a Holder Binding JWT was included, the Verifier uses the public key from the SD-JWT's cnf claim to verify its signature. This final step confirms that the person presenting the credential is its rightful owner, preventing the use of stolen credentials.
## The Key Comparison: SD-JWT VC vs. mDoc/mDL
The digital identity landscape is not monolithic, and SD-JWT VC is one of several important emerging standards. Its most notable counterpart is mDoc (Mobile Document), the format specified by ISO/IEC for mDL (Mobile Driving Licenses).
### Key Differences in Format and Structure
The fundamental differences between SD-JWT VC and mDoc stem from their origins and intended applications.
- **Data Format**: SD-JWT VC is built upon JSON and JWTs. These are text-based, web-native technologies that are deeply familiar to the vast majority of software developers, making integration into web services and APIs straightforward. In contrast, mDoc is based on CBOR (Concise Binary Object Representation), a binary data format designed for efficiency, resulting in smaller message sizes that are well-suited for constrained environments like device-to-device communication.
- **Standardization Body**: The development of SD-JWT VC is led by the IETF (Internet Engineering Task Force), the body responsible for many core internet protocols. Its evolution is driven by the web and open identity communities. mDoc/mDL, on the other hand, is an ISO/IEC standard (specifically ISO/IEC 18013-5), which was standardized by ISO/IEC (JTC 1/SC 17) with significant input from issuing authorities and government stakeholders.
### Use Cases: When to Choose One Over the Other
The choice between SD-JWT VC and mDoc depends heavily on the specific interaction model.
- **Choose SD-JWT VC for**: Web-based authentication flows, online identity verification for services like banking or e-commerce, and any scenario where developer familiarity with the ubiquitous JSON/JWT ecosystem is a key consideration. Its text-based format is optimized for standard web protocols.
- **Choose mDoc/mDL for**: High-assurance, in-person interactions, particularly those that may require offline verification capabilities using transport protocols like NFC, Bluetooth, or QR codes. It is the mandated or preferred format for government-issued mobile identity documents, such as the mDL, in many jurisdictions.
The mandated support for both formats within comprehensive frameworks like Europe's eIDAS 2.0 is not a sign of indecision but a strategic acknowledgment that a single credential format cannot efficiently serve the entire spectrum of digital identity needs. The modern identity ecosystem requires both a solution optimized for the online world and one for the physical world. This implies that the future is multi-format, placing a significant responsibility on the digital wallet, which must act as a universal translator and protocol manager, abstracting this complexity away from the end-user and ensuring seamless interoperability across different contexts.
Ultimately, the final deciding factor comes down to the use case and jurisdiction under which the credential should be deployed.
### A Quick-Reference Comparison Table
The following table summarizes the key distinctions between the two credential formats.
| Feature | SD-JWT VC | mDoc/mDL |
|----------------------------|--------------------------------------------------------|-----------------------------------------------------------------|
| **Standardization Body** | IETF (Internet Engineering Task Force) | ISO/IEC (International Organization for Standardization) |
| **Data Format** | JSON (Text-based) | CBOR (Binary) |
| **Primary Use Case**| Online / Remote Verification | In-Person / Device-to-Device Verification |
| **Common Transport Protocols** | HTTP-based (e.g., OID4VP) | NFC, Bluetooth, Wi-Fi Aware, QR Codes |
| **Developer Ecosystem** | Familiar to web developers (JWT, JSON) | Requires familiarity with binary data structures |
| **Example Application** | Verifying identity for a web service | Presenting a mobile driver's license to law enforcement |
## A Practical Example: Create & Verify an SD-JWT VC via API
To move from theory to practice, we will do a complete end-to-end flow using the open-source APIs of walt.id. First, we will create a OID4VCI credential offer for an SD-JWT VC. Second, we will receive the credential in the walt.id web-wallet. Third, we will request and verify the SD-JWT VC
using the walt.id Verifier API via OID4VP.
All the API endpoints we will use come from the walt.id Community Stack Issuer, Verifier and Wallet product.
### 1. Create an OID4VCI Offer for an SD-JWT VC
To create the OID4VCI credential offer, we will use the `/issue` endpoint of the walt.id Issuer API. We will be issuing an identity credential
in the SD-JWT VC format and will mark the `birthdate` attribute as selectively disclosable.
Use the following command from your terminal to create the offer:
```
curl -X 'POST' \
'https://issuer.demo.walt.id/openid4vc/sdjwt/issue' \
-H 'accept: text/plain' \
-H 'statusCallbackUri: https://example.com/$id' \
-H 'Content-Type: application/json' \
-d '{
"issuerKey": {
"type": "jwk",
"jwk": {
"kty": "OKP",
"d": "mDhpwaH6JYSrD2Bq7Cs-pzmsjlLj4EOhxyI-9DM1mFI",
"crv": "Ed25519",
"kid": "Vzx7l5fh56F3Pf9aR3DECU5BwfrY6ZJe05aiWYWzan8",
"x": "T3T4-u1Xz3vAV2JwPNxWfs4pik_JLiArz_WTCvrCFUM"
}
},
"issuerDid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp",
"credentialConfigurationId": "identity_credential_vc+sd-jwt",
"credentialData": {
"given_name": "John",
"family_name": "Doe",
"email": "johndoe@example.com",
"phone_number": "+1-202-555-0101",
"address": {
"street_address": "123 Main St",
"locality": "Anytown",
"region": "Anystate",
"country": "US"
},
"birthdate": "1940-01-01",
"is_over_18": true,
"is_over_21": true,
"is_over_65": true
},
"mapping": {
"id": "",
"iat": "",
"nbf": "",
"exp": ""
},
"authenticationMethod": "PRE_AUTHORIZED",
"selectiveDisclosure": {
"fields": {
"birthdate": {
"sd": true
}
},
"decoyMode": "NONE",
"decoys": 0
}
}'
```
Your response should look like the following:
```
openid-credential-offer://issuer.potential.walt-test.cloud/?credential_offer_uri=https%3A%2F%2Fissuer.potential.walt-test.cloud%2Fopenid4vc%2FcredentialOffer%3Fid%3D9aabdb65-defe-464b-baa0-9cc13b36074a
```
### 2. Receive the SD-JWT VC in the Wallet
We can now take the credential offer from the previous step and paste it into the input field after clicking the "Receive" button in the walt.id Web-Wallet. Click [here](https://wallet.demo.walt.id/) to go to the wallet.
### 3. Verify the Credential via OID4VP
To request and verify the SD-JWT VC we just received via OID4VP, we will use the
`/verify` endpoint of the walt.id Verifier API.
Use the following command from your terminal to create the OID4VP credential
request.
```
curl -X POST 'https://verifier.demo.walt.id/openid4vc/verify' \
-H 'accept: */*' \
-H 'authorizeBaseUrl: openid4vp://authorize' \
-H 'responseMode: direct_post' \
-H 'successRedirectUri: https://example.com/success?id=$id' \
-H 'errorRedirectUri: https://example.com/error?id=$id' \
-H 'Content-Type: application/json' \
-d '{
"request_credentials": [
{ "vct": "identity_credential", "format": "dc+sd-jwt" }
]
}'
```
Your response should look like the following:
```
openid4vp://authorize?response_type=vp_token&client_id=https%3A%2F%2Fverifier.demo.walt.id%2Fopenid4vc%2Fverify&response_mode=direct_post&state=sHti6w6UMPgX&presentation_definition_uri=https%3A%2F%2Fverifier.demo.walt.id%2Fopenid4vc%2Fpd%2FsHti6w6UMPgX&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=3d8605c5-348f-466c-b6fa-f87371ffed88&response_uri=https%3A%2F%2Fverifier.demo.walt.id%2Fopenid4vc%2Fverify%2FsHti6w6UMPg
```
### 4. Present the Credential
Back in [the wallet](https://wallet.demo.walt.id/), you can press the "Present"
button and paste what you got back in the previous call in the input field. If
you get redirected to https://example.com/success you know the verification was a success.
**Note:**
Note: We configured `example.com/success` as the success redirect URI when we
created the OID4VP credential request with the verifier API.
If you want to check out the detailed verification response, you can check out the guide [here](https://docs.walt.id/community-stack/verifier/credential-verification/sd-jwt-vc-oid4vc.md#retrieving-the-verification-status).
## Advanced Topics You Need to Know
To fully grasp the capabilities and nuances of SD-JWT VC, it is important to understand several advanced concepts that are critical for real-world implementations.
### Understanding Credential Type Metadata
An important feature for ensuring interoperability in a decentralized ecosystem is the ability to understand the structure and semantics of a credential. SD-JWT VC addresses this through the `vct` (Verifiable Credential Type) claim.
The `vct` claim, defined and commonly required by profiles, is a non-disclosable string within the JWT payload that acts as a unique identifier or schema for the credential. Its value can be a URL that points to a Type Metadata document. This external JSON document provides rich information about the credential type, including:
- Human-readable names and descriptions for display purposes.
- Display and rendering information, allowing wallets to present credentials from different issuers in a consistent and user-friendly way.
- Most importantly, a formal JSON Schema that defines the expected claims, their data types, and structural rules.
This metadata mechanism is powerful. It enables wallets to render unfamiliar credentials correctly and allows verifiers to perform automated validation against a known schema, ensuring that a received credential conforms to the expected format before processing its contents.
**Note:**
**Using Your Own VCT URLs**: Issuers can configure their own VCT URLs, enabling the use of shared VCTs in ecosystem setups where multiple issuers reference the same schema repository. This is particularly useful for organizations that require standardized schemas across their ecosystem. The credential data is validated against the schema defined in the VCT during issuance. Learn more about configuring custom VCT URLs in the [Enterprise Stack](https://docs.walt.id/enterprise-stack/services/issuer-service/configurations/general-config.md) or [Community Stack](https://docs.walt.id/community-stack/issuer/configurations/config-files/credential-issuer-metadata.md) documentation.
### The Challenge of SD-JWT VC Revocation Strategies
A credential that is valid upon issuance may need to be revoked later (e.g., if an employee leaves a company or a license is suspended). An effective revocation mechanism is therefore a critical piece of identity infrastructure. SD-JWT VC provides a modern, privacy-preserving solution for this challenge through an optional `status` claim in the JWT payload.
The primary mechanism specified is the use of **Status Lists**. The status claim within a credential can contain information that points a verifier to a **Status List Token**, which is itself a JWT issued by a trusted authority (e.g. the issuer). This token contains a highly compressed bitstring (a list of 0s and 1s). Each position (or index) in the bitstring corresponds to a specific credential, and the value of the bit indicates its status (e.g., `0` for valid, `1` for revoked).
This approach is highly efficient and privacy-enhancing. To check if a credential has been revoked, a verifier only needs to fetch the latest Status List and check the value of a single bit at a specific index. The verifier does not need to reveal to the status authority which specific credential it is checking, thus preserving the privacy of the interaction. This is a significant improvement over older revocation methods like Certificate Revocation Lists (CRLs), which were often large and slow to process.
## Become an SD-JWT VC IETF Issuer, Verifier, or Wallet Provider with walt.id
Learn how to issue, verify, and store SD-JWT VCs quickly and easily. Choose the free open-source Community Stack (Apache-2.0) or the production-ready Enterprise Stack from walt.id, trusted by 25,000+ developers and organizations worldwide.
### Product Editions
- [The Community Stack](https://docs.walt.id/community-stack/home/the-community-stack.md) (Apache-2.0) — trusted open source multi-platform libs, powerful APIs and easy-to-use white label apps for digital identity and wallets.
- [The Enterprise Stack](https://docs.walt.id/enterprise-stack/home/the-enterprise-stack.md) — a solution for building secure, compliant and robust identity and wallet solutions or platforms at scale.
### Guides
- [How to issue an SD-JWT VC](https://docs.walt.id/community-stack/issuer/credential-issuance/sd-jwt-vc-oid4vc.md)
- [How to verify an SD-JWT VC](https://docs.walt.id/community-stack/verifier/credential-verification/sd-jwt-vc-oid4vc.md)
- [How to Store an SD-JWT VC](https://docs.walt.id/community-stack/wallet/credential-exchange/guides/accept-w3c-vc-oid4vci.md)
- [How to Present an SD-JWT VC](https://docs.walt.id/community-stack/wallet/credential-exchange/guides/present-digital-credentials-oid4vp.md)
--------------------------------------------------------------------------------
PAGE: What Is a Mobile Driver's License (mDL)? Your 2026 Guide
URL: https://docs.walt.id/concepts/digital-credentials/mdoc-mdl-iso
RAW: https://docs.walt.id/concepts/digital-credentials/mdoc-mdl-iso.md
STACK: Concepts (stack-agnostic, applies to both Community and Enterprise Stack)
--------------------------------------------------------------------------------
# What Is a Mobile Driver's License (mDL)? Your 2026 Guide
The mobile driver's license (mDL) is here, and it represents one of the most significant shifts in personal identity since the creation of the photo ID. Stored securely in your smartphone's digital wallet, an mDL is far more than just a picture of your plastic license; it is a live, cryptographically secure digital credential issued and managed by a trusted issuer, e.g. a government authority. This technology promises a future of greater convenience, enhanced security, and greater personal privacy. In this guide, you will learn exactly what an mDL is, how the underlying technology works, its key benefits, where it's available globally, and a clear-eyed look at the critical debate surrounding its security and privacy implications.
## What is a Mobile Driver's License (mDL)?
To understand the future of identity, it is crucial to grasp the fundamental concepts that make a mobile driver's license a new piece of technology. It is not simply about moving a physical object into a digital format; it is about creating an entirely new, more secure, and **more private way to prove who you are**.
### Beyond a Digital Photo: Defining a True mDL
A true **mobile driver's license** is a dynamic, verifiable credential that is provisioned directly from an issuing authority, like a state's Department of Motor Vehicles (DMV), to a secure application on your mobile device. Unlike a static photo, an mDL contains the same data elements as a physical card and the issuer can update certain elements after issuance. When your device or the verifier has network connectivity, status information (such as revocation or privilege changes) can be checked with the issuer or refreshed from recent synchronizations. As a result, updates can be reflected more easily than with a plastic card.
The core of a true mDL is its ability to be authenticated electronically. When you present your mDL, the data is transmitted securely to a verifier's device, where its authenticity is confirmed through cryptographic checks. This makes it a significant improvement over physical credentials, which are vulnerable to damage, loss, and sophisticated counterfeiting.
### What is the difference between an mDL and a simple digital ID?
The primary difference between a true mDL and a simple digital ID, such as a scanned image or a basic app displaying your license photo, is **verifiability**. A simple digital image is little more than a "rendering of a physical driver's license" that can be easily manipulated with modern graphics tools. It relies on human visual inspection, which is prone to error and cannot detect sophisticated fakes.
A true mDL, by contrast, is designed to be machine-readable and cryptographically secure. The data within the mDL is digitally signed by the issuing authority. When a verifier scans your mDL, their system uses the issuer's public key to confirm that the digital signature is valid and that the data has not been altered since it was issued. This process provides a level of trust and assurance that a simple picture can never achieve.
### The Global Standard: Understanding ISO/IEC 18013-5
The entire global mDL ecosystem is made possible by a single, critical technical standard: [**ISO/IEC 18013-5**](https://www.iso.org/standard/69084.html). Published in 2021, this standard provides the universal framework for how mDLs are structured, secured, and transmitted. It is the "secret sauce" that ensures security, privacy, and, most importantly, **interoperability**.
Interoperability means that an mDL issued in one jurisdiction can be read and verified in another, so long as both systems adhere to the standard. This standard is more than just a technical document; it is a globally agreed-upon trust framework. It solves the fundamental problem of digital identity: how can a verifier in one country trust a digital ID issued by a government in another? Without this common language, mDLs would be a fragmented collection of proprietary apps with limited utility. By defining the rules for data formats, communication protocols (like NFC and QR codes), and security mechanisms, [ISO/IEC 18013-5](https://www.iso.org/standard/69084.html) creates the foundation for mDLs to function as a global utility, much like how the EMV standard allows credit cards to work at terminals worldwide.
## How Do mDLs Actually Work?
The technology behind a mobile driver's license is a sophisticated blend of secure hardware, advanced cryptography, and user-centric design. While the process is complex behind the scenes, it is designed to be simple and intuitive for the end-user.
### The Role of Your Smartphone's Secure Hardware
A mobile driver's license is not stored like a typical photo or document on your phone. Instead, it is anchored to your device's most secure hardware components. On Apple devices, this is the Secure Enclave, and on Android devices, it is a similar component known as the Trusted Execution Environment (TEE).
These are isolated, tamper-resistant microprocessors designed to handle highly sensitive data, such as cryptographic keys and biometric information. When an mDL is created on your phone, its unique private keys are generated and stored within this hardware "fortress". These keys never leave the secure hardware and are not accessible by the main operating system or any other apps. This deep hardware integration makes it incredibly difficult for malware or an attacker to extract the cryptographic material needed to clone your identity, even if the phone's primary software is compromised.
### A Step-by-Step Guide: How Age Verification Works
To understand how an mDL works in practice, let's walk through one of the most common use cases: proving your age to purchase a restricted item.
1. **Initiation**: You arrive at the checkout counter and the cashier asks for ID. You open your mDL application or **digital wallet** on your phone.
2. **Request**: The cashier's point-of-sale system or verification device sends a request to your phone for a specific piece of information: "Is this person over 21?" The request does not ask for your date of birth, name, or address.
3. **Consent**: Your phone displays a consent screen that clearly states, "XYZ Store is requesting proof you are over 21". To proceed, you must give explicit approval by authenticating with your device's security method, such as Face ID, a fingerprint scan, or your PIN. This step ensures you are in full control of the transaction.
4. **Transmission**: Once you approve, your phone generates a temporary QR code for the cashier to scan, or it uses Near Field Communication (NFC) to establish a direct, encrypted communication channel with the verifier's device. You never have to hand over your phone.
5. **Verification**: Your mDL application transmits only the specific data requested—a cryptographically signed "yes"—along with the digital signature from the issuing authority. The verifier's device uses the issuer's public key to instantly confirm that the response is authentic and has not been tampered with. The screen shows a confirmation, and the transaction is complete.
### Online vs. Offline: Verifying Your Identity Anywhere
A key strength of the [ISO/IEC 18013-5](https://www.iso.org/standard/69084.html) standard is its ability to function in both online and offline environments. The age verification scenario described above is a perfect example of an **offline transaction**. The verifier's device does not need an internet connection to the DMV; it only needs a pre-loaded list of trusted public keys from issuing authorities to validate the mDL's signature. This design is crucial for privacy, as it means no central server is involved in the interaction.
**Online verification** is designed for remote situations, such as opening a bank account from your laptop. This process is governed by a complementary standard, [ISO/IEC 18013-7](https://www.iso.org/standard/91154.html), which defines how a verifier (like a bank's website) can establish a secure connection over the internet with your mDL to request and authenticate your identity data, with your consent.
## Key Benefits: Why You'll Want an mDL in Your Digital Wallet
Mobile driver's licenses offer a compelling set of advantages over their physical counterparts, centered on enhanced security, user privacy, and everyday convenience.
### Enhanced Security: How mDLs Prevent Fraud
- **Cryptographic Authentication**: Physical IDs rely on visual security features like holograms, which can be expertly counterfeited. mDLs are protected by strong public-key cryptography, making them virtually impossible to forge. A verifier isn't just looking at an ID; their device is mathematically proving its authenticity.
- **Real-Time Validity**: A physical license could be expired, suspended, or revoked, and a verifier would have no way of knowing. An mDL can be updated or invalidated by the issuing authority in real-time. This ensures that the credential being presented is valid at the exact moment of verification.
- **Biometric Binding**: Access to your mDL is locked behind your phone's biometrics (Face ID, fingerprint) or PIN. This means that even if your phone is stolen, a thief cannot use your mDL without being able to unlock it first.
### Total Privacy Control: Sharing Only the Data You Need
This is perhaps the most revolutionary benefit for individuals. The technology is built on the principles of **data minimization** and **selective disclosure**.
- **Contextual Information Sharing**: With a physical ID, you reveal all your information—full name, address, date of birth—in every transaction. With an mDL, you only share what is necessary. For age verification, the mDL can simply provide a "yes/no" answer to whether you are over a certain age, without revealing your actual birth date or address.
- **User Consent is Paramount**: You are always in control. An mDL cannot share any information without your explicit consent for each transaction. The consent screen shows you exactly which data points a verifier is requesting before you approve the exchange. This fundamentally shifts the power dynamic of identity verification to the individual.
### Unmatched Convenience: For Citizens and Businesses
- **For Citizens**: Your official ID is always with you on the device you carry everywhere. Administrative tasks like updating your address can often be done remotely through the mDL app, saving a trip to the DMV. If your phone is lost or stolen, the mDL can be remotely deactivated and quickly re-provisioned to a new device, a much faster process than replacing a lost physical card.
- **For Businesses**: The verification process is faster, more accurate, and less susceptible to human error than visually inspecting a physical card. This efficiency improves customer experience. Furthermore, by receiving only the data required for a transaction, businesses reduce their liability and the risk associated with storing sensitive personal information they don't need.
## Where Are Mobile Driver's Licenses Available? A Global Overview
The adoption of mobile driver's licenses is accelerating worldwide, with numerous countries and U.S. states moving from pilot programs to full-scale public rollouts. While the United States is implementing mDLs on a state-by-state basis, many other nations are deploying them as part of broader, nationwide digital identity initiatives. The following table provides a snapshot of the global mDL landscape as of 2026, highlighting key jurisdictions where the technology is live or in advanced stages.
| **Region/Country** | **Status** | **Issuing App / Wallet Compatibility** | **Key Notes** |
|---|---|---|---|
| **NORTH AMERICA** | | | |
| USA - Arizona | Live | Arizona Mobile ID, Apple/Google/Samsung Wallet | One of the earliest adopters, launching in 2021. |
| USA - California | Live | CA DMV Wallet, Apple/Google Wallet | Launched a public pilot program in 2023. |
| USA - Colorado | Live | myColorado, Apple/Google/Samsung Wallet | Integrates mDL with a wide range of other state services. |
| USA - Georgia | Live | Apple/Google/Samsung Wallet | Supports major OEM digital wallets. |
| USA - Louisiana | Live | LA Wallet | The first U.S. state to launch a live mDL in 2018. |
| USA - Maryland | Live | Apple/Google/Samsung Wallet | Launched in 2022 with support for major wallets. |
| **EUROPE** | | | The EU is planning an interoperable digital driving license. |
| Austria | Live | eAusweise | Issued since 2022, part of a broader digital ID suite. |
| France | Live | France Identité | Nationwide rollout began in 2024. |
| Norway | Live | Proprietary App | The first country in the EU/EEA to issue a nationwide mDL. |
| Ukraine | Live | Diia App | Digital ID has the same legal force as its physical counterpart. |
| **ASIA-PACIFIC** | | | |
| Australia (Queensland) | Live | Queensland Digital Licence App | High adoption, with a popular "proof of age" feature. |
| South Korea | Live | PASS App | Developed in collaboration with major telecom operators. |
| **LATIN AMERICA** | | | |
| Mexico (Mexico City) | In Development| N/A | Announced as part of a city-wide digitalization effort. |
## Addressing the Big Questions: Security and Privacy
As with any new identity technology, mDLs raise important questions about security and privacy. A trustworthy system must be robust against attack while also protecting citizens' personal data from misuse. This requires a careful balance and a clear understanding of both the technology's capabilities and its potential risks.
### What are the main mDL security features?
The security of an mDL is built in layers, from the hardware of your phone to the cryptography that protects the data itself. The core security features include:
- **End-to-End Encryption**: When your mDL communicates with a verifier's device, the data is transmitted over a secure, encrypted channel, protecting it from eavesdropping.
- **On-Device Secure Storage**: As discussed, the cryptographic keys that form the foundation of your mDL's security are stored in your phone's hardware-based Secure Enclave or TEE, isolated from the rest of the device.
- **Digital Signatures**: Every piece of data in your mDL is digitally signed by the issuing authority. This makes the data tamper-evident; any modification would invalidate the signature, which a verifier's device would immediately detect.
- **Real-Time Revocation**: If you report your mDL as compromised (for example, if you believe your phone's security has been breached), the issuing authority can remotely revoke the credential, rendering it invalid for any future verification attempts.
- **Biometric Access Control**: You, the holder, must authorize every data-sharing transaction using your device's PIN, fingerprint, or face scan, ensuring that your mDL cannot be used without your active consent.
### Can mDLs be tracked by the government or companies?
This is the central question in the privacy debate, and the answer is nuanced. The technology itself is designed to be privacy-preserving, but the policies of the organizations implementing it are what ultimately determine the outcome for users.
The ISO 18013-5 standard was explicitly designed to prevent tracking and enhance privacy. For in-person, offline transactions, the data exchange is peer-to-peer between your device and the verifier's device. No data is sent to the DMV or any other central server, leaving no centralized record of the interaction. The standard itself does not include any mechanism for a central reporting database of mDL usage.
However, civil liberties advocates raise valid concerns about how the technology is implemented in the real world. While the standard is private by design, the specific mDL app provided by a state, or the digital wallet from a tech company, could be built with "phone home" functionality that logs details about your transactions—when, where, and with whom you verified your identity. The risk is not in the protocol, but in the policy layer. A perfectly secure standard can be used within an ecosystem that creates new data trails for corporate data mining or government surveillance. Therefore, the ultimate privacy guarantee depends on the data collection policies of the specific app you use and the laws in your jurisdiction.
For example, in the European Union, [eIDAS2](https://walt.id/white-paper/eidas2) and the [EUDI Wallet Architecture & Reference Framework](https://eu-digital-identity-wallet.github.io/eudi-doc-architecture-and-reference-framework/2.4.0/architecture-and-reference-framework-main/) explicitly prohibit tracking by wallet providers, require user consent and selective disclosure, and mandate transparency through local transaction logs—ensuring that privacy protections are not only technical but also legally enforceable.
### What happens if I lose my phone?
Losing a phone containing your mDL is less risky than losing a physical wallet. Multiple layers of security protect your identity.
1. **Device-Level Security**: First, your phone is protected by its own lock screen (PIN, fingerprint, or face scan). A thief would need to bypass this primary layer of security to access anything on the device.
2. **App-Level Security**: The mDL itself is a secure application that requires a separate authentication step—your PIN or biometrics—before it can be used to share any data. This provides a crucial second layer of defense.
3. **Remote Deactivation**: In the event your phone is lost or stolen, you have two powerful remote options. First, you can use your device's built-in services, like Apple's Find My or Google's Find My Device, to remotely locate, lock, or even completely wipe the phone's data. More importantly, you can contact your issuing authority (DMV) to have the mDL credential itself remotely revoked. This action makes the mDL on the lost device permanently invalid, even if a thief later manages to unlock the phone.
## The Future of Digital Identity
Mobile driver's licenses are the first step toward a broader ecosystem of digital identity. As the technology matures and adoption grows, mDLs will unlock new use cases and fundamentally change how we interact with businesses and government services.
### Top Use Cases: Travel, Car Rentals, and Financial Services
- **Travel**: This is one of the most successful early use cases. The U.S. Transportation Security Administration (TSA) already accepts mDLs for identity verification at security checkpoints in more than 250 airports. This allows for a faster, contactless, and more secure screening process for travelers.
- **Financial Services**: mDLs are poised to revolutionize how banks and financial institutions comply with "Know Your Customer" (KYC) and anti-money-laundering (AML) regulations. Instead of relying on scanned physical documents, which can be easily forged, banks can use mDLs for high-assurance digital identity verification when opening new accounts online or authorizing high-value transactions. This can significantly reduce fraud and streamline the customer onboarding process.
- **Car Rentals**: This is a key area where the ecosystem is still developing. As of 2026, major car rental companies such as Hertz, Avis, and Enterprise do not yet accept mDLs for vehicle rentals; they still require a physical driver's license at the counter. This highlights that while the technology is ready, broad private-sector adoption will take time as businesses update their policies and infrastructure.
### What's Next? The Road to Global Adoption
The next phase of mDL adoption will be driven by **standards-led interoperability** paired with **regulatory pull**. Around the world, policymakers and large institutions are moving from pilots to production wallets and trust frameworks.
In the EU, eIDAS2 requires member states to roll out digital ID wallets and issue high-assurance credentials like the mDL. Beyond Europe, momentum is accelerating: the U.S. expands TSA use with OEM wallet pilots; Australia aligns under [ISO/IEC 18013-5](https://www.iso.org/standard/69084.html); New Zealand enforces an accreditation regime; South Korea runs a live, telco-backed mDL; and Gulf states embed mDL features in e-gov apps.
Adoption typically arrives in waves:
- (1) government-anchored credentials like mDLs seed wallets with high-assurance data;
- (2) regulated sectors (airports, finance, mobility) adopt selective-disclosure flows that lower liability;
- (3) platform ecosystems (OEM wallets, cloud identity, IAM vendors) normalize UX and accelerate verifier uptake.
The ultimate vision is a future where secure digital credentials on your phone serve as a universal key for proving your identity—privately, securely, and seamlessly across both the physical and digital worlds.
## Become an mDL Issuer, Verifier, or Wallet Provider with walt.id
Learn how to issue, verify, and store mobile driver’s licenses (mDLs) quickly and easily. Choose the open-source Community Stack (Apache-2.0) or the production-ready Enterprise Stack from walt.id, trusted by 20,000+ developers and organizations worldwide.
### Product Editions
- [The Community Stack](https://docs.walt.id/community-stack/home/the-community-stack.md) (Apache-2.0) — trusted open source multi-platform libs, powerful APIs and easy-to-use white label apps for digital identity and wallets.
- [The Enterprise Stack](https://docs.walt.id/enterprise-stack/home/the-enterprise-stack.md) - solution for building secure, compliant and robust identity and wallet solutions or platforms at scale.
### Guides
- [How to issue a Mobile Driver's License (mDL)](https://docs.walt.id/community-stack/issuer/credential-issuance/mdl-oid4vc.md)
- [How to veify a Mobile Driver's License (mDL)](https://docs.walt.id/community-stack/verifier/credential-verification/mdl-oid4vc.md)
- [How to accept a Mobile Driver's License (mDL) in a wallet](https://docs.walt.id/community-stack/wallet/credential-exchange/guides/accept-iso-18013-5-mdl-oid4vci.md)
- [How to present a Mobile Driver's License (mDL) from a wallet](https://docs.walt.id/community-stack/wallet/credential-exchange/guides/present-digital-credentials-oid4vp.md)
--------------------------------------------------------------------------------
PAGE: Selective Disclosure
URL: https://docs.walt.id/concepts/selective-disclosure
RAW: https://docs.walt.id/concepts/selective-disclosure.md
STACK: Concepts (stack-agnostic, applies to both Community and Enterprise Stack)
--------------------------------------------------------------------------------
# Selective Disclosure & SD-JWTs: The Complete Guide to Privacy-Preserving Digital Identity
**Selective disclosure** is a core capability in modern digital identity, enabling users to reveal only the information that is strictly necessary. This guide explains what selective disclosure is, why it matters for privacy-preserving digital identity, and how it works across credential formats such as SD-JWT VCs, mDL, and mono-claim credentials.
**In this guide, you will learn** the key concepts behind selective disclosure, the technical flows used during issuance and verification, and practical guidance for choosing the right approach when implementing real-world digital identity systems.
## What is Selective Disclosure?
Selective disclosure is one of the most important capabilities in decentralized identity and Verifiable Credential (VC) ecosystems. It enables a holder to reveal **only the minimum amount of information required** for a transaction, nothing more.
In today’s digital world, personal data is copied, stored, sold, breached, and shared far beyond user expectations. Centralized and federated identity systems force users to overshare information. For example, showing a driver’s licence at a bar reveals your **name, address, licence number, and date of birth,** even though the verifier only needs to know that you are **over 18.**
Selective disclosure reverses this pattern. Instead of the verifier receiving the entire credential, users share only the specific claims that matter:
* Prove age without revealing a date of birth
* Prove residency without showing a full address
* Prove credential validity without sharing unnecessary attributes
This solves a fundamental privacy and data minimization problem: how to prove something about yourself **without exposing more personal data than necessary**. It directly aligns with modern regulations such as GDPR, CCPA, and eIDAS2, all of which require organizations to limit the amount of data they collect and process.
Selective disclosure also depends on three actors working together:
- **Issuer** – Creates and signs the credential.
- **Holder** – Stores the credential inside a digital identity wallet.
- **Verifier** – Requests only the specific attributes needed for an interaction.
These roles form the foundation of decentralized identity ecosystems. (For a deeper overview of issuers, holders, and verifiers, see the [Decentralized Identity Playbook](https://walt.id/white-paper/decentralized-identity-playbook) by walt.id.)
## Why Selective Disclosure Matters: Benefits for Users and Enterprises
Selective disclosure is central to the global shift toward data minimization. Identity systems have moved from centralized models, where every service stored its own user database and repeatedly exposed people to breaches and redundant KYC, to federated logins that improved usability but concentrated power and enabled cross-service tracking. Decentralized and self-sovereign identity introduced a new approach where users hold credentials in their own wallet and verifiers rely on cryptographic proofs. In this model, selective disclosure becomes a native feature because holders share only the attributes required, not their entire identity document.
### For Individuals: Privacy and Control
Selective disclosure gives people meaningful control over their personal data:
* Only essential attributes are shared.
* Less data is exposed, reducing tracking and correlation.
* Minimal transmission and storage of PII lowers breach and identity theft risk.
### For Enterprises and Governments: Compliance and Efficiency
Organizations benefit by requesting only what they need:
* Strong alignment with GDPR, CCPA, and eIDAS2.
* Reduced liability from storing unnecessary personal data.
* Increased trust and smoother onboarding because users see exactly what is being requested.
Selective disclosure improves privacy, reduces risk, and enables modern identity ecosystems built on minimal, purpose-based data sharing.
## How Selective Disclosure Actually Works
At its core, selective disclosure relies on modern cryptographic techniques that allow information to remain hidden until the holder intentionally reveals it. Hash functions, salts, and digital signatures ensure that each claim inside a credential can be individually committed to and later verified without exposing the entire dataset.
Protocols such as OpenID for Verifiable Presentations and its query language, DCQL, enable verifiers to request only the precise fields necessary for an interaction. These protocols define how a digital wallet receives a request, evaluates which credentials can satisfy it, and constructs a privacy-preserving presentation containing only the required disclosures. An end-to-end flow with a code example will be presented in a later section to illustrate how this works in practice.
### The Credential Standards That Enable Selective Disclosure
Different credential formats support selective disclosure in different ways. Some, like SD-JWT VCs, enable issuers to choose which claims can be revealed on a per-attribute basis, while others, like ISO mDL/mdoc, hide all data by default and only disclose specific elements during an authenticated session. Mono-claim credentials take a simpler approach by encoding only a single attribute per credential, and W3C VCs can also support selective disclosure when signed using SD-JWT.
This section explains how each format works, how selective disclosure is achieved, and how these formats differ from one another.
#### SD-JWT VC (IETF)
Selective Disclosure JWT VCs enable claim-level selective disclosure through a hashing-based mechanism defined by the emerging IETF SD-JWT specification. Instead of storing plaintext values inside the credential, the issuer hashes each selectively disclosable claim and provides the holder with separate “disclosure” objects containing the salted plaintext values. This means a verifier cannot read any claim unless the holder intentionally reveals it. You can view the official IETF SD-JWT standard here or read our guide on the credential type here.
The SD-JWT issuance process works as follows:
1. The issuer prepares the VC payload, including context, types, schemas, and claims.
2. For each claim that should be selectively disclosable, the issuer generates a random salt, concatenates it with the claim name and value, encodes it to form a disclosure, and then hashes that disclosure.
3. These hashed claim values are stored in the SD-JWT, and optional decoy hashes can be added to obscure the true number of claims.
4. The issuer signs the SD-JWT using JWS (for example, EdDSA) and sends the signed credential plus all disclosures to the holder using for example the OID4VCI exchange standard.

For example, an SD-JWT VC for age verification might contain a full date of birth and other personal data, but the holder only discloses a single derived claim such as `age_over_18: true` to the verifier.
Verification follows a similar pattern. A verifier requests specific attributes using common protocols like OID4VP, and the holder returns only the relevant disclosures along with the SD-JWT. The verifier re-hashes each disclosure, compares it to the stored hashed value, and verifies the issuer’s signature. If everything matches, the verifier knows the claim is valid and untampered. You can refer to the OID4VP guide for further detail on how a typical verification flow looks like.
walt.id’s implementation follows the IETF specification closely and is continuously updated to align with new versions of the standard as they are published.
#### mDL/mdoc (ISO 18013-5 and 18013-7)
ISO mobile driving licences (mDL) and mobile documents (mdoc) implement selective disclosure very differently from SD-JWT VC (IETF). Instead of hashing selectively disclosable claims, **all data elements are hidden by default**. A verifier can only access requested attributes during an authenticated, device-bound session initiated through NFC, BLE, QR code scanning or via OID4VP with 18013-7.
For example, an mDL could disclose only a single attribute such as `age_over_18` or `driving_privilege: yes`, while keeping all other personal details—like full name, address, or licence number—hidden by default.
In this model, the mdoc proves possession of its keys and authorizes the release of specific data elements during the session. It is highly optimized for offline or proximity-based interactions such as border control, airport checkpoints, police stops, and age-restricted purchases.
**Characteristics of ISO mDL/mdoc:**
* Selective disclosure at the data-element level
* Strong device binding (proof the data is coming from the legitimate device)
* Ephemeral sessions that protect against correlation
* Use of ISO rather than JWT/JWS cryptographic primitives
Where SD-JWT is ideal for web-based, API-driven online verification flows, mDLs excel in controlled, physical environments with secure device-to-verifier sessions, but can also support online verification through ISO 18013-7 and OID4VP.
#### Mono-Claim Credentials
Mono-claim credentials achieve selective disclosure through **credential granularity** instead of cryptography. Each credential contains exactly one attribute, meaning the holder can share or withhold individual attributes simply by choosing which credential to present. Because they rely on this structural approach rather than a specific cryptographic format, mono-claim credentials are largely standard-independent and can be issued as W3C VCs, SD-JWT VCs, or other compatible credential types.
**Example of an age-verification credential:**
```json
{
"@context": ["[https://www.w3.org/2018/credentials/v1](https://www.w3.org/2018/credentials/v1)"],
"type": ["VerifiableCredential", "VerifiableAttestation", "ProofOfAge"],
"credentialSubject": {
"id": "did:key:z6MkrHKzgsahxBLyNAbLQyB1pcWNYC9GmywiWPgkrvntAZcj",
"over18": "true"
},
"issuer": {
"id": "did:key:z6MkrHKzgsahxBLyNAbLQyB1pcWNYC9GmywiWPgkrvntAZcj",
"name": "Government of Anytown"
},
"issuanceDate": "2021-08-31T00:00:00Z"
}
```
This model is simple, interoperable, and works with any VC stack. While the example is shown in W3C VC format, mono-claim credentials are not tied to a single standard and can equally be issued using SD-JWT VCs or other compatible credential frameworks. It is especially useful when attributes rarely need to be combined and when minimal implementation complexity is important.
#### W3C VC With SD-JWT Signature
A W3C Verifiable Credential can also support selective disclosure when its proof layer uses an SD-JWT signature. In this model, the credential still follows the W3C VC Data Model, but its proof section adopts SD-JWT as the underlying cryptographic mechanism for hashing claims and supplying disclosures.
This approach is ideal for issuers who want to use the W3C VC model while adopting SD-JWT as the privacy-preserving signature suite. It combines the interoperability of the W3C VC ecosystem with the selective disclosure capabilities of SD-JWT.
### How To Decide Which Credential Standard Is Best
Selecting the right credential format depends on the use case, the environment in which verification occurs, and the level of privacy required. To help readers evaluate their options, this section will include a comparison table summarizing the strengths and trade-offs of SD-JWT VCs, mDL/mdoc, mono-claim credentials, and W3C VCs with SD-JWT signatures.
Because some advanced schemes like BBS+ and ZKPs are still emerging and not yet widely deployed in production, they will be mentioned briefly at the bottom with links to future articles once dedicated coverage is available.
| Feature / Requirement | SD-JWT VC (IETF) | mDL / mdoc (ISO 18013-5/7) | Mono-Claim Credentials | W3C VC + SD-JWT Signature |
|-------------------------------|-------------------------------------------|------------------------------------------|-----------------------------------------|---------------------------------------|
| **Granularity of Disclosure** | Claim-level (highly granular) | Data-element level | Single attribute per credential | Claim-level (highly granular) |
| **Default Visibility** | All fields visible unless disclosed | Everything hidden by default | Entire credential visible (one claim) | All fields visible unless disclosed |
| **Interaction Model** | Online: Web/API-based | Offline: Session (NFC/BLE/QR) or Online: Web via ISO 18013-7 | Online or Offline | Online: Web/API-based |
| **Best For** | Online verification, EUDI Wallets, enterprise flows | Border control, mobility, offline checks | Simple attestations: age, nationality, membership | Web-native credentials needing SD behavior |
| **Production Maturity** | Emerging but rapidly adopted | Deployed in US & global pilots | Mature (stable standards, production deployments) | Emerging |
| **Supported by walt.id** | Yes | Yes | Yes | Yes |
## Key Use Cases and Examples
Selective disclosure is already powering real-world digital ID interactions by allowing users to reveal only the specific information needed for a transaction. Common use cases include:
- Age verification (prove you are over 18 without sharing your date of birth)
- Mobile Driver’s License (mDL) (share only required attributes like “over 21” or “valid licence”)
- Mono-claim credentials such as “Over 18,” “Resident of X,” or “Employed by Y,” issued as single-purpose Verifiable Credentials. These can also be leveraged for address verification (confirm residency without revealing full address details) or Salary verification (prove income range without exposing exact salary)
## Global Initiatives & Regulations Driving Selective Disclosure Adoption
Selective disclosure is being accelerated by major global digital identity programs. Mobile Driver’s Licenses (mDLs) and ISO mdocs are already deployed across many US states and are expanding internationally, enabling privacy-preserving ID checks using data-element–level disclosure. In the EU, eIDAS2 and the EUDI Wallet initiative mandate support for selective disclosure through both SD-JWT VCs (IETF) and ISO mDL/mdoc, establishing these as the foundational standards for next-generation digital identity. Together, these efforts are setting worldwide expectations for interoperable, privacy-first credential sharing
## End-to-End Selective Disclosure Example (with Code)
This section walks through a complete SD-JWT VC flow from issuance to verification, demonstrating how selective disclosure works in practice. At each step, we explain the cryptographic mechanics that make privacy-preserving credential sharing possible.
### Understanding How SD-JWT Hashes Work
Before diving into the code, it's essential to understand how SD-JWT achieves selective disclosure through cryptographic hashing.
When an issuer creates an SD-JWT VC, selectively disclosable claims are **not stored in plaintext** inside the credential. Instead, each claim undergoes a transformation:
1. **Salt Generation**: The issuer generates a cryptographically random salt for each selectively disclosable claim.
2. **Disclosure Creation**: The salt, claim name, and claim value are combined into a JSON array: `[salt, claim_name, claim_value]`.
3. **Base64url Encoding**: This array is encoded as a base64url string, creating the "disclosure."
4. **Hash Computation**: The disclosure is hashed using SHA-256, and the resulting hash is base64url encoded.
5. **Storage**: The hash (not the plaintext) is stored in the credential's `_sd` array. The disclosures are provided separately to the holder.
**Example Transformation:**
For a claim `"birthdate": "1940-01-01"`, the issuer:
```
Disclosure: ["zqdrNfRPzxYivbvZevEh1w", "birthdate", "1940-01-01"]
↓ Base64url encode
Encoded: WyJ6cWRyTmZSUHp4WWl2YnZaZXZFaDF3IiwiYmlydGhkYXRlIiwiMTk0MC0wMS0wMSJd
↓ SHA-256 hash → Base64url encode
Hash: vyCkk8nkNZcSNBqzLyhSzDp1AuwnS911ukrFgmFZPFI
```
The credential payload contains only the hash in `_sd`:
```json
{
"given_name": "John",
"family_name": "Doe",
"_sd": ["vyCkk8nkNZcSNBqzLyhSzDp1AuwnS911ukrFgmFZPFI"]
}
```
During verification, the verifier re-hashes any disclosures provided by the holder and compares them against the stored hashes to confirm validity.
### Step 1: Issue an SD-JWT VC with Selective Disclosure
**Product:** [walt.id Issuer API](https://docs.walt.id/community-stack/issuer/getting-started.md) (Open Source)
We use the walt.id Issuer API to create an identity credential where the `birthdate` and `family_name` fields are selectively disclosable—meaning the holder can later choose whether to reveal them.
The following request uses an example signing key and issues a credential with selective disclosure configured. You can run this directly against the [hosted demo issuer](https://issuer.demo.walt.id) or [set up your own instance](https://docs.walt.id/community-stack/issuer/setup.md):
```bash
curl -X 'POST' \
'https://issuer.demo.walt.id/openid4vc/sdjwt/issue' \
-H 'accept: text/plain' \
-H 'Content-Type: application/json' \
-d '{
"issuerKey": {
"type": "jwk",
"jwk": {
"kty": "OKP",
"d": "mDhpwaH6JYSrD2Bq7Cs-pzmsjlLj4EOhxyI-9DM1mFI",
"crv": "Ed25519",
"kid": "Vzx7l5fh56F3Pf9aR3DECU5BwfrY6ZJe05aiWYWzan8",
"x": "T3T4-u1Xz3vAV2JwPNxWfs4pik_JLiArz_WTCvrCFUM"
}
},
"issuerDid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp",
"credentialConfigurationId": "identity_credential_vc+sd-jwt",
"credentialData": {
"given_name": "John",
"family_name": "Doe",
"email": "johndoe@example.com",
"birthdate": "1940-01-01",
"is_over_18": true,
"is_over_21": true
},
"mapping": {
"id": "",
"iat": "",
"nbf": "",
"exp": ""
},
"selectiveDisclosure": {
"fields": {
"birthdate": { "sd": true },
"family_name": { "sd": true }
},
"decoyMode": "NONE",
"decoys": 0
},
"authenticationMethod": "PRE_AUTHORIZED"
}'
```
**Response (Credential Offer URL):**
```
openid-credential-offer://?credential_offer_uri=https%3A%2F%2Fissuer.demo.walt.id%2Fdraft13%2FcredentialOffer%3Fid%3D080726d1-70e0-426c-ab06-10e41225853f
```
### Step 2: Claim the Credential in a Wallet
**Product:** [walt.id Web Wallet](https://docs.walt.id/community-stack/wallet/getting-started.md) (Open Source)
The holder uses a wallet to claim the credential using the offer URL. The easiest way to try this is using the **walt.id Demo Wallet** hosted at [wallet.demo.walt.id](https://wallet.demo.walt.id). You can also [run the wallet locally](https://docs.walt.id/community-stack/wallet/setup.md) or build your own wallet using the [Wallet API](https://docs.walt.id/community-stack/wallet/credential-exchange/overview.md).
1. **Create an account** at [wallet.demo.walt.id](https://wallet.demo.walt.id) (or log in if you already have one).
2. Click the **"Receive"** button at the top of the home screen.
3. **Paste the Credential Offer URL** (from Step 1) into the text field.
4. Review and **accept** the credential.
The credential is now stored in your wallet.
**Note:**
**Tip:** You can also scan the credential offer URL as a QR code if you generate one from the URL.
You can learn more about the different [credential delivery methods here](https://docs.walt.id/community-stack/issuer/capabilities/credential-delivery.md).
**What the wallet receives and stores:**
The wallet now holds:
- The signed SD-JWT credential (containing hashes in `_sd`)
- All disclosures provided by the issuer
The credential payload stored in the wallet looks like this:
```json
{
"given_name": "John",
"email": "johndoe@example.com",
"is_over_18": true,
"is_over_21": true,
"id": "urn:uuid:16db85c2-6a60-4339-94f8-617ef7a7852f",
"iat": 1765984477,
"nbf": 1765984477,
"exp": 1797520477,
"_sd_alg": "sha-256",
"iss": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp",
"cnf": {
"jwk": {
"kty": "EC",
"crv": "P-256",
"kid": "g5NZkDNcGXI06zy7mFLG09PvFBwdBEDO7I3bn78etlM",
"x": "woHr8cHiXFB4HYBnIqqeudpZme6XkmkwVgLg9QqlGs0",
"y": "OtX0EgOF28c6-FYpHfr5xceyGjllH75CV6S92AwBTcU"
}
},
"vct": "https://issuer.demo.walt.id/draft13/identity_credential",
"_sd": [
"VcCZB3lnwdRmwsLoiAhXET3U_i09mS-gKO_STqyK9n0",
"jAyj3j4i8mPCR98nV1jPccTqvqTdZ0tSfmHhQ1HsdFw"
]
}
```
Notice that `birthdate` and `family_name` are **not visible** in the payload, only their hashes appear in `_sd`. The holder (wallet) has the corresponding disclosures that can reveal these values when needed.
### Step 3: Verify the Credential (Request Specific Claims)
**Product:** [walt.id Verifier API](https://docs.walt.id/community-stack/verifier/getting-started.md) (Open Source)
Now a verifier wants to confirm the holder is over 18 and learn their family name—but does **not** need their birthdate. Using the walt.id Verifier API, we create a presentation request that specifies exactly which claims are required. You can run this against the [hosted demo verifier](https://verifier.demo.walt.id) or [set up your own instance](https://docs.walt.id/community-stack/verifier/setup.md):
```bash
curl -X 'POST' \
'https://verifier.demo.walt.id/openid4vc/verify' \
-H 'accept: */*' \
-H 'authorizeBaseUrl: openid4vp://authorize' \
-H 'responseMode: direct_post' \
-H 'successRedirectUri: https://example.com/success?id=$id' \
-H 'errorRedirectUri: https://example.com/error?id=$id' \
-H 'Content-Type: application/json' \
-d '{
"vp_policies": ["presentation-definition"],
"request_credentials": [
{
"vct": "https://issuer.demo.walt.id/draft13/identity_credential",
"format": "dc+sd-jwt",
"input_descriptor": {
"id": "identity_check",
"constraints": {
"fields": [
{
"path": ["$.family_name"],
"filter": { "type": "string" }
},
{
"path": ["$.is_over_18"],
"filter": { "type": "boolean", "const": true }
}
],
"limit_disclosure": "required"
}
}
}
]
}'
```
**Response (Verification Request URL):**
```
openid4vp://authorize?response_type=vp_token&client_id=https%3A%2F%2Fverifier.demo.walt.id...&state=HMUgYMpanNr8...
```
### Step 4: Present the Credential with Selective Disclosure
**Product:** [walt.id Web Wallet](https://docs.walt.id/community-stack/wallet/credential-exchange/overview.md) (Open Source)
Using the **walt.id Demo Wallet** at [wallet.demo.walt.id](https://wallet.demo.walt.id), you can fulfill the verification request:
1. In the wallet, click the **"Scan to receive or present credentials"** button (top right corner).
2. **Paste the Verification Request URL** (from Step 3) into the text field below the QR scanner.
3. The wallet will show which credentials match the request and **which claims will be disclosed**.
4. **Select which attributes to disclose** — you can choose to reveal or hide the selectively disclosable fields (`family_name` and `birthdate`).
5. Review and **approve** the presentation.
**Try it yourself:** Since the verifier's request requires `family_name` and `is_over_18`, the verification outcome depends on your selection:
- **Success**: If you disclose `family_name` (and `is_over_18` is already visible), verification passes.
- **Failure**: If you choose **not** to disclose `family_name`, verification will fail because the required field is missing.
- **Privacy preserved**: Whether you disclose `birthdate` or not has no effect on the result—it wasn't requested by the verifier.
**What happens during presentation:**
The wallet constructs the `vp_token` as follows:
```
~~
```
Where:
- `` is the original signed credential (with hashes)
- `` is **only** the disclosure for `family_name`
- `` proves the holder controls the credential
The `birthdate` disclosure is **omitted**—the verifier cannot learn its value.
Example:
```
eyJraWQiOiJkaWQ6a2V5Ono2TWtqb1JocTFqU05KZExpcnVTWHJGRnhhZ3FyenRaYVhIcUhHVVRLSmJjTnl3cCN6Nk1ram9SaHExalNOSmRMaXJ1U1hyRkZ4YWdxcnp0WmFYSHFIR1VUS0piY055d3AiLCJ0eXAiOiJ2YytzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJnaXZlbl9uYW1lIjoiSm9obiIsImVtYWlsIjoiam9obmRvZUBleGFtcGxlLmNvbSIsImlzX292ZXJfMTgiOnRydWUsImlzX292ZXJfMjEiOnRydWUsImlkIjoidXJuOnV1aWQ6MTZkYjg1YzItNmE2MC00MzM5LTk0ZjgtNjE3ZWY3YTc4NTJmIiwiaWF0IjoxNzY1OTg0NDc3LCJuYmYiOjE3NjU5ODQ0NzcsImV4cCI6MTc5NzUyMDQ3NywiX3NkX2FsZyI6InNoYS0yNTYiLCJpc3MiOiJkaWQ6a2V5Ono2TWtqb1JocTFqU05KZExpcnVTWHJGRnhhZ3FyenRaYVhIcUhHVVRLSmJjTnl3cCIsImNuZiI6eyJqd2siOnsia3R5IjoiRUMiLCJjcnYiOiJQLTI1NiIsImtpZCI6Imc1TlprRE5jR1hJMDZ6eTdtRkxHMDlQdkZCd2RCRURPN0kzYm43OGV0bE0iLCJ4Ijoid29IcjhjSGlYRkI0SFlCbklxcWV1ZHBabWU2WGtta3dWZ0xnOVFxbEdzMCIsInkiOiJPdFgwRWdPRjI4YzYtRllwSGZyNXhjZXlHamxsSDc1Q1Y2UzkyQXdCVGNVIn19LCJ2Y3QiOiJodHRwczovL2lzc3Vlci5kZW1vLndhbHQuaWQvZHJhZnQxMy9pZGVudGl0eV9jcmVkZW50aWFsIiwiX3NkIjpbIlZjQ1pCM2xud2RSbXdzTG9pQWhYRVQzVV9pMDltUy1nS09fU1RxeUs5bjAiLCJqQXlqM2o0aThtUENSOThuVjFqUGNjVHF2cVRkWjB0U2ZtSGhRMUhzZEZ3Il19.FF9kDs-vxqsd6serp-5TouRmw3l0JBAINhNcBbATb-3_kEHXSSjURJv4UmVLTc5vV8HDRwmHllfteLb-pR5_Bg~WyJZYmp2aDVjQUg5QlBiVmpHMWJJYnV3IiwiZmFtaWx5X25hbWUiLCJEb2UiXQ~eyJraWQiOiJnNU5aa0ROY0dYSTA2enk3bUZMRzA5UHZGQndkQkVETzdJM2JuNzhldGxNIiwidHlwIjoia2Irand0IiwiYWxnIjoiRVMyNTYifQ.eyJpYXQiOjE3NjU5ODY5ODYsImF1ZCI6Imh0dHBzOi8vdmVyaWZpZXIuZGVtby53YWx0LmlkL29wZW5pZDR2Yy92ZXJpZnkiLCJub25jZSI6ImEzMDUwZTQ1LWU0MGQtNDQzNy1hMjkyLWY2YzBhOTFhY2E2ZiIsInNkX2hhc2giOiJncmF6UGNqaGR5RWxvOFpycllfV3VUX1JTRzNxWkhaUU9vWW9GVjgwajFjIn0.rpgfWZTLLfw55hbEux69wbNmZLa4p0Wb2EPMzW4Y5ULOX5VGH_fqoeA90jugsMXefu65uA86VB08TjSVV_jGEg
```
### Step 5: Verification Result
The verifier receives the presentation and performs these checks:
1. **Signature verification**: Confirms the SD-JWT was signed by a trusted issuer.
2. **Disclosure verification**: Re-hashes each provided disclosure and matches against `_sd`.
3. **Key binding verification**: Confirms the presenter holds the credential's private key.
```bash
curl -X 'GET' \
'https://verifier.demo.walt.id/openid4vc/session/{session-id}' \
-H 'accept: */*'
```
**Note:**
You can get the session id from the id query param when you got redirected to the success URL during
presentation.
## Get Started with Selective Disclosure
Ready to implement selective disclosure in your own identity solution? Explore the resources below to dive deeper into credential formats and start building with walt.id's open-source stack.
### Learn More About Credential Formats
Each credential format enables selective disclosure differently. Choose the one that fits your use case:
- [SD-JWT VC Guide](https://docs.walt.id/concepts/digital-credentials/sd-jwt-vc.md)
- [mDL / mdoc Guide](https://docs.walt.id/concepts/digital-credentials/mdoc-mdl-iso.md)
- [W3C VC Guide](https://docs.walt.id/concepts/digital-credentials/verifiable-credentials-w3c.md)
### Start Building with walt.id
The walt.id Identity Stack provides everything you need to issue, hold, and verify credentials with selective disclosure support:
- [Issuer Getting Started](https://docs.walt.id/community-stack/issuer/getting-started.md) - Issue SD-JWT VCs, W3C VCs, and mDL credentials with configurable selective disclosure
- [Verifier Getting Started](https://docs.walt.id/community-stack/verifier/getting-started.md) - Request and verify specific claims using OID4VP and presentation definitions
- [Wallet Getting Started](https://docs.walt.id/community-stack/wallet/getting-started.md) - Store credentials and present them with user-controlled disclosure selection
All components listed are **open source** and available on [GitHub](https://github.com/walt-id/waltid-identity).
--------------------------------------------------------------------------------
PAGE: Credential Status & Revocation
URL: https://docs.walt.id/concepts/credential-lifecycle/credential-status-and-revocation
RAW: https://docs.walt.id/concepts/credential-lifecycle/credential-status-and-revocation.md
STACK: Concepts (stack-agnostic, applies to both Community and Enterprise Stack)
--------------------------------------------------------------------------------
# Credential Status & Revocation
The validity of digital credentials are fundamental to make digital identity work. Ensuring
that verifiable credentials are up-to-date and valid without compromising user privacy is essential. Let's understand
why
we need a credential status, explore possible solutions, and learn more about [the Bitstring
Status List v1.0](https://www.w3.org/TR/vc-bitstring-status-list/).
## Why we need credential status
Digital credentials, such as certificates, badges, and IDs, must have mechanisms to indicate their current
status—whether they are active, suspended, or revoked. The ability to check the status of a credential is crucial for
several reasons:
1. **Security**: Verifying that a credential has not been revoked or tampered with is vital to prevent fraud and misuse.
2. **Trust**: Ensuring that only valid credentials are accepted maintains trust in digital identity systems.
3. **Compliance**: Many regulatory frameworks require mechanisms to manage and verify the status of digital credentials.
Traditional methods like Certificate Revocation Lists (CRLs) and the Online Certificate Status Protocol (OCSP) have
limitations, including potential privacy breaches and inefficiency in handling large volumes of data.
## Possible Solutions
Addressing the need for an efficient and privacy-preserving way to manage credential status has led to various
solutions:
- **Certificate Revocation Lists (CRLs)**: Lists employed to manage the revocation status of digital certificates. They
can
be large and cumbersome to distribute.
- **Online Certificate Status Protocol (OCSP)**: Provides real-time status information but can expose user actions to
tracking.
Each of these solutions has trade-offs, particularly regarding privacy and scalability. To enhance privacy and
efficiency, new mechanisms like the Bitstring Status List have been developed.
## Bitstring Status List Explained
The Bitstring Status List, specified [here](https://www.w3.org/TR/vc-bitstring-status-list/), proposes a novel approach
to managing
credential status using bitstrings. This method offers several advantages over traditional solutions:
- **Privacy**: By bundling the status of many credentials into a single list, the Bitstring Status List mitigates
tracking
risks associated with querying individual credential statuses.
- **Efficiency**: The use of bitstrings and data compression techniques like GZIP ensures that the status information is
highly space-efficient and easy to distribute, even for a large userbase.
In essence, the status of a large number of VCs can be grouped into a single list using bitstrings. A bitstring is a
sequence of bits, where each bit represents the status of one credential. If a bit is set (1), the credential is revoked
or suspended; if unset (0), the credential is active.
### Example StatusListCredentials
To illustrate how the Bitstring Status List works, let's look at some practical examples.
#### Simple Status List Credential
A simple status list credential is a straightforward implementation of the Bitstring Status List mechanism. It
associates a single status list entry with a verifiable credential.
```json
{
"@context": [
"https://www.w3.org/ns/credentials/v2"
],
"id": "https://example.com/credentials/status/3",
"type": [
"VerifiableCredential",
"BitstringStatusListCredential"
],
"issuer": "did:example:12345",
"validFrom": "2021-04-05T14:27:40Z",
"credentialSubject": {
"id": "https://example.com/status/3#list",
"type": "BitstringStatusList",
"statusPurpose": "revocation",
"encodedList": "uH4sIAAAAAAAAA-3BMQEAAADCoPVPbQwfoAAAAAAAAAAAAAAAAAAAAIC3AYbSVKsAQAAA"
}
}
```
This example shows a minimal setup where a single status list oversees the revocation status of credentials. The
encodedList property contains the compressed bitstring representing the status of numerous credentials, ensuring
efficient data handling and privacy preservation.
#### Multiple Status Purposes
The Bitstring Status List mechanism can also support multiple status purposes within a single credential. This
capability enhances flexibility by allowing various types of status updates (e.g., revocation, suspension) to be managed
together.
```json
{
"@context": [
"https://www.w3.org/ns/credentials/v2"
],
"id": "https://example.com/credentials/23894672394",
"type": [
"VerifiableCredential"
],
"issuer": "did:example:12345",
"issuanceDate": "2021-04-05T14:27:42Z",
"credentialStatus": [
{
"id": "https://example.com/credentials/status/5#94567",
"type": "BitstringStatusListEntry",
"statusPurpose": "revocation",
"statusListIndex": "94567",
"statusListCredential": "https://example.com/credentials/status/5"
},
{
"id": "https://example.com/credentials/status/5#12345",
"type": "BitstringStatusListEntry",
"statusPurpose": "suspension",
"statusListIndex": "12345",
"statusListCredential": "https://example.com/credentials/status/5"
}
],
"credentialSubject": {
"id": "did:example:6789",
"type": "Person"
}
}
```
This example demonstrates a more complex implementation where a single verifiable credential can be marked as both
revoked and suspended. This dual-purpose credential status allows managing different characteristics of credential
status in a consolidated manner, further enhancing flexibility and efficiency.
## Credential Status and Revocation with walt.id
Using walt.id's libs and services, you can issue and verify credentials leveraging the Bistring StatusList standard. At the
moment, our policies support checking for any state in credentials. Learn more [here](https://docs.walt.id/community-stack/verifier/credential-verification/policies/overview.md).
- [Issuer API](https://docs.walt.id/community-stack/issuer/getting-started.md)
- [Verifier API](https://docs.walt.id/community-stack/verifier/getting-started.md)
--------------------------------------------------------------------------------
PAGE: Data Exchange Protocols
URL: https://docs.walt.id/concepts/data-exchange-protocols
RAW: https://docs.walt.id/concepts/data-exchange-protocols.md
STACK: Concepts (stack-agnostic, applies to both Community and Enterprise Stack)
--------------------------------------------------------------------------------
# Data Exchange Protocols for Digital Identity (SSI)
Different authentication and data exchange protocols are used to securely transfer identity data (e.g. VCs, VPs) between
parties (e.g. from an Issuer to a Holder). They typically establish a mutually authenticated and encrypted data channel
between the communicating parties.
### Protocols
- **OpenID4VCI (Issuance)**: [What is OpenID4VCI? The Developer's Guide](https://docs.walt.id/concepts/data-exchange-protocols/openid4vci.md)
- **OpenID4VP (Presentation)**: [What is OpenID4VP? The Developer's Guide](https://docs.walt.id/concepts/data-exchange-protocols/openid4vp.md)
- **DC-API (Browser-Based)**: [What is DC-API? The Developer's Guide](https://docs.walt.id/concepts/data-exchange-protocols/dc-api.md)
- **HAIP (High Assurance Profile)**: [What is the HAIP Profile? The Developer's Guide](https://docs.walt.id/concepts/data-exchange-protocols/haip.md)
- **DCQL (Query Language)**: [DCQL: The Developer's Guide](https://docs.walt.id/concepts/data-exchange-protocols/dcql.md)
--------------------------------------------------------------------------------
PAGE: What is OpenID4VCI? The Developer's Guide (2026)
URL: https://docs.walt.id/concepts/data-exchange-protocols/openid4vci
RAW: https://docs.walt.id/concepts/data-exchange-protocols/openid4vci.md
STACK: Concepts (stack-agnostic, applies to both Community and Enterprise Stack)
--------------------------------------------------------------------------------
# OpenID4VCI: A Developer's Guide to Verifiable Credential Issuance
Welcome to the developer's guide to **OpenID for Verifiable Credential Issuance (OID4VCI)**. In the world of digital identity, if a **Verifiable Credential** is your new digital passport, then **OID4VCI** is the standardized, secure, and interoperable process for the government office (the **Issuer**) to hand it to you and place it in your digital **Wallet**.
This protocol is not just a theoretical proposal; it's a foundational component of the next generation of digital identity infrastructure. In this guide, we will move beyond abstract specifications to provide a complete walkthrough of the protocol, from its core concepts and technical flows to practical implementation examples. You will learn exactly how **OID4VCI** works, how it relates to its counterpart, **OpenID4VP**, and how you can be used together with already existing identity platforms like Keycloak.
## What is OpenID4VCI? (The Simple Explanation)
**OpenID for Verifiable Credential Issuance (OID4VCI)** is an open standard that defines a secure API for issuing Verifiable Credentials (VCs). The protocol is built on top of the widely adopted and developer-friendly patterns of **OAuth 2.0** and **OpenID Connect**. This design choice significantly lowers the barrier to entry for developers, allowing existing identity systems to be extended to support credential issuance.
However, the true significance of **OID4VCI** lies in its role as **core infrastructure for digital identity initiatives** worldwide. In Europe, eIDAS 2.0 mandates that by 2026 every member state must offer a European Digital Identity Wallet (EUDI Wallet). To make this ecosystem work, credential issuance must be standardized, and the EU’s Architecture and Reference Framework (ARF) explicitly names OID4VCI as the required protocol.
Globally, momentum is similar. The U.S. is piloting mobile driver’s licenses, while South Korea, Japan, and Australia align digital ID programs with global standards. Canada and several Latin American countries are testing digital credentials. Across regions, **OID4VCI is emerging as the common language** enabling verifiable, privacy-preserving credentials to flow into wallets everywhere.
Learning **OID4VCI** is not just about understanding a new protocol; it’s about equipping yourself with the technical mechanism that governments, enterprises, and ecosystems worldwide are aligning on to make digital identity practical, interoperable, and trustworthy.
## Core Concepts: The Actors and Components
To understand **OID4VCI**, you first need to understand the key players and the components they exchange. The protocol defines a clear set of roles and data structures that enable a secure and interoperable ecosystem.
### Issuer, Holder, Wallet, and Verifier
The **OID4VCI** ecosystem is composed of four primary actors:
- **Issuer**: This is the organization that creates, cryptographically signs, and issues a **Verifiable Credential**. In the context of **OID4VCI**, the **Issuer** acts as an OAuth 2.0 Resource Server and may also serve as an Authorization Server, managing the authorization process for issuance. Examples include a university issuing a digital diploma or a government agency issuing a digital driver's license.
- **Holder**: This is the entity that receives and maintains control over a credential, typically through a digital **Wallet**. In most cases, the **Holder** is the end-user or subject to whom the claims in the credential apply (e.g., a student holding their diploma). However, the **Holder** may also act on behalf of the subject — for example, a parent holding a child’s credential or an organization holding credentials about its employees.
- **Wallet**: This is the software, typically a mobile application, that the **Holder** uses to request, receive, store, and later present their credentials. From a technical perspective, during the **OID4VCI** flow, the Wallet acts as an OAuth 2.0 client application.
- **Verifier**: This is an organization that requests proof of a credential from the **Holder** in order to grant access to a service or resource. For example, a car rental agency acts as a **Verifier** when it asks to see a driver's license. While the **Verifier** is the primary recipient in the **OpenID4VP** (presentation) flow, defining it here is essential for understanding the full credential lifecycle.
### What is a Verifiable Credential?
A **Verifiable Credential (VC)** is a digital, tamper-evident collection of claims (or attributes) that an Issuer makes about a **Holder**. A key feature of the **OID4VCI** protocol is that it is format-agnostic, meaning it can be used to issue credentials in multiple formats. This flexibility allows it to support a wide range of use cases and technical requirements. The most common formats include:
- [**W3C Verifiable Credentials Data Model (VCDM)**](https://docs.walt.id/concepts/digital-credentials/verifiable-credentials-w3c.md): A flexible JSON-based format.
- [**SD-JWT VC**](https://docs.walt.id/concepts/digital-credentials/sd-jwt-vc.md): A format based on Selective Disclosure JWTs that enhances privacy by allowing the Holder to reveal only specific claims from a credential.
- [**ISO mdoc**](https://docs.walt.id/concepts/digital-credentials/mdoc-mdl-iso.md): The format used for mobile Driver's Licenses (mDL) as defined in the ISO/IEC 18013-5 standard.
Here is a simple example of what the claims inside a W3C Verifiable Credential might look like in JSON:
```json
{
// The contextual information required for data integrity and 1.api
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1"
],
"id": "http://example.gov/credentials/3732",
// The credential's unique identifier
"type": [
"VerifiableCredential",
"UniversityDegree"
// Specifies the kind of credential being issued
],
"issuer": {
"id": "did:web:vc.transmute.world"
// The issuer's unique identifier (DID)
},
"issuanceDate": "2020-03-10T04:24:12.164Z",
// When the credential was issued
"credentialSubject": {
// Information about the credential's recipient and the degree earned
"id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
"degree": {
"type": "BachelorDegree",
"name": "Bachelor of Science and Arts"
}
}
}
```
### The Credential Offer & The Credential Issuer Metadata
Two final components are crucial for initiating the issuance flow:
- **Credential Issuer Metadata**: For a **Wallet** to interact with an **Issuer**, it must first know what the **Issuer**'s capabilities are. This is achieved through a standardized discovery document. The **Issuer** hosts a public JSON file at a well-known location (`/.well-known/openid-credential-issuer`) that contains its metadata. This file tells the **Wallet** everything it needs to know, including the location of its `credential_endpoint` and `token_endpoint`, the types of credentials it supports (`credential_configurations_supported`), and the required cryptographic methods. This metadata-driven approach is the key to the protocol's scalability, as it allows any compliant **Wallet** to interact with any compliant **Issuer** without prior manual configuration.
- **Credential Offer**: This is an invitation from the **Issuer** to the **Wallet** to begin the issuance process. It can be delivered as a complete JSON object or, more commonly, as a URL (`credential_offer_uri`) that points to the object. This offer is often embedded in a QR code or a deep link. The offer contains the `credential_issuer`'s URL, which the **Wallet** uses to fetch the metadata, and a list of `credential_configuration_ids` that specify which credentials are being offered.
### Issuance Flow Variations
- **Issuer-initiated**: The Credential Issuer starts the issuance by creating and communicating a Credential Offer to the wallet (for example via a QR code or deep link). The wallet then sends the issuance-related requests in response to that offer.
- **Wallet-initiated**: The wallet starts the issuance by sending requests to the issuer without first receiving a Credential Offer. Instead, the wallet relies on issuer metadata or prior knowledge of the issuer and starts the OAuth/issuance flow directly.
## The Complete OpenID4VCI Flow Explained (With Diagrams)
The **OID4VCI** protocol defines a precise sequence of interactions between the **Wallet** and the **Issuer**. The process is designed to be both secure and flexible, accommodating different use cases through two primary authorization flows.
**Pre-Auth Flow**

### Step 1: The Credential Offer (QR Code, Deep Link)
The journey begins when you, the **Holder**, use your **Wallet** to interact with a **Credential Offer**. Most commonly, this involves scanning a QR code on an **Issuer**'s website (e.g., a university portal after graduation) or clicking a deep link sent to you via email. This offer acts as the inital step, providing your **Wallet** with the initial information it needs to find the **Issuer** and begin the process.
### Step 2: The Authorization Flow (Authorization Code vs. Pre-Authorized Code)
After processing the offer and fetching the **Issuer**'s metadata, the **Wallet** must get authorization to receive the credential. **OID4VCI** provides two distinct flows for this purpose, and the choice depends on whether your identity is already known to the Issuer at the time of the offer.
- **Authorization Code Flow**: This is the standard, browser-based login flow familiar to anyone who has used "Sign in with Google." It is used when the Credential Offer is generic or public, and the **Issuer** needs to verify your identity before issuing a personalized credential. Your **Wallet** will redirect you to the **Issuer**'s authorization server, where you authenticate (e.g., with a username and password). Upon success, the server sends an authorization code back to the **Wallet**.
- **Pre-Authorized Code Flow**: This flow is used when the **Issuer** has already authenticated you and knows who you are. For instance, you might be logged into your secure online banking portal and click a button to "Get Digital Account Statement." In this case, the **Issuer** generates a secure, single-use pre-authorized code and embeds it directly into the Credential Offer. Your Wallet receives this code and can proceed directly to the next step without requiring you to log in again.
### The Token Request & The Proof of Possession (c_nonce and JWT)
With either an authorization code or a pre-authorized code in hand, your **Wallet** makes a direct, back-channel POST request to the **Issuer**'s Token Endpoint. The goal is to exchange the code for an **Access Token**, which serves as the key to unlock the final credential.
This step includes a critical security mechanism called **Proof of Possession**. The issued credential must be cryptographically bound to a key that only your **Wallet** controls.
To prove this, the **Wallet** first obtains a fresh `c_nonce` from the **Issuer**’s Nonce Endpoint. It then creates a signed JSON Web Token (JWT) `proof` that includes the **Issuer**’s identifier as `aud` and the `c_nonce` in the nonce claim, signing it with the **Wallet**’s private key. This `proof` is then sent in the Credential Request (the final step) so the Issuer can verify the **Wallet** controls the key to which the credential will be bound, preventing interception and replay. (Earlier drafts of OID4VCI provided the `c_nonce` via the Token Endpoint, but the current specification uses the Nonce Endpoint and/or Credential Response for this purpose)
### Step 4: The Credential Request & Issuance
The final step is the credential request itself. The **Wallet** makes a POST request to the **Issuer**'s **Credential Endpoint**. This request includes two vital pieces of information:
1. The **Access Token** obtained in Step 3, which authorizes the request.
2. The signed **JWT** `proof` containing the `c_nonce`, which proves possession of the key.
The **Issuer** validates the access token and the proof. If everything is correct, it constructs the **Verifiable Credential**, signs it with its own private key, and returns it in the response body. Your **Wallet** receives the credential and stores it securely, ready for you to use.
## Practical Implementation: A Hands-On Walkthrough
Theory is essential, but code makes it concrete. Let's walk through how you can interact with an **OID4VCI Issuer** using practical examples. You can use a dedicated tool like walt.id's Issuer to see how these offers are managed in a real-world environment.
### Step 1: View Live Issuer Metadata
Before a **Wallet** can do anything, it must fetch and parse the **Issuer**'s metadata. This is a simple HTTP GET request to the `/.well-known/openid-credential-issuer` endpoint.
Here is an example of what that metadata file looks like, with comments explaining the key fields:
```json
{
// REQUIRED — The Credential Issuer's identifier.
// MUST be the same origin used to fetch this metadata (/.well-known/openid-credential-issuer).
"credential_issuer": "https://issuer.demo.walt.id/draft13",
// OPTIONAL — List of OAuth 2.0 Authorization Server identifiers the issuer relies on.
"authorization_servers": [
"https://issuer.demo.walt.id/draft13"
],
// REQUIRED — HTTPS URL of the Credential Endpoint where the Wallet requests credentials.
"credential_endpoint": "https://issuer.demo.walt.id/draft13/credential",
// OPTIONAL — HTTPS URL of the Deferred Credential Endpoint to poll for later-ready credentials.
"deferred_credential_endpoint": "https://issuer.demo.walt.id/draft13/credential_deferred",
// OPTIONAL — HTTPS URL of a Notification Endpoint the Wallet can call to report status events.
"notification_endpoint": "https://issuer.demo.walt.id/draft13/notification",
// OPTIONAL — Server capabilities for encrypting the Credential Request (application-layer encryption on top of TLS).
"credential_request_encryption": {
// REQUIRED — JWK Set containing public keys the Wallet uses for key agreement/encryption of the request.
"jwks": {
"keys": [
{
"kty": "EC",
"crv": "P-256",
"x": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"y": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",
"kid": "req-key-1",
"use": "enc"
}
]
},
// REQUIRED — Supported JWE 'enc' values the endpoint can decrypt for requests.
"enc_values_supported": ["A256GCM"],
// OPTIONAL — Supported JWE 'zip' compression values for requests.
"zip_values_supported": ["DEF"],
// REQUIRED — Whether application-layer encryption of requests is required.
"encryption_required": false
},
// OPTIONAL — Server capabilities for encrypting the Credential Response (on top of TLS).
"credential_response_encryption": {
// REQUIRED — Supported JWE 'alg' values for encrypting the response JWT.
"alg_values_supported": ["ECDH-ES"],
// REQUIRED — Supported JWE 'enc' values for encrypting the response JWT.
"enc_values_supported": ["A256GCM"],
// OPTIONAL — Supported JWE 'zip' compression values for responses.
"zip_values_supported": ["DEF"],
// REQUIRED — Whether encryption of responses is required. If true, Wallet MUST supply keys in the request.
"encryption_required": false
},
// OPTIONAL — Indicates support for issuing multiple credentials in one call to the same Credential Endpoint.
// The presence of this object means multiple proofs can be supplied, and multiple credentials can be returned.
"batch_credential_issuance": {
// REQUIRED — Maximum array size for the 'proofs' parameter in a Credential Request. MUST be >= 2.
"batch_size": 5
},
// OPTIONAL — Human-readable branding for the issuer, possibly localized.
"display": [
{
// OPTIONAL — Friendly display name of the issuer.
"name": "walt.id Demo Issuer",
// OPTIONAL — BCP47 language tag for this display object. One object per locale.
"locale": "en",
// OPTIONAL — Issuer logo metadata.
"logo": {
// REQUIRED in logo object — URI to fetch the logo (https:, data:, etc.). (
"uri": "https://issuer.demo.walt.id/assets/logo.png",
// OPTIONAL — Alt text for the logo.
"alt_text": "walt.id demo logo"
}
}
],
// REQUIRED — Map describing each credential this issuer can issue.
// Keys are configuration IDs that also appear in Credential Offers and Authorization Details.
"credential_configurations_supported": {
// Example configuration ID for a JWT-VC (non-JSON-LD) credential.
"BankId_jwt_vc_json": {
// REQUIRED — Credential format identifier. For JWT VC (non-JSON-LD) use "jwt_vc_json".
"format": "jwt_vc_json",
// OPTIONAL — Scope value usable in the Authorization Request when requesting this credential.
"scope": "BankId",
// OPTIONAL — Algorithms the issuer uses to sign this credential format (JWS alg names).
"credential_signing_alg_values_supported": ["EdDSA", "ES256", "ES256K", "RS256"],
// OPTIONAL — How the issued credential is bound to Holder-controlled key material.
// MUST be present when key binding is required; omit if not required.
// Use 'jwk', 'cose_key', or DID method tags like 'did:web', 'did:key', etc.
"cryptographic_binding_methods_supported": ["did:web", "jwk"],
// OPTIONAL (and REQUIRED if cryptographic_binding_methods_supported is present) —
// Proof type(s) the issuer accepts at the Credential Endpoint; Wallet references one of these in the request.
"proof_types_supported": {
// 'jwt' proof type: Wallet signs a JWT including 'aud' (issuer) and 'nonce' (c_nonce).
"jwt": {
// REQUIRED within proof type — Accepted signing algs for the proof JWT.
"proof_signing_alg_values_supported": ["EdDSA", "ES256", "ES256K", "RS256"]
}
},
// JWT VC (non-JSON-LD) format-specific parameter: credential_definition with VC types.
"credential_definition": {
// REQUIRED — VC type array as per VCDM; commonly includes "VerifiableCredential" and the specific type.
"type": ["VerifiableCredential", "BankId"]
},
// OPTIONAL — Hints for Wallet UI on how to present claims and the credential card.
"credential_metadata": {
// OPTIONAL — Claim display metadata with JSON-path-like 'path' pointers.
"claims": [
{ "path": ["credentialSubject", "given_name"] },
{ "path": ["credentialSubject", "family_name"] },
{ "path": ["credentialSubject", "account_number"] }
],
// OPTIONAL — Overall card display hints.
"display": [
{
"name": "Bank Identity",
"locale": "en",
"logo": {
"uri": "https://issuer.demo.walt.id/assets/bankid.png",
"alt_text": "Bank ID"
},
"background_color": "#12107c",
"text_color": "#FFFFFF"
}
]
}
}
}
}
```
[Explore Example Metadata](https://issuer.demo.walt.id/swagger/index.html#/oidc/get__standardVersion___well_known_openid_credential_issuer)
### Step 2: Create a Credential Offer via a UI
For many use cases, generating a Credential Offer doesn't require writing code. An administrator can simply use a web portal, like the walt.id Web Issuer Portal to configure the credential and it's data, and the system will generate the corresponding QR code or offer link.
Try the Issuer Portal [here](https://portal.demo.walt.id/)
### Step 3: Create a Credential Offer Programmatically via API
For automated workflows you can trigger the process directly via an API call. The open-source **Issuer** API from walt.id for example will handle the subsequent exchanges with the **Wallet** to deliver the final credential.
You would typically make a POST request like the one below to create a credential offer. Execute the call in your termninal to create a credential offer for a Verifiable University Degree. You can claim it afterwards using the walt.id **Wallet** [here](https://wallet.demo.walt.id/).
```curl
curl -X 'POST' \
'https://issuer.demo.walt.id/openid4vc/jwt/issue' \
-H 'accept: text/plain' \
-H 'statusCallbackUri: https://example.com/$id' \
-H 'Content-Type: application/json' \
-d '{
"issuerKey": {
"type": "jwk",
"jwk": {
"kty": "OKP",
"d": "JvJIpga2GD8LJeRu4Sv-mL4thE31DuFlr9PA04CIoZY",
"crv": "Ed25519",
"kid": "iJMS5bkZVIlncfq_Lf_SuxJ2JtQ5Hvaz7tWPnAjUUds",
"x": "FZdvwC8aGhRwqzWptej0NZgtwYAI1SyFg1mKDETOfqE"
}
},
"issuerDid": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiaUpNUzVia1pWSWxuY2ZxX0xmX1N1eEoySnRRNUh2YXo3dFdQbkFqVVVkcyIsIngiOiJGWmR2d0M4YUdoUndxeldwdGVqME5aZ3R3WUFJMVN5RmcxbUtERVRPZnFFIn0",
"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": "",
"issuer": {
"id": ""
},
"credentialSubject": {
"id": ""
},
"issuanceDate": "",
"expirationDate": ""
},
"authenticationMethod": "PRE_AUTHORIZED",
"standardVersion": "DRAFT13"
}'
```
The API would respond with the Credential Offer, which includes the URI that you can then convert into a QR code or send to the user:
```
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
```
Follow the full API Guide [here](https://docs.walt.id/community-stack/issuer/credential-issuance/vc-oid4vc.md).
## OpenID4VCI vs. OpenID4VP: The Full Lifecycle
A common point of confusion for developers new to this space is the relationship between **OID4VCI** and **OpenID4VP**. The distinction is simple yet fundamental:
- **OpenID4VCI** is for **Issuance**: It defines the protocol for an Issuer to securely give you a credential.
- **OpenID4VP** (Verifiable Presentations) is for **Presentation**: It defines the protocol for you to securely show that credential to a **Verifier**.
These are two separate but complementary specifications that fall under the broader **OpenID for Verifiable Credentials (OID4VC)** umbrella. Together, they cover the entire lifecycle of a credential. The architectural separation is a deliberate design choice that enhances privacy and security. By separating the issuance and presentation flows, the original **Issuer** is not involved in the day-to-day use of the credential. This means the **Issuer** cannot track where or when you present your credential, a core principle of decentralized, user-centric identity.
## Integrating OpenID4VCI with Keycloak
One of the most powerful features of **OID4VCI** is its ability to integrate with existing, standard-compliant Identity Providers (IdPs). Because the protocol leverages the standard OAuth 2.0 **Authorization Code Flow**, you can use an IdP like **Keycloak** to handle user authentication and provide the claims for a credential.
This allows you to issue credentials based on data that is verified in real-time by your existing, trusted identity system. The process works as follows:
1. A user scans a QR code, and their **Wallet** initiates the issuance flow with your **OID4VCI** service.
2. Your service, acting as an OAuth 2.0 client to **Keycloak**, redirects the user to your organization's **Keycloak** login page.
3. The user authenticates successfully using their existing corporate or customer credentials.
4. **Keycloak** returns an authorization token to your OID4VCI service.
5. Your service uses this token to securely call **Keycloak**'s `/userinfo` endpoint and fetch verified claims about the user (e.g., name, email, role, user ID).
6. This trusted data is then mapped into the attributes of the **Verifiable Credential**, which is then signed and issued to the user's **Wallet**.
This method is perfect for enterprise or customer-facing scenarios where user data must be pulled from a source of truth like **Keycloak**, rather than being provided manually.
[walt.id's Issuer API](https://docs.walt.id/community-stack/issuer/getting-started.md) service can therefore be configured to use
Keycloak or any other OIDC compliant Identity Provider to authenticate users and populate **Verifiable Credentials** with verified user claims.
## Understanding OID4VCI Versions (Draft 11 vs. Draft 13 vs. OID4VCI 1.0)
The **OID4VCI** specification has evolved through multiple drafts to OID4VCI 1.0. The table below summarizes the key changes across Draft 11, Draft 13, and OID4VCI 1.0.
| Area | Draft 11 | Draft 13 | OID4VCI 1.0 |
|------|----------|----------|------------|
| Credential Offer – key parameter name | credentials (array; strings and, in earlier drafts, objects seen in examples) | Renamed to credential_configuration _ids (array of string identifiers) | Kept as credential_configuration _ids |
| Issuer metadata – list of credential options | credentials_supported (structure evolved across early drafts) | Renamed to credential_configurations _supported; structure changed from array → map | Same (credential _configurations _supported) |
| Pre-Authorized Code flow – end-user code | Parameter user_pin_required | Replaced by a structured tx_code object (with description, length, input_mode, etc.) | tx_code retained |
| Credential Offer – by value vs by reference | credential_offer / credential_offer_uri defined | Same, clarified; examples updated and statement that offer cannot be signed | Same behavior and discovery model |
| Authorization request expression | authorization_details type openid_credential (and scope) | Adds option to reference by credential_configuration_id (not only format/type) | Same; plus end-to-end identifiers guidance |
| Token response identifiers | — | Option to return authorization_details with credential_identifiers to correlate in later steps | Same; referenced in flow summary |
| Request/response encryption parameters | Separate fields (credential_encryption _jwk, credential_response _encryption_alg, …_enc) | Grouped into credential_response _encryption object | v1 also defines request encryption via credential_request _encryption in Issuer metadata |
| Authorization server discovery list | authorization_servers singular (earlier text) | Now an array | Remains array |
| Batch issuance mechanics | Dedicated Batch Credential Endpoint (§ 8) | Still present as an optional endpoint | Removed as a separate endpoint; batching happens at the Credential Endpoint for multiple creds with the same format & dataset in one request |
| Nonce handling for proofs | c_nonce discussed within credential flow | Clarifications | New dedicated Nonce Endpoint (optional) to fetch fresh c_nonce; requirements for including c_nonce in proofs |
| Notification / status callback | — | — | New optional Notification mechanism: Issuer may receive notifications about status of issued credential(s) |
Implementations like walt.id's Issuer support multiple draft versions and OID4VCI 1.0 coming in Q4 2025.
## Next steps
- [OID4VP](https://docs.walt.id/concepts/data-exchange-protocols/openid4vp.md) - Explore the protocol Verifiers use in more detail.
- [How to issue a Verifiable Credential via OID4VCI](https://docs.walt.id/community-stack/issuer/credential-issuance/vc-oid4vc.md) - Checkout the walt.id Issuer API docs for issuing a VC via OID4VCI.
--------------------------------------------------------------------------------
PAGE: What is OpenID4VP? The Developer's Guide (2026)
URL: https://docs.walt.id/concepts/data-exchange-protocols/openid4vp
RAW: https://docs.walt.id/concepts/data-exchange-protocols/openid4vp.md
STACK: Concepts (stack-agnostic, applies to both Community and Enterprise Stack)
--------------------------------------------------------------------------------
# OpenID4VP: A Developer's Guide to Verifiable Presentations
In today's digital world, proving who you are online is often a clumsy and risky process. We're asked to share entire identity documents just to prove a single fact, like our age, leading to oversharing of sensitive personal data. The current landscape relies on centralized identity providers, creating data silos and giving users little control over their own information. This model is not only inefficient but also ripe for phishing and fraud. The solution to this problem is a standardized, decentralized approach to identity verification.
**OpenID for Verifiable Presentations (OpenID4VP)** is a protocol built on the foundations of **OAuth 2.0** that standardizes how a person (a **Holder**) can present cryptographically secure digital credentials to an online service (a **Verifier**) in a secure, privacy-preserving, and user-controlled way. It creates a common language for requesting and receiving proof of identity attributes, putting you in control via your digital **Wallet** and enabling the crucial principle of data minimization.
In essence, **OpenID4VP** is the technical framework for a digital "show your ID" online, but with the power to show only the specific pieces of information required for a given transaction.
## What is OpenID4VP, Exactly?
To fully grasp the protocol, it's helpful to look at it from a few different angles—from a simple real-world analogy to its formal definition and the key players involved in every interaction.
### In Simple Terms: A Digital "Show Your ID" for the Internet
Imagine you're at a concert venue, and a security guard needs to verify that you are old enough to enter. You pull out your driver's license. The guard looks at your photo to see it's you and checks your date of birth to confirm you're over 21. Critically, they don't need to know your home address or any other attribute on the credential. They verify the one piece of information they need and let you pass. OpenID4VP is the technical framework that enables this exact, privacy-preserving interaction on the internet. It allows a website (**Verifier**) to ask your digital Wallet for proof of a specific claim (like being over 21), and for your wallet to provide just that proof without revealing your entire digital identity.
### The Official Definition Explained
Formally, **OpenID4VP** is a protocol that extends standards like **OAuth 2.0** and **OpenID Connect**, enabling users to present **Verifiable Presentations (VPs)**—which are packages containing one or more [**Verifiable Credentials (VCs)**](https://docs.walt.id/concepts/digital-credentials/verifiable-credentials-w3c.md)—to prove claims about themselves.
Let's break down the key components of this definition:
- **Extends OAuth 2.0**: The protocol is built upon the widly used authorization flows of **OAuth 2.0.** This is a significant advantage for developers, as it uses a familiar security architecture, reducing the learning curve and making it easier to integrate into existing systems.
- **Verifiable Credentials (VCs)**: These are the digital equivalents of your physical credentials like a driver's license, a university diploma, or an event ticket. A VC is a set of claims about a subject, issued by a trusted entity (like a government or university), and is cryptographically signed to be tamper-evident. These VCs are stored securely in your digital **wallet**.
- **Verifiable Presentations (VPs)**: This is a data package created by your wallet at the moment of presentation. It contains the specific VCs (or just parts of them) that a **Verifier** has requested. The VP is then digitally signed by you, the Holder, which serves as cryptographic proof of your consent and control over the data being shared in that specific transaction.
### Key Actors: The Verifier, the Holder, and the Wallet
Every **OpenID4VP** interaction involves three distinct roles, and understanding them is fundamental to understanding the protocol's architecture.
- **The Holder**: This is you, the end-user. The **Holder** is the individual who possesses the digital credentials and has ultimate control over their own identity data. You are the subject of the claims being asserted in the VCs.
- **The Verifier (or Relying Party)**: This is the application, website, or service that needs to verify some piece of information about the **Holder**. A **Verifier** could be an e-commerce site checking your age, a bank performing a Know Your Customer (KYC) check, or a venue scanning your digital ticket.
- **The Wallet**: This is the software, typically a mobile application, that you use to securely store your VCs, receive verification requests, and manage the presentation process. The **Wallet** acts as your digital agent, working on your behalf to interpret requests, get your explicit consent, and construct the response for the **Verifier**.
This three-party model represents a fundamental architectural shift away from traditional, federated identity systems. In a typical "Sign in with Google" flow, the interaction is primarily between you and the Identity Provider (IdP); the service you're accessing simply trusts the IdP. The verifiable credentials model decouples these roles. An **Issuer** creates the credential, the **Holder** stores it, and the **Verifier** consumes it, often without the **Issuer** being directly involved in the verification transaction at all. This decoupling is the core change. A **Verifier** doesn't need a pre-existing technical integration with every possible **Issuer**. Instead, it only needs to be able to verify the cryptographic proof of the credential presented by the **Holder**, creating a more open, interoperable, and scalable "web of trust".
## How the OpenID4VP Protocol Works (A Step-by-Step Flow)
The OpenID4VP protocol defines a clear, secure sequence of interactions between the **Verifier**, the **Holder**, and the **Wallet**. While the underlying cryptography is complex, the flow itself is logical and designed for a seamless user experience, whether on a single device or across multiple devices (like a laptop and a phone).
The following diagram illustrates the standard cross-device flow, which is the most common implementation.

### Step 1: The Verifier Creates a Presentation Request
The process always begins with the Verifier needing to confirm one or more claims about a user. To do this, it constructs an **Authorization Request**. At the heart of this request is a `presentation_definition` or, in the latest specification, a `dcql_query`, which precisely defines what credentials and claims are needed (e.g., "a government-issued ID that contains a date_of_birth claim"). This request is then encoded into a URL, which is typically presented to the user as a QR code for cross-device interactions or a deep link for same-device flows
### Step 2: The Holder's Wallet Presents the Credentials
The **Holder** initiates the interaction by scanning the QR code with their **Wallet** application. The **Wallet** then takes over as the user's agent:
1. It decodes the URL from the QR code and uses the information to fetch the full **Authorization Request** object from the **Verifier**.
2. It parses the request to understand the **Verifier**'s requirements.
3. The **Wallet** searches its secure storage for any VCs that satisfy the request.
4. Crucially, it prompts the **Holder** for explicit consent. The user is shown exactly what information is being requested and who is requesting it (e.g., "OnlineStore.com would like to verify you are over 18."). No data is shared without this consent.
5. Once the user approves, the **Wallet** prepars the presentation. Depending on the credential format:
- W3C VC: Create a Verifiable Presentation (JWT-VP or Data-Integrity VP) controlled and signed by the Holder.
- SD-JWT VC: Return the SD-JWT with the selected Disclosures and, if holder binding is required, add a Key Binding JWT.
- ISO mDL/mdoc (ISO/IEC 18013-7): Return a DeviceResponse, bound to the Verifier’s request through the SessionTranscript and OID4VP hand-over structure.
6. It packages the result into a `vp_token` (and, if applicable, a `presentation_submission`) and send it to the **Verifier**’s HTTPS endpoint using **OID4VP**’s `response_mode=direct_post`.
### Step 3: The Verifier Receives and Validates the Presentation
The **Wallet**’s Authorization Response arrives at the **Verifier**’s HTTPS endpoint via **OID4VP**’s `response_mode=direct_post`. It includes a `vp_token` (and sometimes a `presentation_submission`), whose content depends on the credential format.
The **Verifier** then validates:
1. Holder binding & replay protection: Confirm that nonces and audience values match the request, ensuring the response is fresh and bound to this Verifier.
2. Issuer authenticity: Verify the Issuer’s signatures — on credentials (W3C), the SD-JWT, or mdoc data elements. Resolve trust chains (e.g., via DIDs) where required.
3. Selective disclosure (if applicable): For SD-JWTs, recompute disclosed values against the digests.
4. Claim satisfaction: Ensure the presented data fulfills the **Verifier**’s original request.
5. Status & validity: Check that credentials or documents have not been revoked, suspended, or expired.
6. Format-specific checks:
- W3C VC: Validate the Verifiable Presentation (JWT-VP or Data-Integrity VP) structure.
- SD-JWT VC: Validate the Key Binding JWT when holder binding is required.
- ISO mDL/mdoc: Validate the SessionTranscript and OID4VP hand-over structure for integrity and replay protection.
If all checks succeed, the **Verifier** can trust the authenticity, integrity, and freshness of the claims and proceed with the transaction (e.g., granting access or completing a purchase).
## OpenID4VP vs. OpenID4VCI: What's the Difference?
Within the decentralized identity ecosystem, you will frequently encounter two related acronyms: **OpenID4VP** and [**OpenID4VCI**](https://docs.walt.id/concepts/data-exchange-protocols/openid4vci.md). While they sound similar and are part of the same family of specifications, they serve two distinct and complementary purposes: presentation and issuance. Understanding the difference is key to understanding the full lifecycle of a verifiable credential.
### OpenID4VCI (Issuance): Minting the Digital Credential
[**OpenID for Verifiable Credential Issuance (OpenID4VCI)**](https://docs.walt.id/concepts/data-exchange-protocols/openid4vci.md) is the protocol used to get a new credential into your wallet in the first place. The primary interaction in this flow is between an **Issuer** (e.g., a university, government agency, or employer) and the **Holder**'s Wallet. The **Issuer** uses the [**OID4VCI**](https://docs.walt.id/concepts/data-exchange-protocols/openid4vci.md) protocol to securely offer and deliver a newly created VC to the **Holder** after they have authenticated and proven their eligibility. You can think of this as the "enrollment" or "credential pickup" phase.
### OpenID4VP (Presentation): Showing the Digital Credential
As we've discussed, **OpenID4VP** is the protocol for using a credential that is already stored in your wallet. The primary interaction here is between the **Holder**'s **Wallet** and a **Verifier**. The **Holder** uses **OpenID4VP** to present proof of claims from their existing credentials to a third party that needs to verify them. This is the "verification" or "show your ID" phase.
### How They Work Together in a Decentralized Identity Ecosystem
**OID4VCI** and **OID4VP** are not competing standards; they are two sides of the same coin, designed to work together to enable a complete, end-to-end credential lifecycle. A credential has no value if it cannot be both reliably issued and reliably presented.
Consider this complete workflow:
1. [**Issuance (OID4VCI)**](https://docs.walt.id/concepts/data-exchange-protocols/openid4vci.md): You graduate from a university (**Issuer**). You log into the university's alumni portal and request a digital version of your diploma. The university uses [**OpenID4VCI**](https://docs.walt.id/concepts/data-exchange-protocols/openid4vci.md) to issue a `VerifiableDiploma` credential directly and securely to your digital **Wallet**.
2. **Presentation (OpenID4VP)**: Months later, you apply for a job at a new company (**Verifier**). The company's online application portal requires proof of your degree. The portal generates a request using **OpenID4VP**. You scan a QR code, and your **Wallet** uses **OpenID4VP** to present your `VerifiableDiploma` to the company for instant, cryptographic verification.
These two protocols are the fundamental pillars of the broader **OpenID for Verifiable Credentials (OID4VC)** suite, which aims to standardize the entire lifecycle of a digital credential. The combined adoption of both [**OID4VCI**](https://docs.walt.id/concepts/data-exchange-protocols/openid4vci.md) and **OID4VP** by major initiatives like the EU Digital Identity Wallet demonstrates that a mature, interoperable ecosystem requires standardized solutions for both getting credentials and using them.
| Feature | OpenID4VCI (Issuance) | OpenID4VP (Presentation) |
|--------------------------|----------------------------------------------------|---------------------------------------------------|
| **Primary Goal** | To securely issue a new VC to a wallet. | To securely present an existing VC from a wallet. |
| **Primary Actors** | Issuer ↔ Holder's Wallet | Verifier ↔ Holder's Wallet |
| **Direction of Credential** | Issuer → Holder | Holder → Verifier |
| **Analogy** | Receiving your driver's license at the DMV. | Showing your driver's license to a bouncer. |
| **Core Protocol Action** | Credential Offer & Issuance Request | Presentation Request & Authorization Response |
## Understanding the Specification: From Drafts to DCQL
Like many open standards, the **OpenID4VP** specification has evolved over time through a collaborative, public process. This has led to some confusion among developers regarding different draft versions and, most importantly, a significant technical upgrade in how credential requests are made.
### Why Are There So Many Drafts? A Look at the OIDF Process
Standards developed at the OpenID Foundation (OIDF) go through a rigorous, iterative process before being finalized. They are published as a series of numbered "implementer's drafts," which allow the global community of developers, companies, and security experts to build prototypes, provide feedback, and refine the protocol based on real-world testing. This open process is crucial for ensuring that the final `v1.0` standard is robust, secure, and, most importantly, interoperable across different wallets and verifier systems, as proven in recent successful interoperability events.
### The Big Change: From Presentation Definition to DCQL
Early drafts of the **OpenID4VP** specification relied on a mechanism called Presentation Definition to allow **Verifiers** to specify their credential requirements. This mechanism, which comes from the Decentralized Identity Foundation's (DIF) Presentation Exchange specification, is a declarative JSON format that lets a **Verifier** describe the credentials it is looking for.
While functional for simple requests, the latest versions of the **OpenID4VP** specification (starting with Draft 22 in October 2024) have introduced a more powerful and expressive mechanism: the **Digital Credentials Query Language (DCQL)**. The final, stable `v1.0` version of the specification includes **DCQL** as a core, standardized component.
### What is DCQL (Decentralized Query Language)?
**DCQL** is a JSON-encoded query language designed specifically for requesting **Verifiable Presentations**. It allows a **Verifier** to construct precise, structured queries to request credentials that match specific and even complex criteria.
DCQL offers several advantages over the older **Presentation Definition** approach:
- **Greater Expressiveness**: DCQL can formulate much more complex and logical queries. For example, a **Verifier** can use **DCQL** to ask for "a `VerifiableID` where the `country` is 'US' AND an `EmployeeCredential` where the role is 'Engineer'", all within a single, structured request.
- **Standardization**: It provides a single, native query language defined within the **OpenID4VP** specification itself.
- **Future-Proofing**: DCQL is already being mandated as the required query language in high-assurance industry profiles.
This evolution from **Presentation Definition** to **DCQL** signals a critical maturation of the protocol. It represents a shift from simple "credential matching" to sophisticated "verifiable data querying." Early use cases were straightforward: "Do you have a credential of type X?" As the ecosystem and its use cases have grown more complex, the need for a true query language became apparent. **DCQL** enables a **Verifier** to treat a user's wallet less like a simple folder of documents and more like a private, queryable, user-controlled database.
## What is a Verifiable Presentation (VP)?
In **OpenID4VP**, the response is a `vp_token` —a container that can hold W3C Verifiable Presentations and/or other presentation types depending on the credential format. The spec states that a VP Token “contains one or more Verifiable Presentations and/or Presentations … in the same or different Credential formats,” and supports W3C VCDM, ISO mdoc, and IETF SD-JWT VC. In other words: a VP is central when you use the W3C VC Data Model, but other formats use their own presentation artefacts.
### The Core Components of a W3C Verifiable Presentation (VP)
A W3C **Verifiable Presentation** is a data structure that **packages data from one or more Verifiable Credentials** so the authorship is verifiable. Common fields include:
- `@context`: Specifies the JSON-LD context, which defines the vocabulary and semantics of the data model (e.g., the W3C Verifiable Credentials v1 standard).
- `type`: An array that declares the object's type, which must include `VerifiablePresentation`.
- `verifiableCredential`: An array containing one or more VCs. These can be the full, original VCs or, in the case of selective disclosure, just specific claims from them.
- `holder` - An identifier for the user (often a **Decentralized Identifier (DID)**) who is creating and presenting the VP. This identifies who is making the presentation.
For JWT-encoded VPs, the vp claim MUST be present in the JWT payload.
### How the Credential Format changes the “presentation”
#### W3C VCDM (e.g., VC-JWT / Data Integrity):
Inside the VP, verifiableCredential can hold VC-JWTs (compact JWTs) or JSON-LD credentials, and the VP is signed by the Holder (e.g., via JWS/JWT or Data Integrity proof). The Data Model details both embedded (Linked Data) and external (JWT) proofs and the required vp claim for JWT VPs.
#### SD-JWT VC (IETF):
The presentation is an SD-JWT presentation consisting of the SD-JWT plus selected Disclosures and optionally a KB-JWT for holder binding—not a W3C VP wrapper by default. The IETF draft states: “An SD-JWT consists of … the SD-JWT itself, optional disclosures, and an optional KB-JWT,” and “A presentation of an SD-JWT VC MAY contain a KB-JWT.” OpenID4VP transports this as a Presentation inside the VP Token.
#### mDL / mdoc (ISO 18013-5, 18013-7):
OpenID4VP carries the ISO DeviceResponse CBOR structure in the VP Token (again, not a W3C VP). The spec: “The VP Token contains the base64url-encoded DeviceResponse CBOR structure as defined in ISO/IEC 18013-5 ….” ISO/IEC 18013-7 adds over-the-internet presentation via OpenID4VP.
#### Example of a JWT-based Verifiable Presentation (JSON Snippet)
```json
{
// The @context specifies the data model, in this case, the W3C Verifiable Credentials v1 standard.
"@context": [
"https://www.w3.org/2018/credentials/v1"
],
// The id provides a unique identifier for this specific presentation instance.
"id": "Fs2M6HVrER7y",
// The type array declares that this object is a Verifiable Presentation.
"type": [
"VerifiablePresentation"
],
// The holder property contains the Decentralized Identifier (DID) of the user who is presenting the credential.
"holder": "did:key:z6MkwQx8J3LwFzYjv7Lqk7h2b7Zr5dF6Qe5mQ3E8iJ2o8YkP",
// This array contains the actual credential(s) being presented. It's a JWT string in this example.
"verifiableCredential": [
"eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJ..."
]
}
```
## Real-World OpenID4VP Use Cases & Implementations
The true value of a technical protocol lies in its ability to solve real-world problems. **OpenID4VP** is already being deployed across various industries to create more secure, efficient, and privacy-preserving user experiences. Use-case examples include:
### Use Case 1: Streamlining KYC for Financial Services
- **The Problem**: Traditional Know Your Customer (KYC) processes for opening a bank account are notoriously slow, require manual document verification, and force users to upload highly sensitive information, creating a large attack surface for data breaches.
- **The OpenID4VP Solution**: A bank, acting as a **Verifier**, can use **OpenID4VP** to request the necessary claims (e.g., `full_name`, `date_of_birth`, `address`) from a user's government-issued digital ID (like an mDL). The user simply scans a QR code and consents to the data sharing from their **Wallet**. The bank receives a cryptographically verified and trustworthy attestation in seconds, dramatically reducing onboarding friction, minimizing fraud, and improving regulatory compliance
### Use Case 2: Age Verification for Restricted Content
- **The Problem**: Websites that sell age-restricted products like alcohol or offer services like online gambling must verify a user's age. Simply asking for a date of birth is not sufficient, but asking for a full ID scan means the user needs to share information that is not directly required (e.g. address).
- **The OpenID4VP Solution**: An e-commerce site (Verifier) can request a single, boolean claim: `is_over_18`: true. A user with a digital driver's license in their wallet can selectively disclose only this claim. They prove they meet the age requirement without revealing their name, address, or exact date of birth. This is a perfect example of data minimization in practice, enhancing user privacy while still meeting business needs.
### Use Case 3: Digital Ticketing for Events and Venues
- **The Problem**: Traditional paper tickets or simple QR codes are easy to counterfeit, scalp, or fraudulently resell, leading to revenue loss for organizers and frustration for attendees.
- **The OpenID4VP Solution**: An event organizer (**Issuer**) uses [**OID4VCI**](https://docs.walt.id/concepts/data-exchange-protocols/openid4vci.md) to issue a Verifiable Credential as a ticket, which is cryptographically bound to the purchaser's identity (**Holder**). When the attendee arrives at the venue (**Verifier**), they present the ticket from their wallet using **OpenID4VP**. The scanner at the gate can instantly verify the ticket's authenticity, confirm that it has not been used before, and ensure that it is being presented by its legitimate owner, effectively eliminating ticket fraud.
## Practical Implementation: A Hands-On Walkthrough
For a developer building a service that needs to verify user data, implementing **OpenID4VP** involves creating the request and handling the response. Here is a high-level walkthrough of the **Verifier**'s role in the process.
### Step 1: Define What You Need (DCQL)
The first step for any **Verifier** is to precisely define the data it needs to request. With the finalization of the `v1.0` specification, **DCQL** is the modern, standard way to construct this query. You would create a JSON object that specifies the credential types, formats, and specific claims you require. Numerous open-source libraries and tools are emerging to help build and validate these DCQL queries programmatically.
You can for example, explore the walt.id's open-source verifier [here](https://docs.walt.id/community-stack/verifier), which let's you request and validate credentials via UI, API & SDKs.
### Step 2: Generate a Presentation Request and Present it via a UI (QR Code Flow)
This is the most common flow for web applications interacting with a user's mobile wallet.
1. Your backend service generates the full **Authorization Request**, including the DCQL query, a unique cryptographic `nonce` to prevent replay attacks, and the `response_uri` where the wallet should send the final VP.
2. Your service then creates a `request_uri` that points to this full request object.
3. Your frontend application takes this `request_uri` and embeds it into a standardized URL with the scheme `openid4vp://`. This complete URL is then rendered as a QR code on the screen for the user to scan with their wallet app.
You can experience this in action by trying out the open-source walt.id verifier portal [here](https://portal.demo.walt.id/).
### Step 3: Generate a Presentation Request via API and Send it as a Direct Link (Same-Device Flow)
In this flow, a backend service constructus the Authorization Request and sends it to the user as a link, in
for example an email.
1. Your application constructs the `openid4vp://` URL containing the `request_uri` just as in the cross-device flow.
2. Instead of rendering a QR code, your app uses a deep link to invoke the user's installed wallet app directly, passing the request parameters seamlessly. The wallet opens, prompts the user for consent, and returns the response to your application, all within a smooth, in-app workflow.
You can use the walt.id open-source Verifier API to handle the Authorization Request creation and perform various checks on presented credentials. Learn more [here](https://docs.walt.id/community-stack/verifier/credential-verification/vc-oid4vc.md).
## OpenID4VP FAQ
### What is the OpenID standard?
The **OpenID Foundation (OIDF)** is a non-profit international standards body that develops open, interoperable standards for digital identity. Its most famous standard is **OpenID Connect**, which is used by billions of people for secure logins across the web, but it also maintains the entire **OpenID for Verifiable Credentials (OID4VC)** family of specifications, including **OpenID4VP**.
### Is OpenID4VP the same as OpenID Connect?
No. **OpenID Connect** is an identity layer on top of **OAuth 2.0** used to authenticate the End-User and optionally return claims. **OpenID4VP** is a separate **OpenID Foundation specification** that defines a mechanism on top of **OAuth 2.0** to request and deliver presentations of credentials (often via a user’s **Wallet**). Both reuse **OAuth 2.0**, but their primary goals differ: OIDC = authentication, OID4VP = credential presentation.
### Where does OpenID4VP fit in the Self-Sovereign Identity (SSI) model?
Self-Sovereign Identity (SSI) is a conceptual model for digital identity that puts individuals in complete control of their own personal data, independent of any central authority. OpenID4VP is a crucial piece of the technical implementation that makes the SSI vision a reality. It provides the standardized, interoperable protocol that allows a user (**Holder**) to exercise their sovereign control by selectively sharing their data from their personal **Wallet** with **Verifiers**. It is a key specification that makes the principles of SSI practical, secure, and ready for widespread adoption.
## Next steps
- [OID4VCI](https://docs.walt.id/concepts/data-exchange-protocols/openid4vci.md) - Explore the protocol Issuers use in more detail.
- [How to verify a Verifiable Credential via OID4VP](https://docs.walt.id/community-stack/verifier/credential-verification/vc-oid4vc.md) - Checkout the walt.id Verifier API docs for verifying a VC via OID4VCP.
--------------------------------------------------------------------------------
PAGE: What is the Digital Credentials API? The Developer's Guide (2026)
URL: https://docs.walt.id/concepts/data-exchange-protocols/dc-api
RAW: https://docs.walt.id/concepts/data-exchange-protocols/dc-api.md
STACK: Concepts (stack-agnostic, applies to both Community and Enterprise Stack)
--------------------------------------------------------------------------------
# W3C Digital Credentials API (DC-API): A Developer's Guide to Browser-Based Digital Credentials
In the evolving landscape of digital identity, one of the biggest gaps has been a **native, browser-based way** for users to present and receive digital credentials – without QR codes, custom URL schemes, or hard-to-debug cross-device hacks.
Protocols like **OpenID for Verifiable Credential Issuance (OpenID4VCI)** and **OpenID for Verifiable Presentations (OpenID4VP)** standardize the issuance and presentation of verifiable credentials across wallets and services. But until recently, these flows have largely lived **outside** the browser (mobile apps, deep links, QR flows).
**The W3C Digital Credentials API (often called “DC-API” or “DC API”)** changes that. It is a web platform API that lets websites request and receive digital credentials **directly in the browser**, with the user agent mediating the interaction between the website and the user’s wallet or credential store.
**Note:**
**TL;DR:**
- **DC-API** is a W3C browser API that mediates **presentation and issuance** of digital credentials.
- It is **protocol-agnostic** but works hand-in-hand with protocols like **OpenID4VP** and **OpenID4VCI**.
- Chrome and Safari have **shipping implementations**, with cross-device flows for mobile wallets and desktop browsers.
- It’s on the W3C **Recommendation track** and evolving quickly, so you should design for change but can already prototype in real browsers.
In essence, **Digital Credentials API is the bridge between web apps and digital wallets**: it brings verifiable credentials into everyday web experiences while preserving cryptographic security and user privacy.
## What is Digitial Credentials API (DC-API), Exactly?
### In Simple Terms: A Browser-Based “Show Your ID” Button
Imagine you’re on an e-commerce site and want to buy age-restricted goods. Today you might:
- Upload a photo of your ID,
- Or switch to a mobile app,
- Or scan a QR code with another device.
With **Digigtal Credentials API**, the website can instead ask:
> “Please prove you’re over 21.”
Your **browser**:
1. Shows you a system UI explaining what’s being requested and by which site.
2. Talks to your wallet (on the same device or another device).
3. Lets you pick a credential that satisfies the request.
4. Returns a **cryptographically verifiable presentation** back to the website.
You stay in the browser. You see clearly what’s being shared. The site gets a **verifiable proof** instead of a blurry JPG.
### The Official Definition (De-Jargonized)
Formally, the **W3C Digital Credentials API** is a JavaScript API that enables user agents (browsers) to mediate **presentation and issuance** of digital credentials (such as mobile IDs, mDLs, or verifiable credentials). It:
- **Builds on Credential Management Level 1**, extending the `navigator.credentials` surface with a `digital` member.
- Is **protocol-agnostic**, but defines a **registry of supported protocols** (e.g., OpenID4VP over Digital Credentials API).
- Treats the browser as a **trusted mediator**: it can inspect requests, enforce permissions, and show UX that keeps users in control.
Key idea: The digital credentials API is not itself a “credential protocol”. It’s a browser API that carries credential protocols between the website and the wallet.
## Key Actors: Website, User, User Agent (Browser) … and Wallet
A typical Digital Credentials API interaction involves:
### Website (Verifier or Issuer)
The web app calling navigator.credentials.get() (for presentation) or navigator.credentials.create() (for issuance). It knows what claims it needs (e.g. age_over_21, full_name).
### User (Holder)
The person who owns the credentials. They see the browser’s consent UI and decide whether to share or accept a credential.
### User Agent (Browser)
The gatekeeper. It:
- Validates the origin.
- Mediates access to the wallet(s) and identity APIs on the platform.
- Presents a credential chooser / consent UI.
### Wallet / Credential Store
A platform wallet (e.g. Apple Wallet, Google Wallet) or another app registered as a wallet. It holds the actual credentials and builds the cryptographic presentation or issuance response.
This is a shift from “mobile-app + QR codes” flows to browser-native credential flows, but the underlying issuer-holder-verifier model stays the same.
## How Digital Credentials API (DC-API) Works (A Step-by-Step Flow)
The **DC-API** protocol defines a clear sequence of interactions between the website, the browser, and the user's credential store. While the underlying implementation may vary depending on the browser and wallet integration, the developer-facing API remains consistent and straightforward.
### Step 1: The Website Requests a Credential
The process begins when a website needs to request a credential from the user. The website's JavaScript code calls the **DC-API**, typically using a method like `navigator.credentials.get()` with a credential request object. This request specifies:
- The type of credential needed (e.g., a government-issued ID, a university diploma)
- The specific claims required (e.g., `age_over_21`, `full_name`)
- The expected origin(s) for security validation
- Any protocol-specific parameters (if using **OpenID4VCI** or **OpenID4VP** under the hood)
The browser receives this request and validates that it comes from an authorized origin, preventing malicious websites from requesting credentials without user consent.
### Step 2: The Browser Presents the Request to the User
The browser acts as the user's agent, presenting the credential request in a secure, user-friendly interface. This interface shows:
- Which website is requesting the credential
- What specific information is being requested
- What credential will be used to satisfy the request
- Clear options to approve or deny the request
This step is crucial for user privacy and security. The browser ensures that users have full visibility into what is being requested and who is requesting it, preventing phishing attacks and ensuring informed consent.
### Step 3: The User Approves and the Browser Retrieves the Credential
If the user approves the request, the browser securely communicates with the user's credential store (which may be built into the browser, a browser extension, or a connected mobile wallet). The credential store:
1. Locates the appropriate credential(s) that satisfy the request
2. Constructs the necessary presentation or issuance response (depending on whether this is a presentation or issuance flow)
3. Applies selective disclosure if only specific claims are needed
4. Signs the response with the user's private key (for holder binding)
5. Returns the credential data to the browser
### Step 4: The Browser Returns the Credential to the Website
The browser receives the credential from the credential store and returns it to the website's JavaScript code via the **DC-API** response. The website can then:
- Validate the cryptographic signatures
- Extract the claims it needs
- Proceed with the business logic (e.g., granting access, completing a purchase)
The entire flow happens within the browser, creating a seamless user experience while maintaining the security and privacy guarantees of verifiable credentials.
## Digital Credentials API (DC-API) vs. OpenID4VCI/VP: Understanding the Relationship
A common point of confusion for developers is understanding how **DC-API** relates to **OpenID4VCI** and **OpenID4VP**. The relationship is complementary rather than competitive:
### OpenID4VCI/VP: The Protocol Layer
**OpenID4VCI** and **OpenID4VP** are protocol specifications that define the exact messages, endpoints, and cryptographic mechanisms for credential issuance and presentation. They specify:
- How authorization requests are structured
- What endpoints wallets and issuers/verifiers must implement
- How cryptographic proofs are created and validated
- The exact format of credential offers, presentation requests, and responses
These protocols are transport-agnostic—they can work over HTTP, mobile deep links, QR codes, or any other communication channel.
### Digital Credentials API (DC-API): The Browser API Layer
**DC-API** is a browser API that provides a JavaScript interface for web developers. It abstracts away the underlying protocol details, allowing developers to request credentials without needing to understand the intricacies of **OpenID4VCI** or **OpenID4VP**.
**DC-API** can use **OpenID4VCI** or **OpenID4VP** as its underlying protocol, but it could also work with other protocols. The browser implementation handles the protocol-specific details, translating between the simple JavaScript API calls and the complex protocol messages.
### How They Work Together
Consider a website that needs to verify a user's age:
1. **Website constructs an authorization request**: The website constructs an authorization request (using a Verifier Server which supports OpenID4VP), including a DCQL query specifying the required credential and claims needed for the age verification.
2. **Browser uses DC-API**: The website's JavaScript calls `navigator.credentials.get()` with the mapped object from the authorization request.
3. **Wallet processes the request**: The user's wallet receives the **OpenID4VP** request, matches it against stored credentials, and creates a verifiable presentation.
4. **Browser returns via DC-API**: The browser receives the **OpenID4VP** response and translates it back into a simple JavaScript object that the website then sends back to the Verifier Server.
5. **Verifier Server validates the response**: The Verifier Server validates the response and returns the result to the website.
6. **Website displays the result**: The website displays the result to the user.
This layered architecture provides the best of both worlds: the standardization and interoperability of **OpenID4VCI/VP** at the protocol level, combined with the developer-friendly simplicity of **DC-API** at the application level.
| Feature | OpenID4VCI/VP | DC-API |
|---------|---------------|--------|
| **Layer** | Protocol specification | Browser API |
| **Transport** | Protocol-agnostic (HTTP, QR codes, deep links) | Browser-based (JavaScript) |
| **Developer Experience** | Requires understanding of protocol details | Simple JavaScript API calls |
| **Use Case** | Mobile wallets, backend services, cross-device flows | Web applications, same-device browser flows |
| **Protocol Support** | Defines specific protocol | Can use OpenID4VCI/VP or other protocols |
## Real-World Digital Credentials API (DC-API) Use Cases
The true value of **DC-API** lies in its ability to solve real-world problems with a seamless user experience. Here are some practical examples:
### Use Case 1: Age Verification for E-Commerce
**The Problem**: Online retailers selling age-restricted products (alcohol, tobacco, adult content) need to verify customer age, but traditional methods are either insecure (self-reported age) or intrusive (uploading full ID documents).
**The DC-API Solution**: An e-commerce website can use **DC-API** to request a single claim: `age_over_21: true`. The user's browser presents a clear consent prompt, and if approved, the website receives cryptographically verified proof of age without learning the user's name, address, or exact date of birth. This enables compliance with age restrictions while maximizing user privacy.
### Use Case 2: Digital Credential Issuance for Online Learning
**The Problem**: Educational platforms want to issue digital certificates and diplomas, but the current process often requires manual verification, email delivery, and separate credential management systems.
**The DC-API Solution**: After a student completes a course, the learning platform can use **DC-API** to issue a verifiable credential directly to the student's browser-based wallet. The student receives their credential instantly, and it's cryptographically signed and tamper-evident. The student can then use **DC-API** to present this credential to employers or other educational institutions, all within their browser.
### Use Case 3: Streamlined KYC for Financial Services
**The Problem**: Financial institutions must perform Know Your Customer (KYC) checks, but traditional processes require customers to upload sensitive documents, wait for manual verification, and often share more information than necessary.
**The DC-API Solution**: A bank's website can use **DC-API** to request specific KYC claims from a government-issued digital ID. The user sees exactly what information is being requested, approves the request, and the bank receives cryptographically verified identity data in seconds. This dramatically reduces onboarding time, improves security, and enhances user privacy through selective disclosure.
## Current Status and Browser Support (2026)
**Standardization Status**
• The W3C Digital Credentials API is currently a Working Draft on the W3C Recommendation track.
• It is maintained by the Federated Identity Working Group and was first published as a Public Working Draft in July 2025; the latest Working Draft (December 2025) continues to refine the model, security, privacy, and registry of supported protocols.
**Browser & Platform Implementations**
Support is still emerging, but already quite real:
- Chrome (Android & Desktop)
- DC-API is available as an origin trial starting in Chrome 128 on Android for same-device presentation flows.
- From Chrome 136, cross-device support lets desktop Chrome display a QR code that the user scans with an Android phone; credentials are then presented from the phone’s wallet back to the desktop site.
- Access currently requires enabling a flag and/or participation in the origin trial, but the pattern is clear: DC-API is the primary web surface for Android wallets.
- Safari & WebKit (Apple Platforms)
- Safari 26 on macOS, iOS, and iPadOS introduces web support for the W3C Digital Credentials API.
- Websites can request mobile IDs (e.g., a state ID in Apple Wallet) and get a signed, encrypted response.
- Safari also supports cross-device flows where a Mac or iPad uses an iPhone as the wallet device via a QR-based handoff.
- Developer Sandbox & Test Verifier
- The digitalcredentials.dev site and tools provide cross-browser demos, test verifiers, and documentation for experimenting with DC-API and OpenID4VP.
In practice, DC-API support is good enough for pilots and developer experimentation. For production systems, you should:
- Implement feature detection & graceful fallback.
- Avoid assuming a specific browser or OS.
- Keep up with spec changes (the API is still a Working Draft).
## Security and Privacy Considerations
**DC-API** is designed with security and privacy as fundamental principles. Key security features include:
### Origin Validation
The browser validates that credential requests come from authorized origins, preventing malicious websites from requesting credentials without user consent. The `expectedOrigins` parameter allows websites to specify which origins are allowed to receive credential responses, providing an additional layer of protection against cross-origin attacks.
### User Consent and Transparency
Every credential request must be explicitly approved by the user through a browser-mediated consent interface. Users see exactly what information is being requested and which website is making the request, ensuring informed consent and preventing phishing attacks.
### Cryptographic Security
**DC-API** leverages the same cryptographic security mechanisms as **OpenID4VCI** and **OpenID4VP**, including:
- **Holder Binding**: Credentials are cryptographically bound to keys controlled by the user, preventing credential theft and replay attacks.
- **Selective Disclosure**: Users can share only the specific claims needed, minimizing data exposure.
- **Tamper Evidence**: Credentials are cryptographically signed, making any tampering immediately detectable.
### Protocol-Level Security
It's important to note that **DC-API** does not provide data integrity on credential requests or responses itself—this responsibility lies with the underlying protocol (e.g., **OpenID4VCI** or **OpenID4VP**). The API provides a secure transport mechanism, but the cryptographic guarantees come from the credential format and exchange protocol being used.
## Practical Implementation: A Hands-On Walkthrough
For developers building web applications that need to request credentials, implementing **DC-API** involves calling the browser API and handling the response. For a complete implementation example, check out the [walt.id Digital Credential Button project](https://github.com/walt-id/waltid-identity/tree/main/waltid-applications/waltid-digital-credentials), which provides a reference implementation for invoking the Browser-based DC-API.
## Digital Credentials API (DC-API) FAQ
### Is DC-API a W3C standard yet?
Not yet. As of late 2025, the Digital Credentials API is a W3C Working Draft on the Recommendation track. It is stable enough for experimentation, but you should expect some changes in the IDL, registry details, and protocol integration over time.
### What is the relationship between DC-API and Credential Management Level 1?
**DC-API** builds upon the **Credential Management Level 1** specification, which defines a general framework for credential storage and retrieval in browsers. **DC-API** extends this framework specifically for verifiable credentials, adding support for cryptographic proofs, selective disclosure, and protocol-agnostic credential exchange.
### Can DC-API work with mobile wallets?
Yes, **DC-API** can work with mobile wallets through browser extensions or connected services. The browser acts as the intermediary, communicating with the mobile wallet to retrieve credentials. This allows web applications to leverage the security and convenience of mobile wallet implementations while providing a seamless browser-based user experience.
### Is DC-API only for credential presentation, or can it also be used for issuance?
**DC-API** supports both credential issuance and presentation. The same API can be used to request credentials from users (presentation) or to issue new credentials to users (issuance), depending on the underlying protocol being used (**OpenID4VP** for presentation, **OpenID4VCI** for issuance).
### How does DC-API compare to WebAuthn?
**WebAuthn** is a browser API for passwordless authentication using public key cryptography. **DC-API** is a browser API for exchanging verifiable credentials. While both use similar browser infrastructure (Credential Management), they serve different purposes:
- **WebAuthn**: Authenticates users ("prove you are who you claim to be")
- **DC-API**: Exchanges credentials ("share verified attributes about yourself")
They can be used together—for example, a website might use **WebAuthn** for authentication and **DC-API** for requesting additional verified attributes.
### What happens if a user doesn't have the required credential?
If the user doesn't have a credential that satisfies the request, the browser will return a `NotFoundError`. Your application should handle this gracefully, potentially:
- Falling back to an alternative verification method
- Redirecting the user to an issuer where they can obtain the required credential
- Providing clear instructions on how to obtain the credential
## Next Steps
- [OpenID4VP](https://docs.walt.id/concepts/data-exchange-protocols/openid4vp.md) - Explore the presentation protocol that DC-API can use under the hood.
- [OpenID4VCI](https://docs.walt.id/concepts/data-exchange-protocols/openid4vci.md) - Learn about the issuance protocol for credentials.
- [How to verify credentials via Digital Credentials API (Community Stack)](https://docs.walt.id/community-stack/verifier2/credential-verification/dc-api.md) - Check out the walt.id Verifier API docs for DC-API verification flows.
- [How to verify credentials via Digital Credentials API (Enterprise Stack)](https://docs.walt.id/enterprise-stack/services/verifier2-service/credential-verification/dc-api.md) - Check out the walt.id Verifier API docs for DC-API verification flows.
### Additional Resources
- [W3C Digital Credentials API Working Draft and its integration with Credential Management and the protocol registry](https://www.w3.org/TR/digital-credentials/)
- [W3C blog announcing the Digital Credentials API and noting early implementations from Google and Apple](https://www.w3.org/blog/2025/w3c-digital-credentials-api-publication-the-next-step-to-privacy-preserving-identities-on-the-web/)
- [Chrome origin trial announcements for the Digital Credentials API on Android and cross-device from desktop Chrome, including usage details and flags](https://developer.chrome.com/blog/digital-credentials-api-origin-trial)
- [WebKit blog introducing Digital Credentials API support in Safari 26 across Apple platforms and describing same-device and cross-device flows](https://webkit.org/blog/17431/online-identity-verification-with-the-digital-credentials-api/)
--------------------------------------------------------------------------------
PAGE: What is the HAIP Profile? The Developer's Guide (2026)
URL: https://docs.walt.id/concepts/data-exchange-protocols/haip
RAW: https://docs.walt.id/concepts/data-exchange-protocols/haip.md
STACK: Concepts (stack-agnostic, applies to both Community and Enterprise Stack)
--------------------------------------------------------------------------------
# OpenID4VC HAIP Profile: A Developer's Guide to High-Assurance Digital Credentials
In the rapidly evolving world of digital identity, interoperability and security are often at odds. Different wallets, issuers, and verifiers implement the same specifications in subtly different ways, leading to fragmentation and security gaps. The **OpenID4VC High Assurance Interoperability Profile (HAIP) v1.0** solves this problem by defining a strict, security-focused profile that ensures all parties in a credential ecosystem speak the same language—and speak it securely.
This guide provides a comprehensive walkthrough of HAIP v1.0: what it is, what it enforces, and why it's becoming the foundation for high-assurance digital identity ecosystems worldwide.
## What is the HAIP Profile? (The Simple Explanation)
**HAIP (High Assurance Interoperability Profile)** is a technical specification published by the OpenID Foundation that defines a strict subset of the OpenID for Verifiable Credentials (OpenID4VC) specifications. Think of it as a "security-hardened configuration" for digital credential systems.
While [OpenID4VCI](https://docs.walt.id/concepts/data-exchange-protocols/openid4vci.md) and [OpenID4VP](https://docs.walt.id/concepts/data-exchange-protocols/openid4vp.md) define flexible protocols with many optional features, HAIP takes these specifications and says: "If you want high security and guaranteed interoperability, here's exactly what you MUST implement."
The profile was approved as a Final Specification by the OpenID Foundation membership on December 24, 2025, with 87 approval votes. It successfully passed interoperability testing in November 2025, achieving **98% passing rates** on credential presentation workflows—demonstrating that the specification delivers on its promise of interoperability.
## TL;DR:
- **HAIP** is a security-focused profile of OpenID4VCI and OpenID4VP
- It mandates specific credential formats: **SD-JWT VC** and **ISO mdoc**
- It requires **FAPI 2.0 security controls**, including DPoP and PKCE
- It enforces **X.509 certificate-based trust** for issuer and verifier authentication
- It's designed for ecosystems requiring **high security and privacy**, including eIDAS 2.0 implementations
## Why Does HAIP Exist? The Problem It Solves
### The Interoperability Challenge
Digital identity specifications like OpenID4VCI and OpenID4VP are intentionally flexible. They support multiple credential formats, various authentication methods, and numerous optional features. This flexibility is valuable for adoption, but it creates a problem: two implementations can both be "compliant" with the specification yet completely unable to communicate with each other.
Consider a scenario where:
- **Wallet A** supports only SD-JWT VC credentials with `jwk` binding
- **Wallet B** supports only ISO mdoc credentials
- **Issuer X** uses unsigned metadata and pre-authorized code flow
- **Issuer Y** requires signed metadata and authorization code flow with PAR
All four implementations might be valid according to the base specifications, but they can't interoperate. This fragmentation undermines the entire value proposition of standardized digital credentials.
### The Security Gap
Beyond interoperability, the base specifications allow configurations that may not meet the security requirements of high-assurance use cases. For example:
- Optional encryption of responses could be skipped
- Weak cryptographic algorithms might be used
- Trust establishment mechanisms could be inconsistent
HAIP addresses both challenges by defining a **mandatory baseline** that ensures both interoperability and security.
## Core Security Goals of HAIP
HAIP v1.0 targets two primary security properties that are essential for high-assurance digital identity:
### 1. Authenticity of Claims
There must be **strong assurance that the claims within a Credential or Presentation are valid and bound to the correct Holder**. This encompasses:
- **Issuance Security**: The process by which credentials are created and delivered must be secure
- **Credential Protection**: Credentials must be protected both at rest (in the wallet) and during presentation
- **Issuer Trust**: Verifiers must have access to trustworthy information about the Issuer to validate credentials
### 2. Holder Authentication
There must be **strong assurance that the Credential is presented by its legitimate Holder** in a given transaction. This involves:
- **Proof of Holder Binding**: Cryptographic proof that the presenter controls the key bound to the credential
- **Claim-based Binding**: Support for additional binding mechanisms built on top of cryptographic binding
These security goals directly address the most critical threats in digital identity systems: credential theft, impersonation, and unauthorized disclosure.
## What HAIP Enforces: The Technical Requirements
HAIP v1.0 profiles four core specifications and defines mandatory requirements for each. Let's examine what the profile enforces across the credential lifecycle.
### Credential Formats: SD-JWT VC and ISO mdoc
HAIP mandates support for at least one of two credential formats:
**IETF SD-JWT VC** (Selective Disclosure JWT Verifiable Credentials):
- Compact serialization MUST be supported
- The `cnf` claim MUST include a JWK for cryptographic holder binding
- Status management via Token Status List is required when using the `status` claim
- X.509 certificate chain MUST be included in the `x5c` JOSE header for issuer key resolution
**ISO mdoc** (Mobile Document as defined in ISO/IEC 18013-5):
- Used for mobile Driver's Licenses (mDL) and similar documents
- DeviceResponse structure for presentations
- MSO revocation mechanisms as defined in ISO/IEC 18013-5
Both formats support selective disclosure, allowing holders to share only the specific claims required for a transaction—a fundamental privacy feature.
### OpenID4VCI Requirements (Credential Issuance)
For credential issuance, HAIP enforces:
**Authorization Flow**:
- Authorization code flow MUST be supported
- PKCE with `S256` challenge method is REQUIRED
- Pushed Authorization Requests (PAR) MUST be used when using the Authorization Endpoint
- DPoP (Demonstrating Proof of Possession) MUST be supported for sender-constrained access tokens
**Wallet Attestation**:
- Wallets MUST authenticate at OAuth2 endpoints using Wallet Attestation
- Attestations MUST NOT be reused across different Issuers (preventing tracking)
- X.509 certificates MUST be used for attestation validation
**Key Attestation**:
- Wallets MUST support key attestations to prove key properties
- The `jwt` proof type with `key_attestation` and `attestation` proof type are supported
- X.509 certificates validate key attestation signatures
**Issuer Metadata**:
- Credential Issuer metadata MUST include a scope for every Credential Configuration
- When higher assurance is required, signed Credential Issuer Metadata MUST be supported
- X.509 certificate-based key resolution for metadata validation
### OpenID4VP Requirements (Credential Presentation)
For credential presentation, HAIP enforces:
**Request Authentication**:
- Verifiers MUST use the `x509_hash` Client Identifier Prefix for signed requests
- X.509 certificates authenticate the Verifier to the Wallet
- Self-signed certificates are NOT allowed
**Query Language**:
- DCQL (Digital Credentials Query Language) MUST be used for credential requests
- Authority Key Identifier (AKI)-based Trusted Authority Query MUST be supported
**Response Encryption**:
- Response encryption is MANDATORY
- ECDH-ES with P-256 curve for key agreement
- A128GCM and A256GCM encryption algorithms
- Ephemeral encryption keys specific to each request
**Same-Device Flow**:
- Verifiers and Wallets MUST support same-device flow
- Redirect back to Verifier is REQUIRED for session binding
- Verifiers MUST reject presentations if redirect is not followed
**W3C Digital Credentials API**:
- Support for browser-based credential presentation via [DC-API](https://docs.walt.id/concepts/data-exchange-protocols/dc-api.md)
- Response mode `dc_api.jwt` MUST be supported
- Unsigned, signed, and multi-signed requests are supported
### Cryptographic Requirements
HAIP mandates specific cryptographic algorithms to ensure consistent security:
**Digital Signatures**:
- ECDSA with P-256 and SHA-256 (`ES256`) MUST be supported at minimum
- COSE algorithm identifiers `-7` or `-9` for mdoc contexts
**Hash Algorithms**:
- SHA-256 MUST be supported for all digest operations
**Key Sizes**:
- Implementations must follow guidance from NIST SP 800-131A, NIST SP 800-57, or BSI TR-02102-1
## HAIP and eIDAS 2.0: The Regulatory Connection
One of the most significant drivers for HAIP adoption is the European Union's eIDAS 2.0 regulation, which mandates that by 2026, every EU member state must offer a European Digital Identity Wallet (EUDI Wallet).
HAIP is explicitly designed to support eIDAS 2.0 implementations. The specification notes:
> "This specification fulfils some, but not all, of the requirements to meet the 'High' Level of Assurance (LoA) as defined in the eIDAS Regulation. While this specification defines features intended for scenarios targeting a high level of security, these features must be combined with additional measures outside of the scope of HAIP to achieve LoA High compliance."
This means HAIP provides the **technical foundation** for eIDAS 2.0 compliance, but ecosystems must layer additional policy and procedural requirements on top.
### Trust Establishment
At the foundation, HAIP uses **X.509 PKI** (Public Key Infrastructure) for trust establishment across all parties:
1. **Issuer Trust**: Credentials include the issuer's X.509 certificate chain in the `x5c` header, allowing verifiers to validate the issuer's identity against trusted root certificates
2. **Verifier Trust**: Presentation requests are signed with X.509 certificates, allowing wallets to verify the verifier's identity before sharing credentials
3. **Wallet Trust**: Wallet Attestations prove the wallet's authenticity to issuers, preventing rogue applications from obtaining credentials
4. **Key Trust**: Key Attestations prove that cryptographic keys are properly protected (e.g., in secure hardware)
### Credential Protection
HAIP ensures credentials are protected throughout their lifecycle:
- **At Issuance**: DPoP tokens prevent token theft; Wallet Attestation ensures only legitimate wallets receive credentials
- **At Rest**: Holder binding ensures credentials are cryptographically bound to keys the holder controls
- **At Presentation**: Response encryption prevents eavesdropping; session binding prevents phishing
## Ecosystem Extension Points
HAIP intentionally leaves certain decisions to ecosystems, recognizing that different contexts have different requirements:
| Extension Point | Options |
|----------------|---------|
| **Flows** | Presentation only, issuance only, or both |
| **Presentation Method** | DC-API, redirects with custom URL schemes, or claimed HTTPS URIs |
| **Credential Format** | SD-JWT VC, ISO mdoc, or both |
| **Issuer Metadata** | Signed or unsigned |
| **Credential Offer Delivery** | QR code, deep link, or other mechanisms |
| **Attestation Formats** | HAIP-defined or ecosystem-specific |
| **X.509 Profiles** | Ecosystem-defined certificate policies |
| **Additional Crypto** | Ecosystem may mandate additional algorithms |
This flexibility allows HAIP to serve as a foundation that ecosystems can build upon while maintaining baseline interoperability.
## Practical Implementation Considerations
### Custom URL Schemes
HAIP defines two custom URL schemes for wallet invocation:
- `haip-vci://` - For credential issuance flows
- `haip-vp://` - For credential presentation flows
These schemes allow verifiers and issuers to invoke HAIP-compliant wallets directly.
## Real-World HAIP Use Cases
### Government Digital Identity
A national government implementing digital identity cards can use HAIP to ensure:
- All approved wallets meet security requirements via Wallet Attestation
- Citizens can present credentials to any compliant verifier
- Credentials cannot be stolen or impersonated due to holder binding
- Privacy is protected through selective disclosure
### Financial Services KYC
Banks implementing digital KYC can use HAIP to:
- Receive cryptographically verified identity attributes
- Trust that credentials come from authorized issuers via X.509 trust chains
- Ensure presentations are encrypted and session-bound
- Meet regulatory requirements for identity verification
### Cross-Border Credential Recognition
International credential recognition (e.g., EU-US driver's license verification) can use HAIP to:
- Establish mutual trust through X.509 certificate hierarchies
- Ensure consistent security regardless of issuing country
- Support both SD-JWT VC and ISO mdoc formats for flexibility
## HAIP FAQ
### Does HAIP replace OpenID4VCI and OpenID4VP?
No. HAIP is a **profile** of these specifications, not a replacement. It defines which features of OpenID4VCI and OpenID4VP must be implemented and how they must be configured for high-assurance use cases.
### Is HAIP required for eIDAS 2.0 compliance?
HAIP provides a strong technical foundation for eIDAS 2.0, but it alone is not sufficient for full compliance. Additional policy, procedural, and potentially technical measures are required to achieve "High" Level of Assurance under eIDAS.
### Can I use HAIP for lower-assurance use cases?
Yes. The specification notes that while it's aimed at high-assurance use cases, it can also be used for lower-assurance scenarios. The security features don't hurt lower-assurance use cases—they just provide more protection than strictly necessary.
### What if my ecosystem needs features not in HAIP?
HAIP is designed to be extended. Ecosystems can add requirements on top of HAIP while maintaining baseline interoperability with other HAIP-compliant implementations.
### How does HAIP handle offline scenarios?
HAIP v1.0 focuses on online scenarios. Offline presentation (e.g., over BLE) is explicitly listed as out of scope for this version but may be addressed in future versions.
## Next Steps
- [OpenID4VCI](https://docs.walt.id/concepts/data-exchange-protocols/openid4vci.md) - Understand the credential issuance protocol that HAIP profiles
- [OpenID4VP](https://docs.walt.id/concepts/data-exchange-protocols/openid4vp.md) - Learn about the presentation protocol and DCQL queries
- [DC-API](https://docs.walt.id/concepts/data-exchange-protocols/dc-api.md) - Explore browser-based credential presentation supported by HAIP
- [SD-JWT VC](https://docs.walt.id/concepts/digital-credentials/sd-jwt-vc.md) - Deep dive into the SD-JWT VC credential format
- [ISO mdoc](https://docs.walt.id/concepts/digital-credentials/mdoc-mdl-iso.md) - Learn about the ISO mobile document format
### Additional Resources
- [HAIP v1.0 Final Specification](https://openid.net/specs/openid4vc-high-assurance-interoperability-profile-1_0.html) - The official specification document
- [OpenID Foundation HAIP Announcement](https://openid.net/openid4vc-high-assurance-interoperability-profile-haip-1-0-final-specification-approved/) - Official announcement of HAIP 1.0 approval
- [HAIP GitHub Repository](https://github.com/openid/oid4vc-haip) - Source repository for the specification
- [OIDF Interoperability Testing Results](https://openid.net/haip-1-0-openid4vp-1-0-achieve-98-in-oidf-interop-testing/) - Results from the November 2025 interop event
--------------------------------------------------------------------------------
PAGE: DCQL: The Developer's Guide to the Digital Credentials Query Language
URL: https://docs.walt.id/concepts/data-exchange-protocols/dcql
RAW: https://docs.walt.id/concepts/data-exchange-protocols/dcql.md
STACK: Concepts (stack-agnostic, applies to both Community and Enterprise Stack)
--------------------------------------------------------------------------------
# DCQL: The Digital Credentials Query Language
**DCQL (Digital Credentials Query Language)** is the mechanism verifiers use inside an [OpenID4VP](https://docs.walt.id/concepts/data-exchange-protocols/openid4vp.md) request to express exactly which credentials a wallet should present, and in what combinations. It was introduced in OpenID4VP Draft 22 (October 2024) and is part of the final `v1.0` specification, replacing the older Presentation Exchange / Presentation Definition approach.
This guide explains how DCQL is structured, what each part does, and where the common implementation mistakes occur.
## The Mental Model
DCQL is the verifier's answer to one specific question: "out of the credentials the wallet holds, which ones — and in what combinations — can satisfy what I need from this transaction?"
There are only ever two layers of decision:
1. **A list of credential queries** — "I want a credential that looks like X."
2. **(Optional) logic over those queries** — "I need (A and B), or (A and C), but never just A on its own."
That is the entire shape of the language. Every detail in the specification hangs off those two ideas.
**Compared to Presentation Exchange** (which DCQL replaces), this is dramatically simpler. Presentation Exchange required JSON Schema-like constraint descriptors with JSONPath fragments and nested filter blocks. DCQL replaces that constraint-language sprawl with named fields, a single claim path syntax, and explicit AND/OR operators.
## The Top-Level Shape
Every DCQL query is an object with two possible keys:
```json
{
"credentials": [ ... ],
"credential_sets": [ ... ]
}
```
- `credentials` is **required** — your list of "I want a credential like X" entries.
- `credential_sets` is **optional** — AND/OR logic across those entries.
If you only provide `credentials` and no `credential_sets`, the wallet must satisfy every entry — an implicit AND. You add `credential_sets` only when you need OR, optionality, or alternative combinations.
**Note:**
This is where most implementations over-engineer. If the use case is "give me a PID and an employment credential," do not reach for `credential_sets`. Two entries in `credentials` already expresses that.
## Anatomy of a Credential Query
Each entry in `credentials` follows this structure:
```json
{
"id": "digitalID",
"format": "dc+sd-jwt",
"meta": { "vct_values": ["https://issuer.demo.walt.id/draft13/digitalID"] },
"claims": [ ... ],
"claim_sets": [ ... ],
"trusted_authorities": [ ... ],
"require_cryptographic_holder_binding": true,
"multiple": false
}
```
The required fields are `id`, `format`, and `meta`. Everything else is optional and each unlocks one specific capability.
| Field | Required | Description |
|---|---|---|
| `id` | Yes | Your handle for this query. The wallet keys its `vp_token` response by these ids. Must be unique within the request. |
| `format` | Yes | Credential format identifier: `dc+sd-jwt`, `mso_mdoc`, `jwt_vc_json`. |
| `meta` | Yes | Format-specific filter. The structure differs per format — see the [Format-Specific Gotchas](#format-specific-gotchas) section below. |
| `claims` | No | Limits which claims the wallet should disclose. Omitting this is not the same as requesting everything — see below. |
| `claim_sets` | No | Alternative combinations of claims within this single credential. |
| `trusted_authorities` | No | Restricts matches to credentials from specific trust anchors. |
| `require_cryptographic_holder_binding` | No | Defaults to `true`. Set `false` only when a key binding proof is genuinely not needed. |
| `multiple` | No | Defaults to `false`. Set `true` to allow the wallet to return more than one matching credential of the same type. |
The minimum viable query is exactly the three required fields and nothing else. The wallet returns the entire credential.
## Selective Disclosure: Claims and the Claims Path Pointer
Add a `claims` array to tell the wallet which fields you actually need:
```json
"claims": [
{ "path": ["given_name"] },
{ "path": ["family_name"] },
{ "path": ["age_over_18"] }
]
```
### The `path` syntax
`path` is an array, not a string. Each segment is one of:
- A **string** — an object key (e.g. `"address"`)
- An **integer** — an array index
- **`null`** — matches all elements at that array level
So `["address", "locality"]` means "the `locality` field inside the `address` object." This is the Claims Path Pointer defined in §7 of the spec. It is not JSONPath, it is not JSON Pointer — it is its own syntax and you will trip on it if you assume otherwise.
### Format affects path semantics
For JSON-based credentials (`dc+sd-jwt`, `jwt_vc_json`), paths walk the credential's claim structure as you'd expect.
For **mdoc**, the rule is explicit: the first segment is the **namespace**, the second is the **element identifier**, and that is the entire path — mdoc paths are always exactly two segments long. So for an mDL `given_name`, you write:
```json
{ "path": ["org.iso.18013.5.1", "given_name"] }
```
### What "no claims" means
If `claims` is absent, you are requesting no selectively-disclosable claims. For an SD-JWT VC, the wallet returns only the always-disclosed envelope and the key binding JWT. Omitting `claims` does **not** mean "give me everything."
## Value Matching with `values`
You can constrain a claim to a specific value:
```json
{ "path": ["nationality"], "values": ["AT", "DE"] }
```
The wallet should return the claim only if its type and value match at least one of the listed values. Strings, integers, and booleans are accepted.
**Info:**
Value matching leaks information about claim non-possession. If the wallet refuses because the nationality is not in the list, the verifier learns the holder's nationality is something else.
For mdoc credentials, CBOR values must be JSON-converted (per RFC 8949 §6.1) before matching. That conversion is not always lossless — date types, byte strings, and similar types can behave unexpectedly. Do not rely on value matching for mdoc data types beyond the simple primitives.
## `claim_sets` — Alternative Claim Combinations Within One Credential
`claim_sets` answers the question: "which combinations of claims would satisfy me from this single credential?"
```json
"claims": [
{ "id": "gn", "path": ["given_name"] },
{ "id": "fn", "path": ["family_name"] },
{ "id": "dob", "path": ["birth_date"] },
{ "id": "over18", "path": ["age_over_18"] }
],
"claim_sets": [
["gn", "fn", "over18"],
["gn", "fn", "dob"]
]
```
Read this as: "I would accept either {given_name, family_name, age_over_18} or {given_name, family_name, birth_date}." The order of the sets expresses the verifier's **preference** — the wallet should return the first option it can satisfy, but is not strictly required to. Put the set you most prefer first: if you want to signal a preference for minimum disclosure, list the smaller or more privacy-preserving set earlier.
Once `claim_sets` is used, the `id` field on each entry in `claims` becomes mandatory — the sets reference claims by those ids.
`claim_sets` is for **alternative claim combinations within one credential**. It is not a mechanism for making individual claims optional. DCQL has no concept of an optional claim by design.
If you need to express an optional claim, there are two patterns:
1. **Within a single credential query** — use `claim_sets` with the "richer" set first and the minimal set as a fallback. The wallet will return the richer set if it can satisfy it, and fall back to the minimal one if not. This is not true optionality (the user has no choice), but it gracefully handles credentials that may or may not contain a given claim.
2. **Across two credential queries** — model the optional claim as a separate credential query inside an optional `credential_set` (see the next section). This is the more explicit pattern and ties the optionality to a declared purpose.
## Multiple Credentials — Implicit AND
Two entries in `credentials` with no `credential_sets`:
```json
{
"credentials": [
{ "id": "pid", "format": "dc+sd-jwt", "meta": { ... }, "claims": [ ... ] },
{ "id": "employment", "format": "dc+sd-jwt", "meta": { ... }, "claims": [ ... ] }
]
}
```
Both must be satisfied. The `vp_token` in the response will be an object with two keys — `pid` and `employment` — each holding a presentation. The keys are exactly the `id` values you chose in the query, so pick readable names.
By default, each key maps to a single presentation. If you set `multiple: true` on a credential query, the wallet may return an array of presentations for that key instead.
## `credential_sets` — Explicit Logic Across Credentials
`credential_sets` is where you express OR, optionality, and alternative credential combinations. Each set has two properties:
```json
"credential_sets": [
{
"options": [ ["pid"], ["mdl"] ],
"required": true
}
]
```
- `options` is an array of arrays. Each inner array is one acceptable combination of credential ids. The wallet must satisfy at least one inner array in full.
- `[["pid"], ["mdl"]]` reads as "(pid) OR (mdl)."
- To express "(pid AND id_card) OR (mdl)" you write `[["pid", "id_card"], ["mdl"]]`.
- `required` defaults to `true`. Setting it to `false` makes the entire set optional.
**Modelling optional credentials** — "give me a PID, and if you happen to have a diploma, include it":
```json
"credential_sets": [
{ "options": [["pid"]] },
{ "required": false, "options": [["diploma"]] }
]
```
**Note:**
When you use `credential_sets`, the sets become the sole source of truth for what is required. The `credentials` array defines the universe of possible credential shapes — their format, claims, and constraints — but it is the sets that determine what actually gets requested. Entries in `credentials` that are not referenced by any set are inert: the wallet has no instruction to return them.
## Trust Framework Filtering with `trusted_authorities`
For EUDI and other regulated ecosystems, `trusted_authorities` is where policy constraints end up:
```json
"trusted_authorities": [
{ "type": "etsi_tl", "values": ["https://eu-lotl.example/lotl.xml"] }
]
```
Three authority types are defined:
| Type | Description |
|---|---|
| `aki` | Authority Key Identifier, base64url-encoded. The values are the key identifiers of the trusted issuer CA certificates (their Subject Key Identifier). A credential matches if its certificate chain contains a certificate whose AKI extension's keyIdentifier component references one of those trusted CAs. Use when trusting a specific issuer CA key directly. |
| `etsi_tl` | An ETSI Trusted List URL (TS 119 612). The wallet walks the LOTL/TL cascade and matches if any cert in the credential's chain appears on the referenced trust list. The natural fit for QTSP-issued credentials under eIDAS. |
| `openid_federation` | An Entity Identifier. The wallet must be able to construct a valid trust chain from the credential's issuer up to that entity. |
**Note:**
The spec is explicit: `trusted_authorities` is a **data-minimization hint to the wallet**, not a verifier-side trust check. The verifier is still responsible for independently verifying issuer trust during validation. Do not treat this field as a substitute for that check.
## Holder Binding
```json
"require_cryptographic_holder_binding": false
```
This field defaults to `true`. Set it to `false` only when the credential genuinely does not require cryptographic key binding — cinema tickets, biometric-bound credentials (mDL portrait), or claims-bound diplomas.
There are real consequences when you set it to `false`:
- The Verifiable Presentation will not include a key binding proof, making the credential replayable.
- Nonce-based replay protection no longer applies. The spec requires `state` in the Authorization Request with at least 128 bits of entropy as a substitute.
## Format-Specific Gotchas — The `meta` Block
The `meta` object has a different shape for each credential format.
### SD-JWT VC (`dc+sd-jwt`)
```json
"meta": { "vct_values": ["https://issuer.demo.walt.id/draft13/EducationalID"] }
```
`vct_values` is an **array of strings**. The wallet matches if the credential's `vct` claim equals exactly one of the values listed.
### mdoc (`mso_mdoc`)
```json
"meta": { "doctype_value": "org.iso.18013.5.1.mDL" }
```
`doctype_value` is a **single string**, not an array. The wallet matches against the credential's MSO doctype.
### W3C VCDM (`jwt_vc_json`)
```json
"meta": {
"type_values": [
["VerifiableCredential", "UniversityDegreeCredential"]
]
}
```
`type_values` is an **array of arrays of strings**. The outer array is OR; the inner array is AND-over-types. So `[["VerifiableCredential", "UniversityDegreeCredential"]]` means "the credential's `type` array contains both of these values." This is easy to get wrong — a single-level array is a common mistake.
## Request credentials using DCQL with walt.id
The Community Stack verifier API accepts DCQL queries directly inside an OpenID4VP presentation request. Pass your `dcql_query` object in the request body when initiating a verification session.
- [Verify a Credential (OID4VP)](https://docs.walt.id/community-stack/verifier/credential-verification/vc-oid4vc.md) — Initiate a presentation request with a `dcql_query` using the community verifier API.
- [Verify an SD-JWT VC](https://docs.walt.id/community-stack/verifier/credential-verification/sd-jwt-vc-oid4vc.md) — SD-JWT VC-specific verification flow showing how DCQL claim paths map to disclosed fields.
## Related concepts
- [OpenID4VP](https://docs.walt.id/concepts/data-exchange-protocols/openid4vp.md) — The presentation protocol that carries the `dcql_query` inside an Authorization Request.
- [DC API](https://docs.walt.id/concepts/data-exchange-protocols/dc-api.md) — Browser-native credential presentation, also using `dcql_query` as its query format.
- [HAIP](https://docs.walt.id/concepts/data-exchange-protocols/haip.md) — The high-assurance profile that also uses DCQL and which is imporant for eIDAS 2.q
--------------------------------------------------------------------------------
PAGE: Trust Systems
URL: https://docs.walt.id/concepts/trust-systems
RAW: https://docs.walt.id/concepts/trust-systems.md
STACK: Concepts (stack-agnostic, applies to both Community and Enterprise Stack)
--------------------------------------------------------------------------------
# Trust Systems
Trust systems are the foundational infrastructure that enables secure, verifiable interactions in digital identity ecosystems. They answer the critical question: *"How do I know I can trust this entity?"*
## Why Trust Systems Matter
In decentralized identity ecosystems, parties that have never met must be able to:
- **Verify credentials** – Confirm that a credential was issued by a legitimate authority
- **Authenticate wallets** – Ensure wallet applications are genuine and secure
- **Establish authority** – Determine which entities are authorized to issue specific credentials
- **Enable interoperability** – Support cross-border and cross-jurisdictional recognition
Trust systems provide the technical and governance infrastructure to make these verifications possible at scale.
## Core Concepts
### Trust Anchors
A trust anchor is a cryptographic public key (and associated metadata) that serves as the root of trust for verifying other entities. Trust anchors are typically:
- Published in trusted lists or registries
- Managed by authoritative bodies (governments, standards organizations)
- Used to verify certificate chains and credential signatures
### Trust Lists
Trust lists are authoritative, machine-readable registries of trusted entities. They enable automated verification by providing:
- Public keys and identifiers for trusted entities
- Status information (active, suspended, revoked)
- Metadata about entity capabilities and authorizations
### Attestations
Attestations are cryptographic proofs that vouch for specific properties of an entity, such as:
- Wallet application integrity
- Key storage security
- Compliance with standards or regulations
## Trust Systems in This Section
This section covers three major trust system approaches used in digital identity:
### [EU Trust Lists](https://docs.walt.id/concepts/trust-systems/eu-trust-lists.md)
The European Union's standardized trust infrastructure for the EUDI Wallet ecosystem, based on ETSI standards and mandated by eIDAS 2.0. Covers:
- National Trusted Lists and the EU List of Trusted Lists (LoTL)
- Trust anchors for Wallet Providers, PID Providers, and Attestation Providers
- ETSI TS 119 612 format and implementation
### [Trust in ISO mDL: VICALs and IACAs](https://docs.walt.id/concepts/trust-systems/iso-trust-vicals.md)
The trust infrastructure defined by ISO/IEC 18013-5 for mobile driver's licenses and mobile documents. Covers:
- Issuing Authority Certificate Authorities (IACAs)
- Verified Issuer Certificate Authority Lists (VICALs)
- Reader authentication mechanisms
- Regional implementations (AAMVA, Austroads)
### [Wallet Attestations](https://docs.walt.id/concepts/trust-systems/wallet-attestations.md)
Cryptographic proofs that establish trust in digital identity wallets. Covers:
- Wallet Instance Attestations (WIA)
- Key Attestations and Wallet Trust Evidence (WTE)
- Integration with OpenID4VCI
- Platform-specific attestation mechanisms
## Comparison of Trust Approaches
| Aspect | EU Trust Lists | VICAL (ISO 18013-5) | Wallet Attestations |
|--------|---------------|---------------------|---------------------|
| **Primary Purpose** | Ecosystem-wide trust | mDL issuer verification | Wallet integrity |
| **Scope** | EU Member States | Regional (per VICAL) | Per wallet instance |
| **Format** | XML (ETSI) | CBOR/COSE | JWT/COSE |
| **Governance** | European Commission | Regional bodies | Wallet Providers |
| **Update Frequency** | Periodic publication | Versioned refresh | Per-instance lifecycle |
## Further Reading
- [ID Ecosystems](https://docs.walt.id/concepts/id-ecosystems.md) – Overview of identity ecosystem components
- [Data Exchange Protocols](https://docs.walt.id/concepts/data-exchange-protocols.md) – How credentials are exchanged
- [Credential Lifecycle](../6.credential-lifecycle/) – Issuance, verification, and revocation
--------------------------------------------------------------------------------
PAGE: EU Trust Lists
URL: https://docs.walt.id/concepts/trust-systems/eu-trust-lists
RAW: https://docs.walt.id/concepts/trust-systems/eu-trust-lists.md
STACK: Concepts (stack-agnostic, applies to both Community and Enterprise Stack)
--------------------------------------------------------------------------------
# EU Trust Lists
EU Trust Lists are the backbone of trust infrastructure in the European Digital Identity ecosystem. They provide a standardized, machine-readable mechanism for establishing and verifying the trusted status of entities participating in digital identity transactions across the European Union.
## What Are EU Trust Lists?
EU Trust Lists are cryptographically signed, authoritative registries that contain trust anchors (public keys and identifiers) for entities authorized to participate in the European Digital Identity ecosystem. They enable wallets, relying parties, and other ecosystem components to automatically verify that an entity is supervised and trusted.
The trust list infrastructure operates at two levels:
| Level | Description | Managed By |
|-------|-------------|------------|
| **National Trusted Lists** | Country-specific lists of trusted entities | Individual Member States |
| **EU List of Trusted Lists (LoTL)** | Aggregated list linking all national trusted lists | European Commission |
The source of trust for the LoTL is the Official Journal of the European Union (OJEU), providing a legally binding foundation for the entire trust infrastructure.
## Entities Covered by Trust Lists
Under the eIDAS 2.0 framework and the EUDI Wallet Architecture Reference Framework (ARF), trust lists contain trust anchors for:
- **Wallet Providers** – Organizations authorized to provide EUDI Wallet applications
- **PID Providers** – Issuers of Personal Identification Data (national identity credentials)
- **QEAA Providers** – Qualified Electronic Attestation of Attributes providers
- **PuB-EAA Providers** – Public Electronic Attestation of Attributes providers
- **Access Certificate Providers** – Issuers of certificates for relying party authentication
- **Registration Certificate Providers** – Issuers of certificates for entity registration
### Verification Process
When a wallet or relying party needs to verify an entity's trusted status:
1. **Retrieve the LoTL** – Download the EU List of Trusted Lists from the European Commission
2. **Locate the National TL** – Find the relevant Member State's trusted list
3. **Verify the Entity** – Check if the entity's trust anchor appears in the national list
4. **Validate Signatures** – Cryptographically verify the chain of signatures from the entity back to the LoTL
## Technical Standards
EU Trust Lists are governed by ETSI (European Telecommunications Standards Institute) specifications:
| Standard | Purpose |
|----------|---------|
| **ETSI TS 119 612** | Defines the format and structure of trusted lists |
| **ETSI TS 119 602** | Specifies trust list policies for the EUDI Wallet ecosystem |
| **ETSI EN 319 412** | Certificate profiles for trust service providers |
### Trust List Format
Trust lists are published in XML format with the following key components:
- **Scheme Information** – Metadata about the trust list (version, operator, territory)
- **Trust Service Provider List** – Entries for each trusted entity
- **Service Information** – Details about each trust service (type, status, certificates)
- **Digital Signature** – Cryptographic signature ensuring integrity and authenticity
## eIDAS 2.0 and Trust Lists
The revised eIDAS Regulation (EU) 2024/1183 significantly expands the role of trust lists:
### New Trust Service Types
eIDAS 2.0 introduces additional qualified trust services that must be registered in trust lists:
- Qualified electronic attestation of attributes (QEAA)
- Qualified electronic archiving services
- Qualified electronic ledger services
- Management of remote qualified electronic signature/seal creation devices
### Timeline for Implementation
| Date | Milestone |
|------|-----------|
| **April 2026** | Trust List v6 (TLv6) format enforcement |
| **April 2026** | Implementing Decision (EU) 2025/2164 applies |
| **2026-2027** | Full EUDI Wallet ecosystem deployment |
**Error:**
**Migration Required**: All stakeholders must ensure system compatibility with the new TLv6 format by April 28, 2026, to avoid validation failures.
## Benefits of EU Trust Lists
### For Relying Parties
- **Simplified Trust Decisions** – Single source of truth for entity verification
- **Cross-Border Recognition** – Automatic trust in entities from other Member States
- **Reduced Compliance Burden** – No need to maintain individual trust relationships
### For Trust Service Providers
- **Market Access** – Inclusion enables cross-border service provision
- **Legal Recognition** – Qualified status provides legal certainty
- **Interoperability** – Standardized format ensures compatibility
### For Citizens
- **Security Assurance** – Only verified entities can participate in the ecosystem
- **Privacy Protection** – Trusted entities must comply with data protection requirements
- **Cross-Border Usability** – Credentials work seamlessly across the EU
## Comparison with Other Trust Frameworks
| Aspect | EU Trust Lists | ICAO PKD | VICAL (ISO 18013-5) |
|--------|---------------|----------|---------------------|
| **Scope** | EU Member States | Global (passports) | Regional (mDL) |
| **Governance** | European Commission | ICAO | National/Regional bodies |
| **Primary Use** | Digital identity ecosystem | Travel documents | Mobile driver's licenses |
| **Format** | XML (ETSI TS 119 612) | LDAP/X.509 | CBOR/COSE |
## Working with EU Trust Lists
### Accessing Trust Lists
Trust lists are publicly available through:
- **EU Digital Building Blocks Portal** – Official repository for all national trust lists
- **National Supervisory Bodies** – Direct access to country-specific lists
- **EUDI Wallet Reference Implementation** – Test environment for developers
## Further Reading
- [EU Trusted Lists Portal](https://digital-strategy.ec.europa.eu/en/policies/eu-trusted-lists)
- [ETSI Trust Service Providers](https://portal.etsi.org/TB-SiteMap/ESI/Trust-Service-Providers)
- [EUDI Wallet Architecture Reference Framework](https://eu-digital-identity-wallet.github.io/eudi-doc-architecture-and-reference-framework/)
- [eIDAS Regulation (EU) 2024/1183](https://eur-lex.europa.eu/eli/reg/2024/1183)
--------------------------------------------------------------------------------
PAGE: Trust in ISO mDL: VICALs and IACAs
URL: https://docs.walt.id/concepts/trust-systems/iso-trust-vicals
RAW: https://docs.walt.id/concepts/trust-systems/iso-trust-vicals.md
STACK: Concepts (stack-agnostic, applies to both Community and Enterprise Stack)
--------------------------------------------------------------------------------
# Trust in ISO mDL: VICALs and IACAs
The ISO/IEC 18013-5 standard for mobile driver's licenses (mDLs) defines a comprehensive trust infrastructure that enables secure, cross-jurisdictional verification of digital credentials. At the heart of this infrastructure are Verified Issuer Certificate Authority Lists (VICALs) and Issuing Authority Certificate Authorities (IACAs).
## The Challenge of Distributed Trust
In digital identity ecosystems where multiple authorities independently issue credentials, establishing trust becomes complex. Consider a scenario where:
- Each state or province operates its own driver's license issuing authority
- Each authority maintains its own certificate infrastructure
- Verifiers (law enforcement, businesses, service providers) need to validate credentials from any jurisdiction
Without a centralized trust mechanism, verifiers would need to establish and maintain individual trust relationships with every issuing authority—an approach that doesn't scale.
## Issuing Authority Certificate Authority (IACA)
An IACA is the root X.509 certificate operated by an issuing authority for mobile documents. It forms the foundation of the trust chain for mDL credentials.
### IACA Characteristics
| Property | Description |
|----------|-------------|
| **Validity Period** | Up to 20 years |
| **Certificate Type** | Self-signed X.509 |
| **Purpose** | Signs Document Signer Certificates (DSCs) |
| **Verification** | Verified against its own public key |
### Trust Chain Structure
```
┌─────────────────────────────────────────────────────────────────┐
│ IACA (Root Certificate) │
│ Validity: Up to 20 years │
└─────────────────────────────────────────────────────────────────┘
│
│ Signs
▼
┌─────────────────────────────────────────────────────────────────┐
│ Document Signer Certificate (DSC) │
│ Validity: Shorter than IACA │
└─────────────────────────────────────────────────────────────────┘
│
│ Signs
▼
┌─────────────────────────────────────────────────────────────────┐
│ Mobile Security Object (MSO) │
│ Embedded in mDoc credential │
└─────────────────────────────────────────────────────────────────┘
```
When verifying an mDL, the verifier must:
1. Extract the Mobile Security Object (MSO) from the mDoc
2. Verify the MSO signature against the Document Signer Certificate
3. Verify the DSC signature against the IACA
4. Confirm the IACA is trusted (via VICAL or direct trust)
## Verified Issuer Certificate Authority List (VICAL)
A VICAL is a cryptographically signed, authoritative list of trusted IACAs that consolidates trust relationships into a single, manageable artifact.
### How VICAL Works
```
┌─────────────────────────────────────────────────────────────────┐
│ VICAL Provider │
│ (Trust Anchor) │
└─────────────────────────────────────────────────────────────────┘
│
│ Cryptographically signs
▼
┌─────────────────────────────────────────────────────────────────┐
│ VICAL │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ IACA Entry │ │ IACA Entry │ │ IACA Entry │ ... │
│ │ (State A) │ │ (State B) │ │ (State C) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
│ Distributed to
▼
┌─────────────────────────────────────────────────────────────────┐
│ Relying Parties │
│ (Law enforcement, businesses, services) │
└─────────────────────────────────────────────────────────────────┘
```
### VICAL Process Flow
| Step | Description |
|------|-------------|
| **Collection** | VICAL provider gathers public keys and metadata from recognized IACAs |
| **Validation** | Each IACA is validated and linked to specific credential types |
| **Signing** | The complete list is digitally signed by the VICAL provider |
| **Distribution** | Verifiers retrieve the signed VICAL via download or API |
| **Verification** | Verifiers check credentials against the VICAL |
### VICAL Technical Structure
VICALs use a signed, machine-readable format as defined in ISO/IEC 18013-5:
```
VICAL Structure (COSE_Sign):
├── Protected Header
│ └── Algorithm identifier
├── Unprotected Header
│ └── X.509 certificate chain
├── Payload
│ ├── Version identifier
│ ├── Provider name
│ ├── Issuance timestamp
│ ├── Expiration timestamp
│ ├── List identifier
│ └── IACA Records[]
│ ├── Issuer information
│ ├── Credential type(s) (docType)
│ ├── IACA public key
│ ├── Validity periods
│ └── Digital signatures
└── Signature
```
## Reader Authentication
ISO 18013-5 also defines mechanisms for authenticating verifiers (readers) to wallets, ensuring that sensitive credential data is only shared with legitimate parties.
### Reader Authentication Certificate
When a verifier requests credential data, they can provide a reader authentication certificate that proves their identity to the wallet. This enables:
- **Selective Disclosure** – Wallets can make informed decisions about what data to share
- **Audit Trails** – Credential holders can see who requested their data
- **Access Control** – Issuers can restrict which verifiers can access certain data elements
### ReaderAuth Structure
The `ReaderAuth` mechanism defined in ISO/IEC 18013-5 Section 9.1.4 includes:
- Reader certificate (attached via `x5chain` header per RFC 9360)
- Cryptographic signature proving possession of the private key
- Session transcript binding to prevent replay attacks
## Regional Implementations
### North America: AAMVA Digital Trust Service
The American Association of Motor Vehicle Administrators (AAMVA) operates the mDL Digital Trust Service (DTS) for North America:
| Component | Description |
|-----------|-------------|
| **VICAL** | Maintained list of all state-level IACAs |
| **Access** | Free download for relying parties |
| **Updates** | Regular refresh with versioning |
| **Compliance** | Adheres to ISO 18013-5 and AAMVA Implementation Guidelines |
### Australia: Austroads
Austroads coordinates the VICAL for Australian digital credentials, enabling cross-state verification of mobile driver's licenses.
### Alternative Approaches
Some regions have adopted different trust models:
- **New Zealand** – No centralized VICAL; exploring alternative frameworks
- **South Korea** – Blockchain-based trust management for mDLs
- **European Union** – EUDI Wallet ecosystem uses EU Trust Lists (ETSI-based)
## Benefits of VICAL-Based Trust
### For Verifiers
- **Simplified Integration** – Single trust relationship with VICAL provider
- **Cross-Jurisdictional Support** – Verify credentials from any participating issuer
- **Automatic Updates** – Trust changes propagate through VICAL refresh
### For Issuing Authorities
- **Standardized Onboarding** – Clear process for VICAL inclusion
- **Broad Acceptance** – Credentials recognized by all VICAL-trusting verifiers
- **Key Rotation Support** – VICAL versioning handles certificate updates
### For Credential Holders
- **Wider Acceptance** – mDL works across jurisdictions
- **Privacy Protection** – Reader authentication enables informed consent
- **Interoperability** – Standard format ensures compatibility
## Comparison with Other Trust Models
| Aspect | VICAL (ISO 18013-5) | EU Trust Lists | OpenID Federation |
|--------|---------------------|----------------|-------------------|
| **Format** | CBOR/COSE | XML (ETSI) | JSON/JWT |
| **Scope** | Regional (mDL) | EU-wide | Global |
| **Update Model** | Versioned refresh | Periodic publication | Dynamic metadata |
| **Primary Use** | Mobile documents | Digital identity | Federated identity |
## Implementation Considerations
### For Verifier Applications
1. **VICAL Caching** – Cache the VICAL locally with appropriate refresh intervals
2. **Offline Support** – Ensure verification works without network connectivity
3. **Version Management** – Handle VICAL updates gracefully
4. **Certificate Validation** – Implement full chain validation including revocation checks
### For Issuing Authorities
1. **IACA Management** – Plan for certificate lifecycle (20-year validity)
2. **DSC Rotation** – Implement regular Document Signer Certificate rotation
3. **VICAL Participation** – Coordinate with regional VICAL providers
4. **Compliance** – Adhere to ISO 18013-5 and regional implementation guidelines
## Further Reading
- [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html) – Personal identification — ISO-compliant driving licence — Part 5: Mobile driving licence (mDL) application
- [AAMVA mDL Digital Trust Service](https://www.aamva.org/identity/mobile-driver-license-digital-trust-service)
- [AAMVA VICAL Download](https://vical.dts.aamva.org/)
- [ISO/IEC 18013-7](https://www.iso.org/standard/82772.html) – Mobile driving licence (mDL) add-on functions
--------------------------------------------------------------------------------
PAGE: Wallet Attestations
URL: https://docs.walt.id/concepts/trust-systems/wallet-attestations
RAW: https://docs.walt.id/concepts/trust-systems/wallet-attestations.md
STACK: Concepts (stack-agnostic, applies to both Community and Enterprise Stack)
--------------------------------------------------------------------------------
# Wallet Attestations
Wallet attestations are cryptographic proofs that establish trust in digital identity wallets. They enable credential issuers and relying parties to verify that they are communicating with legitimate, secure wallet applications before sharing sensitive identity data.
## Why Wallet Attestations Matter
In a digital identity ecosystem, trust flows in multiple directions:
- **Issuers** need assurance that credentials are being issued to secure, legitimate wallets
- **Verifiers (Relying Parties)** need confidence that presented credentials come from trustworthy wallet applications
- **Users** need protection against malicious apps impersonating legitimate wallets
Wallet attestations address these requirements by providing cryptographic proof of a wallet's integrity, security properties, and authorization status.
## OAuth 2.0 Attestation-Based Client Authentication
The foundation for wallet attestations in modern digital identity systems is the **OAuth 2.0 Attestation-Based Client Authentication** specification (IETF draft-ietf-oauth-attestation-based-client-auth). This standard extends OAuth 2.0 to enable traditionally "public" clients (like mobile wallet apps) to authenticate with authorization servers.
### The Problem with Traditional OAuth Clients
In classic OAuth 2.0, clients are categorized as:
| Client Type | Description | Authentication |
|-------------|-------------|----------------|
| **Confidential** | Server-side apps that can securely store secrets | Client secret or private key |
| **Public** | Mobile/browser apps that cannot securely store secrets | No authentication possible |
Digital identity wallets are inherently public clients—they run on user devices where secrets cannot be reliably protected. Yet issuers need strong assurance about which wallet is requesting credentials.
### The Attestation Solution
Attestation-based client authentication solves this by introducing a trusted third party (the Wallet Provider) who vouches for the wallet instance:
```
┌─────────────────────────────────────────────────────────────────┐
│ Wallet Provider │
│ (Trusted Authority) │
└─────────────────────────────────────────────────────────────────┘
│ │
│ (1) Validates wallet │ (3) Issues Client
│ integrity & security │ Attestation JWT
│ │
▼ ▼
┌─────────────────────────────────────────────────────────────────┐
│ Wallet Instance │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ (2) Generates key pair in secure hardware │ │
│ │ (4) Creates Proof of Possession (PoP) │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
│ (5) Client Attestation JWT + PoP
▼
┌─────────────────────────────────────────────────────────────────┐
│ Issuer / Verifier │
│ (Authorization Server) │
└─────────────────────────────────────────────────────────────────┘
```
### Two-Part Authentication
The attestation consists of two JWTs sent together, separated by a `~` character:
| Component | Created By | Purpose |
|-----------|------------|---------|
| **Client Attestation JWT** | Wallet Provider | Vouches for the wallet instance's authenticity and security |
| **Client Attestation PoP** | Wallet Instance | Proves possession of the key bound in the attestation |
**Token Request Example:**
```http
POST /token HTTP/1.1
Host: issuer.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=SplxlOBeZQQYbYS6WxSbIA&
client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-client-attestation&
client_assertion=eyJhbGciOiJFUzI1NiIs...~eyJhbGciOiJFUzI1NiIs...
```
## How an Issuer Trusts a Wallet
When a wallet requests a credential from an issuer, the issuer must verify that:
1. The wallet is a legitimate, authorized application
2. The wallet meets security requirements for the credential type
3. The credential will be stored securely
### Issuer Trust Flow (OpenID4VCI)
```
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Wallet │ │ Wallet │ │ Issuer │
│ Instance │ │ Provider │ │ │
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
│ 1. Generate key pair │ │
│ in secure hardware │ │
│ │ │
│ 2. Request attestation │ │
│ (with platform evidence) │ │
│──────────────────────────────>│ │
│ │ │
│ │ 3. Validate: │
│ │ - App integrity │
│ │ - Device security │
│ │ - Key storage type │
│ │ │
│ 4. Client Attestation JWT │ │
│<──────────────────────────────│ │
│ │ │
│ 5. Discover issuer metadata │
│──────────────────────────────────────────────────────────────>│
│ │ │
│ 6. Authorization request (PAR) │
│ + Client Attestation + PoP │
│──────────────────────────────────────────────────────────────>│
│ │ │
│ │ 7. Validate attestation: │
│ │ - Signature (Provider) │
│ │ - PoP (Wallet key) │
│ │ - Provider in Trust List│
│ │ - Security claims │
│ │ │
│ 8. Authorization response │
│<──────────────────────────────────────────────────────────────│
│ │ │
│ 9. Token request + attestation │
│──────────────────────────────────────────────────────────────>│
│ │ │
│ 10. Access token │
│<──────────────────────────────────────────────────────────────│
│ │ │
│ 11. Credential request │
│ + Key proof (+ optional WTE) │
│──────────────────────────────────────────────────────────────>│
│ │ │
│ │ 12. Verify key binding │
│ │ Issue credential │
│ │ │
│ 13. Credential │
│<──────────────────────────────────────────────────────────────│
```
### What the Issuer Validates
| Check | Purpose |
|-------|---------|
| **Attestation Signature** | Confirms the Wallet Provider issued this attestation |
| **Proof of Possession** | Confirms the wallet controls the attested key |
| **Provider Trust** | Verifies the Wallet Provider is in the EU Trust List |
| **Expiration** | Ensures the attestation is still valid |
| **Security Claims** | Checks key storage type, user authentication requirements |
| **Revocation Status** | Confirms the wallet instance hasn't been revoked |
### Client Attestation JWT Claims
```json
{
"typ": "wallet-attestation+jwt",
"alg": "ES256",
"kid": "wallet-provider-key-1"
}
.
{
"iss": "https://wallet-provider.example.com",
"sub": "https://wallet-provider.example.com/wallet/v1.6.0",
"iat": 1704067200,
"exp": 1704153600,
"wallet_name": "Example EUDI Wallet",
"wallet_version": "1.6.0",
"key_type": "STRONGBOX",
"user_authentication": "SYSTEM_BIOMETRIC",
"attested_security_context": "https://trust-list.eu/asc/high",
"cnf": {
"jwk": {
"kty": "EC",
"crv": "P-256",
"x": "TCAER19Zvu3OHF4j4W4vfSVoHIP1ILilDls7vCeGemc",
"y": "ZxjiWWbZMQGHVWKVQ4hbSIirsVfuecCE6t4jT9F2HZQ"
}
}
}
```
### Wallet Trust Evidence (WTE)
For high-assurance credentials, issuers may require additional proof about the key that will hold the credential. **Wallet Trust Evidence** is conveyed at the Credential Endpoint:
```json
{
"format": "dc+sd-jwt",
"credential_identifier": "eu.europa.ec.eudi.pid",
"proofs": {
"jwt": {
"proof_type": "jwt",
"jwt": "eyJhbGciOiJFUzI1NiIs..."
},
"attestation": {
"attestation": "eyJhbGciOiJFUzI1NiIs...",
"proof": "eyJhbGciOiJFUzI1NiIs..."
}
}
}
```
The WTE attestation includes claims about:
| Claim | Description |
|-------|-------------|
| `key_type` | Hardware security level (e.g., "strong_box", "tee", "software") |
| `user_authentication` | Required user auth (e.g., "biometric", "pin", "none") |
| `device_keys` | Information about device-bound keys |
| `cnf` | The public key that will be bound to the credential |
## How a Verifier Trusts a Wallet
When a wallet presents credentials to a verifier (relying party), the trust requirements differ from issuance. The verifier primarily needs to trust the **credential** and its **issuer**, but may also want assurance about the wallet.
### Verifier Trust Flow (OpenID4VP)
```
┌──────────┐ ┌──────────┐
│ Wallet │ │ Verifier │
│ Instance │ │ (RP) │
└────┬─────┘ └────┬─────┘
│ │
│ 1. Authorization Request │
│ (presentation_definition) │
│<──────────────────────────────────────────────────│
│ │
│ 2. User consent │
│ │
│ 3. Create Verifiable Presentation │
│ - Select credentials │
│ - Apply selective disclosure │
│ - Sign with holder key │
│ │
│ 4. Authorization Response │
│ (vp_token + optional wallet attestation) │
│──────────────────────────────────────────────────>│
│ │
│ 5. Validate: │
│ - Credential signatures │
│ - Issuer trust (Trust List)│
│ - Holder binding │
│ - Credential status │
│ - (Optional) Wallet attestation│
│ │
│ 6. Access granted │
│<──────────────────────────────────────────────────│
```
### What the Verifier Validates
| Check | Primary Trust Source | Purpose |
|-------|---------------------|---------|
| **Credential Signature** | Issuer's key | Confirms credential authenticity |
| **Issuer Trust** | EU Trust List | Verifies issuer is authorized |
| **Holder Binding** | Presentation signature | Confirms presenter controls the credential |
| **Credential Status** | Status list | Checks for revocation/suspension |
| **Wallet Attestation** | Wallet Provider | (Optional) Additional assurance about wallet security |
### When Verifiers Require Wallet Attestation
Wallet attestation during presentation is **optional** but may be required for:
| Scenario | Reason |
|----------|--------|
| **High-value transactions** | Additional security assurance |
| **Regulated industries** | Compliance requirements |
| **Government services** | Policy mandates |
| **Sensitive data access** | Risk mitigation |
### Verifier Attestation (Reverse Trust)
OpenID4VP also supports **Verifier Attestation**, allowing wallets to verify the legitimacy of verifiers before sharing credentials:
```json
{
"client_id": "https://verifier.example.com",
"client_id_scheme": "verifier_attestation",
"verifier_attestation": "eyJhbGciOiJFUzI1NiIs..."
}
```
This enables wallets to:
- Confirm the verifier is a legitimate organization
- Check the verifier's authorization to request specific credentials
- Display verified verifier information to users
## Types of Wallet Attestations
### Wallet Instance Attestation (WIA)
The WIA attests to the wallet application's integrity and authorization:
| Claim | Description |
|-------|-------------|
| `iss` | Wallet Provider identifier |
| `sub` | Wallet instance/solution identifier |
| `wallet_name` | Human-readable wallet name |
| `wallet_version` | Application version |
| `client_status` | Current status (active, suspended, revoked) |
| `cnf` | Bound public key |
### Key Attestation
Proves properties of the cryptographic keys:
| Property | Description |
|----------|-------------|
| `key_type` | Storage type (STRONGBOX, TEE, SOFTWARE) |
| `user_authentication` | Required authentication (BIOMETRIC, PIN, NONE) |
| `key_storage_status` | Key validity and revocation info |
### Attestation Lifecycle Models
| Model | Description | Use Case |
|-------|-------------|----------|
| **Long-lived** | Reused across transactions; requires revocation checking | Reduced latency; simpler wallet implementation |
| **Ephemeral** | Single-use per transaction; no revocation needed | Maximum privacy; no tracking across verifiers |
## Integration with OpenID Protocols
### OpenID4VCI (Credential Issuance)
Wallet attestation is used at multiple points:
| Endpoint | Attestation Use |
|----------|-----------------|
| **PAR Endpoint** | Client authentication via attestation |
| **Token Endpoint** | Client authentication via attestation |
| **Credential Endpoint** | Key attestation / WTE for credential binding |
### OpenID4VP (Credential Presentation)
| Component | Attestation Use |
|-----------|-----------------|
| **Authorization Request** | Verifier attestation (optional) |
| **Authorization Response** | Wallet attestation (optional) |
## Privacy Considerations
Wallet attestations must be designed to protect user privacy:
| Requirement | Implementation |
|-------------|----------------|
| **No unique identifiers** | Attestations must not contain values that enable user tracking |
| **Unlinkability** | Different attestations should be used with different verifiers |
| **Minimal disclosure** | Only necessary claims should be included |
| **Short validity** | Limits the window for correlation attacks |
**Error:**
**Privacy Risk**: Using the same wallet attestation across multiple verifiers could enable correlation of user activity. Implementations should consider using ephemeral attestations or rotating attestations per verifier.
## Regulatory Framework
Wallet attestations are mandated by the eIDAS 2.0 framework:
| Regulation | Requirement |
|------------|-------------|
| **eIDAS 2.0** | Wallet Providers must ensure wallet integrity and security |
| **CIR 2024/2977** | Technical requirements for wallet attestations |
| **CIR 2024/2979** | Certification requirements for wallet solutions |
| **ETSI 119 476-3** | Technical specification for WUA and WIA (in development) |
### Level of Assurance
| LoA | Key Storage | User Authentication | Attestation Requirements |
|-----|-------------|---------------------|-------------------------|
| **Low** | Software | None required | Basic WIA |
| **Substantial** | Hardware recommended | PIN or biometric | WIA + Key attestation |
| **High** | Hardware required | Strong authentication | WIA + WTE + certified WSCD |
## Further Reading
- [OAuth 2.0 Attestation-Based Client Authentication](https://datatracker.ietf.org/doc/draft-ietf-oauth-attestation-based-client-auth/)
- [OpenID for Verifiable Credential Issuance](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html)
- [OpenID for Verifiable Presentations](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html)
- [EUDI Wallet Architecture Reference Framework](https://eu-digital-identity-wallet.github.io/eudi-doc-architecture-and-reference-framework/)
- [ETSI TS 119 476](https://www.etsi.org/deliver/etsi_ts/119400_119499/119476/) – Electronic Signatures and Trust Infrastructures
################################################################################
# PART 2 OF 3: COMMUNITY STACK — OPEN SOURCE (200 pages)
# The Community Stack is walt.id's FREE, OPEN-SOURCE product line: standalone
# Issuer API, Verifier API, and Wallet API services plus Kotlin multi-platform
# libraries. Single-tenant, self-hosted (Docker), no license required.
# Do NOT confuse with the commercial Enterprise Stack.
################################################################################
--------------------------------------------------------------------------------
PAGE: Getting Started
URL: https://docs.walt.id/community-stack/home
RAW: https://docs.walt.id/community-stack/home.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Getting Started
Explore our issuer, verifier and wallet product below or learn more about the walt.id Community Stack and its
architecture [here](https://docs.walt.id/community-stack/home/the-community-stack.md).
Video: https://youtu.be/KINBsck7Jzg
#### [Issuer](https://docs.walt.id/community-stack/issuer)
- [API](https://docs.walt.id/community-stack/issuer/getting-started.md) - Issue credentials via OID4VC using the issuer API
- [Apps](https://docs.walt.id/community-stack/issuer/apps/portal/overview.md) - White-label issuer portal to create and configure different types of credential offers.
#### [Verifier](https://docs.walt.id/community-stack/verifier)
- [API](https://docs.walt.id/community-stack/verifier/getting-started.md) - Request and verify credentials from holders via OID4VP using the verifier API.
- [Apps](https://docs.walt.id/community-stack/verifier/apps/portal/overview.md) - White-label verifier portal to request, configure and verify various types of credentials.
#### [Wallet](https://docs.walt.id/community-stack/wallet)
- [API](https://docs.walt.id/community-stack/wallet/getting-started.md) - Create and manage digital ID wallets for your users via the wallet API (custodial).
- [Apps](https://docs.walt.id/community-stack/wallet/apps/overview.md) - White-label web wallet and PWA (Progressive Web App) solution.
---
### Quick Start
If you want to try out all the walt.id services quickly, you can use
our [docker compose](https://github.com/walt-id/waltid-identity/tree/main/docker-compose) shown below. This launches
locally an
instance of
the [issuer API](https://github.com/walt-id/waltid-identity/tree/main/waltid-services/waltid-issuer-api), [verifier API](https://github.com/walt-id/waltid-identity/tree/main/waltid-services/waltid-verifier-api)
and [wallet API](https://github.com/walt-id/waltid-identity/tree/main/waltid-services/waltid-wallet-api) and
our [Web Wallet](https://github.com/walt-id/waltid-identity/tree/main/waltid-applications/waltid-web-wallet), [Web Portal](https://github.com/walt-id/waltid-identity/tree/main/waltid-applications/waltid-web-portal)
and [Verifiable Credentials Repo](https://github.com/walt-id/waltid-credentials). We also provide links to all services
deployed in our test environment below.
**Clone [walt.id identity](https://github.com/walt-id/waltid-identity)**
```bash
git clone https://github.com/walt-id/waltid-identity.git && cd waltid-identity
```
**Launch the services**
```bash
cd docker-compose && docker compose up
```
#### Access Services
Click the link(s) below to open the related services. Local environment link only works if you executed the command
above.
**APIs**
- **Issuer API** - [Deployed](https://issuer.demo.walt.id/swagger/index.html) | [Local](http://localhost:7002/)
- **Verifier API** - [Deployed](https://verifier.demo.walt.id/swagger/index.html) | [Local](http://localhost:7003/)
- **Verifier2 API** - [Deployed](https://verifier2.demo.walt.id/swagger/index.html) | [Local](http://localhost:7004/)
- **Wallet API** - [Deployed](https://wallet.walt.id/swagger/index.html#/) | [Local](http://localhost:7001/)
**Apps**
- **Web Wallet** - [Deployed](https://wallet.demo.walt.id/login) | [Local](http://localhost:7101/)
- **Web Portal** - [Deployed](https://portal.walt.id/) | [Local](http://localhost:7102/)
- **Verifiable Credential Repo** - [Deployed](https://credentials.walt.id/) | [Local](http://localhost:7103/)
### Guides
- [Quickstart (5 min)](https://docs.walt.id/community-stack/home/quickstart-5-min.md) - Get started quickly by creating a wallet, issuing a credential and presenting it.
- [Tutorial (30 min)](https://docs.walt.id/community-stack/home/tutorial-30-min.md) - End-to-end ID use-case using issuer, verifier and wallet API.
- [Issuer Protocol Flows](https://docs.walt.id/community-stack/issuer/protocol-flows) - Full issuance flow from portal to wallet, including KMS signing.
- [Verifier Protocol Flows](https://docs.walt.id/community-stack/verifier/protocol-flows.md) - Full verification flow from portal to wallet presentation.
- [FAQ](https://docs.walt.id/community-stack/home/faq.md) - Frequently asked questions about the Community Stack.
--------------------------------------------------------------------------------
PAGE: Quickstart (5 min)
URL: https://docs.walt.id/community-stack/home/quickstart-5-min
RAW: https://docs.walt.id/community-stack/home/quickstart-5-min.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Quickstart (5 min)
Video: https://youtu.be/fufhtqb_yjQ?si=zKkQSr20BpgX4Brt
In this quickstart guide, we will get ourselves a digital ID wallet, issue a credential into the wallet, and then present that credential to a verifier.
### The Components
- [**The Wallet**](https://wallet.demo.walt.id/){:target="_blank"}: This will utilize a demo version of the walt.id web wallet.
- [**The Issuer**](https://portal.demo.walt.id/){:target="_blank"}: This will utilize a demo version of the walt.id web portal.
- [**The Verifier**](https://portal.demo.walt.id/){:target="_blank"}: This will also utilize a demo version of the walt.id web portal.
## 1. Create Wallet
Visit the walt.id web-wallet [here](https://wallet.demo.walt.id){:target="_blank"} and [register a new account](https://wallet.demo.walt.id/signup){:target="_blank"} using your email and password.
## 2. Issue Credential
To issue a credential using the [walt.id web portal](https://portal.demo.walt.id/){:target="_blank"}, follow these steps:
1. Access the Portal: Visit the walt.id web portal [here](https://portal.demo.walt.id/){:target="_blank"} and select the credential you wish to issue, such as `BankID`. Click "Start" located at the top right corner.
2. (optional) Configure Credential Issuance: On the next page, you can configure the credential issuance settings. You have the option to:
- Choose the credential format, such as [W3C (JWT/SD-JWT)](https://docs.walt.id/concepts/digital-credentials/verifiable-credentials-w3c.md){:target="_blank"}, [IETF SD-JWT VC](https://docs.walt.id/concepts/digital-credentials/sd-jwt-vc.md){:target="_blank"}, or others.
- Specify the [DID](https://docs.walt.id/concepts/decentralised-identifiers.md){:target="_blank"} method to be used as the issuer DID.
- Under "Security Settings," select the authentication method. By default, it is set to "pre-authorized," meaning the user is already authenticated when receiving the credential offer.
3. Issue the Credential: Use the configured settings or leave the default credential issuance settings and click "Issue." This will generate an OID4VC credential offer using the issuer API in the background.
4. Receive the Credential Offer: The OID4VC offer will be displayed on the next screen as a QR code and also provided as a direct link behind the "Open Web Wallet" button.
- The QR code is ideal if the wallet is on a mobile device, as the device can easily scan the code.
- The direct link is useful if the receiving device also has the wallet installed.
5. Open the Credential offer in [Web Wallet](https://wallet.demo.walt.id/){:target="_blank"}: Click "Open Web Wallet" to view the offer in the web wallet.
## 3. Receive Credential
Accept the credential offer displayed in the wallet. You should now have the credential in your wallet.
## 4. Verify Credential
To verify the credential in your wallet using the [walt.id web portal](https://portal.demo.walt.id/){:target="_blank"}, follow these steps:
1. Access the Portal: Visit the walt.id web portal [here](https://portal.demo.walt.id/){:target="_blank"} and select the credential you issued in the previous step to verify, such as `BankID`. Click "Start" located at the top right corner.
2. Switch to the 'Verify' tab at the top below the smiley.
3. (optional) Configure Credential Verification with the following options:
- Choose the credential format, such as [W3C (JWT/SD-JWT)](https://docs.walt.id/concepts/digital-credentials/verifiable-credentials-w3c.md){:target="_blank"}, [IETF SD-JWT VC](https://docs.walt.id/concepts/digital-credentials/sd-jwt-vc.md){:target="_blank"}, or others.
- Specify the [DID](https://docs.walt.id/concepts/decentralised-identifiers.md){:target="_blank"} method
- Under "Credential Policies", select the [policies](https://docs.walt.id/community-stack/verifier/credential-verification/policies/overview.md){:target="_blank"} which should be applied against the credential.
4. Verify the Credential: Use the configured settings or leave the default credential verification settings and click "Verify." This will generate an OID4VP authorization request using the verifier API in the background.
5. Receive Credential Authorization Request: The OID4VP authorization URL will be displayed on the next screen as a QR code and also provided as a direct link behind the "Open Web Wallet" button.
- The QR code is ideal if the wallet is on a mobile device, as the device can easily scan the code.
- The direct link is useful if the receiving device also has the wallet installed.
6. Open the Credential authorization request in [Web Wallet](https://wallet.demo.walt.id/){:target="_blank"}: Click "Open Web Wallet" to view the verification request in the web wallet.
## 5. Present Credential
Accept the verification and inspect the verification results on the page you get redirected to.
## Next Steps
**Note:**
If you enjoy our tools, please leave us a star ⭐ [on GitHub](https://github.com/walt-id/waltid-identity).
[Checkout our tutorial](https://docs.walt.id/community-stack/home/tutorial-30-min.md) for an end-to-end use-case using our APIs (Issuer, Verifier and Wallet).
--------------------------------------------------------------------------------
PAGE: Tutorial (30 min)
URL: https://docs.walt.id/community-stack/home/tutorial-30-min
RAW: https://docs.walt.id/community-stack/home/tutorial-30-min.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Tutorial (30 min)
Video: https://youtu.be/1Qbj2kVMee8
In this 30 min tutorial, we will take a look at the issuer, verifier, and wallet API of
the walt.id Community Stack. If you want to try it out via a UI first, please go [here](https://docs.walt.id/community-stack/home/quickstart-5-min.md).
The tutorial will be split into the following sections:
1. Create a wallet
2. Issue a credential into the wallet
3. Request & verify the credential from the wallet
With that, we are covering a regular digital ID use-case end-to-end using the walt.id API
components:
- [Issuer API](https://docs.walt.id/community-stack/issuer/getting-started.md)
- [Verifier API](https://docs.walt.id/community-stack/verifier/getting-started.md)
- [Wallet API](https://docs.walt.id/community-stack/wallet/getting-started.md)
### The Credential
For this tutorial, we will be issuing ourselves a University Degree Credential in
the [W3C credential format](https://docs.walt.id/concepts/digital-credentials/verifiable-credentials-w3c.md)
using a JWT signature.
**Note:**
If you are curious to learn more about other types of credential formats, please
go [here](https://docs.walt.id/concepts/digital-credentials.md).
#### Example University Degree Credential
```json
{
// The contextual information required for data integrity and 1.api
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1"
],
"id": "http://example.gov/credentials/3732",
// The credential's unique identifier
"type": [
"VerifiableCredential",
"UniversityDegree"
// Specifies the kind of credential being issued
],
"issuer": {
"id": "did:web:vc.transmute.world"
// The issuer's unique identifier (DID)
},
"issuanceDate": "2020-03-10T04:24:12.164Z",
// When the credential was issued
"credentialSubject": {
// Information about the credential's recipient and the degree earned
"id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
"degree": {
"type": "BachelorDegree",
"name": "Bachelor of Science and Arts"
}
}
}
```
## Setup
To bring up
the [issuer](https://docs.walt.id/community-stack/issuer/getting-started.md), [wallet](https://docs.walt.id/community-stack/wallet/getting-started.md)
and [verifier API](https://docs.walt.id/community-stack/verifier/getting-started.md) locally, we will be using
our [docker compose](https://github.com/walt-id/waltid-identity/tree/main/docker-compose).
**Clone [walt.id identity](https://github.com/walt-id/waltid-identity)**
```bash
git clone https://github.com/walt-id/waltid-identity.git && cd waltid-identity
```
**Launch the services**
```bash
cd docker-compose && docker compose up
```
#### Access APIs
- **Issuer API** - [http://localhost:7002/](http://localhost:7002/)
- **Verifier API** - [http://localhost:7003/](http://localhost:7003/)
- **Wallet API** - [http://localhost:7001/](http://localhost:7001/)
## Create a Wallet
To create our wallet, we will use the [walt.id wallet API](https://docs.walt.id/community-stack/wallet/getting-started.md). This API will
be managed by the operator, which means that the
wallet we create will be a custodial wallet. In a custodial wallet, the keys are controlled by the operator of the
software, unlike in a non-custodial wallet where the user has full control over their keys.
### Create Wallet Account
**Option: CURL**
[API Reference](https://wallet.demo.walt.id/swagger/index.html#/Authentication/post_wallet_api_auth_create)
Each created account receives a default "did:key" and private key (Ed25519).
```bash
curl -X 'POST' \
'http://0.0.0.0:7001/wallet-api/auth/register' \
-H 'accept: */*' \
-H 'Content-Type: application/json' \
-d '{
"type": "email",
"name": "Test",
"email": "test@email.com",
"password": "test"
}'
```
**Body Parameters**
```json
{
"name": "string",
"email": "string",
"password": "string",
"type": "{type}"
}
```
- `name`: _[string]_ - The name of the user.
- `email`: _[string]_ - The email of the user.
- `password`: _[string]_ - The password of the user.
- `type`: _[string]_ - The type of the user account. `email` or `address` (for web3 login).
### Login to Wallet Account
**Option: CURL**
[API Reference](https://wallet.demo.walt.id/swagger/index.html#/Authentication/post_wallet_api_auth_login)
```bash
curl -X 'POST' \
'http://0.0.0.0:7001/wallet-api/auth/login' \
-H 'accept: */*' \
-H 'Content-Type: application/json' \
-d '{
"type": "email",
"email": "test@email.com",
"password": "test"
}'
```
**Body Parameters**
```json
{
"type": "{type}",
"email": "string",
"password": "string"
}
```
- `type`: _[string]_ - The type of the user account. `email` or `address` (for web3 login).
- `email`: _[string]_ - The email of the user.
- `password`: _[string]_ - The password of the user.
**Example Response**
Now a session is automatically created for cookie-based authentication. For Bearer Token Authentication, the `token`
returned
must be provided in the header for each request that needs authentication. Refer to
the [overview section](https://docs.walt.id/community-stack/wallet/authentication/overview.md#cookie-based-authentication) for more details.
```json
{
"token": "KL-a_dk1qO8moCX4gxaGfb7_TS8RK-JWVKZk9BBP0-s",
"id": "018045e5-942c-4362-b535-658c4dd581ef",
"username": "user@email.com"
}
```
### Retrieve Wallet ID
As there could be support for multiple wallets per account in the future. We will need to retrieve our
wallet ID for the one wallet that got created with the following call. We should save this
ID for future reference.
**Option: CURL**
[API Reference](https://wallet.demo.walt.id/swagger/index.html#/Accounts/get_wallet_api_wallet_accounts_wallets)
```bash
curl -X 'GET' \
'http://0.0.0.0:7001/wallet-api/wallet/accounts/wallets' \
-H 'accept: application/json' \
-H 'authorization: Bearer {token}'
```
**Header Parameters**
- `authorization`: _String_ - Bearer token obtained from the [login step](#login-to-wallet-account). Format: `Bearer {token}`.
**Example Response**
```json
{
"account": "108753e3-5ab4-4096-8699-ad0653c79a09",
"wallets": [
{
// ⬇️ Save for later ⬇️
"id": "4d7ef7c6-a76b-409b-b042-68d15a484082",
// ⬆️ Save for later ⬆️
"name": "Wallet of Test",
"createdOn": "2025-04-16T09:19:11.585Z",
"addedOn": "2025-04-16T09:19:11.597Z",
"permission": "ADMINISTRATE"
}
]
}
```
**Note:**
Make sure to save the ID for future reference.
Now that we have created the wallet account and have the wallet ID, we can proceed to the issuer to create a credential
offer. This offer can then be received in the wallet we just set up. Additionally, it's important to note that each
wallet is
automatically assigned a set of keys and a did:jwk. This feature can be disabled if desired through the [wallet
configuration settings](/community-stack/wallet/configurations/config-files/features).
## Issue Credential
### Create Credential Offer
**Option: CURL**
**Endpoint:
** `/openid4vc/jwt/issue` | [API Reference](https://issuer.demo.walt.id/swagger/index.html#/Credential%20Issuance/post_openid4vc_jwt_issue)
**Example Request**
```bash
curl -X 'POST' \
'http://localhost:7002/openid4vc/jwt/issue' \
-H 'accept: text/plain' \
-H 'Content-Type: application/json' \
-d '{
"issuerKey": {
"type": "jwk",
"jwk": {
"kty": "OKP",
"d": "JvJIpga2GD8LJeRu4Sv-mL4thE31DuFlr9PA04CIoZY",
"crv": "Ed25519",
"kid": "iJMS5bkZVIlncfq_Lf_SuxJ2JtQ5Hvaz7tWPnAjUUds",
"x": "FZdvwC8aGhRwqzWptej0NZgtwYAI1SyFg1mKDETOfqE"
}
},
"issuerDid": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiaUpNUzVia1pWSWxuY2ZxX0xmX1N1eEoySnRRNUh2YXo3dFdQbkFqVVVkcyIsIngiOiJGWmR2d0M4YUdoUndxeldwdGVqME5aZ3R3WUFJMVN5RmcxbUtERVRPZnFFIn0",
"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": "",
"issuer": {
"id": ""
},
"credentialSubject": {
"id": ""
},
"issuanceDate": "",
"expirationDate": ""
},
"authenticationMethod": "PRE_AUTHORIZED",
"standardVersion": "DRAFT13"
}'
```
**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`
or `https://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 id
- `type`: _String_ - the event type
- `data`: _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` with `jwt` being the issued jwt
- `sdjwt_issue` with `sdjwt` being the issued sdjwt
- `batch_jwt_issue` with `jwt` being the issued jwt
- `batch_sdjwt_issue` with `sdjwt` being the issued sdjwt
- `generated_mdoc` with `mdoc` 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**
```json
{
"issuerKey": {
"type": "jwk",
"jwk": {
"kty": "OKP",
"d": "JvJIpga2GD8LJeRu4Sv-mL4thE31DuFlr9PA04CIoZY",
"crv": "Ed25519",
"kid": "iJMS5bkZVIlncfq_Lf_SuxJ2JtQ5Hvaz7tWPnAjUUds",
"x": "FZdvwC8aGhRwqzWptej0NZgtwYAI1SyFg1mKDETOfqE"
}
},
"issuerDid": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiaUpNUzVia1pWSWxuY2ZxX0xmX1N1eEoySnRRNUh2YXo3dFdQbkFqVVVkcyIsIngiOiJGWmR2d0M4YUdoUndxeldwdGVqME5aZ3R3WUFJMVN5RmcxbUtERVRPZnFFIn0",
"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": "",
"issuer": {
"id": ""
},
"credentialSubject": {
"id": ""
},
"issuanceDate": "",
"expirationDate": ""
},
"authenticationMethod": "PRE_AUTHORIZED",
"standardVersion": "DRAFT13"
}
```
**Body Parameters**
- `issuerKey`: _JSON_ - A JWK or reference object to a key stored in an external KMS to sign the credential with.
Supported algorithms: ed25519, secp256k1, secp256r1, or
RSA.
**JWK Format:** `{"type": "jwk", "jwk": Here JSON Web Key Object}`. Must be provided as JWK object to "issuerKey".
**KMS Key**: Please refer to the [Key Management Section](https://docs.walt.id/community-stack/issuer/key-management/overview.md) and the
KMS you want to
use for more details on the structure
of the reference object.
- `issuerDid`: _String_ - The DID related to the provided key.
- `credentialConfigurationId`: _String_ - Reference to a specific credential configuration the issuer supports. As our
issuer currently supports W3C JWT & SD-JWT credentials and SD-JWT VCs (IETF). The structure of the
credentialConfigurationId is the following: "
credentialType_jwt_vc_json" for W3C JWT & SD-JWT credentials
and "credentialType_vc+sd-jwt" for SD-JWT VCs (IETF). E.g. "UniversityDegree_jwt_vc_json" or "
UniversityDegree_vc+sd-jwt".
You can also view
the credentialConfigurationIds by visiting the `/.well-known/openid-credential-issuer` endpoint of your issuer.
The metadata of our deployed issuer for testing can be
seen [here](https://issuer.demo.walt.id/swagger/index.html#/oidc/get__standardVersion___well_known_openid_credential_issuer).
- `credentialData`: _JSON_ - A credential data structure to sign (W3C compliant). A list of predefined valid structures
can be
found [here](https://credentials.walt.id/).
- `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](https://docs.walt.id/community-stack/issuer/data-functions).
- `standardVersion`: (optional) _String_ - Defines which OIDC4VCI standard version is used. The supported standard
versions are: `DRAFT13` and `DRAFT11`. If no value is provided, it will default to `DRAFT13`.
- `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 as `PRE_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 server
- `ID_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 the `vpRequestedValue` as explained below. The policies which are applied to the presented
credential
are 'signature,' 'expired,' and 'not-before.' Learn more about
policies [here](https://docs.walt.id/community-stack/verifier/credential-verification/policies/overview.md).
- `NONE` - used for authorization code flow with none authentication method
- `PRE_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.demo.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
}
}
}
```
The response will get from the issuer API will be an OIDCVI offer URL. This URL can then be passed to the wallet to
claim the credentials.
### Receive Credential in Wallet
**Option: CURL**
**Example Call
** | [Api Reference](https://wallet.demo.walt.id/swagger/index.html#/Credential%20exchange/post_wallet_api_wallet__wallet__exchange_useOfferRequest)
```bash
curl -X 'POST' \
'http://0.0.0.0:7001/wallet-api/wallet/{walletID}/exchange/useOfferRequest' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-H 'authorization: Bearer {loginToken}' \
-d '{credentialOfferFromIssuer}'
```
**Path Parameters**
- `walletID`: _String_ - the ID of our wallet, retrieved [previously](#retrieve-wallet-id). Provided as part of the URL path.
**Header Parameters**
- `authorization`: _String_ - Bearer token obtained from the [login step](#login-to-wallet-account). Format: `Bearer {loginToken}`.
**Body**
```json
credentialOfferFromIssuerAPI
```
**Note:**
When you are using the terminal the credentialOffer return from the issuer might have a `%` at the end of it. Make
sure to remove this before passing it to the wallet.
**Example Response**
```json
[
{
"wallet": "8abd7a5f-ae86-46e5-9693-1bb8a98f7c5d",
"id": "urn:uuid:d730bc77-8a9b-4f9b-8d56-50689aefe3d7",
"document": "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaWFVcE5VelZpYTFwV1NXeHVZMlp4WDB4bVgxTjFlRW95U25SUk5VaDJZWG8zZEZkUWJrRnFWVlZrY3lJc0luZ2lPaUpHV21SMmQwTTRZVWRvVW5keGVsZHdkR1ZxTUU1YVozUjNXVUZKTVZONVJtY3hiVXRFUlZSUFpuRkZJbjAjaUpNUzVia1pWSWxuY2ZxX0xmX1N1eEoySnRRNUh2YXo3dFdQbkFqVVVkcyIsInR5cCI6IkpXVCIsImFsZyI6IkVkRFNBIn0.eyJpc3MiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaWFVcE5VelZpYTFwV1NXeHVZMlp4WDB4bVgxTjFlRW95U25SUk5VaDJZWG8zZEZkUWJrRnFWVlZrY3lJc0luZ2lPaUpHV21SMmQwTTRZVWRvVW5keGVsZHdkR1ZxTUU1YVozUjNXVUZKTVZONVJtY3hiVXRFUlZSUFpuRkZJbjAiLCJzdWIiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaVNtTXlWWHByY1RWRU5uaHJRbXAyWlRGVk5tRlJibU4yUzJkdFVrNTFTVU50Y1ZGTmIzVlFXVFJ5WXlJc0luZ2lPaUpFZEVoalgyTmZVSGgyY3pkb2NYRkRXRVZJTjIxWlRYSkZjWGgwY25jeVZWZGlRemhRYjA1WVRVMVJJbjAiLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL2V4YW1wbGVzL3YxIl0sImlkIjoidXJuOnV1aWQ6ZDczMGJjNzctOGE5Yi00ZjliLThkNTYtNTA2ODlhZWZlM2Q3IiwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIlVuaXZlcnNpdHlEZWdyZWUiXSwiaXNzdWVyIjp7ImlkIjoiZGlkOmp3azpleUpyZEhraU9pSlBTMUFpTENKamNuWWlPaUpGWkRJMU5URTVJaXdpYTJsa0lqb2lhVXBOVXpWaWExcFdTV3h1WTJaeFgweG1YMU4xZUVveVNuUlJOVWgyWVhvM2RGZFFia0ZxVlZWa2N5SXNJbmdpT2lKR1dtUjJkME00WVVkb1VuZHhlbGR3ZEdWcU1FNWFaM1IzV1VGSk1WTjVSbWN4YlV0RVJWUlBabkZGSW4wIn0sImlzc3VhbmNlRGF0ZSI6IjIwMjUtMDQtMTZUMTI6MDg6MzEuOTMwOTQ0MDg1WiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmp3azpleUpyZEhraU9pSlBTMUFpTENKamNuWWlPaUpGWkRJMU5URTVJaXdpYTJsa0lqb2lTbU15VlhwcmNUVkVObmhyUW1wMlpURlZObUZSYm1OMlMyZHRVazUxU1VOdGNWRk5iM1ZRV1RSeVl5SXNJbmdpT2lKRWRFaGpYMk5mVUhoMmN6ZG9jWEZEV0VWSU4yMVpUWEpGY1hoMGNuY3lWVmRpUXpoUWIwNVlUVTFSSW4wIiwiZGVncmVlIjp7InR5cGUiOiJCYWNoZWxvckRlZ3JlZSIsIm5hbWUiOiJCYWNoZWxvciBvZiBTY2llbmNlIGFuZCBBcnRzIn19LCJleHBpcmF0aW9uRGF0ZSI6IjIwMjYtMDQtMTZUMTI6MDg6MzEuOTMxMjU1NjY4WiJ9LCJqdGkiOiJ1cm46dXVpZDpkNzMwYmM3Ny04YTliLTRmOWItOGQ1Ni01MDY4OWFlZmUzZDciLCJleHAiOjE3NzYzNDEzMTEsImlhdCI6MTc0NDgwNTMxMSwibmJmIjoxNzQ0ODA1MzExfQ.tjhXghX6WtMYm0R31cULd0aKVrqRRFkINI-KEjYsDeyoALxl833dEdESosLKUm197Yh8WK5QmX0jMc-gyvnnCw",
"disclosures": "",
"addedOn": "2025-04-16T12:08:32.036587543Z",
"pending": false,
"format": "jwt_vc_json",
"parsedDocument": {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1"
],
"id": "urn:uuid:d730bc77-8a9b-4f9b-8d56-50689aefe3d7",
"type": [
"VerifiableCredential",
"UniversityDegree"
],
"issuer": {
"id": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiaUpNUzVia1pWSWxuY2ZxX0xmX1N1eEoySnRRNUh2YXo3dFdQbkFqVVVkcyIsIngiOiJGWmR2d0M4YUdoUndxeldwdGVqME5aZ3R3WUFJMVN5RmcxbUtERVRPZnFFIn0"
},
"issuanceDate": "2025-04-16T12:08:31.930944085Z",
"credentialSubject": {
"id": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiSmMyVXprcTVENnhrQmp2ZTFVNmFRbmN2S2dtUk51SUNtcVFNb3VQWTRyYyIsIngiOiJEdEhjX2NfUHh2czdocXFDWEVIN21ZTXJFcXh0cncyVVdiQzhQb05YTU1RIn0",
"degree": {
"type": "BachelorDegree",
"name": "Bachelor of Science and Arts"
}
},
"expirationDate": "2026-04-16T12:08:31.931255668Z"
}
}
]
```
## Request & Verify Credential
In the next section, we will be using the walt.id verifier API to generate an OID4VP authorization request. This request
we will then pass to the wallet to present the credential we received and stored in the previous step.
### Create Authorization Request
Below, we will outline the specific credentials and the format we wish to request from a wallet. In our example, we will
be requesting a `UniversityDegree` in the W3C Verifiable Credential (VC) format (`jwt_vc_json`). The Verifier API will
take the incoming request and convert it into a Presentation Definition which becomes part of the returned
Authorization request. The Authorization request with the presentation definition allows the wallet to understand which
credentials it needs to present to the verifier.
**Option: CURL**
```bash
curl -X 'POST' \
'http://0.0.0.0:7003/openid4vc/verify' \
-H 'accept: */*' \
-H 'authorizeBaseUrl: openid4vp://authorize' \
-H 'responseMode: direct_post' \
-H 'Content-Type: application/json' \
-d '{
"request_credentials": [
{ "type": "UniversityDegree", "format": "jwt_vc_json" }
]
}'
```
**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 here
`https://wallet.demo.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](#verification-status-policies-response)
- **statusCallbackApiKey** (optional) - If the endpoint you provide via `statusCallbackUri` is protected, you can use
the `statusCallbackApiKey` 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](https://docs.walt.id/community-stack/verifier/ecosystems/ebsi/overview.md).
- `ISO_18013_7_MDOC`: For mdoc Openid4VP.
**Body**
```json
{
"request_credentials": [
{
"type": "UniversityDegree",
"format": "jwt_vc_json"
}
]
}
```
**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:
1. **W3C JWT Credential**
- **type** Specifies the type of credential (e.g., `VerifiableID`, `VerifiableDiploma`). This maps to the `type`
attribute in W3C credentials.
- **format**: Describes the format of the credential. For W3C JWT credentials, this would
be `jwt_vc_json`.
```json
{ "type": "ProofOfResidence", "format": "jwt_vc_json" }
```
2. **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`.
```json
{ "vct": "test.com/identity_credential", "format": "vc+sd-jwt" }
```
3. **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`.
```json
{ "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 optional `policies` and `id`
attribute
- **policies**: An array of policies to apply to the specified credential. A list of all policies can be
found [here](https://docs.walt.id/community-stack/verifier/credential-verification/policies/overview.md).
- **id**: Used to set a specific id for the
generated [Presentation Definition](https://identity.foundation/presentation-exchange/spec/v1.0.0/#presentation-definition).
If not set,
the verifier API auto-assigns a generated one.
**Full Examples**
```json
{
"type": "ProofOfResidence",
"format": "jwt_vc_json",
"policies": [
"schema",
{
"policy": "webhook",
"args": "https://example.org/abc/xyz"
}
],
"id": "test123"
}
```
**Example Response**
**Raw Response**
```text
openid4vp://authorize?response_type=vp_token&client_id=http%3A%2F%2Fhost.docker.internal%3A7003%2Fopenid4vc%2Fverify&response_mode=direct_post&state=5smqTtxA1L0F&presentation_definition_uri=http%3A%2F%2Fhost.docker.internal%3A7003%2Fopenid4vc%2Fpd%2F5smqTtxA1L0F&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=2afa2fdc-b8ab-4d4c-a4b6-bd299949fb4b&response_uri=http%3A%2F%2Fhost.docker.internal%3A7003%2Fopenid4vc%2Fverify%2F5smqTtxA1L0F
```
**Decoded Response**
```text
openid4vp://authorize?
response_type=vp_token
&client_id=http://host.docker.internal:7003/openid4vc/verify
&response_mode=direct_post
&state=5smqTtxA1L0F
&presentation_definition_uri=http://host.docker.internal:7003/openid4vc/pd/5smqTtxA1L0F
&client_id_scheme=redirect_uri
&client_metadata={
"authorization_encrypted_response_alg":"ECDH-ES",
"authorization_encrypted_response_enc":"A256GCM"
}
&nonce=2afa2fdc-b8ab-4d4c-a4b6-bd299949fb4b
&response_uri=http://host.docker.internal:7003/openid4vc/verify/5smqTtxA1L0F
```
**Presentation Definition URI result**
Below we see the [presentation definition](https://identity.foundation/presentation-exchange/spec/v2.0.0/) the verifier
API has generated based on our request. This helps the wallet find matching credentials to present.
```json
{
"id": "EjRyJNiWqKcc",
"input_descriptors": [
{
"id": "UniversityDegree",
"format": {
"jwt_vc_json": {
"alg": [
"EdDSA"
]
}
},
"constraints": {
"fields": [
{
"path": [
"$.vc.type"
],
"filter": {
"type": "string",
"pattern": "UniversityDegree"
}
}
]
}
}
]
}
```
## Present Credential
### 1. Extract Presentation Definition
First, we will need to decode the Authorization Request we got from the verifier.
**Example Authorization Request**
```text
openid4vp://authorize?response_type=vp_token&client_id=http%3A%2F%2Fhost.docker.internal%3A7003%2Fopenid4vc%2Fverify&response_mode=direct_post&state=5smqTtxA1L0F&presentation_definition_uri=http%3A%2F%2Fhost.docker.internal%3A7003%2Fopenid4vc%2Fpd%2F5smqTtxA1L0F&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=2afa2fdc-b8ab-4d4c-a4b6-bd299949fb4b&response_uri=http%3A%2F%2Fhost.docker.internal%3A7003%2Fopenid4vc%2Fverify%2F5smqTtxA1L0F
```
For the decoding we can use an online tool like [this](https://www.url-encode-decode.com/){:target="_blank"}.
**Example of Decoded Authorization Request**
```text
openid4vp://authorize?
response_type=vp_token
&client_id=http://host.docker.internal:7003/openid4vc/verify
&response_mode=direct_post
&state=5smqTtxA1L0F
&presentation_definition_uri=http://host.docker.internal:7003/openid4vc/pd/5smqTtxA1L0F
&client_id_scheme=redirect_uri
&client_metadata={
"authorization_encrypted_response_alg":"ECDH-ES",
"authorization_encrypted_response_enc":"A256GCM"
}
&nonce=2afa2fdc-b8ab-4d4c-a4b6-bd299949fb4b
&response_uri=http://host.docker.internal:7003/openid4vc/verify/5smqTtxA1L0F
```
Now we need to visit the `presentation_definition_uri` provided and extract the presentation definition.
**Example Presentation Definition**
```json
{
"id": "EjRyJNiWqKcc",
"input_descriptors": [
{
"id": "UniversityDegree",
"format": {
"jwt_vc_json": {
"alg": [
"EdDSA"
]
}
},
"constraints": {
"fields": [
{
"path": [
"$.vc.type"
],
"filter": {
"type": "string",
"pattern": "UniversityDegree"
}
}
]
}
}
]
}
```
### 2. Find Credential(s) Matching Presentation Definition
Use the `matchCredentialsForPresentationDefinition` endpoint in the wallet API to find all user credentials matching the
presentation request constraints.
**Option: CURL**
**Endpoint:**
`/wallet-api/wallet/{wallet}/exchange/matchCredentialsForPresentationDefinition`| [API Reference](https://wallet.demo.walt.id/swagger/index.html#/Credential%20exchange/post_wallet_api_wallet__wallet__exchange_matchCredentialsForPresentationDefinition)
**Example Request**
```bash
curl -X 'POST' \
'http://0.0.0.0:7001/wallet-api/wallet/{wallet_id}/exchange/matchCredentialsForPresentationDefinition' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-H 'authorization: Bearer {loginToken}' \
-d '{presentationDefinitionExtractedFromAuthorizationRequest}'
```
**Path Parameters**
- **wallet_id** - Use the saved wallet ID from earlier or extract it via the `/wallet-api/wallet/accounts/wallets`
endpoint as explained [here](#retrieve-wallet-id). Provided as part of the URL path.
**Header Parameters**
- `authorization`: _String_ - Bearer token obtained from the [login step](#login-to-wallet-account). Format: `Bearer {loginToken}`.
**Body**
The extracted presentation definition as JSON, as explained above.
```json
{
"id": "EjRyJNiWqKcc",
"input_descriptors": [
{
"id": "UniversityDegree",
"format": {
"jwt_vc_json": {
"alg": [
"EdDSA"
]
}
},
"constraints": {
"fields": [
{
"path": [
"$.vc.type"
],
"filter": {
"type": "string",
"pattern": "UniversityDegree"
}
}
]
}
}
]
}
```
---
**Example Response**
If credentials matching the presentation definition are found, a list of these credentials will be returned. If no match
is found, an empty list will be returned. Take a note of the credential id(s) returned as we will be needing them in
a later step.
```json
[
{
"wallet": "5f2eb7d7-7d04-461c-b93d-28d95efbf15b",
// ⬇️ Note for later ⬇️
"id": "urn:uuid:fb09ba3d-b73d-49ad-9d88-3d018878ea83",
// ⬆️ Note for later ⬆️
"document": "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pTTFsT1pEbEdibmc1U214NVVGWlpkMmRYUmtVek4wVXpSM2RKTUdWSGJFTkxPSGRHYkZkNFIyWndUU0lzSW5naU9pSkdiM1paTWpGTVFVRlBWR3huTFcwdFRtVkxWMmhhUlV3MVlVWnlibEl3ZFdOS2FrUTFWRXR3UjNWbklpd2llU0k2SWtOeVJrcG1SMVJrVURJNVNrcGpZM0JSV0hWNVRVOHpiMmgwZW5KVWNWQjZRbEJDU1ZSWmFqQnZaMEVpZlEiLCJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pTTFsT1pEbEdibmc1U214NVVGWlpkMmRYUmtVek4wVXpSM2RKTUdWSGJFTkxPSGRHYkZkNFIyWndUU0lzSW5naU9pSkdiM1paTWpGTVFVRlBWR3huTFcwdFRtVkxWMmhhUlV3MVlVWnlibEl3ZFdOS2FrUTFWRXR3UjNWbklpd2llU0k2SWtOeVJrcG1SMVJrVURJNVNrcGpZM0JSV0hWNVRVOHpiMmgwZW5KVWNWQjZRbEJDU1ZSWmFqQnZaMEVpZlEiLCJzdWIiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaVEyVTVNbGczUVhVMVRuQlRWV0ZoWlU5VFl6TkpSMjlDTFVacFNUTmtaMjFYT1Y5c2NGTldja3hrYXlJc0luZ2lPaUpIY1VabWRqbFNjemh1TlRrMk4wSXlVR3g0TW1wbFduRnlOWFZxVkRoM2RYWk5XbEpIYlZoeGVFSkZJbjAiLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVmVyaWZpYWJsZUF0dGVzdGF0aW9uIiwiVmVyaWZpYWJsZUlkIl0sImNyZWRlbnRpYWxTY2hlbWEiOnsiaWQiOiJodHRwczovL2FwaS5wcmVwcm9kLmVic2kuZXUvdHJ1c3RlZC1zY2hlbWFzLXJlZ2lzdHJ5L3YxL3NjaGVtYXMvMHhiNzdmODUxNmE5NjU2MzFiNGYxOTdhZDU0YzY1YTllMmY5OTM2ZWJmYjc2YmFlNDkwNmQzMzc0NGRiY2M2MGJhIiwidHlwZSI6IkZ1bGxKc29uU2NoZW1hVmFsaWRhdG9yMjAyMSJ9LCJjcmVkZW50aWFsU3ViamVjdCI6eyJjdXJyZW50QWRkcmVzcyI6WyIxIEJvdWxldmFyZCBkZSBsYSBMaWJlcnTDqSwgNTk4MDAgTGlsbGUiXSwiZGF0ZU9mQmlydGgiOiIxOTkzLTA0LTA4IiwiZmFtaWx5TmFtZSI6IkRPRSIsImZpcnN0TmFtZSI6IkphbmUiLCJnZW5kZXIiOiJGRU1BTEUiLCJpZCI6ImRpZDpqd2s6ZXlKcmRIa2lPaUpQUzFBaUxDSmpjbllpT2lKRlpESTFOVEU1SWl3aWEybGtJam9pUTJVNU1sZzNRWFUxVG5CVFZXRmhaVTlUWXpOSlIyOUNMVVpwU1ROa1oyMVhPVjlzY0ZOV2NreGtheUlzSW5naU9pSkhjVVptZGpsU2N6aHVOVGsyTjBJeVVHeDRNbXBsV25GeU5YVnFWRGgzZFhaTldsSkhiVmh4ZUVKRkluMCIsIm5hbWVBbmRGYW1pbHlOYW1lQXRCaXJ0aCI6IkphbmUgRE9FIiwicGVyc29uYWxJZGVudGlmaWVyIjoiMDkwNDAwODA4NEgiLCJwbGFjZU9mQmlydGgiOiJMSUxMRSwgRlJBTkNFIn0sImV2aWRlbmNlIjpbeyJkb2N1bWVudFByZXNlbmNlIjpbIlBoeXNpY2FsIl0sImV2aWRlbmNlRG9jdW1lbnQiOlsiUGFzc3BvcnQiXSwic3ViamVjdFByZXNlbmNlIjoiUGh5c2ljYWwiLCJ0eXBlIjpbIkRvY3VtZW50VmVyaWZpY2F0aW9uIl0sInZlcmlmaWVyIjoiZGlkOmVic2k6MkE5Qlo5U1VlNkJhdGFjU3B2czFWNUNkakh2THBRN2JFc2kySmI2TGRIS25ReGFOIn1dLCJpZCI6InVybjp1dWlkOmZiMDliYTNkLWI3M2QtNDlhZC05ZDg4LTNkMDE4ODc4ZWE4MyIsImlzc3VlZCI6IjIwMjEtMDgtMzFUMDA6MDA6MDBaIiwiaXNzdWVyIjoiZGlkOmp3azpleUpyZEhraU9pSkZReUlzSW1OeWRpSTZJbEF0TWpVMklpd2lhMmxrSWpvaU0xbE9aRGxHYm5nNVNteDVVRlpaZDJkWFJrVXpOMFV6UjNkSk1HVkhiRU5MT0hkR2JGZDRSMlp3VFNJc0luZ2lPaUpHYjNaWk1qRk1RVUZQVkd4bkxXMHRUbVZMVjJoYVJVdzFZVVp5YmxJd2RXTktha1ExVkV0d1IzVm5JaXdpZVNJNklrTnlSa3BtUjFSa1VESTVTa3BqWTNCUldIVjVUVTh6YjJoMGVuSlVjVkI2UWxCQ1NWUlphakJ2WjBFaWZRIiwidmFsaWRGcm9tIjoiMjAyMS0wOC0zMVQwMDowMDowMFoiLCJpc3N1YW5jZURhdGUiOiIyMDI0LTA4LTIxVDA5OjExOjMzLjI0NjM4OTA0MVoifSwianRpIjoidXJuOnV1aWQ6ZmIwOWJhM2QtYjczZC00OWFkLTlkODgtM2QwMTg4NzhlYTgzIiwiaWF0IjoxNzI0MjMxNDkzLCJuYmYiOjE3MjQyMzE0OTN9.OsPTKQC6tvD6TtxeoCd8FtaIThYrYeYRjCHzDXkmgTUjRw78faT9R2bljT03ncrb0YstK0EmjM9D5lwqMZ9ZIA",
"addedOn": "2024-08-21T09:11:33.284Z",
"format": "jwt_vc"
}
]
```
### 3. Get User Consent (not needed for this tutorial)
Normally, you should ask for the owner's consent before presenting these credentials. Only if the user agrees should you
proceed with sharing them. As we are the user, we don't need this step.
### 4. Share Credential(s) With Verifier
Now that we have identified the credentials to share and received confirmation from the wallet owner, we are ready to
reply to the verifier's presentation request.
However, before we can do so we need to resolve the original presentation
request using the following endpoint `/exchange/resolvePresentationRequest`
#### 4.1. Resolve Presentation Request
**Option: CURL**
**Endpoint:**
`/wallet-api/wallet/{wallet}/exchange/resolvePresentationRequest`| [API Reference](https://wallet.demo.walt.id/swagger/index.html#/Credential%20exchange/post_wallet_api_wallet__wallet__exchange_resolvePresentationRequest)
**Example Request**
```bash
curl -X 'POST' \
'http://0.0.0.0:7001/wallet-api/wallet/{wallet_id}/exchange/resolvePresentationRequest' \
-H 'accept: text/plain' \
-H 'Content-Type: text/plain' \
-H 'authorization: Bearer {loginToken}' \
-d '{authorizationRequestFromVerifier}'
```
**Path Parameters**
- **wallet_id** - Extract needed wallet id parameter via the `/wallet-api/wallet/accounts/wallets` endpoint as explained [here](#retrieve-wallet-id). Provided as part of the URL path.
**Header Parameters**
- `authorization`: _String_ - Bearer token obtained from the [login step](#login-to-wallet-account). Format: `Bearer {loginToken}`.
**Body**
The original presentation request received from the verifier.
```
openid4vp://authorize?
response_type=vp_token&
client_id=https%3A%2F%2Fverifier.demo.walt.id%2Fopenid4vc%2Fverify&
response_mode=direct_post&
state=V3pp6GFoSro6&
presentation_definition_uri=https%3A%2F%2Fverifier.demo.walt.id%2Fopenid4vc%2Fpd%2FV3pp6GFoSro6&
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=0c4e8d3c-1017-4a38-b6d2-468e4a2c13f8&
response_uri=https%3A%2F%2Fverifier.demo.walt.id%2Fopenid4vc%2Fverify%2FV3pp6GFoSro6
```
**Body Parameters**
The original presentation request received from the verifier.
---
**Example Response**
```
openid4vp://authorize?response_type=vp_token&client_id=https%3A%2F%2Fverifier.demo.walt.id%2Fopenid4vc%2Fverify&response_mode=direct_post&state=V3pp6GFoSro6&presentation_definition=%7B%22id%22%3A%22GgLtLahfRjuK%22%2C%22input_descriptors%22%3A%5B%7B%22id%22%3A%22VerifiableId%22%2C%22format%22%3A%7B%22jwt_vc_json%22%3A%7B%22alg%22%3A%5B%22EdDSA%22%5D%7D%7D%2C%22constraints%22%3A%7B%22fields%22%3A%5B%7B%22path%22%3A%5B%22%24.type%22%5D%2C%22filter%22%3A%7B%22type%22%3A%22string%22%2C%22pattern%22%3A%22VerifiableId%22%7D%7D%5D%7D%7D%5D%7D&presentation_definition_uri=https%3A%2F%2Fverifier.demo.walt.id%2Fopenid4vc%2Fpd%2FV3pp6GFoSro6&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=0c4e8d3c-1017-4a38-b6d2-468e4a2c13f8&response_uri=https%3A%2F%2Fverifier.demo.walt.id%2Fopenid4vc%2Fverify%2FV3pp6GFoSro6
```
#### 4.2. Fulfill Presentation Request
**Option: CURL**
**Endpoint:**
`/wallet-api/wallet/{wallet}/exchange/usePresentationRequest`| [API Reference](https://wallet.demo.walt.id/swagger/index.html#/Credential%20exchange/post_wallet_api_wallet__wallet__exchange_usePresentationRequest)
**Example Request**
```bash
curl -X 'POST' \
'http://0.0.0.0:7001/wallet-api/wallet/{wallet_id}/exchange/usePresentationRequest' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-H 'authorization: Bearer {loginToken}' \
-d '{
"presentationRequest": "{resolvedPresentationRequestFromLastStep}",
"selectedCredentials": [
"{credentialIDMatchFromStepBeforeLastStep}"
]
}'
```
**Path Parameters**
- **wallet_id** - Extract needed wallet id parameter via the `/wallet-api/wallet/accounts/wallets` endpoint as explained [here](#retrieve-wallet-id). Provided as part of the URL path.
**Header Parameters**
- `authorization`: _String_ - Bearer token obtained from the [login step](#login-to-wallet-account). Format: `Bearer {loginToken}`.
**Body**
```json
{
"presentationRequest": "openid4vp://authorize?response_type=vp_token&client_id=https%3A%2F%2Fverifier.demo.walt.id%2Fopenid4vc%2Fverify&response_mode=direct_post&state=pXZz119vn5SG&presentation_definition=%7B%22id%22%3A%222k6OTMoo2wbU%22%2C%22input_descriptors%22%3A%5B%7B%22id%22%3A%22VerifiableId%22%2C%22format%22%3A%7B%22jwt_vc_json%22%3A%7B%22alg%22%3A%5B%22EdDSA%22%5D%7D%7D%2C%22constraints%22%3A%7B%22fields%22%3A%5B%7B%22path%22%3A%5B%22%24.type%22%5D%2C%22filter%22%3A%7B%22type%22%3A%22string%22%2C%22pattern%22%3A%22VerifiableId%22%7D%7D%5D%7D%7D%5D%7D&presentation_definition_uri=https%3A%2F%2Fverifier.demo.walt.id%2Fopenid4vc%2Fpd%2FpXZz119vn5SG&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=d319f72a-cadf-4b15-827f-8ed0e9f755c0&response_uri=https%3A%2F%2Fverifier.demo.walt.id%2Fopenid4vc%2Fverify%2FpXZz119vn5SG",
"selectedCredentials": [
"urn:uuid:fb09ba3d-b73d-49ad-9d88-3d018878ea83"
]
}
```
**Body Parameters**
- `presentationRequest`: _String_ - response from previous call
to [resolvePresentationRequest](https://docs.walt.id/community-stack/wallet/credential-exchange/guides/present-vc-oid4vp#_41-resolve-presentation-request)
- `selectedCredentials`: _Array_ - list of credential id's to share which we received from the call
to [find matching credentials](https://docs.walt.id/community-stack/wallet/credential-exchange/guides/present-vc-oid4vp#_2-find-credentials-matching-presentation-definition)
- `disclosures`: (optional) _Object_ - This object specifies which selectively disclosable fields of a credential should
be shared with the verifier. It is applicable only if the credential to be shared contains selective disclosure
attributes. The object uses the credential ID as the key(s) and an array of the disclosures of the fields to be
disclosed by credential as values.
**Example:**
```json
"disclosures": {
"urn:uuid:fb09ba3d-b73d-49ad-9d88-3d018878ea83": [
"WyJiZ1I5OTdnRUVRNEU5bFpXNEhwVjRRPT0iLCJuYW1lIiwiSkZGIHggdmMtZWR1IFBsdWdGZXN0IDMgSW50ZXJvcGVyYWJpbGl0eSJd"
],
"urn:uuid:fb0234-b7234d-55ad-25d88-3d018878ea83": [
"WyJiZ1I5OTdnRUVRNEU5bFpXNEhwVjRRPT0iLCJuYW1lIiwiSkZGIHggdmMtZWR1IFBsdWdGZXN0IDMgSW50ZXJvcGVyYWJpbGl0eSJd"
]
}
```
**Explanation:**
- **Key**: The unique identifier (ID) of the credential to be shared.
- **Value**: An array containing the disclosures of credential fields that should be disclosed.
This structure allows you to specify which parts of a credential are shared on a per-credential basis, enhancing
privacy and control over the information disclosed.
---
**Example Response**
If the verifier provided a redirect URI we will receive it as a response.
```json
{
"redirectUri": null
}
```
## Verify Credential Presentation
Now we return to the Verifier to confirm whether the information provided by the wallet is valid and has successfully
passed all necessary checks.
**Option: CURL**
**Endpoint:**
`/openid4vc/session/{id}`| [API Reference](https://verifier.demo.walt.id/swagger/index.html#/Credential%20Verification/get_openid4vc_session__id_)
First, we need to return to the original Authorization Request that [we created](#create-authorization-request) and
retrieve the `state` value from it.
This state value serves as a unique identifier for this specific verification request.
**Example**
```
openid4vp://authorize?...state=a07bdb17-7d87-4965-9296-1adefcaaddd9...
```
Making the call to receive the verification result
**Example Call**
```bash
curl -X 'GET' \
'http://0.0.0.0:7003/openid4vc/session/{state}' \
-H 'accept: */*'
```
**Path Parameters**
- `state` _String_ - state value from [authorization request created](#create-authorization-request) via
the `openid4vc/verify` endpoint.
**Example Response**
```json
{
"id": "Ag9gyQkYknmI",
"presentationDefinition": {
"id": "8cu3ixcb4cm",
"input_descriptors": [
{
"id": "UniversityDegree",
"format": {
"jwt_vc_json": {
"alg": [
"EdDSA"
]
}
},
"constraints": {
"fields": [
{
"path": [
"$.vc.type"
],
"filter": {
"type": "string",
"pattern": "UniversityDegree"
}
}
]
}
}
],
"customParameters": {}
},
"tokenResponse": {
"vp_token": "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaWVWUk9XRXBFVUZKRU0wUnFVSGRYYXpBMFNHeEhVbGhzVkVod09WTk5OVE4xY0hnelRGcEpMV1JrVlNJc0luZ2lPaUpMWVUwelYxazRObWR4ZHpGdFdsUnRORk00VmtNMGJHeG5RV1ZKVjI1Q2FUZHliMEZ2WVMwNWNVWnZJbjAjMCIsInR5cCI6IkpXVCIsImFsZyI6IkVkRFNBIn0.eyJzdWIiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaWVWUk9XRXBFVUZKRU0wUnFVSGRYYXpBMFNHeEhVbGhzVkVod09WTk5OVE4xY0hnelRGcEpMV1JrVlNJc0luZ2lPaUpMWVUwelYxazRObWR4ZHpGdFdsUnRORk00VmtNMGJHeG5RV1ZKVjI1Q2FUZHliMEZ2WVMwNWNVWnZJbjAiLCJuYmYiOjE3NDQ4NzczNjAsImlhdCI6MTc0NDg3NzQyMCwianRpIjoiOGN1M2l4Y2I0Y20iLCJpc3MiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaWVWUk9XRXBFVUZKRU0wUnFVSGRYYXpBMFNHeEhVbGhzVkVod09WTk5OVE4xY0hnelRGcEpMV1JrVlNJc0luZ2lPaUpMWVUwelYxazRObWR4ZHpGdFdsUnRORk00VmtNMGJHeG5RV1ZKVjI1Q2FUZHliMEZ2WVMwNWNVWnZJbjAiLCJub25jZSI6ImIxNmZkNDY2LTM4NDktNDc2Mi1hZGE4LTJjZmU3MzdkYWJhNSIsImF1ZCI6Imh0dHA6Ly9ob3N0LmRvY2tlci5pbnRlcm5hbDo3MDAzL29wZW5pZDR2Yy92ZXJpZnkiLCJ2cCI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVQcmVzZW50YXRpb24iXSwiaWQiOiI4Y3UzaXhjYjRjbSIsImhvbGRlciI6ImRpZDpqd2s6ZXlKcmRIa2lPaUpQUzFBaUxDSmpjbllpT2lKRlpESTFOVEU1SWl3aWEybGtJam9pZVZST1dFcEVVRkpFTTBScVVIZFhhekEwU0d4SFVsaHNWRWh3T1ZOTk5UTjFjSGd6VEZwSkxXUmtWU0lzSW5naU9pSkxZVTB6VjFrNE5tZHhkekZ0V2xSdE5GTTRWa00wYkd4blFXVkpWMjVDYVRkeWIwRnZZUzA1Y1VadkluMCIsInZlcmlmaWFibGVDcmVkZW50aWFsIjpbImV5SnJhV1FpT2lKa2FXUTZhbmRyT21WNVNuSmtTR3RwVDJsS1VGTXhRV2xNUTBwcVkyNVphVTlwU2taYVJFa3hUbFJGTlVscGQybGhNbXhyU1dwdmFXRlZjRTVWZWxacFlURndWMU5YZUhWWk1scDRXREI0YlZneFRqRmxSVzk1VTI1U1VrNVZhREpaV0c4elpFWmtVV0pyUm5GV1ZsWnJZM2xKYzBsdVoybFBhVXBIVjIxU01tUXdUVFJaVldSdlZXNWtlR1ZzWkhka1IxWnhUVVUxWVZvelVqTlhWVVpLVFZaT05WSnRZM2hpVlhSRlVsWlNVRnB1UmtaSmJqQWphVXBOVXpWaWExcFdTV3h1WTJaeFgweG1YMU4xZUVveVNuUlJOVWgyWVhvM2RGZFFia0ZxVlZWa2N5SXNJblI1Y0NJNklrcFhWQ0lzSW1Gc1p5STZJa1ZrUkZOQkluMC5leUpwYzNNaU9pSmthV1E2YW5kck9tVjVTbkprU0d0cFQybEtVRk14UVdsTVEwcHFZMjVaYVU5cFNrWmFSRWt4VGxSRk5VbHBkMmxoTW14clNXcHZhV0ZWY0U1VmVsWnBZVEZ3VjFOWGVIVlpNbHA0V0RCNGJWZ3hUakZsUlc5NVUyNVNVazVWYURKWldHOHpaRVprVVdKclJuRldWbFpyWTNsSmMwbHVaMmxQYVVwSFYyMVNNbVF3VFRSWlZXUnZWVzVrZUdWc1pIZGtSMVp4VFVVMVlWb3pVak5YVlVaS1RWWk9OVkp0WTNoaVZYUkZVbFpTVUZwdVJrWkpiakFpTENKemRXSWlPaUprYVdRNmFuZHJPbVY1U25Ka1NHdHBUMmxLVUZNeFFXbE1RMHBxWTI1WmFVOXBTa1phUkVreFRsUkZOVWxwZDJsaE1teHJTV3B2YVdWV1VrOVhSWEJGVlVaS1JVMHdVbkZWU0dSWVlYcEJNRk5IZUVoVmJHaHpWa1ZvZDA5V1RrNU9WRTR4WTBobmVsUkdjRXBNVjFKclZsTkpjMGx1WjJsUGFVcE1XVlV3ZWxZeGF6Uk9iV1I0WkhwR2RGZHNVblJPUmswMFZtdE5NR0pIZUc1UlYxWktWakkxUTJGVVpIbGlNRVoyV1ZNd05XTlZXblpKYmpBaUxDSjJZeUk2ZXlKQVkyOXVkR1Y0ZENJNld5Sm9kSFJ3Y3pvdkwzZDNkeTUzTXk1dmNtY3ZNakF4T0M5amNtVmtaVzUwYVdGc2N5OTJNU0lzSW1oMGRIQnpPaTh2ZDNkM0xuY3pMbTl5Wnk4eU1ERTRMMk55WldSbGJuUnBZV3h6TDJWNFlXMXdiR1Z6TDNZeElsMHNJbWxrSWpvaWRYSnVPblYxYVdRNk9EQmhPREUzTnpFdE56azRNUzAwWkdFMkxUZ3laV1F0TWpCaE9USTJOekppWm1GbUlpd2lkSGx3WlNJNld5SldaWEpwWm1saFlteGxRM0psWkdWdWRHbGhiQ0lzSWxWdWFYWmxjbk5wZEhsRVpXZHlaV1VpWFN3aWFYTnpkV1Z5SWpwN0ltbGtJam9pWkdsa09tcDNhenBsZVVweVpFaHJhVTlwU2xCVE1VRnBURU5LYW1OdVdXbFBhVXBHV2tSSk1VNVVSVFZKYVhkcFlUSnNhMGxxYjJsaFZYQk9WWHBXYVdFeGNGZFRWM2gxV1RKYWVGZ3dlRzFZTVU0eFpVVnZlVk51VWxKT1ZXZ3lXVmh2TTJSR1pGRmlhMFp4VmxaV2EyTjVTWE5KYm1kcFQybEtSMWR0VWpKa01FMDBXVlZrYjFWdVpIaGxiR1IzWkVkV2NVMUZOV0ZhTTFJelYxVkdTazFXVGpWU2JXTjRZbFYwUlZKV1VsQmFia1pHU1c0d0luMHNJbWx6YzNWaGJtTmxSR0YwWlNJNklqSXdNalV0TURRdE1UZFVNRGM2TkRnNk5EWXVNelkxTkRrM01EZzJXaUlzSW1OeVpXUmxiblJwWVd4VGRXSnFaV04wSWpwN0ltbGtJam9pWkdsa09tcDNhenBsZVVweVpFaHJhVTlwU2xCVE1VRnBURU5LYW1OdVdXbFBhVXBHV2tSSk1VNVVSVFZKYVhkcFlUSnNhMGxxYjJsbFZsSlBWMFZ3UlZWR1NrVk5NRkp4VlVoa1dHRjZRVEJUUjNoSVZXeG9jMVpGYUhkUFZrNU9UbFJPTVdOSVozcFVSbkJLVEZkU2ExWlRTWE5KYm1kcFQybEtURmxWTUhwV01XczBUbTFrZUdSNlJuUlhiRkowVGtaTk5GWnJUVEJpUjNodVVWZFdTbFl5TlVOaFZHUjVZakJHZGxsVE1EVmpWVnAyU1c0d0lpd2laR1ZuY21WbElqcDdJblI1Y0dVaU9pSkNZV05vWld4dmNrUmxaM0psWlNJc0ltNWhiV1VpT2lKQ1lXTm9aV3h2Y2lCdlppQlRZMmxsYm1ObElHRnVaQ0JCY25SekluMTlMQ0psZUhCcGNtRjBhVzl1UkdGMFpTSTZJakl3TWpZdE1EUXRNVGRVTURjNk5EZzZORFl1TXpZMk1EQTJNemMzV2lKOUxDSnFkR2tpT2lKMWNtNDZkWFZwWkRvNE1HRTRNVGMzTVMwM09UZ3hMVFJrWVRZdE9ESmxaQzB5TUdFNU1qWTNNbUptWVdZaUxDSmxlSEFpT2pFM056WTBNVEl4TWpZc0ltbGhkQ0k2TVRjME5EZzNOakV5Tml3aWJtSm1Jam94TnpRME9EYzJNVEkyZlEuSVNPNmhxRmVNdnpBOE4tRXRKcEFYd2h2dWZTaWIxYk9FYTFWdGhhMWlpSGJHN1plTVFGVll6ZnJmbnBpRmJXM2ExaV9ySVRud3RCS2VLNkVVUGdUQkEiXX19.AUqf8cy4UgaEbL4-2dsJ1fkEf_xPRJkbBhOJJOJHw8XsV6xDAXFEaakw6IMyr47cpUr9C-O1nji4-tHjbocyCg",
"presentation_submission": {
"id": "8cu3ixcb4cm",
"definition_id": "8cu3ixcb4cm",
"descriptor_map": [
{
"id": "UniversityDegree",
"format": "jwt_vp",
"path": "$",
"path_nested": {
"id": "UniversityDegree",
"format": "jwt_vc_json",
"path": "$.verifiableCredential[0]",
"customParameters": {}
}
}
]
},
"state": "Ag9gyQkYknmI",
"customParameters": {}
},
"verificationResult": true,
"policyResults": {
"results": [
{
"credential": "VerifiablePresentation",
"policyResults": [
{
"policy": "signature",
"description": "Checks a JWT credential by verifying its cryptographic signature using the key referenced by the DID in `iss`.",
"is_success": true,
"result": {
"sub": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoieVROWEpEUFJEM0RqUHdXazA0SGxHUlhsVEhwOVNNNTN1cHgzTFpJLWRkVSIsIngiOiJLYU0zV1k4NmdxdzFtWlRtNFM4VkM0bGxnQWVJV25CaTdyb0FvYS05cUZvIn0",
"nbf": 1744877360,
"iat": 1744877420,
"jti": "8cu3ixcb4cm",
"iss": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoieVROWEpEUFJEM0RqUHdXazA0SGxHUlhsVEhwOVNNNTN1cHgzTFpJLWRkVSIsIngiOiJLYU0zV1k4NmdxdzFtWlRtNFM4VkM0bGxnQWVJV25CaTdyb0FvYS05cUZvIn0",
"nonce": "b16fd466-3849-4762-ada8-2cfe737daba5",
"aud": "http://host.docker.internal:7003/openid4vc/verify",
"vp": {
"@context": [
"https://www.w3.org/2018/credentials/v1"
],
"type": [
"VerifiablePresentation"
],
"id": "8cu3ixcb4cm",
"holder": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoieVROWEpEUFJEM0RqUHdXazA0SGxHUlhsVEhwOVNNNTN1cHgzTFpJLWRkVSIsIngiOiJLYU0zV1k4NmdxdzFtWlRtNFM4VkM0bGxnQWVJV25CaTdyb0FvYS05cUZvIn0",
"verifiableCredential": [
"eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaWFVcE5VelZpYTFwV1NXeHVZMlp4WDB4bVgxTjFlRW95U25SUk5VaDJZWG8zZEZkUWJrRnFWVlZrY3lJc0luZ2lPaUpHV21SMmQwTTRZVWRvVW5keGVsZHdkR1ZxTUU1YVozUjNXVUZKTVZONVJtY3hiVXRFUlZSUFpuRkZJbjAjaUpNUzVia1pWSWxuY2ZxX0xmX1N1eEoySnRRNUh2YXo3dFdQbkFqVVVkcyIsInR5cCI6IkpXVCIsImFsZyI6IkVkRFNBIn0.eyJpc3MiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaWFVcE5VelZpYTFwV1NXeHVZMlp4WDB4bVgxTjFlRW95U25SUk5VaDJZWG8zZEZkUWJrRnFWVlZrY3lJc0luZ2lPaUpHV21SMmQwTTRZVWRvVW5keGVsZHdkR1ZxTUU1YVozUjNXVUZKTVZONVJtY3hiVXRFUlZSUFpuRkZJbjAiLCJzdWIiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaWVWUk9XRXBFVUZKRU0wUnFVSGRYYXpBMFNHeEhVbGhzVkVod09WTk5OVE4xY0hnelRGcEpMV1JrVlNJc0luZ2lPaUpMWVUwelYxazRObWR4ZHpGdFdsUnRORk00VmtNMGJHeG5RV1ZKVjI1Q2FUZHliMEZ2WVMwNWNVWnZJbjAiLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL2V4YW1wbGVzL3YxIl0sImlkIjoidXJuOnV1aWQ6ODBhODE3NzEtNzk4MS00ZGE2LTgyZWQtMjBhOTI2NzJiZmFmIiwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIlVuaXZlcnNpdHlEZWdyZWUiXSwiaXNzdWVyIjp7ImlkIjoiZGlkOmp3azpleUpyZEhraU9pSlBTMUFpTENKamNuWWlPaUpGWkRJMU5URTVJaXdpYTJsa0lqb2lhVXBOVXpWaWExcFdTV3h1WTJaeFgweG1YMU4xZUVveVNuUlJOVWgyWVhvM2RGZFFia0ZxVlZWa2N5SXNJbmdpT2lKR1dtUjJkME00WVVkb1VuZHhlbGR3ZEdWcU1FNWFaM1IzV1VGSk1WTjVSbWN4YlV0RVJWUlBabkZGSW4wIn0sImlzc3VhbmNlRGF0ZSI6IjIwMjUtMDQtMTdUMDc6NDg6NDYuMzY1NDk3MDg2WiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmp3azpleUpyZEhraU9pSlBTMUFpTENKamNuWWlPaUpGWkRJMU5URTVJaXdpYTJsa0lqb2llVlJPV0VwRVVGSkVNMFJxVUhkWGF6QTBTR3hIVWxoc1ZFaHdPVk5OTlROMWNIZ3pURnBKTFdSa1ZTSXNJbmdpT2lKTFlVMHpWMWs0Tm1keGR6RnRXbFJ0TkZNNFZrTTBiR3huUVdWSlYyNUNhVGR5YjBGdllTMDVjVVp2SW4wIiwiZGVncmVlIjp7InR5cGUiOiJCYWNoZWxvckRlZ3JlZSIsIm5hbWUiOiJCYWNoZWxvciBvZiBTY2llbmNlIGFuZCBBcnRzIn19LCJleHBpcmF0aW9uRGF0ZSI6IjIwMjYtMDQtMTdUMDc6NDg6NDYuMzY2MDA2Mzc3WiJ9LCJqdGkiOiJ1cm46dXVpZDo4MGE4MTc3MS03OTgxLTRkYTYtODJlZC0yMGE5MjY3MmJmYWYiLCJleHAiOjE3NzY0MTIxMjYsImlhdCI6MTc0NDg3NjEyNiwibmJmIjoxNzQ0ODc2MTI2fQ.ISO6hqFeMvzA8N-EtJpAXwhvufSib1bOEa1Vtha1iiHbG7ZeMQFVYzfrfnpiFbW3a1i_rITnwtBKeK6EUPgTBA"
]
}
}
}
]
},
{
"credential": "UniversityDegree",
"policyResults": [
{
"policy": "signature",
"description": "Checks a JWT credential by verifying its cryptographic signature using the key referenced by the DID in `iss`.",
"is_success": true,
"result": {
"iss": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiaUpNUzVia1pWSWxuY2ZxX0xmX1N1eEoySnRRNUh2YXo3dFdQbkFqVVVkcyIsIngiOiJGWmR2d0M4YUdoUndxeldwdGVqME5aZ3R3WUFJMVN5RmcxbUtERVRPZnFFIn0",
"sub": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoieVROWEpEUFJEM0RqUHdXazA0SGxHUlhsVEhwOVNNNTN1cHgzTFpJLWRkVSIsIngiOiJLYU0zV1k4NmdxdzFtWlRtNFM4VkM0bGxnQWVJV25CaTdyb0FvYS05cUZvIn0",
"vc": {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1"
],
"id": "urn:uuid:80a81771-7981-4da6-82ed-20a92672bfaf",
"type": [
"VerifiableCredential",
"UniversityDegree"
],
"issuer": {
"id": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiaUpNUzVia1pWSWxuY2ZxX0xmX1N1eEoySnRRNUh2YXo3dFdQbkFqVVVkcyIsIngiOiJGWmR2d0M4YUdoUndxeldwdGVqME5aZ3R3WUFJMVN5RmcxbUtERVRPZnFFIn0"
},
"issuanceDate": "2025-04-17T07:48:46.365497086Z",
"credentialSubject": {
"id": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoieVROWEpEUFJEM0RqUHdXazA0SGxHUlhsVEhwOVNNNTN1cHgzTFpJLWRkVSIsIngiOiJLYU0zV1k4NmdxdzFtWlRtNFM4VkM0bGxnQWVJV25CaTdyb0FvYS05cUZvIn0",
"degree": {
"type": "BachelorDegree",
"name": "Bachelor of Science and Arts"
}
},
"expirationDate": "2026-04-17T07:48:46.366006377Z"
},
"jti": "urn:uuid:80a81771-7981-4da6-82ed-20a92672bfaf",
"exp": 1776412126,
"iat": 1744876126,
"nbf": 1744876126
}
}
]
}
],
"time": "PT2.675756835S",
"policiesRun": 2
},
"customParameters": {}
}
```
**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:
```json
{
"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.
## Next Steps
**Note:**
If you enjoy our tools, please leave us a star ⭐ [on GitHub](https://github.com/walt-id/waltid-identity).
You can learn more about the the different APIs in detail below:
- [Wallet](https://docs.walt.id/community-stack/wallet){:target="_blank"}
- [Issuer](https://docs.walt.id/community-stack/issuer){:target="_blank"}
- [Verifier](https://docs.walt.id/community-stack/verifier){:target="_blank"}
--------------------------------------------------------------------------------
PAGE: What is the Community Stack
URL: https://docs.walt.id/community-stack/home/the-community-stack
RAW: https://docs.walt.id/community-stack/home/the-community-stack.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# What is the Community Stack
The walt.id Community Stack is **a set of open-source tools designed for developers and organizations
to build robust end-to-end identity and wallet solutions** across industries and ecosystems. It is designed to
facilitate
the development and deployment of holistic identity solutions, ensuring modularity, interoperability, and ease of use.
## Architecture and Structure
The walt.id Community Stack provides all the necessary functionalities to
launch identity solutions seamlessly. **Its architecture can be broadly categorized into multi-platform libraries,
services and APIs, Applications and external services and integrations** as shown below in the architecture image.

### Multi-Platform Libraries
The multi-platform libraries based on Kotlin Multiplatform bring **support for a variety of programming languages
including
Kotlin/Java, JavaScript and more in the future**. These libraries allow mix and matching of identity features,
credentials
formats, and ecosystem functionalities to ensure a compact, concise and performant codebase.
#### Core Libraries:
- [**Crypto**](https://github.com/walt-id/waltid-identity/tree/main/waltid-libraries/crypto/waltid-crypto): Manages keypair and
signature functionalities.
- [**SD-JWT**](https://github.com/walt-id/waltid-identity/tree/main/waltid-libraries/sdjwt/waltid-sdjwt): Issues Selective
Disclosure JSON-Web Tokens.
- [**DIDs**](https://github.com/walt-id/waltid-identity/tree/main/waltid-libraries/waltid-did): Manages Decentralized
Identifiers.
- [**OpenID4VC**](https://github.com/walt-id/waltid-identity/tree/main/waltid-libraries/protocols/waltid-openid4vc): Facilitates
protocol issuance and verification.
#### Credential Libraries:
- [**Verifiable Credentials**](https://github.com/walt-id/waltid-identity/tree/main/waltid-libraries/credentials/waltid-w3c-credentials): Issues and
verifies W3C Credentials with corresponding signatures (JWT, SD-JWT, JSON-LD).
- [**mdoc Credentials**](https://github.com/walt-id/waltid-identity/tree/main/waltid-libraries/credentials/waltid-mdoc-credentials):
Creates and verifies mdoc credentials (mDL)
### Services and APIs
The services and APIs are designed to enhance applications with Issuer, Verifier, and ID wallet capabilities.
- [**Issuer API**](https://docs.walt.id/community-stack/issuer/getting-started.md): Allows applications to issue credentials (VCs,
mdocs) and tokens (NFTs, SBTs) across ecosystems.
- [**Verifier API**](https://docs.walt.id/community-stack/verifier/getting-started.md): Enables applications to verify credentials or
tokens based on a variety of verification policies.
- [**Wallet API**](https://docs.walt.id/community-stack/wallet/getting-started.md): Extends applications with identity wallet
capabilities such as collecting, storing, managing, and
sharing identity credentials or tokens.
### Applications
To ensure a faster go to market or use-case development, the walt.id Community Stack includes customizable white-label
applications.
- [**Web Issuer**](https://docs.walt.id/community-stack/issuer/apps/portal/overview.md): A web
portal or claim page supporting protocols like OIDC4VC.
- [**Web Verifier**](https://docs.walt.id/community-stack/verifier/apps/portal/overview.md): A web
portal or verification page supporting protocols like OIDC4VP.
- [**Wallet**](https://docs.walt.id/community-stack/wallet/apps/web-wallet/overview.md): A custodial
web-wallet for collecting, managing, and sharing credentials (VCs, mdocs) and displaying tokens (NFTs,
SBTs) based on a progressive web app (PWA) that works across devices.
### External Services & Integrations
The Community Stack is built to be interoperable with third-party services and tools like:
- KMS/Hardware Keystores
- Trust Registries
- TSP Signature providers
- Various data storage options
The openness of the stack allows developers and organizations to enhance the stack by integrating additional external key storage
solutions, trust service providers, and identity verification services not already supported by default.
## Next steps
- [Issuer](https://docs.walt.id/community-stack/issuer) - enable apps to issue credentials (W3C JWTs and SD-JWTs) via OID4VC.
- [Verifier](https://docs.walt.id/community-stack/verifier) - enable apps to verify credentials (W3C JWTs and SD-JWTs) via OID4VP/SIOPv2.
- [Wallet](https://docs.walt.id/community-stack/wallet) - extend apps with wallet capabilities to collect, store, manage and share identity credentials and tokens.
--------------------------------------------------------------------------------
PAGE: FAQs
URL: https://docs.walt.id/community-stack/home/faq
RAW: https://docs.walt.id/community-stack/home/faq.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Frequently Asked Questions
This page addresses common questions about the walt.id Community Stack, its components, setup, and usage.
## General Questions
**Q: What is the walt.id Community Stack?**
A: The walt.id Community Stack is a set of open-source tools designed for developers and organizations to build robust end-to-end digital identity and wallet solutions across industries and ecosystems. It provides all the necessary functionalities to launch identity solutions (issuer, verifier, and wallet) seamlessly, including multi-platform libraries, services and APIs, and white-label applications.
**Q: What's the difference between the Community Stack and Enterprise Stack?**
A: The Community Stack is open-source and designed to build simple end-to-end digital ID solutions in various environments using APIs, SDKs, and white-label apps. Its API services are stateless and typically deployed as single instances via Docker. The SDKs, written in Kotlin and available for Java, enable direct integration of digital ID features. The Enterprise Stack builds on top of the Community Stack and adds enterprise capabilities such as multi-tenancy support, built-in GUI management tools, lifecycle management, credential revocation mechanisms, logs, audits, pre-built integrations, and more. The Enterprise Stack is ideal for large organizations, governments, and businesses offering digital identity platforms or SaaS solutions that need to scale. Learn [here](https://docs.walt.id/enterprise-stack/home.md).
**Q: What are the main components of the Community Stack?**
A: The Community Stack consists of:
- **Multi-Platform Libraries**: Core libraries (Crypto, SD-JWT, DIDs, OpenID4VC), credential libraries (W3C Verifiable Credentials, Mobile Driver's Licenses (ISO/IEC 18013-5 mDLs)), and ecosystem libraries
- **Services and APIs**: [Issuer API](https://docs.walt.id/community-stack/issuer/getting-started.md), [Verifier API](https://docs.walt.id/community-stack/verifier/getting-started.md), and [Wallet API](https://docs.walt.id/community-stack/wallet/getting-started.md)
- **Applications**: White-label web applications (Web Issuer, Web Verifier, Web Wallet)
- **External Services & Integrations**: Support for KMS/Hardware Keystores
**Q: What credential formats does the Community Stack support?**
A: The Community Stack supports multiple credential formats including:
- **SD-JWT VC (IETF)**: Selective Disclosure JSON Web Token Verifiable Credentials
- **W3C Verifiable Credentials**: Versions 1.1+ and 2.0 with JSON Web signatures
- **ISO 18013-5 mDL**: Mobile Driver's License format
- **Custom formats**: The system is extensible to support new or proprietary formats
**Q: Which protocols and standards are supported?**
A: The Community Stack supports:
- **OID4VCI**: Draft 11, Draft 13, and V1 for credential issuance
- **OID4VP**: Draft 14, 20, and V1 for credential verification
- **ISO/IEC 18013-7**: For mobile driver's license online exchange
- **Multiple DID methods**: did:key, did:jwk, did:web, did:cheqd, and more
## Architecture & Components
**Q: How does the Community Stack architecture work?**
A: The Community Stack architecture is categorized into:
1. **Multi-platform libraries** based on Kotlin Multiplatform supporting Kotlin/Java, JavaScript, and more
2. **Services and APIs** that enhance applications with Issuer, Verifier, and ID wallet capabilities
3. **Applications** providing customizable white-label solutions
4. **External services & integrations** for interoperability with third-party tools
**Q: What are the three main APIs and what do they do?**
A: The three main APIs are:
- **Issuer API**: Allows applications to issue credentials (VCs, mdocs) across ecosystems via OID4VCI
- **Verifier API**: Enables applications to verify credentials (VCs, mdocs) based on various verification policies via OID4VP
- **Wallet API**: Extends applications with identity wallet capabilities for collecting, storing, managing, and sharing identity credentials
**Q: What white-label applications are available?**
A: The Community Stack includes:
- **Web Issuer**: A web portal supporting OIDC4VC for credential issuance
- **Web Verifier**: A web portal supporting OIDC4VP for credential verification
- **Web Wallet**: A custodial web-wallet (PWA) for collecting, managing, and sharing credentials
## Setup & Deployment
**Q: How do I get started with the Community Stack?**
A: You can get started in several ways:
1. **Quick Start**: Use our [docker compose](https://github.com/walt-id/waltid-identity/tree/main/docker-compose) to spin up all services locally
2. **Individual APIs**: Set up specific APIs (Issuer, Verifier, Wallet) using Docker or open-source installation
3. **White-label Apps**: Use the pre-built web applications for immediate testing
4. **SDKs**: Integrate the Kotlin/Java libraries directly into your applications
**Q: What are the system requirements for running the Community Stack?**
A: The Community Stack requires:
- **Docker**: For containerized deployment
- **Java/Kotlin**: For SDK usage
- **Node.js**: For web applications (if running locally)
**Q: Can I run the Community Stack without Docker?**
A: Yes, you can run individual components without Docker:
- **APIs**: Build from source using Gradle/Maven
- **Web Apps**: Run locally with Node.js
- **SDKs**: Include as dependencies in your Kotlin/Java projects
**Q: How do I access the services once they're running?**
A: When running via docker-compose, you can access:
- **Issuer API**: [Local](http://localhost:7002/) | [Deployed](https://issuer.demo.walt.id/swagger/index.html)
- **Verifier API**: [Local](http://localhost:7003/) | [Deployed](https://verifier.demo.walt.id/swagger/index.html)
- **Wallet API**: [Local](http://localhost:7001/) | [Deployed](https://wallet.demo.walt.id/swagger/index.html#/)
- **Web Wallet**: [Local](http://localhost:7101/) | [Deployed](https://wallet.demo.walt.id/login)
- **Web Portal**: [Local](http://localhost:7102/) | [Deployed](https://portal.walt.id/)
## Key Management & Security
**Q: How does key management work in the Community Stack?**
A: The Community Stack is KMS-agnostic and supports:
- **External KMS**: AWS KMS, Azure Key Vault, Google Cloud KMS, Hashicorp Vault, OCI KMS
- **Raw key material**: For development and PoC environments
- **Multiple algorithms**: ed25519, secp256k1, secp256r1, and RSA
**Q: What cryptographic algorithms are supported?**
A: The Community Stack supports multiple asymmetric key types including ed25519, secp256k1, secp256r1, and RSA.
**Q: How do I integrate with my existing KMS infrastructure?**
A: The system supports multiple authentication methods:
- **Cloud environments**: Use automatically assigned roles (like AWS IAM roles) for keyless authentication
- **Other KMS providers**: Use API keys for authentication
- **Custom integrations**: Extend the system to support your specific KMS requirements
## Credential Management
**Q: How can I collect and manage credential data?**
A: There are three main approaches:
1. **Before credential offer creation**: Collect all data upfront from external sources (CRM, databases, registries)
2. **After offer creation, before signing**: Use [data functions](https://docs.walt.id/community-stack/issuer/credential-issuance/data-functions.md) to dynamically enrich credentials with timestamps, DIDs, or external API calls
3. **During user authentication**: Pull verified claims from identity providers during the OID4VCI authorization flow
**Q: How do I handle credential revocation and status management?**
A: In the Community Stack, you can issue credentials with status information, but you're responsible for hosting and managing the status credentials externally. The Enterprise Stack includes built-in credential status management with support for standards like Bitstring Status List, Token Status List, and StatusList2021.
**Q: Can I set expiration dates on credentials?**
A: Yes, credentials can include expiration and "valid from" dates. These can be set explicitly during credential creation or generated dynamically using timestamp data functions. This is useful for time-limited credentials like event tickets, student IDs, or transportation passes.
## Integration & Development
**Q: How do I integrate the Community Stack with my existing systems?**
A: The Community Stack is designed for interoperability:
- **APIs**: RESTful APIs for easy integration
- **SDKs**: Kotlin/Java libraries for direct integration
- **External services**: Support for existing KMS, identity providers, and data storage
**Q: What programming languages are supported?**
A: The Community Stack supports:
- **Kotlin/Java**: Primary SDK support
- **Any language**: Via REST APIs
- **Future support**: Additional languages planned
**Q: How do I customize credential appearance in wallets?**
A: Credential branding can be configured in two ways:
1. **Issuer metadata**: Define consistent styling per credential type (background color, logo, description)
2. **Embedded branding**: Include visual attributes directly in individual credentials for personalized presentation
**Q: What if I need to support a new credential format not currently supported?**
A: The walt.id system is built for extensibility. New credential formats can be added as dedicated Kotlin libraries and integrated through the shared digital-credential abstraction layer. Contact us at tamino@walt.id for more information.
## Troubleshooting
**Q: The services won't start. What should I check?**
A: Common issues include:
- **Docker not running**: Ensure Docker is installed and running
- **Port conflicts**: Check if ports 7001, 7002, 7003, 7101, 7102 are available
- **Configuration files**: Verify config files are properly mounted and formatted
**Q: How do I monitor the health of my Community Stack deployment?**
A: The Community Stack provides:
- **Health check endpoints**: `/livez` endpoints on all APIs
- **Docker logs**: Container-level logging for deployment issues
## Compliance & Standards
**Q: Is the Community Stack compliant with eIDAS2 requirements?**
A: The Community Stack is continiously aligned with the EU's emerging digital ID ecosystem under eIDAS2, including ARF and implementing acts. This ensures credentials can be issued to different wallets, including EUDI wallets.
## Ecosystem Support
**Q: What identity ecosystems does the Community Stack support?**
A: The Community Stack supports multiple ecosystems including:
- **[eIDAS2](https://walt.id/eidas2)**: EU digital identity framework
- **EBSI**: European Blockchain Services Infrastructure
- **cheqd**: Decentralized identity network
- **iota**: Distributed ledger technology
- **Custom ecosystems**: Extensible for proprietary or new ecosystems
**Q: How do I integrate with a specific ecosystem?**
A: Ecosystem integration typically involves:
1. **Trust registry setup**: Configure trust registries for the ecosystem
2. **DID method configuration**: Set up appropriate DID methods
3. **Credential schema registration**: Register credential schemas with the ecosystem
4. **Compliance verification**: Ensure compliance with ecosystem requirements
## Support & Community
**Q: Where can I get help with the Community Stack?**
A: Several resources are available:
- **Documentation**: Comprehensive guides and API references
- **GitHub**: [walt.id identity repository](https://github.com/walt-id/waltid-identity) for issues and discussions
- **Community**: [Join our community channel](https://walt.id/discord) for peer support
**Q: How do I contribute to the Community Stack?**
A: Contributions are welcome:
- **GitHub**: Submit issues, feature requests, and pull requests
- **Testing**: Test and report bugs or compatibility issues
- **Community**: Share use cases and best practices
---
### Still Have Questions?
If you couldn't find the answer to your question here, please:
- Check our [comprehensive documentation](https://docs.walt.id/community-stack/home.md)
- Visit our [GitHub repository](https://github.com/walt-id/waltid-identity)
- Contact us at [tamino@walt.id](mailto:tamino@walt.id) for specific inquiries
--------------------------------------------------------------------------------
PAGE: Getting Started
URL: https://docs.walt.id/community-stack/issuer/getting-started
RAW: https://docs.walt.id/community-stack/issuer/getting-started.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Getting Started
Video: https://www.youtube.com/watch?v=uywefBo4XL0
The walt.id issuer API allows you to **sign and issue various types of digital credentials (W3C, SD-JWT-VC and mDL) via OID4VC**. The issuer and
subject can optionally be identified by various DID methods like did:key, did:jwk, did:web, did:cheqd and more.
Use [data functions](https://docs.walt.id/community-stack/issuer/credential-issuance/data-functions.md) to dynamically populate the credential with up-to-date information from
your backend during issuance.
## Supported Standards
| | |
|:-------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **Credential Formats:** | [SD-JWT VC (IETF)](https://docs.walt.id/concepts/digital-credentials/sd-jwt-vc.md), [W3C VC (v1.1+, v2.0)](https://docs.walt.id/concepts/digital-credentials/verifiable-credentials-w3c.md), [ISO 18013-5 mDL](https://docs.walt.id/concepts/digital-credentials/mdoc-mdl-iso.md) |
| **Credential Exchange:** | [OID4VCI](https://docs.walt.id/concepts/data-exchange-protocols.md) (Draft 11/13, v1 coming soon),[ ISO/IEC 18013-7](https://docs.walt.id/concepts/data-exchange-protocols.md) |
| **Credential Status:** | [StatusList2021, Bitstring Status List, Token Status List](https://docs.walt.id/concepts/credential-lifecycle/credential-status-and-revocation.md) |
| **Signing Algorithms:** | ed25519, secp256k1, secp256r1, RSA |
## Setup
- [Open-Source](https://docs.walt.id/community-stack/issuer/setup.md) - Use the issuer API available via the [walt.id identity-repo](https://github.com/walt-id/waltid-identity.git).
- [Docker compose quick start](https://docs.walt.id/community-stack/home.md#quick-start) - Spin up the issuer API locally together with the other services.
- [Full API reference](https://docs.walt.id/community-stack/issuer/full-api-reference.md) - Explore all available endpoints via Swagger.
## Features
- [Keys / DIDs](https://docs.walt.id/community-stack/issuer/key-management/overview.md) - Generate and use keys/DIDs secured by an external KMS or your custom storage.
- [Credentials](https://docs.walt.id/community-stack/issuer/credential-issuance/overview.md) - Issue credentials via OID4VC.
- [Data Functions](https://docs.walt.id/community-stack/issuer/credential-issuance/data-functions.md) - Populate credentials with up-to-date information.
Checkout the full feature overview [here](https://docs.walt.id/community-stack/issuer/features).
## Ecosystems
- [EBSI](https://docs.walt.id/community-stack/issuer/ecosystems/ebsi/overview.md) - Issue credentials that are compliant with the European Blockchain Services Infrastructure (EBSI).
## Configurations
Use [the feature manager](https://docs.walt.id/community-stack/issuer/configurations/feature-manager.md) to enable or disable API functionality depending on your use-case.
## Guides
- [W3C VC issuance](https://docs.walt.id/community-stack/issuer/credential-issuance/vc-oid4vc.md) - Issue W3C Verifiable Credentials with JWT or SD-JWT signatures.
- [SD-JWT VC issuance](https://docs.walt.id/community-stack/issuer/credential-issuance/sd-jwt-vc-oid4vc.md) - Issue IETF SD-JWT VC credentials.
- [mDL issuance](https://docs.walt.id/community-stack/issuer/credential-issuance/mdl-oid4vc.md) - Issue ISO/IEC 18013-5 Mobile Driver's Licenses.
--------------------------------------------------------------------------------
PAGE: Overview
URL: https://docs.walt.id/community-stack/issuer/capabilities
RAW: https://docs.walt.id/community-stack/issuer/capabilities.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Overview
The Community Stack Issuer API is a stateless, lightweight REST API designed for building credential issuance solutions.
## Credential Issuance Capabilites
--------------------------------------------------------------------------------
PAGE: Issuer Identifiers
URL: https://docs.walt.id/community-stack/issuer/capabilities/did-identifiers
RAW: https://docs.walt.id/community-stack/issuer/capabilities/did-identifiers.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Issuer Identifiers
The Community Stack supports multiple identifier methods for your issuer—from decentralized identifiers (DIDs) to traditional X.509 certificates. This gives you flexibility to align with your ecosystem's trust model, regulatory requirements, and operational constraints.
**Note:**
"How is the issuer identified in credentials?"
## What's included
The Community Stack supports three main approaches to issuer identification, each with different operational characteristics.
### DID-based Identifiers
Your issuer uses a **Decentralized Identifier (DID)** as its identity. The DID resolves to a DID document containing the issuer's public keys, service endpoints, and metadata. When a verifier checks a credential, they resolve the issuer DID and verify the signature against the published keys.
**Supported DID methods** include, but are not limited to:
- **`did:key`** – Self-contained DID where the key is embedded in the identifier itself
- **`did:jwk`** – DID based on JSON Web Key format
- **`did:web`** – Web-based DID that resolves via HTTPS
- **`did:ebsi`** – EBSI-compliant DID for European ecosystem interoperability
### X.509 Certificate-based Identifiers
Your issuer uses an **X.509 certificate** issued by a trusted Certificate Authority (CA). The certificate contains the issuer's public key and identity information. Verifiers validate credentials by checking the certificate chain back to a trusted root CA.
### Direct Key References
Your issuer uses a **direct cryptographic key reference** (such as a JWK – JSON Web Key) without a broader identity framework. The key itself serves as the identifier, and trust is established through out-of-band mechanisms (pre-shared key lists, trust registries, or direct configuration).
## Trust & Verification
Identifier methods tell you *who* the issuer is. But verifiers also need to determine *whether to trust that issuer* for a given credential type.
Ecosystems establish trust through mechanisms like:
- **Trust registries and trust lists** (e.g., EBSI TAO, eIDAS2 trust lists)
- **Certificate Authority hierarchies** (traditional PKI trust chains)
- **Direct trust relationships** (pre-configured trusted issuers)
These trust mechanisms work independently of the identifier method you choose. A DID, certificate, or direct key can all be registered in a trust registry or validated against ecosystem policies.
## Key Management Integration
Regardless of which identifier method you choose, the underlying cryptographic keys used for signing are stored and managed through the Community Stack's [key management system](https://docs.walt.id/community-stack/issuer/key-management/overview.md).
For proof-of-concepts and testing, local key storage is available. For production deployments, external key management systems keep key material secure (Hashicorp Vault, Azure Key Vault, AWS KMS, OCI Vault, etc.). Learn more [here](https://docs.walt.id/community-stack/issuer/key-management/overview.md).
## FAQs
**Do I need a DID to issue credentials?**
No. You can use X.509 certificates or direct key references. The choice depends on your ecosystem's trust model and regulatory requirements.
**What's the difference between a DID and a key?**
A key is the cryptographic material used to sign credentials. A DID is a persistent identifier that resolves to a DID document containing keys (and other metadata). Keys can be rotated; the DID stays the same. A "direct key reference" uses the key itself as an identifier, which means rotating the key changes the identifier.
**Can I switch identifier methods later?**
Yes, but previously issued credentials will still reference the old identifier. Switching is effectively creating a new issuer identity. Plan your identifier strategy early; changes require ecosystem coordination (updating trust registries, verifier configurations, etc.).
## Get Started
- **[Set up your issuer](https://docs.walt.id/community-stack/issuer/setup.md)** – Configure your first issuer.
- **[Key management](https://docs.walt.id/community-stack/issuer/key-management/overview.md)** – Learn how to securely store and manage the cryptographic keys backing your issuer identifier.
- **[Issue your first credential](https://docs.walt.id/community-stack/issuer/credential-issuance/vc-oid4vc.md)** – See how your identifier appears in issued credentials and how wallets verify it.
--------------------------------------------------------------------------------
PAGE: Data & Identity Sourcing
URL: https://docs.walt.id/community-stack/issuer/capabilities/data-sourcing
RAW: https://docs.walt.id/community-stack/issuer/capabilities/data-sourcing.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Data & Identity Sourcing
Data & Identity Sourcing describes how the Community Stack Issuer obtains the attributes that go into a credential – for example name, date of birth, identifiers, roles, or any other claims about the subject.
The Community Stack gives you flexibility in where subject data lives and when it is fetched, allowing you to plug into existing systems (CRM, registries, IdPs, etc.) and design flows that fit your business, security, and UX requirements.
At a high level, this capability answers one question:
**Note:**
"Where do the attributes in my credential actually come from – and at what point in the issuance flow do we fetch them?"
## What's included
The Community Stack supports three complementary ways of sourcing data for credentials. You can use them individually or combine them in a single flow.
#### 1. Use your own backend as the source of truth (before offer creation)
Your backend collects all required subject attributes up front and passes them to the Community Stack Issuer API when creating a credential offer.
**This means:**
- You stay in control of where identity data lives (databases, registries, etc.).
- The Issuer API receives a complete, ready-to-sign data structure and focuses on protocol handling and signing.
- Simple, straightforward integration for most use cases.
#### 2. Enrich credentials via data functions & webhooks (after offer, before signing)
You can mark specific credential attributes as "to be filled later" and let the Issuer call [data functions](https://docs.walt.id/community-stack/issuer/credential-issuance/data-functions.md) before signing – including webhooks into your systems. Returned values are mapped into the credential right before issuance.
**This means:**
- Fetch dynamic or time-sensitive data at the last possible moment (e.g. timestamps, status flags, IDs, expiration dates).
- Call external APIs via webhooks for custom logic (e.g. fraud checks, entitlement checks, policy decisions) and use the result directly in the credential.
- Combine static attributes (provided up front) with dynamic attributes (via data functions) in a single issuance flow.
#### 3. Fetch identity data via authorization servers (OID4VCI authorization code flow)
When using the OID4VCI authorization code flow, the user is required to authenticate with an external Authorization Server / Identity Provider (IdP) before claiming a credential. After successful authentication, the Issuer API can use the resulting tokens to fetch user claims and map them into credential attributes.
**This means:**
- Use your existing identity infrastructure (e.g. Keycloak, Azure AD, custom OIDC IdP) as the trusted identity source.
- Ensure that attributes are tied to a fresh, interactive authentication event (step-up, MFA, etc.).
- Align with established IAM policies (scopes, consents, attribute release policies) while issuing verifiable credentials.
**Info:**
**Coming Soon:** Full OID4VCI authorization code flow support with external IdP integration for dynamic claims fetching is currently in development for the Community Stack and will be available soon.
## FAQs
- **Can I mix different data sources in a single credential issuance flow?** — Yes. A single issuance can combine attributes that come from your backend, attributes returned via webhooks/data functions, and (coming soon) attributes obtained from an Authorization Server after user authentication.
- **Do I need an Identity Provider to use Data & Identity Sourcing?** — No. You can issue credentials entirely based on data from your backend or via webhooks. Authorization code flow with IdP integration for dynamic claims fetching is coming soon to the Community Stack.
- **Can I keep sensitive attributes out of the credential while still using them in decisions?** — Yes. You can use data functions and webhooks to call external systems that perform risk or eligibility checks. Only the decision (e.g. "eligible = true") has to be written into the credential; sensitive raw data can remain in your own systems.
- **Can I start simple and evolve my data sourcing strategy later?** — Yes. Many deployments start with a simple backend-only model and gradually introduce data functions and webhooks as requirements become more sophisticated – without changing the underlying credential formats or wallet integration.
## Get Started
- **[Issue a credential using pre-defined data](https://docs.walt.id/community-stack/issuer/credential-issuance/sd-jwt-vc-oid4vc.md)** – Step-by-step guide showing how to pass complete subject attributes from your backend when creating a credential offer (approach 1).
- **[Use data functions to enrich credentials dynamically](https://docs.walt.id/community-stack/issuer/credential-issuance/data-functions.md)** – Learn how to mark attributes for late binding and use webhooks or built-in functions to fetch data right before signing (approach 2).
--------------------------------------------------------------------------------
PAGE: Credential Branding
URL: https://docs.walt.id/community-stack/issuer/capabilities/credential-branding
RAW: https://docs.walt.id/community-stack/issuer/capabilities/credential-branding.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Credential branding
The Community Stack Issuer API lets you control how credentials appear to wallets and users when issued—setting claim labels, issuer logos, descriptions, and background/text colors (and localized variants) so wallets can render a consistent, branded credential. Use issuer-level metadata to keep visuals consistent across a credential type, or embed display data per credential instance when you need finer-grained differentiation (e.g., ticket tiers or program variants).
## What's included
- **Issuer metadata for each credential type**
Define colors, logo, description, and display fields once per credential type (e.g. digital diploma) so wallets can render predictable visuals without hard-coding styling.
- **Per-credential display overrides**
Embed display attributes in a specific credential instance when variants of the same type need distinct branding (e.g., VIP vs. standard ticket).
- **User-friendly display fields**
Provide human-friendly labels, value hints, and ordering for claims so wallets show meaningful attribute names instead of raw keys.
- **Localization for labels**
Supply localized display metadata so wallets can show claim labels and descriptions in the user's language.
## Include display metadata at issuance (data function)
You can pull configured display metadata into an issuance request using the `display` data function in the mapping object of the issuance request. This lets you embed visuals per credential instance without re-specifying them in the payload. Learn more in the [data functions documentation](https://docs.walt.id/community-stack/issuer/credential-issuance/data-functions.md).
Note: This is optional, as the wallet could also fetch the display information from the issuer metadata directly. It really depends on the implementation and the wallet you are using.
## FAQs
- **Will other wallets show branding reliably?** — Issuer metadata follows standardized display conventions, so it's the most interoperable way to share visuals (including localization). Per-credential embedded display data is not yet standardized; the walt.id wallet supports both approaches, but third-party wallets may only honor issuer metadata and ignore in-credential display info until they add support.
- **What is a credential type?** — A concrete subtype within a credential standard. For example, under W3C VC you might have a digital diploma or digital ID; under ISO mdoc you might have mDL or a photo ID; under SD-JWT VC (IETF) you might have an identity credential. Each type can carry its own display metadata so wallets know how to present it.
## Get Started
- **[Configure credential display metadata](https://docs.walt.id/community-stack/issuer/configurations/config-files/credential-issuer-metadata.md)** – Define colors, logos, descriptions, and display fields for each credential type in the issuer metadata configuration file.
- **[Use display data functions](https://docs.walt.id/community-stack/issuer/credential-issuance/data-functions.md)** – Learn how to embed display metadata dynamically during credential issuance.
--------------------------------------------------------------------------------
PAGE: Credential Lifecycle
URL: https://docs.walt.id/community-stack/issuer/capabilities/credential-lifecycle
RAW: https://docs.walt.id/community-stack/issuer/capabilities/credential-lifecycle.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Credential lifecycle
The Community Stack Issuer API enables you to issue credentials that support lifecycle management by embedding status references and validity timestamps. You can issue credentials that reference status lists for revocation or suspension (using standards like TokenStatusList, Bitstring Status List v1.0), and include validity windows via validFrom and validUntil fields. The actual hosting and management of status credentials is handled by your own infrastructure.
## What's included
- **Embed status references in credentials**
The Community Stack can sign and issue credentials that include status field references pointing to external status lists. Supports standard formats (Bitstring Status List v1.0, TokenStatusList, StatusList2021) for tracking revocation, suspension, or custom states across credential types (W3C VC, SD-JWT VC, ISO mdoc). You provide the status list URL and bit position during issuance.
- **Set validity timestamps during issuance**
Configure validFrom and validUntil per credential—statically or via data functions—to control when a credential becomes valid and when it expires. These timestamps are embedded directly in the issued credential, requiring no external status infrastructure.
**Info:**
**Important:** The Community Stack can issue credentials with status information, but does not provide built-in hosting, management, or automatic updates of status credentials. You are responsible for hosting the status credential (status list) on your own infrastructure, updating it when credentials need to be revoked or suspended, re-signing after updates, and publishing the updated version so verifiers can access it. This differs from the Enterprise Stack, which includes a dedicated Credential Status Service that automatically handles all of these operations.
## Status Management in the Community Stack
When you issue a credential with a status field in the Community Stack:
1. **During issuance**: You specify the status credential URL and the bit position in the status list where this credential's status is tracked.
2. **After issuance**: You must implement your own system to:
- Host and serve the status credential (status list) at the URL you referenced
- Update the status list when credentials need to be revoked or suspended
- Re-sign the updated status list
- Publish the new version so verifiers can fetch it
The Issuer API handles signing the credential with the status reference, but lifecycle management is your responsibility.
## FAQs
- **Do verifiers need online connectivity?** — They typically fetch or cache the referenced status list. Offline verification works as long as the verifier has a fresh copy of the list; otherwise, it should treat status as unknown until it can refresh.
- **Can I use the Community Stack for production credential revocation?** — Yes, but you need to build the status credential hosting and management infrastructure yourself. For production deployments that need turnkey status management, consider the Enterprise Stack's Credential Status Service.
- **What status list formats are supported?** — The Community Stack can issue credentials that reference any of the major status list standards: Bitstring Status List v1.0, TokenStatusList, StatusList2021, and RevocationList2020.
## Get Started
- **[Use data functions for validity dates](https://docs.walt.id/community-stack/issuer/credential-issuance/data-functions.md)** – Learn how to set validFrom and validUntil timestamps dynamically.
--------------------------------------------------------------------------------
PAGE: Credential Templates, Types, and Schemas
URL: https://docs.walt.id/community-stack/issuer/capabilities/credential-templates-types-schemas
RAW: https://docs.walt.id/community-stack/issuer/capabilities/credential-templates-types-schemas.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Credential Templates, Types, and Schemas
Credential Templates, Types, and Schemas explains how the Community Stack models what kind of credential you issue (type) and in which format—while letting you define the “template” logic via your own own system.
## What's included
The Community Stack supports:
- Multiple credential **formats** (e.g. W3C VC, SD-JWT VC, ISO mdoc/mDL).
- Arbitrary credential **types** per format (e.g. DigitalDiploma, BankID, identity_credential, PhotoID).
- Flexible **data structures** controlled by you (or by external schemas/standards, depending on the format).
- Alignment with **type metadata** concepts (e.g. SD-JWT VC vct Type Metadata) for future schema enforcement and display definitions.
#### 1. Credential Formats
A **credential format** defines how the credential is structured, serialized, and signed (for example, which fields are required and which cryptographic primitives are used).
Examples include:
- W3C Verifiable Credential (VC)
- SD-JWT VC (IETF)
- mdoc / mDL (ISO 18013-5)
When you issue a credential via the Community Stack API, you choose one of these formats so the issuer knows how to construct and sign the resulting credential.
#### 2. Credential Types
A **credential type** answers: "What is this credential?" – for example:
- DigitalDiploma, EmployeeID, BankID, eID for W3C VC.
- identity_credential, age_over_18, residency_credential for SD-JWT VC (IETF).
- org.iso.18013.5.1.mDL or PhotoID for mdoc/mDL-based credentials.
In the Community Stack, you:
- Define **credential types** in the [issuer metadata configuration](https://docs.walt.id/community-stack/issuer/configurations/config-files/credential-issuer-metadata.md) together with:
- The **format** (W3C VC, SD-JWT VC, mdoc, …).
- Optional type-level **metadata** (e.g. branding).
- Reference these types in issuance requests.
Once a type is configured, any issuance flow can issue credentials "of that type" in the chosen format. The issuer does not hard-code which attributes that type must contain – unless the underlying format or external standards require it.
#### 3. Data Structures & Schemas
Where traditional "template engines" define schema inside the issuer product, the Community Stack assumes:
- Schemas are defined and managed in your own systems, not hard-coded into the issuer.
- The issuer will sign whatever payload you provide, as long as:
- It is valid for the selected format, and
- It respects any external schema/standard you use.
In detail:
**W3C VC**
- You can define your own schemas (for example using JSON Schema) for the claims you want to include.
- You design the structure needed for a given type (e.g. BankID: iban, bankName, holderName, riskLevel, …).
- The Community Stack checks that the credential is a valid W3C VC in JWT/SD-JWT form; it does not impose extra "template rules" on top.
**SD-JWT VC (IETF)**
- You decide which claims exist and which can be selectively disclosed.
- The SD-JWT VC spec introduces:
- `vct` (Verifiable Credential Type) – an identifier for the credential type.
- Type Metadata – which describes which claims MUST / MAY appear and optional display properties.
- This gives you a standards-based way to express "templates" for SD-JWT VCs that wallets and verifiers can understand.
- In the Community Stack, you can freely define and use your own `vct` values. If you rely on externally hosted Type Metadata documents, your own systems are responsible for ensuring that issuance payloads conform to those definitions.
**ISO mdoc**
- These formats come with prescribed data elements and namespaces (e.g. org.iso.18013.5.1.mDL).
- Some attributes are mandatory (e.g. family name, birth date); others are optional.
- The issuer enforces the format-level rules, but does not add an additional, proprietary template layer.
At the end, **you are not limited in what attributes you put into a credential**, as long as they comply with the rules of the chosen format and any external schema or Type Metadata you've decided to follow.
## "Templates" Without a Templating Engine
So how do you get template-like behavior without a "template" feature in the Community Stack?
You combine three layers:
1. **Configuration level (in the Community Stack)**
- Define credential types + formats in your [issuer metadata configuration](https://docs.walt.id/community-stack/issuer/configurations/config-files/credential-issuer-metadata.md).
- Optionally configure display metadata for labels and branding.
- Reference these types in your issuance requests.
2. **Application level (in your backend)**
- Maintain your own schemas or data models for each type.
- Validate data structure and assemble the final credential payload before calling the Issuer API.
- Reuse structures and mappings across your applications (effectively your "templates").
3. **Standards level**
- For **SD-JWT VC**, use `vct` and Type Metadata to describe schemas and display rules.
- For **mdoc / mDL**, rely on ISO-defined docTypes and data element definitions.
- For **W3C VC**, use JSON schemas or other schema languages your stack already uses.
## FAQs
- **Do you support credential templates?**
We don't ship a built-in "template engine" where schemas are hard-coded into the issuer. Instead, we give you a flexible model of formats + types + schemas. You model "templates" in your own systems (or via open standards like SD-JWT VC Type Metadata), and the Community Stack issues credentials based on those definitions.
- **What's the difference between a credential format and a credential type?**
A format defines how the credential is encoded and signed (e.g. W3C VC in JWT/SD-JWT form, SD-JWT VC, mdoc). A type defines what the credential represents (e.g. DigitalDiploma, BankID, identity_credential). You usually define type/format pairs in your issuer metadata.
- **Can I restrict which attributes are allowed for a given credential type?**
Yes – but this restriction lives in your schemas and rules, not in a rigid built-in template. You can:
- Validate against your own schema before calling the Issuer API.
- Use SD-JWT VC Type Metadata (vct) to describe required/optional claims and display rules.
- **Do you limit which claims I can put into a credential?**
No. As long as the credential is valid for the chosen format (and any external schema/standard you use), you are free to include the claims that make sense for your use case. For mdoc/mDL, you must respect the mandatory/optional attributes defined in the ISO standards.
## Get Started
- **[Configure issuer metadata](https://docs.walt.id/community-stack/issuer/configurations/config-files/credential-issuer-metadata.md)** – Learn how to define credential types and formats in the issuer metadata configuration file.
- **[Issue your first credential](https://docs.walt.id/community-stack/issuer/credential-issuance/sd-jwt-vc-oid4vc.md)** – Step-by-step guide showing how to issue credentials using your configured types.
--------------------------------------------------------------------------------
PAGE: Credential Delivery
URL: https://docs.walt.id/community-stack/issuer/capabilities/credential-delivery
RAW: https://docs.walt.id/community-stack/issuer/capabilities/credential-delivery.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Credential Delivery
Credential Delivery describes how a credential gets from the Community Stack Issuer API into the holder's wallet. It focuses on the protocol flows used to issue credentials (based on [OpenID for Verifiable Credential Issuance, OID4VCI](https://docs.walt.id/concepts/data-exchange-protocols/openid4vci.md)) and the device journeys (same-device vs cross-device) used to deliver credential offers.
The Community Stack does not constrain how you integrate issuance into your applications. It exposes simple API endpoints that return an OID4VCI standard compliant credential offer URL. You decide whether that URL becomes a QR code, a deep link behind a button, or both.
**Note:**
"Which OID4VCI flow do we use, and how does the credential offer actually reach the user's wallet – on the same device or across devices?"
## What's included
The Community Stack supports different **issuance flows** and **device journeys** for delivering credentials. These two dimensions are independent and can be combined freely.
**Issuance flows (how users prove they're eligible and how the wallet gets permission to fetch the credential):**
- Pre-Authorized Code Flow (with and without PIN)
- Authorization Code Flow (coming soon)
**Device journeys (how the credential offer reaches the user's wallet – on the same device or across devices):**
- Same-device credential offers (link / button)
- Cross-device credential offers (QR code scanned from another device)
#### 1. Pre-Authorized Code Issuance Flow (with and without PIN)
In the **pre-authorized code issuance flow**, the issuer has already verified the user's eligibility (via your backend, existing session, or business logic). The credential offer is generated for a specific user and includes a pre-authorized code that grants the wallet immediate permission to fetch the credential – no interactive authentication required by the receiver of the credential.
The Community Stack supports both variants defined by OID4VCI:
- **Without PIN** – the wallet uses the pre-authorized code directly to get permission and request the credential.
- **With PIN** – the offer indicates that a user PIN (transaction code) is required. The PIN is delivered via a separate channel (e.g. email, SMS, in-person) and must be entered in the wallet when claiming the credential.
**This means:**
- Eligibility is determined upstream (e.g. back-office process, portal, CRM) before the offer is created.
- You can use existing secure channels (logged-in portals, emails, SMS, letters) to deliver offers and PINs.
- You can add a "second factor" for sensitive credentials (pre-authorized code + PIN).
- You avoid re-authenticating the user at issuance time when you already trust your upstream process.
#### 2. Authorization Code Issuance Flow
In the **authorization code issuance flow**, the credential offer can be shared broadly (e.g. any user visiting a page), but the user must authenticate with an external Authorization Server / Identity Provider (IdP) before they can receive the credential.
**This means:**
- The offer can be long-lived and reused by any eligible user.
- Users prove they're eligible by signing in at your IdP (e.g. Keycloak, Azure AD, custom OIDC IdP) during the flow.
- After successful authentication, the wallet exchanges the authorization code for a token and then uses that permission to request the credential from the Issuer API.
- You can combine this with [Data & Identity Sourcing](https://docs.walt.id/community-stack/issuer/capabilities/data-sourcing.md) to fetch user claims from the Authorization Server and inject them into the credential at issuance time.
**Info:**
**Coming Soon:** Full authorization code flow support with external IdP integration is currently in development for the Community Stack and will be available soon.
### 3. Device Journeys: Same-device & Cross-device Offers
Regardless of which **issuance flow** you use (pre-authorized code or authorization code), the **device journey** defines how the user encounters and accepts the credential offer:
#### Cross-device flow
- The credential offer URL is encoded as a **QR code** on one device (e.g. desktop web application, kiosk).
- The user scans the QR code with their mobile wallet and continues the flow on their phone.
- This is ideal when the user interacts with an issuer on a desktop/laptop, but the wallet lives on a smartphone.
#### Same-device flow
- The credential offer URL is embedded in a link or button on the same device that runs the wallet.
- The user taps the link (e.g. in an email or web page on their phone), which opens the wallet and displays the credential offer.
- This is ideal when everything happens on a single device (e.g. mobile-first experiences).
**With the Community Stack:**
- The Issuer API returns a standards-compliant OID4VCI **credential offer URL**.
- You are free to:
- Turn that URL into a QR code (cross-device).
- Use it directly as a deep link (same-device).
- Offer both options in parallel.
- The choice of same-device vs cross-device is **independent** of the issuance flow (pre-authorized code or authorization code, with or without PIN).
## FAQs
- **Is a PIN required for the pre-authorized code issuance flow?** - No. The PIN is optional. In the Community Stack, you can configure pre-authorized offers with or without a PIN and decide when a second factor (transaction code) is appropriate for your use case.
- **What's the difference between same-device and cross-device flows?** - In a same-device journey, the user taps a link on the same device that holds the wallet (e.g. from an email on their phone). In a cross-device journey, the user interacts with the issuer on one device (e.g. desktop) and scans a QR code with a wallet on another device (e.g. phone). Both can be used with any issuance flow and require no extra issuer configuration beyond generating the offer URL.
- **Does the Community Stack require a specific wallet?** - No. The issuer API uses standard OID4VCI endpoints and credential offer semantics. Any wallet that implements the relevant OID4VCI versions and issuance flows can claim credentials from the Community Stack Issuer API.
- **How do QR codes relate to OID4VCI issuance flows?**
In OpenID for Verifiable Credential Issuance (OID4VCI), a QR code is simply a user-friendly way to deliver an issuance URL to a wallet app – typically a **credential offer URL** (and, depending on the flow, parameters such as a pre-authorized code). The QR code itself usually just encodes this URL. When the user scans it with their wallet, the wallet opens the URL and continues the OID4VCI issuance flow (pre-authorized code or authorization code, with or without PIN). In same-device journeys, the same URL is usually opened via a link or button instead of a QR code. So QR codes are not a separate OID4VCI feature, but a UX pattern for starting issuance flows, especially in cross-device scenarios.
## Get Started
- **[Issue your first credential](https://docs.walt.id/community-stack/issuer/credential-issuance/sd-jwt-vc-oid4vc.md)** – Step-by-step guide showing how to generate credential offers using the pre-authorized code flow.
- **[Configure credential data sourcing](https://docs.walt.id/community-stack/issuer/capabilities/data-sourcing.md)** – Learn how to populate credential claims from your data sources.
--------------------------------------------------------------------------------
PAGE: Setup
URL: https://docs.walt.id/community-stack/issuer/setup
RAW: https://docs.walt.id/community-stack/issuer/setup.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Setup
## Installation & Running the Project
Make sure you have Docker installed on your machine.
### Option 1: Using Docker Pull
**1. Cloning The Identity-Repo**
First, clone the repository to your local machine:
```bash
git clone https://github.com/walt-id/waltid-identity.git && cd waltid-identity
```
**2. Pulling the Docker Image**
To quickly get started, you can pull the pre-built Docker image from Docker Hub:
```bash
docker pull waltid/issuer-api
```
**2. Running the Docker Image**
Once the image is pulled, you can run it using the following command:
```bash
docker run -p 7002:7002 \
-itv $(pwd)/waltid-services/waltid-issuer-api/config:/waltid-issuer-api/config \
-t waltid/issuer-api
```
### Option 2: Building the Image Yourself
**1. Cloning The Identity-Repo**
First, clone the repository to your local machine:
```bash
git clone https://github.com/walt-id/waltid-identity.git && cd waltid-identity
```
**2. Building the Docker Image**
If you prefer to build the Docker image yourself, use the Gradle Ktor/Jib tasks:
```bash
# Development (local Docker daemon, single-arch)
./gradlew :waltid-services:waltid-issuer-api:publishImageToLocalRegistry
# image: waltid/issuer-api:
```
```bash
# Production (multi-arch push to your registry)
export DOCKER_USERNAME=
export DOCKER_PASSWORD=
./gradlew :waltid-services:waltid-issuer-api:publishImage
# image: docker.io//issuer-api:
```
Note: multi-arch images require a registry push. Local tar output is single-arch only.
**3. Running the Docker Image**
After building the image, run it using (adjust the image name if you pushed to your registry):
```bash
docker run -p 7002:7002 \
-itv $(pwd)/waltid-services/waltid-issuer-api/config:/waltid-issuer-api/config \
-t waltid/issuer-api
```
## Start Issuing Credentials
Now that the issuer api is running, you can start issuing credentials.
- [How to Issue W3C Verifiable Credentials (JWT / SD-JWT) via OID4VCI](https://docs.walt.id/community-stack/issuer/credential-issuance/vc-oid4vc.md)
- [How to Issue SD-JWT-based Verifiable Credentials (IETF | SD-JWT VC) via OID4VCI](https://docs.walt.id/community-stack/issuer/credential-issuance/sd-jwt-vc-oid4vc.md)
- [How to Issue Mobile Driver’s Licenses (ISO/IEC 18013-5 MDLs) via OID4VCI](https://docs.walt.id/community-stack/issuer/credential-issuance/mdl-oid4vc.md)
- [Full API Reference](https://issuer.demo.walt.id/swagger/index.html)
--------------------------------------------------------------------------------
PAGE: Overview
URL: https://docs.walt.id/community-stack/issuer/key-management/overview
RAW: https://docs.walt.id/community-stack/issuer/key-management/overview.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Overview
The issuer API is designed to be stateless and does not store cryptographic key material. Therefore, every API call
requires the cryptographic key to be provided. When signing and issuing a credential e.g. via OID4VC, either the complete
key in JWK format or a reference to a key stored in an external KMS solution is
needed. In production systems, the recommended approach is to use an external KMS solution, so that the private key used
to sign a credential never leaves a secure environment.
**Supported KMS Solutions**
- [HashiCorp Vault](https://developer.hashicorp.com/vault)
- [AWS KMS](https://aws.amazon.com/kms/)
- [Azure Key Vault](https://azure.microsoft.com/en-us/products/key-vault/)
## Get Started
- [HashiCorp Vault](https://docs.walt.id/community-stack/issuer/key-management/hashicorp-vault.md) - Use keys from HashiCorp Vault to sign credentials
- [AWS KMS](https://docs.walt.id/community-stack/issuer/key-management/aws-kms/overview.md) - Use keys from AWS KMS to sign credentials
- [Azure Key Vault](https://docs.walt.id/community-stack/issuer/key-management/azure-kms/overview.md) - Use keys from Azure Key Vault to sign credentials
--------------------------------------------------------------------------------
PAGE: Local
URL: https://docs.walt.id/community-stack/issuer/key-management/local
RAW: https://docs.walt.id/community-stack/issuer/key-management/local.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Local
**You can create a private and public key using the issuer API** using one of the following algorithms: ed25519,
secp256k1, secp256r1, or RSA. However, **please note that the issuer API does not automatically store any cryptographic
key material.** It is your responsibility to store the key that is returned and provide it in JWK format for future
signing and issuance operations.
**If you are working in a production setup, it is highly recommended to use KMS providers like Hashicorp Vault to secure
your key material**. This will ensure that your secrets are never exposed outside of a secure environment, reducing the
risk of key compromises. For a list of KMS integrations that are compatible with walt.id, please visit
the [overview page](https://docs.walt.id/community-stack/issuer/key-management/overview.md).
## Key Creation
To create a key and get an associated DID at the same time, we will be utilizing the `/onboard/issuer` endpoint.
**Option: CURL**
**Endpoint:** `/onboard/issuer` | [API Reference](https://issuer.demo.walt.id/swagger/index.html#/Issuer%20onboarding/post_onboard_issuer)
**Example Request**
```bash
curl -X 'POST' \
'http://0.0.0.0:7002/onboard/issuer' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"key": {
"backend": "jwk",
"keyType": "secp256r1"
},
"did": {
"method": "jwk"
}
}'
```
**Body**
```json
{
"key": {
"backend": "jwk",
"keyType": "secp256r1"
},
"did": {
"method": "jwk"
}
}
```
**Body Parameters**
- `key`
- `backend`: _String_ - Specifies the storage type of key. It can be `jwk` (manged by you), `TSE` (managed by
Hashicorp
Vault) and others. Learn more about different types [here](https://docs.walt.id/community-stack/issuer/key-management/overview.md).
- `keyType`: _String_ - the algorithm used to generate the key. For local, it can
be ed25519, secp256k1, secp256r1, or RSA. For the other types and the supported algorithms, please
go [here](https://docs.walt.id/community-stack/issuer/key-management/overview.md).
- `did`:
- `method`: _String_ - Specifies the DID method. It can be key, jwk, web, cheqd.
---
**Example Response**
The onboard/issuer endpoint will return an object containing both the generated key in JWK format and the related DID.
```json
{
"issuerKey": {
"type": "jwk",
"jwk": "{\"kty\":\"EC\",\"d\":\"nnITt7w11Gehk_oA9bxOQrz1GGzMECiBNoVejKOn2CA\",\"crv\":\"P-256\",\"kid\":\"fQod7zGbGsO14yVnNT-cslwFyKgEPjKsYHQIfGqQMFI\",\"x\":\"EJHmd2wZpmLCnmN49bQeOSn6NDb8kfeXrfpr1K1U8FY\",\"y\":\"Wxzi0fayJU80JJZOl1-XUrZzEU5AcAQxUdO69-gmBmc\"}"
},
"issuerDid": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiZlFvZDd6R2JHc08xNHlWbk5ULWNzbHdGeUtnRVBqS3NZSFFJZkdxUU1GSSIsIngiOiJFSkhtZDJ3WnBtTENubU40OWJRZU9TbjZORGI4a2ZlWHJmcHIxSzFVOEZZIiwieSI6Ild4emkwZmF5SlU4MEpKWk9sMS1YVXJaekVVNUFjQVF4VWRPNjktZ21CbWMifQ"
}
```
--------------------------------------------------------------------------------
PAGE: HashiCorp Vault Transit Secrets Engine
URL: https://docs.walt.id/community-stack/issuer/key-management/hashicorp-vault
RAW: https://docs.walt.id/community-stack/issuer/key-management/hashicorp-vault.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# HashiCorp Vault Transit Secrets Engine
Video: https://youtu.be/htSvJ-rIHUk
The issuer API can leverage HashiCorp
Vault's [Transit Secrets Engine](https://developer.hashicorp.com/vault/docs/secrets/transit)
to hook into the "cryptography as a service" or "encryption as a service" features provided
by [HashiCorp Vault](https://developer.hashicorp.com/vault). With
that, the issuer API can sign and verify credentials whilst keeping the secrets (signing keys) in an external
secure environment.
If you are new to HashiCorp Vault, make sure to check out the
guides [here](https://developer.hashicorp.com/vault/tutorials/getting-started/getting-started-intro).
The rest of this document assumes you already know how Vault works and have a transit secrets engine setup and
running.
## Setting Up Authentication Method
Once you have the HashiCorp Server up and running and enabled the transit secrets engine mentioned above, we need to
either enable AppRole Authentication or User/Pass Authentication to use HashiCorp Vault with the walt.id issuer API.
### Setup Policy
Before enabling and configuring authentication methods, a policy must be created to define the permissions and actions
that authenticated users or roles are allowed to perform.
**Create The Policy File**
```bash
echo 'path "transit/*" {
capabilities = ["create", "update", "read", "delete", "list"]
}' > transit-policy.hcl
```
**Check the Client Configuration**
If you are configuring vault locally make sure that the client is configured to
communicate via HTTP. This can be achieved by setting the `VAULT_ADDR` environment
variable.
```bash
export VAULT_ADDR=https://127.0.0.1:8200
```
**Upload the Policy**
```bash
vault policy write transit-policy transit-policy.hcl
# Expected result:
# Success! Uploaded policy: transit-policy
```
### Setup Auth Method
**Option: AppRole Auth**
To enable AppRole auth you have two options, the Web UI of HashiCorp Vault or the CLI:
**Option: Web UI**
1. Navigate to `Access`.
2. Choose `Authentication Methods`.
3. Click on `Enable new method`.
4. Select `Generic / AppRole`.
5. Click `Enable method`.
_Please note: You might want to disable lockout: select the created approle method,
press "Configure", scroll to "User lockout configuration" and select "Disable lockout for this mount"._
**Option: CLI**
In the CLI enter:
```bash
vault auth enable approle
```
_Please note: You might want to disable lockout: Use the `disable_lockout` parameter._
### Configure AppRole
**Add a Role**
```bash
vault write auth/approle/role/my-role token_type=batch
# Additional parameters can include:
# secret_id_ttl, token_num_uses, token_ttl, token_max_ttl, secret_id_num_uses
```
**Show Role ID**
```bash
vault read auth/approle/role/my-role/role-id
# Save the value of role_id for later use.
```
**Issue a Secret ID**
```bash
vault write -f auth/approle/role/my-role/secret-id
# Save the value of secret_id for later use.
```
**Associate Policy with AppRole**
```bash
vault write auth/approle/role/my-role token_policies="transit-policy"
# Expected result:
# Success! Data written to: auth/approle/role/my-role
```
If you already have a token policy with the AppRole:
```bash
vault write auth/approle/role/my-role token_policies="existing-policy,transit-policy"
```
**Option: User/Pass Auth**
**Option: CLI**
**Enable Userpass**
```bash
vault auth enable userpass
# Alternatively with a custom path
vault auth enable -path= userpass
# Expected result:
# Success! Enabled userpass auth method at: userpass/
```
**Add User**
```bash
vault write auth//users/myuser password=mypassword policies=mypolicy
# Creates user "myuser", password "mypassword", associated with "mypolicy".
# Example policy: transit-policy (see above).
```
## Key Creation
To create the key you can use the onboard endpoint provided by the issuer API or the CLI, UI or the API from
Vault's [Transit Secrets Engine](https://developer.hashicorp.com/vault/docs/secrets/transit).
The issuer API accepts walt.id `keyType` values and maps them to the matching HashiCorp Vault Transit key type before creating or resolving the key.
| walt.id `keyType` | Vault Transit key type |
|---|---|
| `Ed25519` | `ed25519` |
| `secp256r1` | `ecdsa-p256` |
| `secp384r1` | `ecdsa-p384` |
| `secp521r1` | `ecdsa-p521` |
| `RSA` | `rsa-2048` |
| `RSA3072` | `rsa-3072` |
| `RSA4096` | `rsa-4096` |
`secp256k1` is not supported by the TSE integration. See the HashiCorp Vault Transit Secrets Engine key types [full list](https://developer.hashicorp.com/vault/docs/secrets/transit#key-types) for the provider-side names.
### Creation via Issuer API
**Option: CURL**
**Endpoint:** `/onboard/issuer` | [API Reference](https://issuer.demo.walt.id/swagger/index.html#/Issuer%20onboarding/post_onboard_issuer)
**Example Request**
**Option: AppRole Auth**
```bash
curl -X 'POST' \
'https://issuer.demo.walt.id/onboard/issuer' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"key": {
"backend": "tse",
"keyType": "Ed25519",
"config": {
"server": "http://127.0.0.1:8200/v1/transit",
"auth": {
"roleId": "my_role",
"secretId": "my_secret"
},
"namespace": "optional"
}
},
"did": {
"method": "key"
}
}'
```
**Body**
```json
{
"key": {
"backend": "tse",
"keyType": "Ed25519",
"config": {
"server": "http://127.0.0.1:8200/v1/transit",
"auth": {
"roleId": "my_role",
"secretId": "my_secret"
},
"namespace": "optional"
}
},
"did": {
"method": "key"
}
}
```
**Body Parameters**
- `key`
- `backend`: _String_ - Specifies the storage type of key. It can be `jwk` (managed by you), `TSE` (managed by
HashiCorp Vault) and others. Learn more about different types [here](https://docs.walt.id/community-stack/issuer/key-management/overview.md).
- `keyType`: _String_ - the algorithm used to generate the key. For Vault TSE, use one of `Ed25519`, `secp256r1`, `secp384r1`, `secp521r1`, `RSA`, `RSA3072`, or `RSA4096`.
- `config`
- `server`: _URL_ - The endpoint of your Vault instance. Following the structure of
`https:///v1/`, e.g. "https://vault.walt.id/v1/transit". By default the transit
engine will live at /transit.
- `auth`:
- **AppRole Auth:**
- `roleId`: _String_ - The Role ID for AppRole authentication.
- `secretId`: _String_ - The Secret ID for AppRole authentication.
- **User/Pass Auth:**
- `username`: _String_ - The username for user/pass authentication.
- `password`: _String_ - The password for user/pass authentication.
- **AccessToken Auth (Deprecated):**
- `accessKey`: _String_ - The access token for token-based authentication.
- `namespace`: _String_ (optional) - The namespace within the Vault instance.
- `did`:
- `method`: _String_ - Specifies the DID method. It can be key, jwk, web, cheqd.
**Option: User/Pass Auth**
```bash
curl -X 'POST' \
'https://issuer.demo.walt.id/onboard/issuer' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"key": {
"backend": "tse",
"keyType": "Ed25519",
"config": {
"server": "http://127.0.0.1:8200/v1/transit",
"auth": {
"username": "foo",
"password": "bar"
},
"namespace": "optional"
}
},
"did": {
"method": "key"
}
}'
```
**Body**
```json
{
"key": {
"backend": "tse",
"keyType": "Ed25519",
"config": {
"server": "http://127.0.0.1:8200/v1/transit",
"auth": {
"username": "foo",
"password": "bar"
},
"namespace": "optional"
}
},
"did": {
"method": "key"
}
}
```
**Body Parameters**
- `key`
- `backend`: _String_ - Specifies the storage type of key. It can be `jwk` (managed by you), `TSE` (managed by
HashiCorp Vault) and others. Learn more about different types [here](https://docs.walt.id/community-stack/issuer/key-management/overview.md).
- `keyType`: _String_ - the algorithm used to generate the key. For Vault TSE, use one of `Ed25519`, `secp256r1`, `secp384r1`, `secp521r1`, `RSA`, `RSA3072`, or `RSA4096`.
- `config`
- `server`: _URL_ - The endpoint of your Vault instance. Following the structure of
`https:///v1/`, e.g. "https://vault.walt.id/v1/transit". By default the transit
engine will live at /transit.
- `auth`:
- **AppRole Auth:**
- `roleId`: _String_ - The Role ID for AppRole authentication.
- `secretId`: _String_ - The Secret ID for AppRole authentication.
- **User/Pass Auth:**
- `username`: _String_ - The username for user/pass authentication.
- `password`: _String_ - The password for user/pass authentication.
- **AccessToken Auth (Deprecated):**
- `accessKey`: _String_ - The access token for token-based authentication.
- `namespace`: _String_ (optional) - The namespace within the Vault instance.
- `did`:
- `method`: _String_ - Specifies the DID method. It can be key, jwk, web, cheqd.
**Option: AccessToken Auth (Deprecated)**
```bash
curl -X 'POST' \
'https://issuer.demo.walt.id/onboard/issuer' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"key": {
"backend": "tse",
"keyType": "Ed25519",
"config": {
"server": "http://127.0.0.1:8200/v1/transit",
"auth": {
"accessKey": "123456"
},
"namespace": "optional"
}
},
"did": {
"method": "key"
}
}'
```
**Body**
```json
{
"key": {
"backend": "tse",
"keyType": "Ed25519",
"config": {
"server": "http://127.0.0.1:8200/v1/transit",
"auth": {
"accessKey": "123456"
},
"namespace": "optional"
}
},
"did": {
"method": "key"
}
}
```
**Body Parameters**
- `key`
- `backend`: _String_ - Specifies the storage type of key. It can be `jwk` (managed by you), `TSE` (managed by
HashiCorp Vault) and others. Learn more about different types [here](https://docs.walt.id/community-stack/issuer/key-management/overview.md).
- `keyType`: _String_ - the algorithm used to generate the key. For Vault TSE, use one of `Ed25519`, `secp256r1`, `secp384r1`, `secp521r1`, `RSA`, `RSA3072`, or `RSA4096`.
- `config`
- `server`: _URL_ - The endpoint of your Vault instance. Following the structure of
`https:///v1/`, e.g. "https://vault.walt.id/v1/transit". By default the transit
engine will live at /transit.
- `auth`:
- **AppRole Auth:**
- `roleId`: _String_ - The Role ID for AppRole authentication.
- `secretId`: _String_ - The Secret ID for AppRole authentication.
- **User/Pass Auth:**
- `username`: _String_ - The username for user/pass authentication.
- `password`: _String_ - The password for user/pass authentication.
- **AccessToken Auth (Deprecated):**
- `accessKey`: _String_ - The access token for token-based authentication.
- `namespace`: _String_ (optional) - The namespace within the Vault instance.
- `did`:
- `method`: _String_ - Specifies the DID method. It can be key, jwk, web, cheqd.
---
**Example Response**
The onboard/issuer endpoint will return an object containing both the generated key in JWK format and the related DID.
```json
{
"issuerKey": {
"type": "tse",
"server": "http://127.0.0.1:8200/v1/transit",
"auth": {
"roleId": "b50427bd-5070-0bec-3a00-dfe470ba349d",
"secretId": "22ffc0f4-c308-e6d6-f5a4-246cd89bc17c"
},
"id": "k732628061",
"_publicKey": [
86,
-55,
122,
-77,
109,
-60,
61,
-40,
-122,
-57,
-101,
-53,
69,
-125,
12,
92,
-9,
89,
61,
-78,
116,
-76,
111,
-58,
-63,
-122,
-26,
79,
-110,
-32,
-73,
-98
],
"_keyType": "Ed25519"
},
"issuerDid": "did:key:z6MkkJ3JAhCpSGe5QD9UQ2WGzna7kcswa9ahMC2o3Dd1KRXb"
}
```
**Properties**
- `type`: _String_ - the type of key can be either "tse" when using HashiCorp Vault or "jwk" when providing the key in
full as JWK.
- `server`: _URL_ - The endpoint of your Vault instance. Following the structure
of `https:///v1/`, e.g. "https://vault.walt.id/v1/transit". By default
the transit engine will live at `/transit`.
- `auth`:
- **AppRole Auth:**
- `roleId`: _String_ - The Role ID for AppRole authentication.
- `secretId`: _String_ - The Secret ID for AppRole authentication.
- **User/Pass Auth:**
- `username`: _String_ - The username for user/pass authentication.
- `password`: _String_ - The password for user/pass authentication.
- **AccessToken Auth (Deprecated):**
- `accessKey`: _String_ - The access token for token-based authentication.
- `id`: _String_ - the ID of the key in the Transit Engine.
- `_publicKey` (optional): _Array_ - The public key can be fetched by the issuer API or directly provided, saving
resources and reducing network requests.
- `_keyType` (optional): _String_ - The key type can be fetched by the issuer API or directly provided, saving resources
and reducing network requests.
## Key Usage
Once you have successfully created a key that is one of the supported types listed above, you can use it in
sign and issue operations offered by the issuer API.
If you've already had a look at
our `/sign`, `/issue`, `/batchIssue`
endpoints, you have seen that they all follow a similar request body structure, where the key that should be
used for signing credentials is provided via the `issuerKey` property. Now instead of providing the key as JWK, we
provide
a reference to a key stored in Vault with the required parameters and access credentials.
Below you can see an example of
`issuerKey` object referencing a key stored in Vault.
**Option: Example IssuerKey Object**
```json
{
"type": "tse",
"server": "https://vault.walt.id/v1/transit",
"auth": {
"roleId": "b50427bd-5070-0bec-3a00-dfe470ba349d",
"secretId": "22ffc0f4-c308-e6d6-f5a4-246cd89bc17c"
},
"id": "k1474327600",
"_publicKey": [
-17,
83,
1,
26,
9,
-104,
-64,
-50,
48,
92,
-112,
-76,
7,
90,
-40,
-100,
72,
106,
-51,
-58,
64,
-26,
73,
121,
26,
-118,
39,
-8,
-25,
-34,
82,
37
],
"_keyType": "Ed25519"
}
```
**Properties**
- `type`: _String_ - the type of key can be either "tse" when using HashiCorp Vault or "jwk" when providing the key in
full as JWK.
- `server`: _URL_ - The endpoint of your Vault instance. Following the structure
of `https:///v1/`, e.g. "https://vault.walt.id/v1/transit". By default
the transit engine will live at `/transit`.
- `auth`:
- **AppRole Auth:**
- `roleId`: _String_ - The Role ID for AppRole authentication.
- `secretId`: _String_ - The Secret ID for AppRole authentication.
- **User/Pass Auth:**
- `username`: _String_ - The username for user/pass authentication.
- `password`: _String_ - The password for user/pass authentication.
- **AccessToken Auth (Deprecated):**
- `accessKey`: _String_ - The access token for token-based authentication.
- `id`: _String_ - the ID of the key in the Transit Engine.
- `_publicKey` (optional): _Array_ - The public key can be fetched by the issuer API or directly provided, saving
resources and reducing network requests.
- `_keyType` (optional): _String_ - The key type can be fetched by the issuer API or directly provided, saving resources
and reducing network requests.
## Example Issuance Request
Below you can see example issuance request
to [`jwt/issue`](https://issuer.demo.walt.id/swagger/index.html#/Credential%20Issuance/post_openid4vc_jwt_issue) using
a key created in the Vault Transit Secrets Engine to
sign the credential.
**Option: CURL**
[Api Reference](https://issuer.demo.walt.id/swagger/index.html#/Credential%20Issuance/post_openid4vc_jwt_issue)
```bash
curl -X 'POST' \
'https://issuer.demo.walt.id/openid4vc/jwt/issue' \
-H 'accept: text/plain' \
-H 'statusCallbackUri: https://example.com/$id' \
-H 'Content-Type: application/json' \
-d '{
"issuerKey": {
"type": "tse",
"server": "https://vault.walt.id/v1/transit",
"auth": {
"roleId": "b50427bd-5070-0bec-3a00-dfe470ba349d",
"secretId": "22ffc0f4-c308-e6d6-f5a4-246cd89bc17c"
},
"id": "k-1481938705"
},
"issuerDid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp",
"credentialData": {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://purl.imsglobal.org/spec/ob/v3p0/context.json"
],
"id": "urn:uuid:THIS WILL BE REPLACED WITH DYNAMIC DATA FUNCTION (see below)",
"type": [
"VerifiableCredential",
"OpenBadgeCredential"
],
"name": "JFF x vc-edu PlugFest 3 Interoperability",
...
},
"mapping": {
"id": "",
...
}
}'
```
**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`
or `https://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 id
- `type`: _String_ - the event type
- `data`: _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` with `jwt` being the issued jwt
- `sdjwt_issue` with `sdjwt` being the issued sdjwt
- `batch_jwt_issue` with `jwt` being the issued jwt
- `batch_sdjwt_issue` with `sdjwt` being the issued sdjwt
- `generated_mdoc` with `mdoc` 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**
As you can see for the property `issuerKey` we only provided the required parameters of the TSE Key Reference Object
described above and left out `_publicKey` and `_keyType`
```json
{
"issuerKey": {
"type": "tse",
"server": "https://vault.walt.id/v1/transit",
"auth": {
"roleId": "b50427bd-5070-0bec-3a00-dfe470ba349d",
"secretId": "22ffc0f4-c308-e6d6-f5a4-246cd89bc17c"
},
"id": "k-1481938705"
},
"issuerDid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp",
"credentialData": {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://purl.imsglobal.org/spec/ob/v3p0/context.json"
],
"id": "urn:uuid:THIS WILL BE REPLACED WITH DYNAMIC DATA FUNCTION (see below)",
"type": [
"VerifiableCredential",
"OpenBadgeCredential"
],
"name": "JFF x vc-edu PlugFest 3 Interoperability",
...
},
"mapping": {
"id": "",
...
},
"standardVersion": "DRAFT13"
}
```
--------------------------------------------------------------------------------
PAGE: OCI Vault
URL: https://docs.walt.id/community-stack/issuer/key-management/oci-vault
RAW: https://docs.walt.id/community-stack/issuer/key-management/oci-vault.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# OCI Vault
OCI Vault (Oracle Cloud Infrastructure Vault) is a service that allows you to store and manage sensitive information such as keys, passwords, and
certificates. It provides a secure and centralized location to store and manage sensitive information, and it provides
access controls to ensure that only authorized users and applications can access the information.
The walt.id Wallet API provides an interface to interact with OCI Vault (Oracle Cloud Infrastructure Vault) to store and manage keys .
## OCI Vault (Oracle Cloud Infrastructure Vault) Configuration
On the Oracle Cloud Infrastructure (OCI) console, you can create a vault and manage the keys in the vault.
### Create a OCI Vault
To create a vault, you can follow the steps below:
1. Go to the OCI console and navigate to the Vault service.
2. Click on the `Create Vault` button.
3. Enter the details for the vault, such as the compartment and name.
4. Click on the `Create` button to create the vault.
5. Once the vault is created, you can manage the keys in the vault.

## Get OCI Vault Details
To get the details of the vault, you can follow the steps below:
1. Go to the OCI console and navigate to the Vault service.
2. Click on the vault to view the details of the vault.
3. You can view the details of the vault, such as the OCID, management endpoint, and crypto endpoint.
4. You can use this information to configure the wallet to use OCI Vault.

## Get tenancy OCID
To get the tenancy OCID, you can follow the steps below:
1. Go to the OCI console and click on the user profile.
2. Click on the `Tenancy:{name-of-tenancy}` to view the details of the tenancy.
3. You can view the details of the tenancy, such as the OCID.
4. You can use this OCID to configure the wallet to use OCI Vault.

## Get user OCID
To get the user OCID, you can follow the steps below:
1. Go to the OCI console and click on the user profile -> My profile.
2. You can view the details of the user, such as the OCID.
3. You can use this OCID to configure the wallet to use OCI Vault.

## Add Vault user
To use the vault api you need to add a user and provide him with the necessary permissions to access the vault.
Once added you can generate a private key for the user and use it to authenticate the wallet to the vault.
You can find more information on how to add a user and generate a fingerprint + private
key [here](https://docs.oracle.com/en-us/iaas/Content/API/Concepts/apisigningkey.htm)
## Instance Principal Authentication
To use oci sdk method you need to configure the vault for instance principal authentication. This is done by adding the
instance principal to the vault and providing the necessary permissions.
You can find more information on how to configure instance principal authentication [here](https://docs.oracle.com/en-us/iaas/Content/Identity/Tasks/callingservicesfrominstances.htm)
## Key Creation
Below you find an example call to create a key in OCI Vault and associate it with a wallet managed by the Wallet API.
The only thing that is important to note for the creation, that our system is only compatible with the following Key
types offered by the OCI Vault:
- RSA & ECDSA
Oracle KMS key
types [full list](https://docs.oracle.com/en-us/iaas/Content/KeyManagement/Tasks/dedicated_kms_pkcs_key_types.htm).
**Option: OCI SDK**
**Endpoint:
** `/onboard/issuer` | [API Reference](https://wallet.demo.walt.id/swagger/index.html#/Keys/post_wallet_api_wallet__wallet__keys_generate)
**Example Request**
```bash
curl -X 'POST' \
'https://wallet.demo.walt.id/wallet-api/wallet/f01f8f55-d098-4c53-b47b-c97552829b39/keys/generate' \
-H 'accept: */*' \
-H 'Content-Type: application/json' \
-d '{
"backend": "oci",
"config": {
"vaultId": "ocid1.vault.oc1.eu-frankfurt-1.entbf645aabf2.abtheljshkb6dsuldqf324kitneb63vkz3dfd74dtqvkd5j2l2cxwyvmefeq",
"compartmentId": "ocid1.compartment.oc1..aaaaaaaawirugoz35riiybcxsvf7bmelqsxo3sajaav5w3i2vqowcwqrllxa"
},
"keyType": "secp256r1"
}'
```
**Body**
```json
{
"backend": "oci",
"config": {
"vaultId": "ocid1.vault.oc1.eu-frankfurt-1.entbf645aabf2.abtheljshkb6dsuldqf324kitneb63vkz3dfd74dtqvkd5j2l2cxwyvmefeq",
"compartmentId": "ocid1.compartment.oc1..aaaaaaaawirugoz35riiybcxsvf7bmelqsxo3sajaav5w3i2vqowcwqrllxa"
},
"keyType": "secp256r1"
}
```
**Body Parameters**
- `backend`: _String_ - The location where the key is stored. In our case `oci` as we want to store it in oracle's
vault.
- `config`
- `vaultId`: _String_ - the OCID of the vault where the key should be stored.
- `compartmentId`: _String_ - the OCID of the compartment where the key should be stored.
- `keyType`: _String_ - the algorithm used to generate the key. For Vault only RSA and secp256r1 is possible.
---
**Example Response**
The API will respond with the ID of the key. This ID is the internal reference and can be used in operations such as DID
create or key delete.
```
Kki22j4lUwo1gtDfdvdCgOE0hhKcNHgIZSzSxU0CugE
```
**Option: OCI REST API**
**Endpoint:
** `/onboard/issuer` | [API Reference](https://wallet.demo.walt.id/swagger/index.html#/Keys/post_wallet_api_wallet__wallet__keys_generate)
**Example Request**
```bash
curl -X 'POST' \
'https://wallet.demo.walt.id/wallet-api/wallet/f01f8f55-d098-4c53-b47b-c97552829b39/keys/generate' \
-H 'accept: */*' \
-H 'Content-Type: application/json' \
-d '{
"backend": "oci-rest-api",
"config": {
"tenancyOcid": "ocid1.tenancy.oc1..aaaaaaaaxxxxxx",
"compartmentOcid": "ocid1.tenancy.oc1..aaaaaaaaxxxxxx",
"userOcid": "ocid1.user.oc1..aaaaaaaaxxxxxx",
"fingerprint": "d4:4b:0c:c8:3a:49:15:7f:87",
"managementEndpoint": "vault-management.region.oraclecloud.com",
"cryptoEndpoint": "vault-crypto.region.oraclecloud.com",
"signingKeyPem": "-----BEGIN PRIVATE KEY-----"
},
"keyType": "secp256r1"
}'
```
**Body**
```json
{
"backend": "oci-rest-api",
"config": {
"tenancyOcid": "ocid1.tenancy.oc1..aaaaaaaaxxxxxx",
"compartmentOcid": "ocid1.tenancy.oc1..aaaaaaaaxxxxxx",
"userOcid": "ocid1.user.oc1..aaaaaaaaxxxxxx",
"fingerprint": "d4:4b:0c:c8:3a:49:15:7f:87",
"managementEndpoint": "vault-management.region.oraclecloud.com",
"cryptoEndpoint": "vault-crypto.region.oraclecloud.com",
"signingKeyPem": "-----BEGIN PRIVATE KEY-----"
},
"keyType": "secp256r1"
}
```
**Body Parameters**
- `backend`: _String_ - The location where the key is stored. In our case `oci-rest-api` as we want to store it in oracle's
vault.
- `config`
- `tenancyOcid`: _String_ - The OCID of the tenancy.
- `compartmentOcid`: _String_ - The OCID of the compartment.
- `userOcid`: _String_ - The OCID of the user.
- `fingerprint`: _String_ - The fingerprint of the user's public key.
- `managementEndpoint`: _URL_ - The management endpoint of the vault.
- `cryptoEndpoint`: _URL_ - The crypto endpoint of the vault.
- `signingKeyPem`: _String_ - The private key of the user.
- `keyType`: _String_ - the algorithm used to generate the key. For Vault only RSA and secp256r1 is possible.
---
**Example Response**
The API will respond with the ID of the key. This ID is the internal reference and can be used in operations such as DID
create or key delete.
```
Kki22j4lUwo1gtDfdvdCgOE0hhKcNHgIZSzSxU0CugE
```
### Creation via Issuer API
**Option: OCI SDK**
**Endpoint:
** `/onboard/issuer` | [API Reference](https://issuer.demo.walt.id/swagger/index.html#/Issuer%20onboarding/post_onboard_issuer)
**Example Request**
```bash
curl -X 'POST' \
'https://issuer.demo.walt.id/onboard/issuer' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"key": {
"backend": "oci",
"keyType": "secp256r1",
"config": {
"tenancyOcid": "ocid1.tenancy.oc1..aaaaaaaaiijfupfvsqwqwgupzdy5yclfzcccmie4ktp2wlgslftv5j7xpk6q",
"compartmentOcid": "ocid1.tenancy.oc1..aaaaaaaaiijfupfvsqwqwgupzdy5yclfzcccmie4ktp2wlgslftv5j7xpk6q",
"userOcid": "ocid1.user.oc1..aaaaaaaaxjkkfjqxdqk7ldfjrxjmacmbi7sci73rbfiwpioehikavpbtqx5q",
"fingerprint": "bb:d4:4b:0c:c8:3a:49:15:7f:87:55:d5:2b:7e:dd:bc",
"managementEndpoint": "entaftlvaaemy-management.kms.eu-frankfurt-1.oraclecloud.com",
"cryptoEndpoint": "entaftlvaaemy-crypto.kms.eu-frankfurt-1.oraclecloud.com",
"signingKeyPem": "-----BEGIN PRIVATE KEY-----
-----END PRIVATE KEY-----
"
}
},
"did": {
"method": "jwk"
}
}'
```
**Body**
```json
{
"key": {
"backend": "oci",
"keyType": "secp256r1",
"config": {
"tenancyOcid": "ocid1.tenancy.oc1..aaaaaaaaiijfupfvsqwqwgupzdy5yclfzcccmie4ktp2wlgslftv5j7xpk6q",
"compartmentOcid": "ocid1.tenancy.oc1..aaaaaaaaiijfupfvsqwqwgupzdy5yclfzcccmie4ktp2wlgslftv5j7xpk6q",
"userOcid": "ocid1.user.oc1..aaaaaaaaxjkkfjqxdqk7ldfjrxjmacmbi7sci73rbfiwpioehikavpbtqx5q",
"fingerprint": "bb:d4:4b:0c:c8:3a:49:15:7f:87:55:d5:2b:7e:dd:bc",
"managementEndpoint": "entaftlvaaemy-management.kms.eu-frankfurt-1.oraclecloud.com",
"cryptoEndpoint": "entaftlvaaemy-crypto.kms.eu-frankfurt-1.oraclecloud.com",
"signingKeyPem": "-----BEGIN PRIVATE KEY-----
-----END PRIVATE KEY-----
"
}
},
"did": {
"method": "jwk"
}
}
```
**Body Parameters**
- `key`
- `backend`: _String_ - Specifies the storage type of key. It can be `jwk` (manged by you), `TSE` (managed by
Hashicorp) or `oci` (managed by Oracle)
- `keyType`: _String_ - the algorithm used to generate the key. For Vault only secp256r1 is possible.
- `did`:
- `method`: _String_ - Specifies the DID method. It can be key, jwk, web, cheqd.
---
**Example Response**
The onboard/issuer endpoint will return an object containing both the generated key in JWK format and the related DID.
```json
{
"issuerKey": {
"type": "oci",
"config": {
"vaultId": "ocid1.vault.oc1.eu-frankfurt-1.entbf645aabf2.abtheljshkb6dsuldqf324kitneb63vkz3dfd74dtqvkd5j2l2cxwyvmefeq",
"compartmentId": "ocid1.compartment.oc1..aaaaaaaawirugoz35riiybcxsvf7bmelqsxo3sajaav5w3i2vqowcwqrllxa"
},
"id": "ocid1.key.oc1.eu-frankfurt-1.enta2fneaadmk.abtheljrlj5snthwkx7ycdmknuftght527dkyjsoz72dcogklixrsdyolo5a",
"_publicKey": "{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"tT1DAZdtp7vUPphTxoilmr6dfZPKcPfwL8G_Ri3K0_E\",\"y\":\"JabPubkHQPK0G7O8eL3bKg75hX4Wkojb_AOepX8xdAs\"}",
"_keyType": "secp256r1"
},
"issuerDid": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6InRUMURBWmR0cDd2VVBwaFR4b2lsbXI2ZGZaUEtjUGZ3TDhHX1JpM0swX0UiLCJ5IjoiSmFiUHVia0hRUEswRzdPOGVMM2JLZzc1aFg0V2tvamJfQU9lcFg4eGRBcyJ9"
}
```
**Properties**
- `type`: _String_ - the type of key can be either "oci" when using OCI Vault or "jwk" when providing the key in
full as JWK.
- `config`: _Object_ - the configuration object containing the necessary information to access the OCI Vault.
- `id`: _String_ - the ID of the key in the OCI Vault.
- `_publicKey` (optional): _Array_ - The public key can be fetched by the issuer API or directly provided, saving
resources and reducing network requests.
- `_keyType` (optional): _String_ - The key type can be fetched by the issuer API or directly provided, saving resources
and reducing network requests.
**Option: OCI REST API**
**Endpoint:
** `/onboard/issuer` | [API Reference](https://issuer.demo.walt.id/swagger/index.html#/Issuer%20onboarding/post_onboard_issuer)
**Example Request**
```bash
curl -X 'POST' \
'https://issuer.demo.walt.id/onboard/issuer' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"issuanceKeyConfig": {
"type": "oci-rest-api",
"tenancyOcid": "ocid1.tenancy.oc1..aaaaaaaaxxxxxx",
"compartmentOcid": "ocid1.tenancy.oc1..aaaaaaaaxxxxxx",
"userOcid": "ocid1.user.oc1..aaaaaaaaxxxxxx",
"fingerprint": "d4:4b:0c:c8:3a:49:15:7f:87",
"managementEndpoint": "vault-management.region.oraclecloud.com",
"cryptoEndpoint": "vault-crypto.region.oraclecloud.com",
"signingKeyPem": "-----BEGIN PRIVATE KEY-----",
"algorithm": "secp256r1"
},
"issuerDidConfig": {
"method": "jwk"
}
}'
```
**Body**
```json
{
"issuanceKeyConfig": {
"type": "oci-rest-api",
"tenancyOcid": "ocid1.tenancy.oc1..aaaaaaaaxxxxxx",
"compartmentOcid": "ocid1.tenancy.oc1..aaaaaaaaxxxxxx",
"userOcid": "ocid1.user.oc1..aaaaaaaaxxxxxx",
"fingerprint": "d4:4b:0c:c8:3a:49:15:7f:87",
"managementEndpoint": "vault-management.region.oraclecloud.com",
"cryptoEndpoint": "vault-crypto.region.oraclecloud.com",
"signingKeyPem": "-----BEGIN PRIVATE KEY-----",
"algorithm": "secp256r1"
},
"issuerDidConfig": {
"method": "key"
}
}
```
**Body Parameters**
- `issuanceKeyConfig`
- `type`: _String_ - Specifies the storage type of key. It can be `jwk` (manged by you), `TSE` (managed by
Hashicorp) or `oci` (managed by Oracle)
- `algorithm`: _String_ - the algorithm used to generate the key. For Vault only secp256r1 is possible.
- `issuerDidConfig`:
- `method`: _String_ - Specifies the DID method. It can be key, jwk, web, cheqd.
---
**Example Response**
The onboard/issuer endpoint will return an object containing both the generated key in JWK format and the related DID.
```json
{
"issuanceKey": {
"type": "oci-rest-api",
"config": {
"tenancyOcid": "ocid1.tenancy.oc1..aaaaaaaaxxxxxx",
"compartmentOcid": "ocid1.tenancy.oc1..aaaaaaaaxxxxxx",
"userOcid": "ocid1.user.oc1..aaaaaaaaxxxxxx",
"fingerprint": "d4:4b:0c:c8:3a:49:15:7f:87",
"managementEndpoint": "vault-management.region.oraclecloud.com",
"cryptoEndpoint": "vault-crypto.region.oraclecloud.com",
"signingKeyPem": "-----BEGIN PRIVATE KEY-----"
},
"id": "ocid1.key.oc1.eu-frankfurt-1.enta2fneaadmk.abtheljrlj5snthwkx7ycdmknuftght527dkyjsoz72dcogklixrsdyolo5a",
"_publicKey": "{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"tT1DAZdtp7vUPphTxoilmr6dfZPKcPfwL8G_Ri3K0_E\",\"y\":\"JabPubkHQPK0G7O8eL3bKg75hX4Wkojb_AOepX8xdAs\"}",
"_keyType": "secp256r1"
},
"issuerDid": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6InRUMURBWmR0cDd2VVBwaFR4b2lsbXI2ZGZaUEtjUGZ3TDhHX1JpM0swX0UiLCJ5IjoiSmFiUHVia0hRUEswRzdPOGVMM2JLZzc1aFg0V2tvamJfQU9lcFg4eGRBcyJ9"
}
```
**Properties**
- `type`: _String_ - the type of key can be either "oci" when using OCI Vault or "jwk" when providing the key in
full as JWK.
- `config`: _Object_ - the configuration object containing the necessary information to access the OCI Vault.
- `id`: _String_ - the ID of the key in the OCI Vault.
- `_publicKey` (optional): _Array_ - The public key can be fetched by the issuer API or directly provided, saving
resources and reducing network requests.
- `_keyType` (optional): _String_ - The key type can be fetched by the issuer API or directly provided, saving resources
and reducing network requests.
## Key Usage
Once you have successfully created a key that is one of the supported types listed above, you can use it in
sign and issue operations offered by the issuer API.
If you've already had a look at
our `/sign`, `/issue`, `/batchIssue`
endpoints, you have seen that they all follow a similar request body structure, where the key that should be
used for signing credentials is provided via the `issuerKey` property. Now instead of providing the key as JWK, we
provide
a reference to a key stored in Vault with the required parameters and access credentials.
Below you can see an example of
`issuerKey` object referencing a key stored in Vault.
**Option: Example IssuerKey Object**
```json
{
"type": "oci-rest-api",
"config": {
"tenancyOcid": "ocid1.tenancy.oc1..aaaaaaaaxxxxxx",
"compartmentOcid": "ocid1.tenancy.oc1..aaaaaaaaxxxxxx",
"userOcid": "ocid1.user.oc1..aaaaaaaaxxxxxx",
"fingerprint": "d4:4b:0c:c8:3a:49:15:7f:87",
"managementEndpoint": "vault-management.region.oraclecloud.com",
"cryptoEndpoint": "vault-crypto.region.oraclecloud.com",
"signingKeyPem": "-----BEGIN PRIVATE KEY-----"
},
"id": "ocid1.key.oc1.eu-frankfurt-1.entaftlvaaemy.abtheljtkwxupytaepjrgvgbhoufoonh2kbevc3cenjetbpeabhgcxyzcmwq",
"_publicKey": "{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"r1FURkMEYWiYL_IKi1bG0Ar-N36Z5bvocBcEKtkGF_s\",\"y\":\"0QP3QWwVXfpZf_i-T9qOaSMfd3_or7t6JtbJr9YkvPY\"}",
"_keyType": "secp256r1"
}
```
**Properties**
- `type`: _String_ - the type of key can be either "oci" when using OCI Vault or "jwk" when providing the key in full
as JWK.
- `config`: _Object_ - the configuration object containing the necessary information to access the OCI Vault.
- `id`: _String_ - the ID of the key in the OCI Vault.
- `_publicKey` (optional): _Array_ - The public key can be fetched by the issuer API or directly provided, saving
resources and reducing network requests.
- `_keyType` (optional): _String_ - The key type can be fetched by the issuer API or directly provided, saving resources
and reducing network requests.
## Example Issuance Request
Below you can see example issuance request
to [`jwt/issue`](https://issuer.demo.walt.id/swagger/index.html#/Credential%20Issuance/post_openid4vc_jwt_issue) using
a key created in the Vault Transit Secret Engine to
sign the credential.
**Option: OCI SDK**
[Api Reference](https://issuer.demo.walt.id/swagger/index.html#/Credential%20Issuance/post_openid4vc_jwt_issue)
```bash
curl -X 'POST' \
'https://issuer.demo.walt.id/openid4vc/jwt/issue' \
-H 'accept: text/plain' \
-H 'Content-Type: application/json' \
-d '{
"issuerKey": {
"type": "oci",
"config": {
"vaultId": "ocid1.vault.oc1.eu-frankfurt-1.entbf645aabf2.abtheljshkb6dsuldqf324kitneb63vkz3dfd74dtqvkd5j2l2cxwyvmefeq",
"compartmentId": "ocid1.compartment.oc1..aaaaaaaawirugoz35riiybcxsvf7bmelqsxo3sajaav5w3i2vqowcwqrllxa"
},
"id": "ocid1.key.oc1.eu-frankfurt-1.entaftlvaaemy.abtheljtkwxupytaepjrgvgbhoufoonh2kbevc3cenjetbpeabhgcxyzcmwq",
"_publicKey": "{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"r1FURkMEYWiYL_IKi1bG0Ar-N36Z5bvocBcEKtkGF_s\",\"y\":\"0QP3QWwVXfpZf_i-T9qOaSMfd3_or7t6JtbJr9YkvPY\"}",
"_keyType": "secp256r1"
},
"issuerDid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp",
"credentialData": {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://purl.imsglobal.org/spec/ob/v3p0/context.json"
],
"id": "urn:uuid:THIS WILL BE REPLACED WITH DYNAMIC DATA FUNCTION (see below)",
"type": [
"VerifiableCredential",
"OpenBadgeCredential"
],
"name": "JFF x vc-edu PlugFest 3 Interoperability",
...
},
"mapping": {
"id": "",
...
},
"standardVersion": "DRAFT13"
}'
```
**Body**
As you can see for the property `issuanceKey` we only provided the required parameters of the OCI Key Reference Object
described above and left out `_publicKey` and `_keyType`
```json
{
"issuanceKey": {
"type": "oci",
"config": {
"vaultId": "ocid1.vault.oc1.eu-frankfurt-1.entbf645aabf2.abtheljshkb6dsuldqf324kitneb63vkz3dfd74dtqvkd5j2l2cxwyvmefeq",
"compartmentId": "ocid1.compartment.oc1..aaaaaaaawirugoz35riiybcxsvf7bmelqsxo3sajaav5w3i2vqowcwqrllxa"
},
"id": "ocid1.key.oc1.eu-frankfurt-1.entaftlvaaemy.abtheljtkwxupytaepjrgvgbhoufoonh2kbevc3cenjetbpeabhgcxyzcmwq",
"_publicKey": "{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"r1FURkMEYWiYL_IKi1bG0Ar-N36Z5bvocBcEKtkGF_s\",\"y\":\"0QP3QWwVXfpZf_i-T9qOaSMfd3_or7t6JtbJr9YkvPY\"}",
"_keyType": "secp256r1"
},
"issuerDid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp",
"credentialData": {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://purl.imsglobal.org/spec/ob/v3p0/context.json"
],
"id": "urn:uuid:THIS WILL BE REPLACED WITH DYNAMIC DATA FUNCTION (see below)",
"type": [
"VerifiableCredential",
"OpenBadgeCredential"
],
"name": "JFF x vc-edu PlugFest 3 Interoperability",
...
},
"mapping": {
"id": "",
...
},
"standardVersion": "DRAFT13"
}
```
**Option: OCI REST API**
[Api Reference](https://issuer.demo.walt.id/swagger/index.html#/Credential%20Issuance/post_openid4vc_jwt_issue)
```bash
curl -X 'POST' \
'https://issuer.demo.walt.id/openid4vc/jwt/issue' \
-H 'accept: text/plain' \
-H 'statusCallbackUri: https://example.com/$id' \
-H 'Content-Type: application/json' \
-d '{
"issuanceKey": {
"type": "oci-rest-api",
"config": {
"tenancyOcid": "ocid1.tenancy.oc1..aaaaaaaaiijfupfvsqwqwgupzdy5yclfzcccmie4ktp2wlgslftv5j7xpk6q",
"compartmentOcid": "ocid1.compartment.oc1..aaaaaaaaxjkkfjqxdqk7ldfjrxjmacmbi7sci73rbfiwpioehikavpbtqx5q",
"userOcid": "ocid1.user.oc1..aaaaaaaaxjkkfjqxdqk7ldfjrxjmacmbi7sci73rbfiwpioehikavpbtqx5q",
"fingerprint": "bb:d4:4b:0c:c8:3a:49:15:7f:87:55:d5:2b:7e:dd:bc",
"cryptoEndpoint": "ens7pgl2aaam2-crypto.kms.eu-frankfurt-1.oraclecloud.com",
"managementEndpoint": "ens7pgl2aaam2-management.kms.eu-frankfurt-1.oraclecloud.com",
"signingKeyPem": "privateKey"
},
"id": "ocid1.key.oc1.eu-frankfurt-1.entaftlvaaemy.abtheljtkwxupytaepjrgvgbhoufoonh2kbevc3cenjetbpeabhgcxyzcmwq",
"_publicKey": "{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"r1FURkMEYWiYL_IKi1bG0Ar-N36Z5bvocBcEKtkGF_s\",\"y\":\"0QP3QWwVXfpZf_i-T9qOaSMfd3_or7t6JtbJr9YkvPY\"}",
"_keyType": "secp256r1"
},
"issuerDid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp",
"credentialData": {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://purl.imsglobal.org/spec/ob/v3p0/context.json"
],
"id": "urn:uuid:THIS WILL BE REPLACED WITH DYNAMIC DATA FUNCTION (see below)",
"type": [
"VerifiableCredential",
"OpenBadgeCredential"
],
"name": "JFF x vc-edu PlugFest 3 Interoperability",
...
},
"mapping": {
"id": "",
...
},
"standardVersion": "DRAFT13"
}'
```
**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`
or `https://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 id
- `type`: _String_ - the event type
- `data`: _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` with `jwt` being the issued jwt
- `sdjwt_issue` with `sdjwt` being the issued sdjwt
- `batch_jwt_issue` with `jwt` being the issued jwt
- `batch_sdjwt_issue` with `sdjwt` being the issued sdjwt
- `generated_mdoc` with `mdoc` 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**
As you can see for the property `issuerKey` we only provided the required parameters of the OCI Key Reference Object
described above and left out `_publicKey` and `_keyType`
```json
{
"issuerKey": {
"type": "oci",
"config": {
"tenancyOcid": "ocid1.tenancy.oc1..aaaaaaaaiijfupfvsqwqwgupzdy5yclfzcccmie4ktp2wlgslftv5j7xpk6q",
"compartmentOcid": "ocid1.compartment.oc1..aaaaaaaawirugoz35riiybcxsvf7bmelqsxo3sajaav5w3i2vqowcwqrllxa",
"userOcid": "ocid1.user.oc1..aaaaaaaaxjkkfjqxdqk7ldfjrxjmacmbi7sci73rbfiwpioehikavpbtqx5q",
"fingerprint": "bb:d4:4b:0c:c8:3a:49:15:7f:87:55:d5:2b:7e:dd:bc",
"managementEndpoint": "entaftlvaaemy-management.kms.eu-frankfurt-1.oraclecloud.com",
"cryptoEndpoint": "entaftlvaaemy-crypto.kms.eu-frankfurt-1.oraclecloud.com",
"signingKeyPem": "-----BEGIN PRIVATE KEY-----
-----END PRIVATE KEY-----
"
},
"id": "ocid1.key.oc1.eu-frankfurt-1.entaftlvaaemy.abtheljtkwxupytaepjrgvgbhoufoonh2kbevc3cenjetbpeabhgcxyzcmwq",
"_publicKey": "{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"r1FURkMEYWiYL_IKi1bG0Ar-N36Z5bvocBcEKtkGF_s\",\"y\":\"0QP3QWwVXfpZf_i-T9qOaSMfd3_or7t6JtbJr9YkvPY\"}",
"_keyType": "secp256r1"
},
"issuerDid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp",
"credentialData": {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://purl.imsglobal.org/spec/ob/v3p0/context.json"
],
"id": "urn:uuid:THIS WILL BE REPLACED WITH DYNAMIC DATA FUNCTION (see below)",
"type": [
"VerifiableCredential",
"OpenBadgeCredential"
],
"name": "JFF x vc-edu PlugFest 3 Interoperability",
...
},
"mapping": {
"id": "",
...
},
"standardVersion": "DRAFT13"
}
```
--------------------------------------------------------------------------------
PAGE: Overview
URL: https://docs.walt.id/community-stack/issuer/key-management/aws-kms/overview
RAW: https://docs.walt.id/community-stack/issuer/key-management/aws-kms/overview.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Overview
The issuer API support AWS KMS as a key management solution. This allows you to store your cryptographic keys in a secure and scalable way. The API is designed to be stateless and does not store cryptographic key material. Therefore, every API call requires the cryptographic key to be provided. When signing and issuing a credential e.g. via OID4VC, either the complete key in JWK format or a reference to a key stored in an external KMS solution is needed. In production systems, the recommended approach is to use an external KMS solution, so that the private key used to sign a credential never leaves a secure environment.
We support the following key types offered by the AWS KMS:
- RSA
- SECP256R1
- SECP256K1
We have created a Kotlin-based extension that enhances walt.id crypto with native AWS key management capabilities. This extension introduces `AwsKey`, a robust implementation leveraging the AWS SDK for Kotlin to manage cryptographic keys. It serves as a more integrated alternative to the platform-agnostic `AWSKeyRestAPI` found in the base walt.id crypto library.
## Key Features
- Native AWS SDK integration for optimal performance
- Kotlin-specific implementation
- Seamless key management through AWS KMS
## Authentication
The extension utilizes AWS SDK's default credential provider chain for authentication, automatically detecting credentials from multiple sources including:
- Environment variables
- AWS credentials file
- IAM roles for EC2
- Container credentials
- SSO credentials
## Comparison to Base Implementation
While the base `AWSKeyRestAPI` offers cross-platform compatibility through REST endpoints, this extension provides:
- Improved performance through direct SDK calls
- Enhanced error handling
- Native integration with AWS services
## Get Started
- [AWS KMS SDK](https://docs.walt.id/community-stack/issuer/key-management/aws-kms/sdk.md) - Use keys from AWS KMS to sign credentials
- [AWS KMS REST API](https://docs.walt.id/community-stack/issuer/key-management/aws-kms/rest-api.md) - Use keys from AWS KMS Rest Api to sign credentials
--------------------------------------------------------------------------------
PAGE: SDK
URL: https://docs.walt.id/community-stack/issuer/key-management/aws-kms/sdk
RAW: https://docs.walt.id/community-stack/issuer/key-management/aws-kms/sdk.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# SDK
This implementation utilizes AWS SDK's default credential provider chain for authentication, automatically detecting
credentials from multiple sources including:
- Environment variables
- AWS credentials file
- IAM roles for EC2
- Container credentials
- SSO credentials
### Key generation
To create the key you can use the onboard endpoint provided by the issuer API and provide the necessary parameters
to create the key in the AWS KMS service.
#### Creation via Issuer API
**Option: Request**
**Endpoint:**
`/onboard/issuer` | [API Reference](https://issuer.demo.walt.id/swagger/index.html#/Issuer%20onboarding/post_onboard_issuer)
**Example Request**
```bash
curl -X 'POST' \
'https://issuer.demo.walt.id/onboard/issuer' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"key": {
"backend": "aws",
"keyType": "secp256r1",
"config": {
"auth": {
"region": "eu-central-1",
},
"tags": {
"key": "value"
}
}
},
"did": {
"method": "jwk"
}
}'
```
**Body**
```json
{
"key": {
"backend": "aws",
"keyType": "secp256r1",
"config": {
"auth": {
"region": "eu-central-1",
},
"tags": {
"key": "value"
}
}
},
"did": {
"method": "jwk"
}
}
```
**Body Parameters**
- `key`
- `backend`: _String_ - Specifies the storage type of key. It can be `jwk` (managed by you), `aws` (managed
by
AWS KMS using their kotlin SDK ) and others. Learn more about different
types [here](https://docs.walt.id/community-stack/issuer/key-management/overview.md).
- `keyType`: _String_ - the algorithm used to generate the key. Supported AWS KMS values are `secp256r1`, `secp384r1`, `secp521r1`, `secp256k1`, `RSA`, `RSA3072`, and `RSA4096`. `Ed25519` is not supported by AWS KMS signing keys.
- `config`
- `auth`
- `region`: _String_ - The region where the AWS KMS service is located.
- `tags`: _Object_ - The metadata tags to add to the key for [AWS](https://docs.aws.amazon.com/kms/latest/developerguide/tagging-keys.html)
- `did`:
- `method`: _String_ - Specifies the DID method. It can be key, jwk, web, cheqd.
**Option: Response**
**Example Response**
The onboard/issuer endpoint will return an object containing both the generated key in JWK format and the related DID.
```json
{
"issuerKey": {
"type": "aws",
"config": {
"auth": {
"region": "eu-central-1"
}
},
"id": "324ebf67-6bcc-4439-8b81-260bf0a82532",
"_publicKey": "{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"RfvJGDXOjz3NOSKgZ0VlijXST8S-j96DrG0C1AMNAK8\",\"y\":\"vSt2q7yIy0-AhAirMuuDmUScxkgf4JVfId-vTETraQA\"}",
"_keyType": "secp256r1"
},
"issuerDid": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6IlJmdkpHRFhPanozTk9TS2daMFZsaWpYU1Q4Uy1qOTZEckcwQzFBTU5BSzgiLCJ5IjoidlN0MnE3eUl5MC1BaEFpck11dURtVVNjeGtnZjRKVmZJZC12VEVUcmFRQSJ9"
}
```
**Properties**
- `type`: _String_ - the type of key can be either "aws" when using AWS KMS or "jwk" when providing the key in
full as JWK.
- `config`
- `auth`
- `region`: _String_ - The region where the AWS KMS service is located.
- `id`: _String_ - the ID of the key in the Transit Engine.
- `_publicKey` (optional): _Array_ - The public key can be fetched by the issuer API or directly provided, saving
resources and reducing network requests.
- `_keyType` (optional): _String_ - The key type can be fetched by the issuer API or directly provided, saving resources
and reducing network requests.
- `issuerDid`: _String_ - the DID of the issuer.
### Key Usage
Once you have successfully created a key that is one of the supported types listed above, you can use it in
sign and issue operations offered by the issuer API.
If you've already had a look at
our `/sign`, `/issue`, `/batchIssue`
endpoints, you have seen that they all follow a similar request body structure, where the key that should be
used for signing credentials is provided via the `issuerKey` property. Now instead of providing the key as JWK, we
provide
a reference to a key stored in Vault with the required parameters and access credentials.
Below you can see an example of
`issuerKey` object referencing a key stored in AWS.
**Option: Example IssuerKey Object**
```json
{
"issuerKey": {
"type": "aws",
"config": {
"auth": {
"region": "eu-central-1",
},
"tags": {
"key": "value"
}
},
"id": "324ebf67-6bcc-4439-8b81-260bf0a82532",
"_publicKey": "{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"RfvJGDXOjz3NOSKgZ0VlijXST8S-j96DrG0C1AMNAK8\",\"y\":\"vSt2q7yIy0-AhAirMuuDmUScxkgf4JVfId-vTETraQA\"}",
"_keyType": "secp256r1"
},
"issuerDid": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6IlJmdkpHRFhPanozTk9TS2daMFZsaWpYU1Q4Uy1qOTZEckcwQzFBTU5BSzgiLCJ5IjoidlN0MnE3eUl5MC1BaEFpck11dURtVVNjeGtnZjRKVmZJZC12VEVUcmFRQSJ9"
}
```
**Properties**
- `type`: _String_ - the type of key can be either "aws" when using AWS KMS or "jwk" when providing the key in
full as JWK.
-
- `id`: _String_ - the ID of the key in the Transit Engine.
- `config`
- `auth`
- `region`: _String_ - The region where the AWS KMS service is located.
- `_publicKey` (optional): _Array_ - The public key can be fetched by the issuer API or directly provided, saving
resources and reducing network requests.
- `_keyType` (optional): _String_ - The key type can be fetched by the issuer API or directly provided, saving resources
and reducing network requests.
**Option: Example Issuance Request**
## Example Issuance Request
Below you can see example issuance request
to [`jwt/issue`](https://issuer.demo.walt.id/swagger/index.html#/Credential%20Issuance/post_openid4vc_jwt_issue) using
a key created in AWS kms to
sign the credential.
[Api Reference](https://issuer.demo.walt.id/swagger/index.html#/Credential%20Issuance/post_openid4vc_jwt_issue)
```bash
curl -X 'POST' \
'https://issuer.demo.walt.id/openid4vc/jwt/issue' \
-H 'accept: text/plain' \
-H 'statusCallbackUri: https://example.com/$id' \
-H 'Content-Type: application/json' \
-d '{
"issuerKey": {
"type": "aws",
"config": {
"auth": {
"region": "eu-central-1",
},
"tags": {
"key": "value"
}
},
"id": "324ebf67-6bcc-4439-8b81-260bf0a82532",
"_publicKey": "{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"RfvJGDXOjz3NOSKgZ0VlijXST8S-j96DrG0C1AMNAK8\",\"y\":\"vSt2q7yIy0-AhAirMuuDmUScxkgf4JVfId-vTETraQA\"}",
"_keyType": "secp256r1"
},
"issuerDid": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6IlJmdkpHRFhPanozTk9TS2daMFZsaWpYU1Q4Uy1qOTZEckcwQzFBTU5BSzgiLCJ5IjoidlN0MnE3eUl5MC1BaEFpck11dURtVVNjeGtnZjRKVmZJZC12VEVUcmFRQSJ9",
"credentialData": {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://purl.imsglobal.org/spec/ob/v3p0/context.json"
],
"id": "urn:uuid:THIS WILL BE REPLACED WITH DYNAMIC DATA FUNCTION (see below)",
"type": [
"VerifiableCredential",
"OpenBadgeCredential"
],
"name": "JFF x vc-edu PlugFest 3 Interoperability",
...
},
"mapping": {
"id": "",
...
}
}'
```
**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`
or `https://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 id
- `type`: _String_ - the event type
- `data`: _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` with `jwt` being the issued jwt
- `sdjwt_issue` with `sdjwt` being the issued sdjwt
- `batch_jwt_issue` with `jwt` being the issued jwt
- `batch_sdjwt_issue` with `sdjwt` being the issued sdjwt
- `generated_mdoc` with `mdoc` 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**
As you can see for the property `issuerKey` we only provided the required parameters of the TSE Key Reference Object
described above and left out `_publicKey` and `_keyType`
```json
{
"issuerKey": {
"type": "aws",
"config": {
"auth": {
"region": "eu-central-1",
},
"tags": {
"key": "value"
}
},
"id": "324ebf67-6bcc-4439-8b81-260bf0a82532",
"_publicKey": "{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"RfvJGDXOjz3NOSKgZ0VlijXST8S-j96DrG0C1AMNAK8\",\"y\":\"vSt2q7yIy0-AhAirMuuDmUScxkgf4JVfId-vTETraQA\"}",
"_keyType": "secp256r1"
},
"issuerDid": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6IlJmdkpHRFhPanozTk9TS2daMFZsaWpYU1Q4Uy1qOTZEckcwQzFBTU5BSzgiLCJ5IjoidlN0MnE3eUl5MC1BaEFpck11dURtVVNjeGtnZjRKVmZJZC12VEVUcmFRQSJ9",
"credentialData": {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://purl.imsglobal.org/spec/ob/v3p0/context.json"
],
"id": "urn:uuid:THIS WILL BE REPLACED WITH DYNAMIC DATA FUNCTION (see below)",
"type": [
"VerifiableCredential",
"OpenBadgeCredential"
],
"name": "JFF x vc-edu PlugFest 3 Interoperability",
...
},
"mapping": {
"id": "",
...
}
}
```
--------------------------------------------------------------------------------
PAGE: REST API
URL: https://docs.walt.id/community-stack/issuer/key-management/aws-kms/rest-api
RAW: https://docs.walt.id/community-stack/issuer/key-management/aws-kms/rest-api.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# REST API
The issuer API can leverage AWS key management service
to hook into the "cryptography as a service" or "encryption as a service" features provided
by AWS. With
that, the issuer API can sign and verify credentials whilst keeping the secrets (signing keys) in an external
secure environment.
If you are new to AWS kms make sure to check out the guides [here](https://docs.aws.amazon.com/kms/latest/developerguide/overview.html).
The rest of this document assumes you already know how AWS KMS works, and you have it setup.
## Method : Using AWS access keys and secret keys
### Prerequisites
- **AWS Account**: You need an active AWS account. If you don't have one, create it at [AWS Console](https://aws.amazon.com/console/).
### Create AWS Access Keys and Secret Keys
To securely interact with AWS services such as KMS, you need to configure your AWS access and secret keys. Here’s how to generate and configure these credentials:
1. Log in to your AWS Management Console.
2. Navigate to **IAM (Identity and Access Management)**.
3. In the left sidebar, click on **Users**, then choose your username.
4. Go to the **Security credentials** tab.
5. Under **Access keys**, click **Create access key**.
6. Copy both the **Access Key ID** and **Secret Access Key**. Store them securely.
### Key generation
To create the key you can use the onboard endpoint provided by the issuer API and provide the necessary parameters
to create the key in the AWS KMS service.
#### Creation via Issuer API
**Option: Request**
**Endpoint:**
`/onboard/issuer` | [API Reference](https://issuer.demo.walt.id/swagger/index.html#/Issuer%20onboarding/post_onboard_issuer)
**Example Request**
```bash
curl -X 'POST' \
'https://issuer.demo.walt.id/onboard/issuer' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"key": {
"backend": "aws-rest-api",
"keyType": "secp256r1",
"config": {
"auth": {
"accessKeyId": "AKIAW......U2TQU5F",
"secretAccessKey": "6YDrEN1t+......I3OCwSc7Sr",
"region": "eu-central-1"
},
"tags": {
"key": "value"
}
}
},
"did": {
"method": "jwk"
}
}'
```
**Body**
```json
{
"key": {
"backend": "aws-rest-api",
"keyType": "secp256r1",
"config": {
"auth": {
"accessKeyId": "AKIAW......U2TQU5F",
"secretAccessKey": "6YDrEN1t+......I3OCwSc7Sr",
"region": "eu-central-1"
},
"tags": {
"key": "value"
}
}
},
"did": {
"method": "jwk"
}
}
```
**Body Parameters**
- `key`
- `backend`: _String_ - Specifies the storage type of key. It can be `jwk` (managed by you), `aws-rest-api` (managed by
AWS KMS using Rest api requests ) and others. Learn more about different types [here](https://docs.walt.id/community-stack/issuer/key-management/overview.md).
- `keyType`: _String_ - the algorithm used to generate the key. Supported AWS KMS values are `secp256r1`, `secp384r1`, `secp521r1`, `secp256k1`, `RSA`, `RSA3072`, and `RSA4096`. `Ed25519` is not supported by AWS KMS signing keys.
- `config`
- `accessKeyId`: _String_ - The access key id for AWS KMS.
- `secretAccessKey`: _String_ - The secret access key for AWS KMS.
- `region`: _String_ - The region where the AWS KMS service is located.
- `tags`: _Object_ - The metadata tags to add to the key for [AWS](https://docs.aws.amazon.com/kms/latest/developerguide/tagging-keys.html)
- `did`:
- `method`: _String_ - Specifies the DID method. It can be key, jwk, web, cheqd.
**Option: Response**
**Example Response**
The onboard/issuer endpoint will return an object containing both the generated key in JWK format and the related DID.
```json
{
"issuerKey": {
"type": "aws-rest-api",
"config": {
"auth": {
"accessKeyId": "AKIAW......U2TQU5F",
"secretAccessKey": "6YDrEN1t+......I3OCwSc7Sr",
"region": "eu-central-1"
},
"tags": {
"key": "value"
}
},
"id": "324ebf67-6bcc-4439-8b81-260bf0a82532",
"_publicKey": "{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"RfvJGDXOjz3NOSKgZ0VlijXST8S-j96DrG0C1AMNAK8\",\"y\":\"vSt2q7yIy0-AhAirMuuDmUScxkgf4JVfId-vTETraQA\"}",
"_keyType": "secp256r1"
},
"issuerDid": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6IlJmdkpHRFhPanozTk9TS2daMFZsaWpYU1Q4Uy1qOTZEckcwQzFBTU5BSzgiLCJ5IjoidlN0MnE3eUl5MC1BaEFpck11dURtVVNjeGtnZjRKVmZJZC12VEVUcmFRQSJ9"
}
```
**Properties**
- `type`: _String_ - the type of key can be either "aws" when using AWS KMS or "jwk" when providing the key in
full as JWK.
- `config`
- `accessKeyId`: _String_ - The access key id for AWS KMS.
- `secretAccessKey`: _String_ - The secret access key for AWS KMS.
- `region`: _String_ - The region where the AWS KMS service is located.
- `id`: _String_ - the ID of the key in the Transit Engine.
- `_publicKey` (optional): _Array_ - The public key can be fetched by the issuer API or directly provided, saving
resources and reducing network requests.
- `_keyType` (optional): _String_ - The key type can be fetched by the issuer API or directly provided, saving resources
and reducing network requests.
- `issuerDid`: _String_ - the DID of the issuer.
### Key Usage
Once you have successfully created a key that is one of the supported types listed above, you can use it in
sign and issue operations offered by the issuer API.
If you've already had a look at
our `/sign`, `/issue`, `/batchIssue`
endpoints, you have seen that they all follow a similar request body structure, where the key that should be
used for signing credentials is provided via the `issuerKey` property. Now instead of providing the key as JWK, we
provide
a reference to a key stored in Vault with the required parameters and access credentials.
Below you can see an example of
`issuerKey` object referencing a key stored in AWS.
**Option: Example IssuerKey Object**
```json
{
"issuerKey": {
"type": "aws-rest-api",
"config": {
"auth": {
"accessKeyId": "AKIAW......U2TQU5F",
"secretAccessKey": "6YDrEN1t+......I3OCwSc7Sr",
"region": "eu-central-1"
},
"tags": {
"key": "value"
}
},
"id": "324ebf67-6bcc-4439-8b81-260bf0a82532",
"_publicKey": "{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"RfvJGDXOjz3NOSKgZ0VlijXST8S-j96DrG0C1AMNAK8\",\"y\":\"vSt2q7yIy0-AhAirMuuDmUScxkgf4JVfId-vTETraQA\"}",
"_keyType": "secp256r1"
},
"issuerDid": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6IlJmdkpHRFhPanozTk9TS2daMFZsaWpYU1Q4Uy1qOTZEckcwQzFBTU5BSzgiLCJ5IjoidlN0MnE3eUl5MC1BaEFpck11dURtVVNjeGtnZjRKVmZJZC12VEVUcmFRQSJ9"
}
```
**Properties**
- `type`: _String_ - the type of key can be either "aws" when using AWS KMS or "jwk" when providing the key in
full as JWK.
-
- `id`: _String_ - the ID of the key in the Transit Engine.
- `config`
- `accessKeyId`: _String_ - The access key id for AWS KMS.
- `secretAccessKey`: _String_ - The secret access key for AWS KMS.
- `region`: _String_ - The region where the AWS KMS service is located.
- `_publicKey` (optional): _Array_ - The public key can be fetched by the issuer API or directly provided, saving
resources and reducing network requests.
- `_keyType` (optional): _String_ - The key type can be fetched by the issuer API or directly provided, saving resources
and reducing network requests.
**Option: Example Issuance Request**
## Example Issuance Request
Below you can see example issuance request
to [`jwt/issue`](https://issuer.demo.walt.id/swagger/index.html#/Credential%20Issuance/post_openid4vc_jwt_issue) using
a key created in AWS kms to
sign the credential.
[Api Reference](https://issuer.demo.walt.id/swagger/index.html#/Credential%20Issuance/post_openid4vc_jwt_issue)
```bash
curl -X 'POST' \
'https://issuer.demo.walt.id/openid4vc/jwt/issue' \
-H 'accept: text/plain' \
-H 'statusCallbackUri: https://example.com/$id' \
-H 'Content-Type: application/json' \
-d '{
"issuerKey": {
"type": "aws-rest-api",
"config": {
"auth": {
"accessKeyId": "AKIAW......U2TQU5F",
"secretAccessKey": "6YDrEN1t+......I3OCwSc7Sr",
"region": "eu-central-1"
},
"tags": {
"key": "value"
}
},
"id": "324ebf67-6bcc-4439-8b81-260bf0a82532",
"_publicKey": "{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"RfvJGDXOjz3NOSKgZ0VlijXST8S-j96DrG0C1AMNAK8\",\"y\":\"vSt2q7yIy0-AhAirMuuDmUScxkgf4JVfId-vTETraQA\"}",
"_keyType": "secp256r1"
},
"issuerDid": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6IlJmdkpHRFhPanozTk9TS2daMFZsaWpYU1Q4Uy1qOTZEckcwQzFBTU5BSzgiLCJ5IjoidlN0MnE3eUl5MC1BaEFpck11dURtVVNjeGtnZjRKVmZJZC12VEVUcmFRQSJ9",
"credentialData": {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://purl.imsglobal.org/spec/ob/v3p0/context.json"
],
"id": "urn:uuid:THIS WILL BE REPLACED WITH DYNAMIC DATA FUNCTION (see below)",
"type": [
"VerifiableCredential",
"OpenBadgeCredential"
],
"name": "JFF x vc-edu PlugFest 3 Interoperability",
...
},
"mapping": {
"id": "",
...
}
}'
```
**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`
or `https://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 id
- `type`: _String_ - the event type
- `data`: _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` with `jwt` being the issued jwt
- `sdjwt_issue` with `sdjwt` being the issued sdjwt
- `batch_jwt_issue` with `jwt` being the issued jwt
- `batch_sdjwt_issue` with `sdjwt` being the issued sdjwt
- `generated_mdoc` with `mdoc` 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**
As you can see for the property `issuerKey` we only provided the required parameters of the TSE Key Reference Object
described above and left out `_publicKey` and `_keyType`
```json
{
"issuerKey": {
"type": "aws-rest-api",
"config": {
"auth": {
"accessKeyId": "AKIAW......U2TQU5F",
"secretAccessKey": "6YDrEN1t+......I3OCwSc7Sr",
"region": "eu-central-1"
},
"tags": {
"key": "value"
}
},
"id": "324ebf67-6bcc-4439-8b81-260bf0a82532",
"_publicKey": "{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"RfvJGDXOjz3NOSKgZ0VlijXST8S-j96DrG0C1AMNAK8\",\"y\":\"vSt2q7yIy0-AhAirMuuDmUScxkgf4JVfId-vTETraQA\"}",
"_keyType": "secp256r1"
},
"issuerDid": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6IlJmdkpHRFhPanozTk9TS2daMFZsaWpYU1Q4Uy1qOTZEckcwQzFBTU5BSzgiLCJ5IjoidlN0MnE3eUl5MC1BaEFpck11dURtVVNjeGtnZjRKVmZJZC12VEVUcmFRQSJ9",
"credentialData": {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://purl.imsglobal.org/spec/ob/v3p0/context.json"
],
"id": "urn:uuid:THIS WILL BE REPLACED WITH DYNAMIC DATA FUNCTION (see below)",
"type": [
"VerifiableCredential",
"OpenBadgeCredential"
],
"name": "JFF x vc-edu PlugFest 3 Interoperability",
...
},
"mapping": {
"id": "",
...
}
}
```
## Method : Instance Authentication
### Prerequisites
- **AWS Account**: You need an active AWS account. If you don't have one, create it at [AWS Console](https://aws.amazon.com/console/).
### Set Up an IAM Role for the EC2 Instance
To authenticate your instance with AWS, You can Set Up an IAM Role for the EC2 Instance in AWS Console by following the steps below:
1. **Log in to your AWS Management Console**.
2. **Navigate to IAM (Identity and Access Management)**.
3. **In the left sidebar, click on Roles**.
4. **Click on Create Role**.
5. **Select EC2 as the service that will use this role**.
6. **Click on Next: Permissions**.
7. **Attach the necessary permissions to the role**.
8. **Click on Next: Tags**.
9. **Add tags if necessary**.
10. **Click on Next: Review**.
11. **Enter a name for the role**.
12. **Click on Create Role**.
13. **Attach the role to the EC2 instance**.
14. **Navigate to the EC2 instance in the AWS Management Console**.
15. **Click on Actions > Security > Modify IAM Role**.
16. **Select the role you created**.
17. **Click on Save**.
18. **The EC2 instance is now authenticated with AWS**.
19. **You can now use the AWS SDK to interact with AWS services**.

When you have set up the IAM role for the EC2 instance,
you can use it to authenticate with AWS services.
### Key generation
To create the key you can use the onboard endpoint provided by the issuer API and provide the necessary parameters
to create the key in the AWS KMS service.
#### Creation via Issuer API
**Option: Request**
**Endpoint:**
`/onboard/issuer` | [API Reference](https://issuer.demo.walt.id/swagger/index.html#/Issuer%20onboarding/post_onboard_issuer)
**Example Request**
```bash
curl -X 'POST' \
'https://issuer.demo.walt.id/onboard/issuer' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"key": {
"backend": "aws-rest-api",
"keyType": "secp256r1",
"config": {
"auth": {
"roleName": "AccessRole",
"region": "eu-central-1"
},
"tags": {
"key": "value"
}
}
},
"did": {
"method": "jwk"
}
}'
```
**Body**
```json
{
"key": {
"backend": "aws-rest-api",
"keyType": "secp256r1",
"config": {
"auth": {
"roleName": "AccessRole",
"region": "eu-central-1"
},
"tags": {
"key": "value"
}
}
},
"did": {
"method": "jwk"
}
}
```
**Body Parameters**
- `key`
- `backend`: _String_ - Specifies the storage type of key. It can be `jwk` (managed by you), `aws-rest-api` (managed by
AWS KMS using Rest api requests ) and others. Learn more about different types [here](https://docs.walt.id/community-stack/issuer/key-management/overview.md).
- `keyType`: _String_ - the algorithm used to generate the key. Supported AWS KMS values are `secp256r1`, `secp384r1`, `secp521r1`, `secp256k1`, `RSA`, `RSA3072`, and `RSA4096`. `Ed25519` is not supported by AWS KMS signing keys.
- `config`
- `roleName`: _String_ - The role name for AWS KMS.
- `region`: _String_ - The region where the AWS KMS service is located.
- `tags`: _Object_ - The metadata tags to add to the key for [AWS](https://docs.aws.amazon.com/kms/latest/developerguide/tagging-keys.html)
- `did`:
- `method`: _String_ - Specifies the DID method. It can be key, jwk, web, cheqd.
**Option: Response**
**Example Response**
The onboard/issuer endpoint will return an object containing both the generated key in JWK format and the related DID.
```json
{
"issuerKey": {
"type": "aws-rest-api",
"config": {
"auth": {
"roleName": "AccessRole",
"region": "eu-central-1"
},
"tags": {
"key": "value"
}
},
"id": "324ebf67-6bcc-4439-8b81-260bf0a82532",
"_publicKey": "{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"RfvJGDXOjz3NOSKgZ0VlijXST8S-j96DrG0C1AMNAK8\",\"y\":\"vSt2q7yIy0-AhAirMuuDmUScxkgf4JVfId-vTETraQA\"}",
"_keyType": "secp256r1"
},
"issuerDid": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6IlJmdkpHRFhPanozTk9TS2daMFZsaWpYU1Q4Uy1qOTZEckcwQzFBTU5BSzgiLCJ5IjoidlN0MnE3eUl5MC1BaEFpck11dURtVVNjeGtnZjRKVmZJZC12VEVUcmFRQSJ9"
}
```
**Properties**
- `type`: _String_ - the type of key can be either "aws" when using AWS KMS or "jwk" when providing the key in
full as JWK.
- `config`
- `roleName`: _String_ - The role name for AWS KMS.
- `region`: _String_ - The region where the AWS KMS service is located.
- `tags`: _Object_ - The metadata tags to add to the key for [AWS](https://docs.aws.amazon.com/kms/latest/developerguide/tagging-keys.html)
- `id`: _String_ - the ID of the key in the Transit Engine.
- `_publicKey` (optional): _Array_ - The public key can be fetched by the issuer API or directly provided, saving
resources and reducing network requests.
- `_keyType` (optional): _String_ - The key type can be fetched by the issuer API or directly provided, saving resources
and reducing network requests.
- `issuerDid`: _String_ - the DID of the issuer.
### Key Usage
Once you have successfully created a key that is one of the supported types listed above, you can use it in
sign and issue operations offered by the issuer API.
If you've already had a look at
our `/sign`, `/issue`, `/batchIssue`
endpoints, you have seen that they all follow a similar request body structure, where the key that should be
used for signing credentials is provided via the `issuerKey` property. Now instead of providing the key as JWK, we
provide
a reference to a key stored in Vault with the required parameters and access credentials.
Below you can see an example of
`issuerKey` object referencing a key stored in AWS.
**Option: Example IssuerKey Object**
```json
{
"issuerKey": {
"type": "aws-rest-api",
"config": {
"auth": {
"roleName": "AccessRole",
"region": "eu-central-1"
},
"tags": {
"key": "value"
}
},
"id": "324ebf67-6bcc-4439-8b81-260bf0a82532",
"_publicKey": "{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"RfvJGDXOjz3NOSKgZ0VlijXST8S-j96DrG0C1AMNAK8\",\"y\":\"vSt2q7yIy0-AhAirMuuDmUScxkgf4JVfId-vTETraQA\"}",
"_keyType": "secp256r1"
},
"issuerDid": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6IlJmdkpHRFhPanozTk9TS2daMFZsaWpYU1Q4Uy1qOTZEckcwQzFBTU5BSzgiLCJ5IjoidlN0MnE3eUl5MC1BaEFpck11dURtVVNjeGtnZjRKVmZJZC12VEVUcmFRQSJ9"
}
```
**Properties**
- `type`: _String_ - the type of key can be either "aws" when using AWS KMS or "jwk" when providing the key in
full as JWK.
-
- `id`: _String_ - the ID of the key in the Transit Engine.
- `config`
- `accessKeyId`: _String_ - The access key id for AWS KMS.
- `secretAccessKey`: _String_ - The secret access key for AWS KMS.
- `region`: _String_ - The region where the AWS KMS service is located.
- `_publicKey` (optional): _Array_ - The public key can be fetched by the issuer API or directly provided, saving
resources and reducing network requests.
- `_keyType` (optional): _String_ - The key type can be fetched by the issuer API or directly provided, saving resources
and reducing network requests.
**Option: Example Issuance Request**
## Example Issuance Request
Below you can see example issuance request
to [`jwt/issue`](https://issuer.demo.walt.id/swagger/index.html#/Credential%20Issuance/post_openid4vc_jwt_issue) using
a key created in AWS kms to
sign the credential.
[Api Reference](https://issuer.demo.walt.id/swagger/index.html#/Credential%20Issuance/post_openid4vc_jwt_issue)
```bash
curl -X 'POST' \
'https://issuer.demo.walt.id/openid4vc/jwt/issue' \
-H 'accept: text/plain' \
-H 'statusCallbackUri: https://example.com/$id' \
-H 'Content-Type: application/json' \
-d '{
"issuerKey": {
"type": "aws-rest-api",
"config": {
"auth": {
"roleName": "AccessRole",
"region": "eu-central-1"
},
"tags": {
"key": "value"
}
},
"id": "324ebf67-6bcc-4439-8b81-260bf0a82532",
"_publicKey": "{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"RfvJGDXOjz3NOSKgZ0VlijXST8S-j96DrG0C1AMNAK8\",\"y\":\"vSt2q7yIy0-AhAirMuuDmUScxkgf4JVfId-vTETraQA\"}",
"_keyType": "secp256r1"
},
"issuerDid": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6IlJmdkpHRFhPanozTk9TS2daMFZsaWpYU1Q4Uy1qOTZEckcwQzFBTU5BSzgiLCJ5IjoidlN0MnE3eUl5MC1BaEFpck11dURtVVNjeGtnZjRKVmZJZC12VEVUcmFRQSJ9",
"credentialData": {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://purl.imsglobal.org/spec/ob/v3p0/context.json"
],
"id": "urn:uuid:THIS WILL BE REPLACED WITH DYNAMIC DATA FUNCTION (see below)",
"type": [
"VerifiableCredential",
"OpenBadgeCredential"
],
"name": "JFF x vc-edu PlugFest 3 Interoperability",
...
},
"mapping": {
"id": "",
...
}
}'
```
**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`
or `https://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 id
- `type`: _String_ - the event type
- `data`: _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` with `jwt` being the issued jwt
- `sdjwt_issue` with `sdjwt` being the issued sdjwt
- `batch_jwt_issue` with `jwt` being the issued jwt
- `batch_sdjwt_issue` with `sdjwt` being the issued sdjwt
- `generated_mdoc` with `mdoc` 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**
As you can see for the property `issuerKey` we only provided the required parameters of the TSE Key Reference Object
described above and left out `_publicKey` and `_keyType`
```json
{
"issuerKey": {
"type": "aws-rest-api",
"config": {
"auth": {
"roleName": "AccessRole",
"region": "eu-central-1"
},
"tags": {
"key": "value"
}
},
"id": "324ebf67-6bcc-4439-8b81-260bf0a82532",
"_publicKey": "{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"RfvJGDXOjz3NOSKgZ0VlijXST8S-j96DrG0C1AMNAK8\",\"y\":\"vSt2q7yIy0-AhAirMuuDmUScxkgf4JVfId-vTETraQA\"}",
"_keyType": "secp256r1"
},
"issuerDid": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6IlJmdkpHRFhPanozTk9TS2daMFZsaWpYU1Q4Uy1qOTZEckcwQzFBTU5BSzgiLCJ5IjoidlN0MnE3eUl5MC1BaEFpck11dURtVVNjeGtnZjRKVmZJZC12VEVUcmFRQSJ9",
"credentialData": {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://purl.imsglobal.org/spec/ob/v3p0/context.json"
],
"id": "urn:uuid:THIS WILL BE REPLACED WITH DYNAMIC DATA FUNCTION (see below)",
"type": [
"VerifiableCredential",
"OpenBadgeCredential"
],
"name": "JFF x vc-edu PlugFest 3 Interoperability",
...
},
"mapping": {
"id": "",
...
}
}
```
--------------------------------------------------------------------------------
PAGE: Azure Key Vault
URL: https://docs.walt.id/community-stack/issuer/key-management/azure-kms/overview
RAW: https://docs.walt.id/community-stack/issuer/key-management/azure-kms/overview.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Overview
The issuer API supports Azure Key Vault as a key management solution. This allows you to store your cryptographic keys
in a secure and scalable way. The API is designed to be stateless and does not store cryptographic key material.
Therefore, every API call requires the cryptographic key to be provided. When signing and issuing a credential
e.g. via OID4VC, either the complete key in JWK format or a reference to a key stored in Azure Key Vault is needed.
In production systems, the recommended approach is to use Azure Key Vault so that the private key used to sign a
credential never leaves a secure environment.
We support the following key types offered by Azure Key Vault:
- RSA
- SECP256R1
- SECP256K1
The platform offers two integration options for Azure Key Vault:
- A native Azure SDK–based backend registered as `azure`
- A REST-based backend registered as `azure-rest-api`
Both backends allow you to generate keys, sign credentials using keys stored in Azure, and retrieve public keys,
while keeping private key material inside Azure Key Vault.
## Key Features
- Native Azure SDK integration for optimal performance (`azure` backend)
- REST-based integration for environments where direct SDK usage is not possible (`azure-rest-api` backend)
- Seamless key management through Azure Key Vault
- Support for attaching metadata tags to keys managed via the issuer API
## Authentication
Depending on which backend you use, authentication towards Azure Key Vault can rely on:
- Client credentials (client ID, client secret, tenant ID)
- Managed identities (for SDK-based integrations, recommended for production)
In all cases, the issuer API only stores references and configuration required to access keys; the keys themselves
are created and held by Azure Key Vault.
## Get Started
- [Azure Key Vault SDK](https://docs.walt.id/community-stack/issuer/key-management/azure-kms/sdk.md) - Use keys from Azure Key Vault to sign credentials.
- [Azure Key Vault REST API](https://docs.walt.id/community-stack/issuer/key-management/azure-kms/rest-api.md) - Use keys from Azure Key Vault via REST API.
--------------------------------------------------------------------------------
PAGE: Azure Key Vault (Azure SDK)
URL: https://docs.walt.id/community-stack/issuer/key-management/azure-kms/sdk
RAW: https://docs.walt.id/community-stack/issuer/key-management/azure-kms/sdk.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Azure Key Vault (Azure SDK)
Azure Key Vault is a managed cloud service for securely storing and using cryptographic keys, secrets, and certificates.
This document describes the **native Azure SDK–based integration** in the walt.id platform, which enables key
generation, signing, verification, and deletion directly via the Azure SDK instead of raw REST calls.
This backend is registered as **`azure`** in the key manager.
The legacy REST-based implementation is now explicitly named **`azure-rest-api`**.
---
## Prerequisites
- An Azure account with an active subscription
https://azure.microsoft.com/free/
- An Azure Key Vault instance
https://learn.microsoft.com/azure/key-vault/general/quick-create-portal
- An Azure AD application (service principal)
- One of the following authentication methods:
- Client Secret
- Managed Identity (recommended for production)
---
## Supported Features
- Key generation in Azure Key Vault
- Signing using Azure-managed private keys
- Public key retrieval
- Key deletion
---
## Key Generation
Key creation is done using the standard onboarding endpoint.
The only difference is the backend type and configuration structure.
### Creation via Issuer API (Azure SDK)
**Option: Request**
**Endpoint**
`/onboard/issuer`
[API Reference](https://issuer.demo.walt.id/swagger/index.html#/Issuer%20onboarding/post_onboard_issuer)
**Example Request**
```bash
curl -X POST \
'https://issuer.demo.walt.id/onboard/issuer' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"key": {
"backend": "azure",
"keyType": "secp256r1",
"config": {
"auth": {
"keyVaultUrl": "url to the vault"
},
"tags": {
"key": "value"
}
}
},
"did": {
"method": "jwk"
}
}'
```
**Body Parameters**
- `key`
- `backend`: _String_ - Must be `azure` for Azure SDK integration
- `keyType`: _String_ - Supported: `secp256r1`, `ed25519` (depending on Key Vault capabilities)
- `config`
- `keyVaultUrl`: _String_ - The URL of your Azure Key Vault
- `tags`: _Object_ - The metadata tags to add to the key for [Azure](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/tag-resources)
- `did`
- `method`: _String_ - Supported: `key`, `jwk`, `web`, `cheqd`
**Option: Response**
```json
{
"issuerKey": {
"type": "azure",
"id": "https://.vault.azure.net/keys/issuer-key/1234567890abcdef",
"_keyType": "secp256r1",
"_publicKey": {
"type": "jwk",
"jwk": {
"kty": "EC",
"crv": "P-256",
"kid": "https://.vault.azure.net/keys/issuer-key/1234567890abcdef",
"x": "",
"y": "",
"key_ops": ["sign", "verify"]
}
}
},
"issuerDid": "did:jwk:..."
}
```
**Properties**
- `type`: _String_ - Always `azure` for Azure SDK keys
- `id`: _String_ - Full Azure Key Vault key identifier
- `_keyType` (optional): _String_
- `_publicKey` (optional): _Object_
- `issuerDid`: _String_
# Key Usage
Once created, Azure SDK–backed keys are used exactly like any other key backend.
The issuer API will transparently delegate cryptographic operations to Azure Key Vault.
The key reference is passed via the issuerKey object.
Example IssuerKey Object (Azure SDK) :
```json
{
"issuerKey": {
"type": "azure",
"id": "https://.vault.azure.net/keys/issuer-key/1234567890abcdef"
},
"issuerDid": "did:jwk:..."
}
```
--------------------------------------------------------------------------------
PAGE: Azure Key Vault ( Rest API)
URL: https://docs.walt.id/community-stack/issuer/key-management/azure-kms/rest-api
RAW: https://docs.walt.id/community-stack/issuer/key-management/azure-kms/rest-api.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Azure Key Vault ( Rest API)
Azure Key Vault is a cloud service for securely storing and managing cryptographic keys, secrets, and certificates. It
provides a secure and reliable way to protect sensitive information and control access to keys used by cloud
applications and services.
## Prerequisites
- An Azure account with an active subscription. [Create an account](https://azure.microsoft.com/en-us/free/).
- An Azure Key Vault
instance. [Create a Key Vault](https://docs.microsoft.com/en-us/azure/key-vault/quick-create-portal).
- An Azure AD application with access to the Key
Vault. [Create an Azure AD application](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app).
## Setup
1. **Create an Azure AD application** with access to the Key Vault. Note down the `Application (client) ID` and
`Directory (tenant) ID`.
2. **Create a client secret** for the Azure AD application. Note down the `Client Secret`.
3. **Assign permissions** to the Azure AD application to access the Key Vault. Grant the `Key permissions` and
`Secret permissions` to the application.
4. **Get the Key Vault URL**. Note down the `Key Vault URL` from the Azure portal.
5. **Configure the issuer API** to use the Azure Key Vault. Provide the `Key Vault URL`, `Application ID`, `Tenant ID`,
and `Client Secret` in the issuer API configuration.
6. **Generate a key** in the Key Vault. Use the issuer API to generate a key in the Azure Key Vault.
7. **Sign and issue credentials** using the key stored in the Azure Key Vault.
## Key Generation
To create the key you can use the onboard endpoint provided by the issuer API and provide the necessary parameters
to create the key in the Azure Key Vault service.
#### Creation via Issuer API
**Option: Request**
**Endpoint:**
`/onboard/issuer` | [API Reference](https://issuer.demo.walt.id/swagger/index.html#/Issuer%20onboarding/post_onboard_issuer)
**Example Request**
```bash
curl -X 'POST' \
'https://issuer.demo.walt.id/onboard/issuer' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"key": {
"backend": "azure-rest-api",
"keyType": "secp256r1",
"config": {
"auth": {
"keyVaultUrl": "https://.vault.azure.net/",
"clientId": "",
"tenantId": "",
"clientSecret": ""
},
"tags": {
"key": "value"
}
}
},
"did": {
"method": "jwk"
}
}'
```
**Body**
```json
{
"key": {
"backend": "azure-rest-api",
"keyType": "secp256r1",
"config": {
"auth": {
"keyVaultUrl": "https://.vault.azure.net/",
"clientId": "",
"tenantId": "",
"clientSecret": ""
},
"tags": {
"key": "value"
}
}
},
"did": {
"method": "jwk"
}
}
```
**Body Parameters**
- `key`
- `backend`: _String_ - Specifies the storage type of key. It can be `jwk` (managed by you), `azure` (managed
by
Azure Key Vault using Rest api requests ) and others. Learn more about different
types [here](https://docs.walt.id/community-stack/issuer/key-management/overview.md).
- `keyType`: _String_ - the algorithm used to generate the key. Supported Azure Key Vault values are `secp256r1`, `secp384r1`, `secp521r1`, `secp256k1`, `RSA`, `RSA3072`, and `RSA4096`. `Ed25519` is not supported.
- `config`
- `keyVaultUrl`: _String_ - The URL of the Azure Key Vault.
- `clientId`: _String_ - The client ID of the Azure AD application.
- `tenantId`: _String_ - The tenant ID of the Azure AD application.
- `clientSecret`: _String_ - The client secret of the Azure AD application.
- `tags`: _Object_ - The metadata tags to add to the key for [Azure](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/tag-resources)
- `did`:
- `method`: _String_ - Specifies the DID method. It can be key, jwk, web, cheqd.
**Option: Response**
**Example Response**
The onboard/issuer endpoint will return an object containing both the generated key in JWK format and the related DID.
```json
{
"issuerKey": {
"type": "azure-rest-api",
"id": "https://kv-nhd-vp-core-dev.vault.azure.net/keys/541693094/be4a95477f274b38826938a92f990fd5",
"auth": {
"keyVaultUrl": "https://.vault.azure.net/",
"clientId": "",
"tenantId": "",
"clientSecret": ""
},
"_keyType": "secp256r1",
"_publicKey": {
"type": "jwk",
"jwk": {
"kty": "EC",
"crv": "P-256",
"kid": "https://kv-nhd-vp-core-dev.vault.azure.net/keys/541693094/be4a95477f274b38826938a92f990fd5",
"key_ops": [
"sign",
"verify"
],
"x": "gWLUzx_B-36rcSdwlf78IjCRqVzchZFj6ino_ueu1y8",
"y": "dL6LiFRFDwSyQphljk5YB0gXGlB5eg8sPZV_m1DTl2o"
}
}
},
"issuerDid": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiaHR0cHM6Ly9rdi1uaGQtdnAtY29yZS1kZXYudmF1bHQuYXp1cmUubmV0L2tleXMvNTQxNjkzMDk0L2JlNGE5NTQ3N2YyNzRiMzg4MjY5MzhhOTJmOTkwZmQ1Iiwia2V5X29wcyI6WyJzaWduIiwidmVyaWZ5Il0sIngiOiJnV0xVenhfQi0zNnJjU2R3bGY3OElqQ1JxVnpjaFpGajZpbm9fdWV1MXk4IiwieSI6ImRMNkxpRlJGRHdTeVFwaGxqazVZQjBnWEdsQjVlZzhzUFpWX20xRFRsMm8ifQ"
}
```
**Properties**
- `type`: _String_ - the type of key can be either "aws" when using AWS KMS or "jwk" when providing the key in
full as JWK.
- `config`
- `keyVaultUrl`: _String_ - The URL of the Azure Key Vault.
- `clientId`: _String_ - The client ID of the Azure AD application.
- `tenantId`: _String_ - The tenant ID of the Azure AD application.
- `clientSecret`: _String_ - The client secret of the Azure AD application.
- `_publicKey` (optional): _Array_ - The public key can be fetched by the issuer API or directly provided, saving
resources and reducing network requests.
- `_keyType` (optional): _String_ - The key type can be fetched by the issuer API or directly provided, saving resources
and reducing network requests.
- `issuerDid`: _String_ - the DID of the issuer.
### Key Usage
Once you have successfully created a key that is one of the supported types listed above, you can use it in
sign and issue operations offered by the issuer API.
If you've already had a look at
our `/sign`, `/issue`, `/batchIssue`
endpoints, you have seen that they all follow a similar request body structure, where the key that should be
used for signing credentials is provided via the `issuerKey` property. Now instead of providing the key as JWK, we
provide
a reference to a key stored in Vault with the required parameters and access credentials.
Below you can see an example of
`issuerKey` object referencing a key stored in AWS.
**Option: Example IssuerKey Object**
```json
{
"issuerKey": {
"type": "azure-rest-api",
"id": "https://kv-nhd-vp-core-dev.vault.azure.net/keys/541693094/be4a95477f274b38826938a92f990fd5",
"auth": {
"keyVaultUrl": "https://.vault.azure.net/",
"clientId": "",
"tenantId": "",
"clientSecret": ""
},
"_keyType": "secp256r1",
"_publicKey": {
"type": "jwk",
"jwk": {
"kty": "EC",
"crv": "P-256",
"kid": "https://kv-nhd-vp-core-dev.vault.azure.net/keys/541693094/be4a95477f274b38826938a92f990fd5",
"key_ops": [
"sign",
"verify"
],
"x": "gWLUzx_B-36rcSdwlf78IjCRqVzchZFj6ino_ueu1y8",
"y": "dL6LiFRFDwSyQphljk5YB0gXGlB5eg8sPZV_m1DTl2o"
}
}
},
"issuerDid": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiaHR0cHM6Ly9rdi1uaGQtdnAtY29yZS1kZXYudmF1bHQuYXp1cmUubmV0L2tleXMvNTQxNjkzMDk0L2JlNGE5NTQ3N2YyNzRiMzg4MjY5MzhhOTJmOTkwZmQ1Iiwia2V5X29wcyI6WyJzaWduIiwidmVyaWZ5Il0sIngiOiJnV0xVenhfQi0zNnJjU2R3bGY3OElqQ1JxVnpjaFpGajZpbm9fdWV1MXk4IiwieSI6ImRMNkxpRlJGRHdTeVFwaGxqazVZQjBnWEdsQjVlZzhzUFpWX20xRFRsMm8ifQ"
}
```
**Properties**
- `type`: _String_ - the type of key can be either "aws" when using AWS KMS or "jwk" when providing the key in
full as JWK.
-
- `id`: _String_ - the ID of the key in the Transit Engine.
- `config`
- `keyVaultUrl`: _String_ - The URL of the Azure Key Vault.
- `clientId`: _String_ - The client ID of the Azure AD application.
- `tenantId`: _String_ - The tenant ID of the Azure AD application.
- `clientSecret`: _String_ - The client secret of the Azure AD application.
- `_publicKey` (optional): _Array_ - The public key can be fetched by the issuer API or directly provided, saving
resources and reducing network requests.
- `_keyType` (optional): _String_ - The key type can be fetched by the issuer API or directly provided, saving resources
and reducing network requests.
**Option: Example Issuance Request**
## Example Issuance Request
Below you can see example issuance request
to [`jwt/issue`](https://issuer.demo.walt.id/swagger/index.html#/Credential%20Issuance/post_openid4vc_jwt_issue) using
a key created in AWS kms to
sign the credential.
[Api Reference](https://issuer.demo.walt.id/swagger/index.html#/Credential%20Issuance/post_openid4vc_jwt_issue)
```bash
curl -X 'POST' \
'https://issuer.demo.walt.id/openid4vc/jwt/issue' \
-H 'accept: text/plain' \
-H 'statusCallbackUri: https://example.com/$id' \
-H 'Content-Type: application/json' \
-d '{
"issuerKey": {
"type": "azure-rest-api",
"id": "https://kv-nhd-vp-core-dev.vault.azure.net/keys/541693094/be4a95477f274b38826938a92f990fd5",
"auth": {
"keyVaultUrl": "https://.vault.azure.net/",
"clientId": "",
"tenantId": "",
"clientSecret": ""
},
"_keyType": "secp256r1",
"_publicKey": {
"type": "jwk",
"jwk": {
"kty": "EC",
"crv": "P-256",
"kid": "https://kv-nhd-vp-core-dev.vault.azure.net/keys/541693094/be4a95477f274b38826938a92f990fd5",
"key_ops": [
"sign",
"verify"
],
"x": "gWLUzx_B-36rcSdwlf78IjCRqVzchZFj6ino_ueu1y8",
"y": "dL6LiFRFDwSyQphljk5YB0gXGlB5eg8sPZV_m1DTl2o"
}
}
},
"issuerDid": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiaHR0cHM6Ly9rdi1uaGQtdnAtY29yZS1kZXYudmF1bHQuYXp1cmUubmV0L2tleXMvNTQxNjkzMDk0L2JlNGE5NTQ3N2YyNzRiMzg4MjY5MzhhOTJmOTkwZmQ1Iiwia2V5X29wcyI6WyJzaWduIiwidmVyaWZ5Il0sIngiOiJnV0xVenhfQi0zNnJjU2R3bGY3OElqQ1JxVnpjaFpGajZpbm9fdWV1MXk4IiwieSI6ImRMNkxpRlJGRHdTeVFwaGxqazVZQjBnWEdsQjVlZzhzUFpWX20xRFRsMm8ifQ"
"credentialData": {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://purl.imsglobal.org/spec/ob/v3p0/context.json"
],
"id": "urn:uuid:THIS WILL BE REPLACED WITH DYNAMIC DATA FUNCTION (see below)",
"type": [
"VerifiableCredential",
"OpenBadgeCredential"
],
"name": "JFF x vc-edu PlugFest 3 Interoperability",
...
},
"mapping": {
"id": "",
...
}
}'
```
**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`
or `https://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 id
- `type`: _String_ - the event type
- `data`: _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` with `jwt` being the issued jwt
- `sdjwt_issue` with `sdjwt` being the issued sdjwt
- `batch_jwt_issue` with `jwt` being the issued jwt
- `batch_sdjwt_issue` with `sdjwt` being the issued sdjwt
- `generated_mdoc` with `mdoc` 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**
As you can see for the property `issuerKey` we only provided the required parameters of the TSE Key Reference Object
described above and left out `_publicKey` and `_keyType`
```json
{
"issuerKey": {
"type": "azure-rest-api",
"id": "https://kv-nhd-vp-core-dev.vault.azure.net/keys/541693094/be4a95477f274b38826938a92f990fd5",
"auth": {
"keyVaultUrl": "https://.vault.azure.net/",
"clientId": "",
"tenantId": "",
"clientSecret": ""
},
"_keyType": "secp256r1",
"_publicKey": {
"type": "jwk",
"jwk": {
"kty": "EC",
"crv": "P-256",
"kid": "https://kv-nhd-vp-core-dev.vault.azure.net/keys/541693094/be4a95477f274b38826938a92f990fd5",
"key_ops": [
"sign",
"verify"
],
"x": "gWLUzx_B-36rcSdwlf78IjCRqVzchZFj6ino_ueu1y8",
"y": "dL6LiFRFDwSyQphljk5YB0gXGlB5eg8sPZV_m1DTl2o"
}
}
},
"issuerDid": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiaHR0cHM6Ly9rdi1uaGQtdnAtY29yZS1kZXYudmF1bHQuYXp1cmUubmV0L2tleXMvNTQxNjkzMDk0L2JlNGE5NTQ3N2YyNzRiMzg4MjY5MzhhOTJmOTkwZmQ1Iiwia2V5X29wcyI6WyJzaWduIiwidmVyaWZ5Il0sIngiOiJnV0xVenhfQi0zNnJjU2R3bGY3OElqQ1JxVnpjaFpGajZpbm9fdWV1MXk4IiwieSI6ImRMNkxpRlJGRHdTeVFwaGxqazVZQjBnWEdsQjVlZzhzUFpWX20xRFRsMm8ifQ"
"credentialData": {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://purl.imsglobal.org/spec/ob/v3p0/context.json"
],
"id": "urn:uuid:THIS WILL BE REPLACED WITH DYNAMIC DATA FUNCTION (see below)",
"type": [
"VerifiableCredential",
"OpenBadgeCredential"
],
"name": "JFF x vc-edu PlugFest 3 Interoperability",
...
},
"mapping": {
"id": "",
...
}
}
```
--------------------------------------------------------------------------------
PAGE: Overview
URL: https://docs.walt.id/community-stack/issuer/credential-issuance/overview
RAW: https://docs.walt.id/community-stack/issuer/credential-issuance/overview.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Overview
## How to create & issue different types of credentials
- [W3C VCs](https://docs.walt.id/community-stack/issuer/credential-issuance/vc-oid4vc.md) - issue W3C Verifiable Credentials with JWT or
SD-JWT signatures.
- [SD-JWT VCs](https://docs.walt.id/community-stack/issuer/credential-issuance/sd-jwt-vc-oid4vc.md) - issue IETF SD-JWT VC credentials.
- [mDLs](https://docs.walt.id/community-stack/issuer/credential-issuance/mdl-oid4vc.md) - issue Mobile Driver’s License (ISO/IEC 18013-5
mDLs) credentials.
## Populate Credential Data At The Time Of Issuance
- [Data Functions](https://docs.walt.id/community-stack/issuer/credential-issuance/data-functions.md) - ensure issued credentials always
contain the most current and accurate information.
--------------------------------------------------------------------------------
PAGE: How to Issue W3C Verifiable Credentials (JWT / SD-JWT) via OID4VCI with walt.id
URL: https://docs.walt.id/community-stack/issuer/credential-issuance/vc-oid4vc
RAW: https://docs.walt.id/community-stack/issuer/credential-issuance/vc-oid4vc.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# How to Issue W3C Verifiable Credentials (JWT / SD-JWT) via OID4VCI with walt.id
**TL;DR**
**What you’ll learn:**
- Generate an issuer signing key and DID with the `/onboard/issuer` endpoint.
- Prepare credential data for a W3C Verifiable Credential template (e.g. a UniversityDegree).
- Call the OID4VCI issuance endpoints for JWT and SD-JWT credentials with the required headers and body parameters.
- Create credential offer URLs that any compliant wallet can redeem for a credential.
**Relevant concepts (optional):**
- [Verifiable Credentials (W3C)](https://docs.walt.id/concepts/digital-credentials/verifiable-credentials-w3c.md) – W3C standard for digital credentials.
- [OID4VCI](https://docs.walt.id/concepts/data-exchange-protocols/openid4vci.md) – protocol for issuing verifiable credentials to wallets.
- [SD-JWT VCs](https://docs.walt.id/concepts/digital-credentials/sd-jwt-vc.md) – selective-disclosure credential format based on JSON Web Tokens.
- [DIDs and DID methods](https://docs.walt.id/concepts/decentralised-identifiers.md) – identifiers used to represent issuers and holders.
Video: https://youtu.be/VZrYZrQPbw0?t=147
This guide provides a comprehensive walkthrough for issuing a Verifiable Credential based on
the [W3C standard](https://www.w3.org/TR/vc-data-model-2.0/)
with a JWT or SD-JWT signature using the walt.id issuer API.
The issuance process will utilize the
[OID4VCI protocol](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-ID1.html).
**Verifiable Credential (VC)**: A digital equivalent of physical credentials such as a driver's license or university
degree.
VCs are cryptographically secure, privacy-respecting, and instantly verifiable.
**OID4VCI**: A protocol specifying how parties can issue credentials and present them in a way that's consistent
and secure across platforms ensuring interoperability.
## Setup
See how to access to the issuer API below.
- [Deployed (Testing Only)](https://issuer.demo.walt.id/swagger/index.html) - Use our deployed version for testing.
- [Local](https://docs.walt.id/community-stack/issuer/setup.md) - Run the API in your environment with our open-source setup.
## Preparing for Issuance: Key Components
Before issuing a verifiable credential, you'll need:
1. **Raw Credential Data**: The information you want to issue as a VC, typically following a specific template or
schema.
2. **Signing Key**: A cryptographic key used to sign the credential, confirming its authenticity and integrity during
verification.
If you don't make any specific configurations, the walt.id issuer API supports:
- **Default credential types** – W3C credential templates that can be found
[here](https://credentials.walt.id/).
- **Custom credential types** – credentials that you add to the supported list in the
[issuer metadata](https://docs.walt.id/community-stack/issuer/configurations/config-files/credential-issuer-metadata.md).
For this example, we will issue the default Verifiable University Degree, though the process is the same for other
credentials.
### Example Credential: Verifiable University Degree
```json
{
// The contextual information required for data integrity and 1.api
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1"
],
"id": "http://example.gov/credentials/3732",
// The credential's unique identifier
"type": [
"VerifiableCredential",
"UniversityDegree"
// Specifies the kind of credential being issued
],
"issuer": {
"id": "did:web:vc.transmute.world"
// The issuer's unique identifier (DID)
},
"issuanceDate": "2020-03-10T04:24:12.164Z",
// When the credential was issued
"credentialSubject": {
// Information about the credential's recipient and the degree earned
"id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
"degree": {
"type": "BachelorDegree",
"name": "Bachelor of Science and Arts"
}
}
}
```
**Note**: Fields like `issuer`, `issuanceDate`, and `credentialSubject.did` are dynamic and will be populated with
actual
data upon issuance.
## Issuing a Credential
In this section, we'll generate a signing key which we store ourselves. For production
environments, we recommend using an external KMS provider for key management due to the enhanced security.
Learn more about the different types of keys and the storage options [here](https://docs.walt.id/community-stack/issuer/key-management/overview.md).
### Step 1: Get a Signing Key & Issuer DID
As the issuer API doesn't store any cryptographic key material by default, you need to provide the key used for signing
the credential in one of the following ways:
- **JWK** – the key is managed by you and provided directly in JWK format.
- **External KMS reference** – a reference object that points to a key stored in a supported external KMS such as
[Hashicorp Vault](https://docs.walt.id/community-stack/issuer/key-management/hashicorp-vault.md) or
[Oracle KMS](https://docs.walt.id/community-stack/issuer/key-management/oci-vault.md).
In a production environment, we recommend using an external KMS provider to secure the key material.
To create keys and a related DID for signing credentials, you can use the `/onboard/issuer` endpoint and choose:
- **Key algorithms**: `ed25519`, `secp256k1`, `secp256r1`, or `RSA`.
- **DID methods**: `did:key`, `did:jwk`, `did:web`, or `did:cheqd`.
Please refer to our [Key Management section](https://docs.walt.id/community-stack/issuer/key-management/overview.md) to learn more about the
different options.
For this guide, we will proceed with generating a JWK ed25519 key and a did:key using the `onboard` endpoint.
#### Create Key & DID
**Option: CURL**
**Endpoint:
** `/onboard/issuer` | [API Reference](https://issuer.demo.walt.id/swagger/index.html#/Issuer%20onboarding/post_onboard_issuer)
**Example Request**
```bash
curl -X 'POST' \
'http://0.0.0.0:7002/onboard/issuer' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"key": {
"backend": "jwk",
"keyType": "secp256r1"
},
"did": {
"method": "jwk"
}
}'
```
**Body**
```json
{
"key": {
"backend": "jwk",
"keyType": "secp256r1"
},
"did": {
"method": "jwk"
}
}
```
**Body Parameters**
- `key`
- `backend`: _String_ - Specifies the storage type of key. It can be `jwk` (manged by you), `TSE` (managed by
Hashicorp
Vault) and others. Learn more about different types [here](https://docs.walt.id/community-stack/issuer/key-management/overview.md).
- `keyType`: _String_ - the algorithm used to generate the key. For local, it can
be ed25519, secp256k1, secp256r1, or RSA. For the other types and the supported algorithms, please
go [here](https://docs.walt.id/community-stack/issuer/key-management/overview.md).
- `did`:
- `method`: _String_ - Specifies the DID method. It can be key, jwk, web, cheqd.
---
**Example Response**
The onboard/issuer endpoint will return an object containing both the generated key in JWK format and the related DID.
```json
{
"issuerKey": {
"type": "jwk",
"jwk": {
"kty": "EC",
"d": "n4F7NylR_6YdB5pMHBkieI5SMry7-EXKrggm8yMg1dU",
"crv": "P-256",
"kid": "nsG9yIDCn5mBrKomGLTUlJPHal47Gdov6l_8Jnq9VJI",
"x": "lL8Yf2PDMnAgyVfNQ-IypgV0pP1_XDFqYKMpy9duuAo",
"y": "Nw0D8VdWMYu6XUBKPiQ7Zo4_rZ-nP-y2dAo1gGbvPys"
}
},
"issuerDid": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoibnNHOXlJRENuNW1CcktvbUdMVFVsSlBIYWw0N0dkb3Y2bF84Sm5xOVZKSSIsIngiOiJsTDhZZjJQRE1uQWd5VmZOUS1JeXBnVjBwUDFfWERGcVlLTXB5OWR1dUFvIiwieSI6Ik53MEQ4VmRXTVl1NlhVQktQaVE3Wm80X3JaLW5QLXkyZEFvMWdHYnZQeXMifQ"
}
```
**Note:**
As we've used the jwk key type, it's important to note that we need to save the returned values ourselves for future
reference.
The API doesn't save any information about created keys or DIDs.
### Step 2: Issue the Credential
Now, we'll issue a verifiable credential using the key and DID obtained (or any other supported JWK).
You can choose between the following signature types:
- **JWT** – a JSON Web Token representation of the credential.
- **SD-JWT** – a Selective-Disclosure JSON Web Token representation of the credential.
Learn more about Selective Disclosure [here](https://docs.walt.id/concepts/selective-disclosure/sd-jwt/intro). In short, it enables your
users to only reveal a subset of the claims in a credential to a verifier when suitable. This increases privacy and
reduces the risk of identity theft and other types of fraud.
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).
**Note:**
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.
When we execute the issuance command, two things will happen:
1. The credential will be signed with the provided key and the chosen signature type (JWT or SD-JWT).
2. The information about the signed credential will be embedded into the OID Credential Offer URL, which we can send to
our users so they can claim the credential(s).
**Option: JWT**
[API Reference](https://issuer.demo.walt.id/swagger/index.html#/Credential%20Issuance/post_openid4vc_jwt_issue)
**Option: CURL**
**Endpoint:** `/openid4vc/jwt/issue`
**Example Request**
```bash
curl -X 'POST' \
'https://issuer.demo.walt.id/openid4vc/jwt/issue' \
-H 'accept: text/plain' \
-H 'statusCallbackUri: https://example.com/$id' \
-H 'Content-Type: application/json' \
-d '{
"issuerKey": {
"type": "jwk",
"jwk": {
"kty": "OKP",
"d": "JvJIpga2GD8LJeRu4Sv-mL4thE31DuFlr9PA04CIoZY",
"crv": "Ed25519",
"kid": "iJMS5bkZVIlncfq_Lf_SuxJ2JtQ5Hvaz7tWPnAjUUds",
"x": "FZdvwC8aGhRwqzWptej0NZgtwYAI1SyFg1mKDETOfqE"
}
},
"issuerDid": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiaUpNUzVia1pWSWxuY2ZxX0xmX1N1eEoySnRRNUh2YXo3dFdQbkFqVVVkcyIsIngiOiJGWmR2d0M4YUdoUndxeldwdGVqME5aZ3R3WUFJMVN5RmcxbUtERVRPZnFFIn0",
"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": "",
"issuer": {
"id": ""
},
"credentialSubject": {
"id": ""
},
"issuanceDate": "",
"expirationDate": ""
},
"authenticationMethod": "PRE_AUTHORIZED",
"standardVersion": "DRAFT13"
}'
```
**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`
or `https://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 id
- `type`: _String_ - the event type
- `data`: _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` with `jwt` being the issued jwt
- `sdjwt_issue` with `sdjwt` being the issued sdjwt
- `batch_jwt_issue` with `jwt` being the issued jwt
- `batch_sdjwt_issue` with `sdjwt` being the issued sdjwt
- `generated_mdoc` with `mdoc` 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**
```json
{
"issuerKey": {
"type": "jwk",
"jwk": {
"kty": "OKP",
"d": "JvJIpga2GD8LJeRu4Sv-mL4thE31DuFlr9PA04CIoZY",
"crv": "Ed25519",
"kid": "iJMS5bkZVIlncfq_Lf_SuxJ2JtQ5Hvaz7tWPnAjUUds",
"x": "FZdvwC8aGhRwqzWptej0NZgtwYAI1SyFg1mKDETOfqE"
}
},
"issuerDid": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiaUpNUzVia1pWSWxuY2ZxX0xmX1N1eEoySnRRNUh2YXo3dFdQbkFqVVVkcyIsIngiOiJGWmR2d0M4YUdoUndxeldwdGVqME5aZ3R3WUFJMVN5RmcxbUtERVRPZnFFIn0",
"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": "",
"issuer": {
"id": ""
},
"credentialSubject": {
"id": ""
},
"issuanceDate": "",
"expirationDate": ""
},
"authenticationMethod": "PRE_AUTHORIZED",
"standardVersion": "DRAFT13"
}
```
**Body Parameters**
- `issuerKey`: _JSON_ - A JWK or reference object to a key stored in an external KMS to sign the credential with.
Supported algorithms: ed25519, secp256k1, secp256r1, or
RSA.
**JWK Format:** `{"type": "jwk", "jwk": Here JSON Web Key Object}`. Must be provided as JWK object to "issuerKey".
**KMS Key**: Please refer to the [Key Management Section](https://docs.walt.id/community-stack/issuer/key-management/overview.md) and the KMS you want to
use for more details on the structure
of the reference object.
- `issuerDid`: _String_ - The DID related to the provided key.
- `credentialConfigurationId`: _String_ - Reference to a specific credential configuration the issuer supports. As our
issuer currently supports W3C JWT & SD-JWT credentials and SD-JWT VCs (IETF). The structure of the
credentialConfigurationId is the following: "
credentialType_jwt_vc_json" for W3C JWT & SD-JWT credentials
and "credentialType_vc+sd-jwt" for SD-JWT VCs (IETF). E.g. "UniversityDegree_jwt_vc_json" or "
UniversityDegree_vc+sd-jwt".
You can also view
the credentialConfigurationIds by visiting the `/.well-known/openid-credential-issuer` endpoint of your issuer.
The metadata of our deployed issuer for testing can be
seen [here](https://issuer.demo.walt.id/swagger/index.html#/oidc/get__standardVersion___well_known_openid_credential_issuer).
- `credentialData`: _JSON_ - A credential data structure to sign (W3C compliant). A list of predefined valid structures
can be
found [here](https://credentials.walt.id/).
- `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](https://docs.walt.id/community-stack/issuer/data-functions).
- `standardVersion`: (optional) _String_ - Defines which OIDC4VCI standard version is used. The supported standard versions are: `DRAFT13` and `DRAFT11`. If no value is provided, it will default to `DRAFT13`.
- `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 as `PRE_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 server
- `ID_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 the `vpRequestedValue` as explained below. The policies which are applied to the presented credential
are 'signature,' 'expired,' and 'not-before.' Learn more about policies [here](https://docs.walt.id/community-stack/verifier/credential-verification/policies/overview.md).
- `NONE` - used for authorization code flow with none authentication method
- `PRE_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.demo.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
}
}
}
```
**Option: SD-JWT**
[API Reference](https://issuer.demo.walt.id/swagger/index.html#/Credential%20Issuance/post_openid4vc_sdjwt_issue)
**Option: CURL**
**Endpoint:** `/openid4vc/sdjwt/issue`
**Example Request**
```bash
curl -X 'POST' \
'https://issuer.demo.walt.id/openid4vc/sdjwt/issue' \
-H 'accept: text/plain' \
-H 'Content-Type: application/json' \
-d '{
"issuerKey": {
"type": "jwk",
"jwk": "{\"kty\":\"OKP\",\"d\":\"mDhpwaH6JYSrD2Bq7Cs-pzmsjlLj4EOhxyI-9DM1mFI\",\"crv\":\"Ed25519\",\"kid\":\"Vzx7l5fh56F3Pf9aR3DECU5BwfrY6ZJe05aiWYWzan8\",\"x\":\"T3T4-u1Xz3vAV2JwPNxWfs4pik_JLiArz_WTCvrCFUM\"}"
},
"issuerDid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp",
"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": "",
"issuer": {
"id": ""
},
"credentialSubject": {
"id": ""
},
"issuanceDate": "",
"expirationDate": ""
},
"authenticationMethod": "PRE_AUTHORIZED",
"selectiveDisclosure": {
"fields": {
"issuanceDate": {
"sd": true
},
"credentialSubject": {
"sd": false,
"children": {
"fields": {
"degree": {
"sd": false,
"children": {
"fields": {
"name": {
"sd": true
}
}
}
}
}
}
}
}
}
}'
```
**Body**
```json
{
"issuerKey": {
"type": "jwk",
"jwk": "{\"kty\":\"OKP\",\"d\":\"mDhpwaH6JYSrD2Bq7Cs-pzmsjlLj4EOhxyI-9DM1mFI\",\"crv\":\"Ed25519\",\"kid\":\"Vzx7l5fh56F3Pf9aR3DECU5BwfrY6ZJe05aiWYWzan8\",\"x\":\"T3T4-u1Xz3vAV2JwPNxWfs4pik_JLiArz_WTCvrCFUM\"}"
},
"issuerDid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp",
"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": "",
"issuer": {
"id": ""
},
"credentialSubject": {
"id": ""
},
"issuanceDate": "",
"expirationDate": ""
},
"authenticationMethod": "PRE_AUTHORIZED",
"selectiveDisclosure": {
"fields": {
"issuanceDate": {
"sd": true
},
"credentialSubject": {
"sd": false,
"children": {
"fields": {
"degree": {
"sd": false,
"children": {
"fields": {
"name": {
"sd": true
}
}
}
}
}
}
}
}
}
}
```
**Body Parameters**
- `issuerKey`: _JSON_ - A JWK or reference object to a key stored in an external KMS to sign the credential with.
Supported algorithms: ed25519, secp256k1, secp256r1, or
RSA.
**JWK Format:** `{"type": "jwk", "jwk": "Here JSON Web Key Object"}`. Please make sure to escape the JWK object as
a string when providing as a value to "issuerKey".
**KMS Key**: Please refer to the [Key Management Section](https://docs.walt.id/community-stack/issuer/key-management/overview.md) and the KMS you want to
use for more details on the structure
of the reference object.
- `issuerDid`: _String_ - The DID related to the provided key.
- `credentialConfigurationId`: _String_ - Reference to a specific credential configuration the issuer supports. As our
issuer currently supports W3C JWT & SD-JWT credentials and SD-JWT VCs (IETF). The structure of the
credentialConfigurationId is the following: "
credentialType_jwt_vc_json" for W3C JWT & SD-JWT credentials
and "credentialType_vc+sd-jwt" for SD-JWT VCs (IETF). E.g. "UniversityDegree_jwt_vc_json" or "
UniversityDegree_vc+sd-jwt".
You can also view
the credentialConfigurationIds by visiting the `/.well-known/openid-credential-issuer` endpoint of your issuer.
The metadata of our deployed issuer for testing can be
seen [here](https://issuer.demo.walt.id/swagger/index.html#/oidc/get__standardVersion___well_known_openid_credential_issuer).
- `credentialData`: _JSON_ - A credential data structure to sign (W3C compliant). A list of predefined valid structures
can be
found [here](https://credentials.walt.id/).
- `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](https://docs.walt.id/community-stack/issuer/credential-issuance/data-functions.md).
- `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 as `PRE_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 server
- `ID_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 the `vpRequestedValue` as explained below. The policies which are applied to the presented credential
are 'signature,' 'expired,' and 'not-before.' Learn more about policies [here](https://docs.walt.id/community-stack/verifier/credential-verification/policies/overview.md).
- `NONE` - used for authorization code flow with none authentication method
- `PRE_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
- [`selectiveDisclosure`](https://docs.walt.id/concepts/selective-disclosure/intro) (optional): _JSON_ -An object that configures which
claims in the credential should be
selectively disclosable. It's manged through the following properties:
- `fields`: An object illustrating the hierarchical structure of the credential contents. Each key in this object
specifies the name of the fields in the credential. Every field, whether high-level or nested, can be represented
by an object with a "sd" key. If the value for "sd" is set to true, it means that the corresponding field is
selectively disclosable. Moreover, fields that have nested attributes are represented with a "children" key which
contains another fields object reflecting the structure of the nested object. For example:
```json
{
"fields": {
"issuanceDate": {
"sd": true
},
"credentialSubject": {
"sd": false,
"children": {
"fields": {
"degree": {
"sd": false,
"children": {
"fields": {
"name": {
"sd": true
}
}
}
}
}
}
}
}
}
```
**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%22280b7b66-b8ba-420e-91f1-f3bb684d3edf%22%7D%2C%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%7B%22pre-authorized_code%22%3A%22eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiIyODBiN2I2Ni1iOGJhLTQyMGUtOTFmMS1mM2JiNjg0ZDNlZGYiLCJpc3MiOiJodHRwczovL2lzc3Vlci5wb3J0YWwud2FsdC5pZCIsImF1ZCI6IlRPS0VOIn0.H0HUMNQ5jSzrNEkaVyKlwK4yDq8_sOzRZuQ-2nCwFKMsTrFzEXj95lsgo0Av5RhnOIrhBd98jGjZ0DDwEONwDg%22%2C%22user_pin_required%22%3Afalse%7D%7D%7D
```
**Decoded**
```
openid-credential-offer://issuer.portal.walt.id/?credential_offer={
"credential_issuer": "https://issuer.demo.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": "280b7b66-b8ba-420e-91f1-f3bb684d3edf"
},
"urn:ietf:params:oauth:grant-type:pre-authorized_code": {
"pre-authorized_code": "eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiIyODBiN2I2Ni1iOGJhLTQyMGUtOTFmMS1mM2JiNjg0ZDNlZGYiLCJpc3MiOiJodHRwczovL2lzc3Vlci5wb3J0YWwud2FsdC5pZCIsImF1ZCI6IlRPS0VOIn0.H0HUMNQ5jSzrNEkaVyKlwK4yDq8_sOzRZuQ-2nCwFKMsTrFzEXj95lsgo0Av5RhnOIrhBd98jGjZ0DDwEONwDg",
"user_pin_required": false
}
}
}
```
When you receive the credential using our web wallet and make a presentation to a verifier, you will see that before
accepting the verification request, you will have the option to select which field(s) to share that were marked as
selectively disclosable.
### Step 3: Receive the Credential Offer
The created credential offer can now be:
- **Embedded into a QR code** for users to scan with their mobile wallet.
- **Pasted manually** into the credential offer field of our
[web wallet](https://wallet.demo.walt.id/).
**Try It Out**: Use our [web wallet](https://wallet.demo.walt.id/) for a practical demonstration:
1. Log in.
2. Click the `request credential` button.
3. Paste the received Offer URL into the text field below the camera.
🎉 Congratulations, you've issued a W3C-compliant Verifiable Credential using OID4VCI! 🎉
To learn how to accept this credential offer programmatically using the Wallet API, see [How to Accept W3C Verifiable Credentials via OID4VCI](https://docs.walt.id/community-stack/wallet/credential-exchange/guides/accept-w3c-vc-oid4vci.md).
--------------------------------------------------------------------------------
PAGE: How to Issue SD-JWT-based Verifiable Credentials (IETF | SD-JWT VC) via OID4VCI with walt.id
URL: https://docs.walt.id/community-stack/issuer/credential-issuance/sd-jwt-vc-oid4vc
RAW: https://docs.walt.id/community-stack/issuer/credential-issuance/sd-jwt-vc-oid4vc.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# How to Issue SD-JWT-based Verifiable Credentials (IETF | SD-JWT VC) via OID4VCI with walt.id
**TL;DR**
**What you’ll learn:**
- Generate issuer keys and DIDs with the `/onboard/issuer` endpoint to sign SD-JWT VCs.
- Issue SD-JWT VCs over OID4VCI using RAW keys or keys managed via an external KMS.
- Configure selective disclosure fields, mapping objects, and authentication methods for issuance sessions.
- Create credential offer URLs that any compliant wallet can redeem for a credential.
**Relevant concepts:**
- [SD-JWT VC](https://docs.walt.id/concepts/digital-credentials/sd-jwt-vc.md) – IETF SD-JWT VCs standard.
- [OID4VCI](https://docs.walt.id/concepts/data-exchange-protocols/openid4vci.md) – protocol for issuing verifiable credentials to wallets.
Video: https://youtu.be/cnxEljh8eyU
This guide provides a comprehensive walkthrough for issuing an SD-JWT VC credential based on
the [IETF standard](https://datatracker.ietf.org/doc/draft-ietf-oauth-sd-jwt-vc/)
using the walt.id issuer API. If you are interested to learn how to issue an W3C Verifiable Credentials, please
go [here](https://docs.walt.id/community-stack/issuer/credential-issuance/vc-oid4vc.md).
The issuance process will utilize the
[OID4VCI protocol](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-ID1.html).
## Setup
See how to access to the issuer API below.
- [Deployed (Testing Only)](https://issuer.demo.walt.id/swagger/index.html) - Use our deployed version for testing.
- [Local](https://docs.walt.id/community-stack/issuer/setup.md) - Run the API in your environment with our open-source setup.
## Preparing for Issuance: Key Components
Before issuing a verifiable credential, you'll need:
1. **Raw Credential Data**: The information you want to issue as a VC, typically following a specific template or
schema.
2. **Issuer DID or X.509 Certificate**: When issuing a SD-JWT VC credential, it can either be signed using the key of an
issuer DID or a key which is trusted by an X.509 certificate.
The walt.id issuer API supports SD-JWT VC credentials in two ways:
- **Default credential types** – available out of the box and listed in the credential issuer metadata endpoint, e.g. [here](https://issuer.demo.walt.id/swagger/index.html#/oidc/get__well_known_openid_credential_issuer).
- **Custom credentials** – added by extending the supported credentials in the [issuer metadata](https://docs.walt.id/community-stack/issuer/configurations/config-files/credential-issuer-metadata.md).
For this example, we will issue the default supported Identity Credential, though the process is the same for other credentials.
### Example ID Credential:
```json
{
"vct": "identity_credential_vc+sd-jwt",
"given_name": "John",
"family_name": "Doe",
"email": "johndoe@example.com",
"phone_number": "+1-202-555-0101",
"address": {
"street_address": "123 Main St",
"locality": "Anytown",
"region": "Anystate",
"country": "US"
},
"birthdate": "1940-01-01",
"is_over_18": true,
"is_over_21": true,
"is_over_65": true
}
```
## Issuing a Credential
Choose below if you want to sign the credential using the key of a DID or an X.509 certificate.
**Option: DID**
We will now generate a key and associated DID via the issuer API. However, we need to store
the key material + DID ourselves.
For production environments, we recommend using an external KMS provider for key management due to the
enhanced security. Learn more about the different types of keys and the storage options [here](https://docs.walt.id/community-stack/issuer/key-management/overview.md).
### Step 1: Get a Signing Key & Issuer DID
As the issuer API doesn't store any cryptographic key material by default, you need to manage and provide the key used for signing the credential. You can:
- **Use a JWK key** – provide the signing key directly in JWK format and manage it yourself.
- **Use an external KMS** – provide a reference object that points to a key stored in a supported KMS such as [Hashicorp Vault](https://docs.walt.id/community-stack/issuer/key-management/hashicorp-vault.md) or [Oracle KMS](https://docs.walt.id/community-stack/issuer/key-management/oci-vault.md).
In a production environment, we recommend using an external KMS provider to secure the key material.
To create keys for signing credentials,
you can use the `/onboard/issuer` endpoint and choose from a variety of algorithms (ed25519,
secp256k1, secp256r1, or RSA). The DID generated by the endpoint is not needed for the SD-JWT VC.
Please refer to our [Key Management section](https://docs.walt.id/community-stack/issuer/key-management/overview.md)
to learn more about the different options.
For this guide, we will proceed with generating a JWK Ed25519 key using the `onboard` endpoint.
#### Create Key
**Option: CURL**
**Endpoint:** `/onboard/issuer` | [API Reference](https://issuer.demo.walt.id/swagger/index.html#/Issuer%20onboarding/post_onboard_issuer)
**Example Request**
```bash
curl -X 'POST' \
'http://0.0.0.0:7002/onboard/issuer' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"key": {
"backend": "jwk",
"keyType": "Ed25519"
},
"did": {
"method": "jwk"
}
}'
```
**Body**
```json
{
"key": {
"backend": "jwk",
"keyType": "Ed25519"
},
"did": {
"method": "jwk"
}
}
```
**Body Parameters**
- `key`
- `backend`: _String_ - Specifies the storage type of key. It can be `jwk` (manged by you), `TSE` (managed by
Hashicorp
Vault) and others. Learn more about different types [here](https://docs.walt.id/community-stack/issuer/key-management/overview.md).
- `keyType`: _String_ - the algorithm used to generate the key. For local, it can
be ed25519, secp256k1, secp256r1, or RSA. For the other types and the supported algorithms, please
go [here](https://docs.walt.id/community-stack/issuer/key-management/overview.md).
- `did`:
- `method`: _String_ - Specifies the DID method. It can be key, jwk, web, cheqd.
---
**Example Response**
The onboard/issuer endpoint will return an object containing both the generated key in JWK format and the related DID.
```json
{
"issuerKey": {
"type": "jwk",
"jwk": {
"kty": "EC",
"d": "n4F7NylR_6YdB5pMHBkieI5SMry7-EXKrggm8yMg1dU",
"crv": "P-256",
"kid": "nsG9yIDCn5mBrKomGLTUlJPHal47Gdov6l_8Jnq9VJI",
"x": "lL8Yf2PDMnAgyVfNQ-IypgV0pP1_XDFqYKMpy9duuAo",
"y": "Nw0D8VdWMYu6XUBKPiQ7Zo4_rZ-nP-y2dAo1gGbvPys"
}
},
"issuerDid": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoibnNHOXlJRENuNW1CcktvbUdMVFVsSlBIYWw0N0dkb3Y2bF84Sm5xOVZKSSIsIngiOiJsTDhZZjJQRE1uQWd5VmZOUS1JeXBnVjBwUDFfWERGcVlLTXB5OWR1dUFvIiwieSI6Ik53MEQ4VmRXTVl1NlhVQktQaVE3Wm80X3JaLW5QLXkyZEFvMWdHYnZQeXMifQ"
}
```
**Important:** As we've used the jwk key type, it's important to note that we need to save the returned values ourselves for future
reference.
The API doesn't save any information about created keys.
### Step 2: Issue the Credential
To issue a verifiable credential, we will use the obtained key or any other supported JWK. When issuing an SD-JWT
VC credential, we have the option to selectively disclose certain claims in the credential. By default, all claims are
visible. The configuration for selectively disclosing claims can be done through a configuration object outlined below.
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).
**Note:**
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.
When we execute the issuance command, two things will happen:
1. The credential will be signed with the provided key.
2. Information about the offered credential will be embedded into the OID Credential Offer
URL, which we can send off to our users to claim the credential(s) later on.
**Note:**
The Verifiable Credential Type (VCT) is resolved through the credentialConfigurationId. The `vct` is found as a parameter within the corresponding entry associated with that id.
Issuer API generates proper resolvable urls for VCT based on provided configuration and host endpoints to retrieve type metadata with the scheme `https:///.well-known/vct/` (e.g. `https://issuer.demo.walt.id/.well-known/vct/identity_credential`).
Find more info [here](https://docs.walt.id/concepts/digital-credentials/sd-jwt-vc.md#verifiable-credential-type-and-type-metadata)
**Option: CURL**
**Endpoint:**
`/openid4vc/sdjwt/issue` | [API Reference](https://issuer.demo.walt.id/swagger/index.html#/Credential%20Issuance/post_openid4vc_sdjwt_issue)
**Example Request**
```bash
curl -X 'POST' \
'https://issuer.demo.walt.id/openid4vc/sdjwt/issue' \
-H 'accept: text/plain' \
-H 'statusCallbackUri: https://example.com/$id' \
-H 'Content-Type: application/json' \
-d '{
"issuerKey": {
"type": "jwk",
"jwk": {
"kty": "OKP",
"d": "mDhpwaH6JYSrD2Bq7Cs-pzmsjlLj4EOhxyI-9DM1mFI",
"crv": "Ed25519",
"kid": "Vzx7l5fh56F3Pf9aR3DECU5BwfrY6ZJe05aiWYWzan8",
"x": "T3T4-u1Xz3vAV2JwPNxWfs4pik_JLiArz_WTCvrCFUM"
}
},
"issuerDid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp",
"credentialConfigurationId": "identity_credential_vc+sd-jwt",
"credentialData": {
"given_name": "John",
"family_name": "Doe",
"email": "johndoe@example.com",
"phone_number": "+1-202-555-0101",
"address": {
"street_address": "123 Main St",
"locality": "Anytown",
"region": "Anystate",
"country": "US"
},
"birthdate": "1940-01-01",
"is_over_18": true,
"is_over_21": true,
"is_over_65": true
},
"mapping": {
"id": "",
"iat": "",
"nbf": "",
"exp": ""
},
"authenticationMethod": "PRE_AUTHORIZED",
"selectiveDisclosure": {
"fields": {
"birthdate": {
"sd": true
}
},
"decoyMode": "NONE",
"decoys": 0
}
}'
```
**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`
or `https://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 id
- `type`: _String_ - the event type
- `data`: _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` with `jwt` being the issued jwt
- `sdjwt_issue` with `sdjwt` being the issued sdjwt
- `batch_jwt_issue` with `jwt` being the issued jwt
- `batch_sdjwt_issue` with `sdjwt` being the issued sdjwt
- `generated_mdoc` with `mdoc` 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**
```json
{
"issuerKey": {
"type": "jwk",
"jwk": {
"kty": "OKP",
"d": "mDhpwaH6JYSrD2Bq7Cs-pzmsjlLj4EOhxyI-9DM1mFI",
"crv": "Ed25519",
"kid": "Vzx7l5fh56F3Pf9aR3DECU5BwfrY6ZJe05aiWYWzan8",
"x": "T3T4-u1Xz3vAV2JwPNxWfs4pik_JLiArz_WTCvrCFUM"
}
},
"issuerDid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp",
"credentialConfigurationId": "identity_credential_vc+sd-jwt",
"credentialData": {
"given_name": "John",
"family_name": "Doe",
"email": "johndoe@example.com",
"phone_number": "+1-202-555-0101",
"address": {
"street_address": "123 Main St",
"locality": "Anytown",
"region": "Anystate",
"country": "US"
},
"birthdate": "1940-01-01",
"is_over_18": true,
"is_over_21": true,
"is_over_65": true
},
"mapping": {
"id": "",
"iat": "",
"nbf": "",
"exp": ""
},
"authenticationMethod": "PRE_AUTHORIZED",
"selectiveDisclosure": {
"fields": {
"birthdate": {
"sd": true
}
},
"decoyMode": "NONE",
"decoys": 0
}
}
```
**Body Parameters**
- `issuerKey`: _JSON_ - A JWK or reference object to a key stored in an external KMS to sign the credential with.
Supported algorithms: ed25519, secp256k1, secp256r1, or
RSA.
**JWK Format:** `{"type": "jwk", "jwk": Here JSON Web Key Object}`. Can be provided as String Or JWK object to "
issuerKey".
**KMS Key**: Please refer to the [Key Management Section](https://docs.walt.id/community-stack/issuer/key-management/overview.md) and the KMS you want to
use for more details on the structure
of the reference object.
- `credentialConfigurationId`: _String_ - Reference to a specific credential configuration the issuer supports. As our
issuer currently supports W3C JWT & SD-JWT credentials and SD-JWT VCs (IETF). The structure of the credentialConfigurationId is the following: "
credentialType_jwt_vc_json" for W3C JWT & SD-JWT credentials
and "credentialType_vc+sd-jwt" for SD-JWT VCs (IETF). E.g. "UniversityDegree_jwt_vc_json" or "UniversityDegree_vc+sd-jwt".
You can also view
the credentialConfigurationIds by visiting the `/.well-known/openid-credential-issuer` endpoint of your issuer.
The metadata of our deployed issuer for testing can be seen [here](https://issuer.demo.walt.id/swagger/index.html#/oidc/get__standardVersion___well_known_openid_credential_issuer).
- `credentialData`: _JSON_ - Credential data structure to sign.
- `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](https://docs.walt.id/community-stack/issuer/data-functions).
In case of IETF SD-JWT, the values could be `id`, `iat`, `nbf` and `exp`.
- `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 as `PRE_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 server
- `ID_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 the `vpRequestedValue` as explained below. The policies which are applied to the presented credential
are 'signature,' 'expired,' and 'not-before.' Learn more about policies [here](https://docs.walt.id/community-stack/verifier/credential-verification/policies/overview.md).
- `NONE` - used for authorization code flow with none authentication method
- `PRE_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
- [`selectiveDisclosure`](https://docs.walt.id/concepts/selective-disclosure/intro) (optional): _JSON_ -An object that configures which
claims in the credential should be
selectively disclosable. It's manged through the following properties:
Expand To Learn More
- `fields`: An object illustrating the hierarchical structure of the credential contents. Each key in this object
specifies the name of the fields in the credential. Every field, whether high-level or nested, can be represented
by an object with a "sd" key. If the value for "sd" is set to true, it means that the corresponding field is
selectively disclosable. Moreover, fields that have nested attributes are represented with a "children" key which
contains another fields object reflecting the structure of the nested object. For example:
```json
{
"fields": {
"issuanceDate": {
"sd": true
},
"credentialSubject": {
"sd": false,
"children": {
"fields": {
"degree": {
"sd": false,
"children": {
"fields": {
"name": {
"sd": true
}
}
}
}
}
}
}
}
}
```
---
**Example Response**
The issuer endpoint will respond with Credential Offer URL.
**Plain Response**
```
openid-credential-offer://issuer.potential.walt-test.cloud/?credential_offer_uri=https%3A%2F%2Fissuer.potential.walt-test.cloud%2Fopenid4vc%2FcredentialOffer%3Fid%3D9aabdb65-defe-464b-baa0-9cc13b36074a
```
**Decoded**
```
{
"credential_issuer": "https://issuer.potential.walt-test.cloud",
"credential_configuration_ids": [
"identity_credential_vc+sd-jwt"
],
"grants": {
"authorization_code": {
"issuer_state": "9aabdb65-defe-464b-baa0-9cc13b36074a"
},
"urn:ietf:params:oauth:grant-type:pre-authorized_code": {
"pre-authorized_code": "eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiI5YWFiZGI2NS1kZWZlLTQ2NGItYmFhMC05Y2MxM2IzNjA3NGEiLCJpc3MiOiJodHRwczovL2lzc3Vlci5wb3RlbnRpYWwud2FsdC10ZXN0LmNsb3VkIiwiYXVkIjoiVE9LRU4ifQ.vl9fBjoYUqr1nrA0jZ3ZjpS45yHLp2roMPCxCoqjuLNyBhTGO0g_PXMw8_NWhD-3qllRq5J0kLAw8WmfvT95Cw"
}
}
}
```
**Option: x.509 certificate**
In this section we will use an x.509 certificate to sign and issue the credential. As our credential is in the SD-JWT
VC format, we have the option to selectively disclose certain claims in the credential. By default, all claims are
visible. The configuration for selectively disclosing claims can be done through a configuration object outlined below.
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).
**Note:**
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.
When we execute the issuance command, two things will happen:
1. The credential will be signed with the provided certificate.
2. Information about the offered credential will be embedded into the OID Credential Offer
URL, which we can send off to our users to claim the credential(s) later on.
**Note:**
The Verifiable Credential Type (VCT) is resolved through the credentialConfigurationId. The `vct` is found as a parameter within the corresponding entry associated with that id.
Issuer API generates proper resolvable urls for VCT based on provided configuration and host endpoints to retrieve type metadata with the scheme `https:///.well-known/vct/` (e.g. `https://issuer.demo.walt.id/.well-known/vct/identity_credential`)
Find more info [here](https://docs.walt.id/concepts/digital-credentials/sd-jwt-vc.md#verifiable-credential-type-and-type-metadata)
**Option: CURL**
**Endpoint:**
`/openid4vc/sdjwt/issue` | [API Reference](https://issuer.demo.walt.id/swagger/index.html#/Credential%20Issuance/post_openid4vc_sdjwt_issue)
**Example Request**
```bash
curl -X 'POST' \
'https://issuer.demo.walt.id/openid4vc/sdjwt/issue' \
-H 'accept: text/plain' \
-H 'statusCallbackUri: https://example.com/$id' \
-H 'Content-Type: application/json' \
-d '{
"issuerKey": {
"type": "jwk",
"jwk": {
"kty": "OKP",
"d": "mDhpwaH6JYSrD2Bq7Cs-pzmsjlLj4EOhxyI-9DM1mFI",
"crv": "Ed25519",
"kid": "Vzx7l5fh56F3Pf9aR3DECU5BwfrY6ZJe05aiWYWzan8",
"x": "T3T4-u1Xz3vAV2JwPNxWfs4pik_JLiArz_WTCvrCFUM"
}
},
"credentialConfigurationId": "identity_credential_vc+sd-jwt",
"credentialData": {
"given_name": "John",
"family_name": "Doe",
"email": "johndoe@example.com",
"phone_number": "+1-202-555-0101",
"address": {
"street_address": "123 Main St",
"locality": "Anytown",
"region": "Anystate",
"country": "US"
},
"birthdate": "1940-01-01",
"is_over_18": true,
"is_over_21": true,
"is_over_65": true
},
"mapping": {
"id": "",
"iat": "",
"nbf": "",
"exp": ""
},
"authenticationMethod": "PRE_AUTHORIZED",
"selectiveDisclosure": {
"fields": {
"birthdate": {
"sd": true
}
},
"decoyMode": "NONE",
"decoys": 0
},
"authenticationMethod": "PRE_AUTHORIZED",
"x5Chain": [
"-----BEGIN CERTIFICATE-----\nMIIBRzCB7qADAgECAgg57ch6mnj5KjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxNRE9DIFJPT1QgQ0EwHhcNMjQwNTAyMTMxMzMwWhcNMjUwNTAyMTMxMzMwWjAbMRkwFwYDVQQDDBBNRE9DIFRlc3QgSXNzdWVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEG0RINBiF+oQUD3d5DGnegQuXenI29JDaMGoMvioKRBN53d4UazakS2unu8BnsEtxutS2kqRhYBPYk9RAriU3gaMgMB4wDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCB4AwCgYIKoZIzj0EAwIDSAAwRQIhAI5wBBAA3ewqIwslhuzFn4rNFW9dkz2TY7xeImO7CraYAiAYhai1NzJ6abAiYg8HxcRdYpO4bu2Sej8E6CzFHK34Yw==\n-----END CERTIFICATE-----"
],
"trustedRootCAs": [
"-----BEGIN CERTIFICATE-----\nMIIBQzCB66ADAgECAgjbHnT+6LsrbDAKBggqhkjOPQQDAjAYMRYwFAYDVQQDDA1NRE9DIFJPT1QgQ1NQMB4XDTI0MDUwMjEzMTMzMFoXDTI0MDUwMzEzMTMzMFowFzEVMBMGA1UEAwwMTURPQyBST09UIENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWP0sG+CkjItZ9KfM3sLF+rLGb8HYCfnlsIH/NWJjiXkTx57ryDLYfTU6QXYukVKHSq6MEebvQPqTJT1blZ/xeKMgMB4wDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwIDRwAwRAIgWM+JtnhdqbTzFD1S3byTvle0n/6EVALbkKCbdYGLn8cCICOoSETqwk1oPnJEEPjUbdR4txiNqkHQih8HKAQoe8t5\n-----END CERTIFICATE-----\n"
]
}'
```
**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`
or `https://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 id
- `type`: _String_ - the event type
- `data`: _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` with `jwt` being the issued jwt
- `sdjwt_issue` with `sdjwt` being the issued sdjwt
- `batch_jwt_issue` with `jwt` being the issued jwt
- `batch_sdjwt_issue` with `sdjwt` being the issued sdjwt
- `generated_mdoc` with `mdoc` 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**
```json
{
"issuerKey": {
"type": "jwk",
"jwk": {
"kty": "OKP",
"d": "mDhpwaH6JYSrD2Bq7Cs-pzmsjlLj4EOhxyI-9DM1mFI",
"crv": "Ed25519",
"kid": "Vzx7l5fh56F3Pf9aR3DECU5BwfrY6ZJe05aiWYWzan8",
"x": "T3T4-u1Xz3vAV2JwPNxWfs4pik_JLiArz_WTCvrCFUM"
}
},
"credentialConfigurationId": "identity_credential_vc+sd-jwt",
"credentialData": {
"given_name": "John",
"family_name": "Doe",
"email": "johndoe@example.com",
"phone_number": "+1-202-555-0101",
"address": {
"street_address": "123 Main St",
"locality": "Anytown",
"region": "Anystate",
"country": "US"
},
"birthdate": "1940-01-01",
"is_over_18": true,
"is_over_21": true,
"is_over_65": true
},
"mapping": {
"id": "",
"iat": "",
"nbf": "",
"exp": ""
},
"selectiveDisclosure": {
"fields": {
"birthdate": {
"sd": true
}
},
"decoyMode": "NONE",
"decoys": 0
},
"authenticationMethod": "PRE_AUTHORIZED",
"x5Chain": [
"-----BEGIN CERTIFICATE-----\nMIIBRzCB7qADAgECAgg57ch6mnj5KjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxNRE9DIFJPT1QgQ0EwHhcNMjQwNTAyMTMxMzMwWhcNMjUwNTAyMTMxMzMwWjAbMRkwFwYDVQQDDBBNRE9DIFRlc3QgSXNzdWVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEG0RINBiF+oQUD3d5DGnegQuXenI29JDaMGoMvioKRBN53d4UazakS2unu8BnsEtxutS2kqRhYBPYk9RAriU3gaMgMB4wDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCB4AwCgYIKoZIzj0EAwIDSAAwRQIhAI5wBBAA3ewqIwslhuzFn4rNFW9dkz2TY7xeImO7CraYAiAYhai1NzJ6abAiYg8HxcRdYpO4bu2Sej8E6CzFHK34Yw==\n-----END CERTIFICATE-----"
],
"trustedRootCAs": [
"-----BEGIN CERTIFICATE-----\nMIIBQzCB66ADAgECAgjbHnT+6LsrbDAKBggqhkjOPQQDAjAYMRYwFAYDVQQDDA1NRE9DIFJPT1QgQ1NQMB4XDTI0MDUwMjEzMTMzMFoXDTI0MDUwMzEzMTMzMFowFzEVMBMGA1UEAwwMTURPQyBST09UIENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWP0sG+CkjItZ9KfM3sLF+rLGb8HYCfnlsIH/NWJjiXkTx57ryDLYfTU6QXYukVKHSq6MEebvQPqTJT1blZ/xeKMgMB4wDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwIDRwAwRAIgWM+JtnhdqbTzFD1S3byTvle0n/6EVALbkKCbdYGLn8cCICOoSETqwk1oPnJEEPjUbdR4txiNqkHQih8HKAQoe8t5\n-----END CERTIFICATE-----\n"
]
}
```
**Body Parameters**
- `issuerKey`: _JSON_ - A JWK or reference object to a key stored in an external KMS to sign the credential with.
Supported algorithms: ed25519, secp256k1, secp256r1, or
RSA.
**JWK Format:** `{"type": "jwk", "jwk": Here JSON Web Key Object}`. Can be provided as String Or JWK object to "
issuerKey".
**KMS Key**: Please refer to the [Key Management Section](https://docs.walt.id/community-stack/issuer/key-management/overview.md) and the KMS you want to
use for more details on the structure
of the reference object.
- `credentialConfigurationId`: _String_ - Reference to a specific credential configuration the issuer supports. As our
issuer currently supports W3C JWT & SD-JWT credentials and SD-JWT VCs (IETF). The structure of the credentialConfigurationId is the following: "
credentialType_jwt_vc_json" for W3C JWT & SD-JWT credentials
and "credentialType_vc+sd-jwt" for SD-JWT VCs (IETF). E.g. "UniversityDegree_jwt_vc_json" or "UniversityDegree_vc+sd-jwt".
You can also view
the credentialConfigurationIds by visiting the `/.well-known/openid-credential-issuer` endpoint of your issuer.
The metadata of our deployed issuer for testing can be seen [here](https://issuer.demo.walt.id/swagger/index.html#/oidc/get__standardVersion___well_known_openid_credential_issuer).
- `credentialData`: _JSON_ - Credential data structure to sign.
- `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](https://docs.walt.id/community-stack/issuer/data-functions).
- `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 as `PRE_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 server
- `ID_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 the `vpRequestedValue` as explained below. The policies which are applied to the presented credential
are 'signature,' 'expired,' and 'not-before.' Learn more about policies [here](https://docs.walt.id/community-stack/verifier/credential-verification/policies/overview.md).
- `NONE` - used for authorization code flow with none authentication method
- `PRE_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
- [`selectiveDisclosure`](https://docs.walt.id/concepts/selective-disclosure/intro) (optional): _JSON_ -An object that configures which
claims in the credential should be
selectively disclosable. It's manged through the following properties:
Expand To Learn More
- `fields`: An object illustrating the hierarchical structure of the credential contents. Each key in this object
specifies the name of the fields in the credential. Every field, whether high-level or nested, can be represented
by an object with a "sd" key. If the value for "sd" is set to true, it means that the corresponding field is
selectively disclosable. Moreover, fields that have nested attributes are represented with a "children" key which
contains another fields object reflecting the structure of the nested object. For example:
```json
{
"fields": {
"issuanceDate": {
"sd": true
},
"credentialSubject": {
"sd": false,
"children": {
"fields": {
"degree": {
"sd": false,
"children": {
"fields": {
"name": {
"sd": true
}
}
}
}
}
}
}
}
}
```
- `x5Chain` - contains the certificate chain used to verify the issuer's digital signature. The last certificate in the
chain must contain the public key of the private key which signs the to be issued credential.
- `trustedRootCAs` - contains trusted root certificates to validate the authenticity of the issuer's certificate chain.
---
**Example Response**
The issuer endpoint will respond with Credential Offer URL.
**Plain Response**
```
openid-credential-offer://issuer.potential.walt-test.cloud/?credential_offer_uri=https%3A%2F%2Fissuer.potential.walt-test.cloud%2Fopenid4vc%2FcredentialOffer%3Fid%3D9aabdb65-defe-464b-baa0-9cc13b36074a
```
**Decoded**
```
{
"credential_issuer": "https://issuer.potential.walt-test.cloud",
"credential_configuration_ids": [
"identity_credential_vc+sd-jwt"
],
"grants": {
"authorization_code": {
"issuer_state": "9aabdb65-defe-464b-baa0-9cc13b36074a"
},
"urn:ietf:params:oauth:grant-type:pre-authorized_code": {
"pre-authorized_code": "eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiI5YWFiZGI2NS1kZWZlLTQ2NGItYmFhMC05Y2MxM2IzNjA3NGEiLCJpc3MiOiJodHRwczovL2lzc3Vlci5wb3RlbnRpYWwud2FsdC10ZXN0LmNsb3VkIiwiYXVkIjoiVE9LRU4ifQ.vl9fBjoYUqr1nrA0jZ3ZjpS45yHLp2roMPCxCoqjuLNyBhTGO0g_PXMw8_NWhD-3qllRq5J0kLAw8WmfvT95Cw"
}
}
}
```
### Step 3: Receive the Credential Offer
The created credential offer can be used in two ways:
- **Scan via QR code** – embed the credential offer URL into a QR code for users to scan with their mobile wallet.
- **Paste manually** – copy the credential offer URL and paste it into the credential offer field of our [web wallet](https://wallet.demo.walt.id/).
To try it out with our web wallet:
1. Open the [web wallet](https://wallet.demo.walt.id/).
2. Log in.
3. Click the **request credential** button.
4. Paste the received Offer URL into the text field below the camera.
🎉 Congratulations, you've issued a SD-JWT VC Credential using OID4VCI! 🎉
To learn how to accept this credential offer programmatically using the Wallet API, see [How to Accept SD-JWT VC Credentials via OID4VCI](https://docs.walt.id/community-stack/wallet/credential-exchange/guides/accept-sd-jwt-vc-oid4vci.md).
--------------------------------------------------------------------------------
PAGE: How to issue Mobile Driver’s Licenses (ISO/IEC 18013-5 mDL) via OID4VCI with walt.id
URL: https://docs.walt.id/community-stack/issuer/credential-issuance/mdl-oid4vc
RAW: https://docs.walt.id/community-stack/issuer/credential-issuance/mdl-oid4vc.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# How to Issue Mobile Driver’s Licenses (ISO/IEC 18013-5 mDLs) via OID4VCI with walt.id
**TL;DR**
**What you’ll learn:**
- Generate IACA root certificates and keys that anchor trust for mDL issuance.
- Provision document signer certificates with EKU and CRL metadata required by ISO/IEC 18013-5.
- Populate mandatory mDL claims and invoke the walt.id Issuer API’s OID4VCI endpoint to sign the mdoc.
- Construct and distribute credential offer URLs so compliant wallets can request the issued credential.
**Relevant concepts:**
- [Mobile Driver's License (mDL)](https://docs.walt.id/concepts/digital-credentials/mdoc-mdl-iso.md) – ISO/IEC 18013-5/7 standard for mobile driver's licenses.
- [OID4VCI](https://docs.walt.id/concepts/data-exchange-protocols/openid4vci.md) – protocol for issuing verifiable credentials to wallets.
Video: https://youtu.be/aRoBfjJvqEI
This guide provides a comprehensive walkthrough for issuing an mDL credential based on
the [ISO/IEC 18103-5 standard](https://www.iso.org/standard/69084.html) using the walt.id Issuer API.
The issuance process will utilize the
[OID4VCI protocol](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-ID1.html).
## Setup
See how to access to the issuer API below.
- [Deployed (Testing Only)](https://issuer.demo.walt.id/swagger/index.html) - Use our deployed version for testing.
- [Local](https://docs.walt.id/community-stack/issuer/setup.md) - Run the API in your environment with our open-source setup.
## Preparing for Issuance: Key Components
The mobile driver's license (mDL) ecosystem builds on established PKI principles that employ X.509 digital
certificates, while introducing domain-specific roles and data models. Its two main components are:
1. **IACA — Issuing Authority Certification Authority**: This is an authority that serves as the root of trust
vouching for authorized credential issuers and is represented by a self-signed X.509 digital certificate that is
published to verifiers.
2. **DS — Document Signer (Credential Issuer)**: This is the entity that issues and cryptographically signs mDL
credential(s). It holds a private key and an X.509 digital certificate issued by an IACA.
Typically, DS certificates are short-lived and are rotated periodically.
3. **The mDL Claims**: The mDL is a verifiable credential that contains claims about the subject (holder), such as
name, birthdate and driving privileges. The [ISO/IEC 18103-5 standard document](https://www.iso.org/standard/69084.html)
defines a set of mandatory fields (e.g., `family_name`, `birth_date`) that must be included in every mDL, as well
as a set of optional fields (e.g., `age_over_NN` attestations, `height`, `weight`, `sex`).
### Example mDL Credential in JSON format:
```json
{
"family_name": "Doe",
"given_name": "John",
"birth_date": "1986-03-22",
"issue_date": "2019-10-20",
"expiry_date": "2024-10-20",
"issuing_country": "AT",
"issuing_authority": "AT DMV",
"document_number": "123456789",
"portrait": [ 141, 12 ],
"driving_privileges": [
{
"vehicle_category_code": "A",
"issue_date": "2018-08-09",
"expiry_date": "2024-10-20"
},
{
"vehicle_category_code": "B",
"issue_date": "2017-02-23",
"expiry_date": "2024-10-20"
}
],
"un_distinguishing_sign": "AT"
}
```
**Note**: mDLs are encoded in CBOR, which is a binary format and is shown in JSON representation above for readability
purposes. Claims values that are encoded as JSON integer arrays, e.g., the `portrait` claim's value, represent byte
arrays.
## Issuing an mDL
In this section, we'll walk through the steps required to successfully issue an mDL by generating signing keys (for
the IACA & the DS) which we store ourselves. For production
environments, we recommend using an external KMS provider for key management due to the enhanced security.
Learn more about the different types of keys and the storage options [here](https://docs.walt.id/community-stack/issuer/key-management/overview.md).
**Note**: At the moment, you can only use `secp256r1` keys to onboard IACAs and DSs and to issue mDLs.
### Step 1: Onboard an Issuing Authority Certification Authority (IACA)
Every mobile driver’s license ecosystem starts with a trusted root certificate of an IACA. The IACA serves as the
cryptographic authority that issues DS certificates that are deemed trustworthy.
**:wrench: What this step does**
- Create a self-signed X.509 root certificate (CA=true).
- Encode in it identity info (e.g., country, organization) for the IACA.
- Include optional extensions (e.g., IACA alternative names, CRL distribution URIs).
#### IACA Onboarding Request
**Option: CURL**
**Endpoint:** `/onboard/iso-mdl/iacas` | [API Reference](https://issuer.demo.walt.id/swagger/index.html#/Onboarding%20Service/post_onboard_iso_mdl_iacas)
**Example Request**
```bash
curl -X 'POST' \
'http://0.0.0.0:7002/onboard/iso-mdl/iacas' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"certificateData": {
"country": "US",
"commonName": "Example IACA",
"issuerAlternativeNameConf": {
"uri": "https://iaca.example.com"
}
}
}'
```
**Body**
```json
{
"certificateData": {
"country": "US",
"commonName": "Example IACA",
"issuerAlternativeNameConf": {
"uri": "https://iaca.example.com"
}
}
}
```
**Body Parameters**
- `certificateData`: _JSON_ - Data that will be encoded in the generated certificate of the IACA.
- `country`: _String_ - Two-letter ISO 3166-1 alpha-2 country code (e.g., `US`, `DE`).
- `commonName`: _String_ - Human-readable name of the issuing authority (e.g., `Ministry of Transport`).
- `issuerAlternativeNameConf`: _JSON_ - Metadata for the IACA; can include uri and/or email.
- `uri`: _String_ - Uniform resource identifier regarding the IACA's contact information.
- `email`: _String_ - RFC822 name for contacting the IACA.
---
**Example Response**
The IACA onboarding endpoint returns an object with:
- **`iacaKey`** – generated signing key of the IACA in JWK format.
- **`certificatePEM`** – PEM-encoded, JSON-stringified self-signed X.509 certificate of the IACA.
- **`certificateData`** – object containing the data that is encoded in the generated certificate (for convenience – useful for the DS onboarding endpoint below).
```json
{
"iacaKey": {
"type": "jwk",
"jwk": {
"kty": "EC",
"d": "u-UvsghdzpSXv5HmG5ngvm4Dv8yyRYw9fKA6mdp1KWs",
"crv": "P-256",
"kid": "R_E_QZ-Ea6etoAdWfUHSjjexRYz447ffnnfIO9kxn_Y",
"x": "n_b1GmZTSEhioK3z8MGqcb7nxXqyjFaLR-OfKOnspwU",
"y": "nGRVvuHTtEAZ1HjgdLaLZnYxrkiRV_e4V2Wz0qVWa-M"
}
},
"certificatePEM": "-----BEGIN CERTIFICATE-----\nMIIBtTCCAVqgAwIBAgIUNlgkpoam39UxORhMNRkwuFzD9pQwCgYIKoZIzj0EAwIwJDELMAkGA1UEBhMCVVMxFTATBgNVBAMMDEV4YW1wbGUgSUFDQTAeFw0yNTA1MjgxMjIzMDFaFw00MDA1MjQxMjIzMDFaMCQxCzAJBgNVBAYTAlVTMRUwEwYDVQQDDAxFeGFtcGxlIElBQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASf9vUaZlNISGKgrfPwwapxvufFerKMVotH458o6eynBZxkVb7h07RAGdR44HS2i2Z2Ma5IkVf3uFdls9KlVmvjo2owaDAdBgNVHQ4EFgQUjCMRsfolTeK5Ds6MqOWj5Nx01BQwEgYDVR0TAQH/BAgwBgEB/wIBADAjBgNVHRIEHDAahhhodHRwczovL2lhY2EuZXhhbXBsZS5jb20wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMCA0kAMEYCIQCnUfp3OyxcaPCT34SQ4dTNyNN0qgxKWpWIDeUXkrs7HwIhALFYrMrINeAats4ZWRxZMK6bykb9dcOwkmBCv96MoZVi\n-----END CERTIFICATE-----\n",
"certificateData": {
"country": "US",
"commonName": "Example IACA",
"issuerAlternativeNameConf": {
"uri": "https://iaca.example.com"
}
}
}
```
**Defaults**:
- A local (`jwk`) secp256r1 key is automatically created.
- Validity defaults to 15 years, unless otherwise specified.
**Note:**
As we've used the local (`jwk`) key type, it's important to note that we need to save the returned values ourselves
for future reference. The API doesn't save any information about created keys.
**Note:**
Remember to save the value of the IACA's PEM-encoded X.509 certificate (`certificatePEM` field in the response) as
it is required for verification of
[mDLs](../../../3.verifier/1.api/3.credential-verification/4.mdl-oid4vc.md#example-verification-request).
### Step 2: Onboard a Document Signer
Document signers are the entities responsible for signing mDL credentials. These entities are identified by an X.509
digital certificate that is signed by an IACA and contains, among others, a special purpose extension that marks it
as valid for mDL issuance.
**:wrench: What this step does**
- Issue a DS certificate signed by the input IACA private key.
- Embed an Extended Key Usage (EKU) value marking it as suitable for mDL signing.
- Include CRL distribution info and various other optional fields.
#### DS Onboarding Request
**Option: CURL**
**Endpoint:** `/onboard/iso-mdl/document-signers` | [API Reference](https://issuer.demo.walt.id/swagger/index.html#/Onboarding%20Service/post_onboard_iso_mdl_document_signers)
**Example Request**
```bash
curl -X 'POST' \
'http://0.0.0.0:7002/onboard/iso-mdl/document-signers' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"iacaSigner": {
"iacaKey": {
"type": "jwk",
"jwk": {
"kty": "EC",
"crv": "P-256",
"kid": "R_E_QZ-Ea6etoAdWfUHSjjexRYz447ffnnfIO9kxn_Y",
"x": "n_b1GmZTSEhioK3z8MGqcb7nxXqyjFaLR-OfKOnspwU",
"y": "nGRVvuHTtEAZ1HjgdLaLZnYxrkiRV_e4V2Wz0qVWa-M",
"d": "u-UvsghdzpSXv5HmG5ngvm4Dv8yyRYw9fKA6mdp1KWs"
}
},
"certificateData": {
"country": "US",
"commonName": "Example IACA",
"notBefore": "2025-05-28T12:23:01Z",
"notAfter": "2040-05-24T12:23:01Z",
"issuerAlternativeNameConf": {
"uri": "https://iaca.example.com"
}
}
},
"certificateData": {
"country": "US",
"commonName": "Example DS",
"crlDistributionPointUri": "https://iaca.example.com/crl"
}
}'
```
**Body**
```json
{
"iacaSigner": {
"iacaKey": {
"type": "jwk",
"jwk": {
"kty": "EC",
"crv": "P-256",
"kid": "R_E_QZ-Ea6etoAdWfUHSjjexRYz447ffnnfIO9kxn_Y",
"x": "n_b1GmZTSEhioK3z8MGqcb7nxXqyjFaLR-OfKOnspwU",
"y": "nGRVvuHTtEAZ1HjgdLaLZnYxrkiRV_e4V2Wz0qVWa-M",
"d": "u-UvsghdzpSXv5HmG5ngvm4Dv8yyRYw9fKA6mdp1KWs"
}
},
"certificateData": {
"country": "US",
"commonName": "Example IACA",
"notBefore": "2025-05-28T12:23:01Z",
"notAfter": "2040-05-24T12:23:01Z",
"issuerAlternativeNameConf": {
"uri": "https://iaca.example.com"
}
}
},
"certificateData": {
"country": "US",
"commonName": "Example DS",
"crlDistributionPointUri": "https://iaca.example.com/crl"
}
}
```
**Body Parameters**
- `iacaSigner`: _JSON_ - Object containing necessary data related to the signing IACA.
- `iacaKey`: _JSON_ - The JSON serialized signing key of the issuing IACA - used as is from the its respective
onboarding endpoint.
- `certificateData`: _JSON_ - The data that is encoded in the IACA's X.509 certificate - used as is from its
respective onboarding endpoint.
- `notBefore`: _String_ - ISO 8601 timestamp defining the start of the certificate's validity period (e.g. `2025-05-28T12:23:01Z`). Required.
- `notAfter`: _String_ - ISO 8601 timestamp defining the end of the certificate's validity period (e.g. `2040-05-24T12:23:01Z`). Required.
- `certificateData`: _JSON_ - Data that will be encoded in the generated certificate of the DS.
- `country`: _String_ - Two-letter ISO 3166-1 alpha-2 country code (e.g., `US`, `DE`).
- `commonName`: _String_ - Human-readable name of the issuing authority (e.g., `Ministry of Transport`).
- `crlDistributionPointUri`: _String_ - URL where the relevant certificate revocation list (CRL) is published.
**Requirements**
- Must provide the IACA's signing key and certificate data (as obtained from the IACA onboarding endpoint).
- Countries and state/province fields must match between IACA and DS.
- CRL URI is *mandatory* for DS onboarding.
---
**Example Response**
The DS onboarding endpoint returns an object with:
- **`documentSignerKey`** – generated signing key of the DS in JWK format.
- **`certificatePEM`** – PEM-encoded, JSON-stringified X.509 certificate of the DS signed by the IACA signing key in the request.
- **`certificateData`** – object that contains the data encoded in the generated certificate of the DS (for convenience).
```json
{
"documentSignerKey": {
"type": "jwk",
"jwk": {
"kty": "EC",
"d": "ZSHgIcRvbwV9s224kHUaFqkEPShCAdwXocGl_w3M42Q",
"crv": "P-256",
"kid": "pX99OZjL2iNqM7OMkE1r1rYyuAObvPntewcDHdc2bMM",
"x": "GWKpdL3jPoPJ5wKgSA-jxS2jgp-ZUDE6sIQbeB86vF0",
"y": "F3xAwH96_xVciV7mFQslU_eRQgP-5pSZiNf8bjMoGfo"
}
},
"certificatePEM": "-----BEGIN CERTIFICATE-----\nMIICCDCCAa2gAwIBAgIUDo8kr194t6sttt6KL3YcnMtcaYYwCgYIKoZIzj0EAwIwJDELMAkGA1UEBhMCVVMxFTATBgNVBAMMDEV4YW1wbGUgSUFDQTAeFw0yNTA1MjkwNzE4MzlaFw0yNjA4MjkwNzE4MzlaMCIxCzAJBgNVBAYTAlVTMRMwEQYDVQQDDApFeGFtcGxlIERTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEGWKpdL3jPoPJ5wKgSA+jxS2jgp+ZUDE6sIQbeB86vF0XfEDAf3r/FVyJXuYVCyVT95FCA/7mlJmI1/xuMygZ+qOBvjCBuzAfBgNVHSMEGDAWgBSMIxGx+iVN4rkOzoyo5aPk3HTUFDAdBgNVHQ4EFgQU7S49LSeg/e0onfT44FVbL/rSKnswDgYDVR0PAQH/BAQDAgeAMCMGA1UdEgQcMBqGGGh0dHBzOi8vaWFjYS5leGFtcGxlLmNvbTAVBgNVHSUBAf8ECzAJBgcogYxdBQECMC0GA1UdHwQmMCQwIqAgoB6GHGh0dHBzOi8vaWFjYS5leGFtcGxlLmNvbS9jcmwwCgYIKoZIzj0EAwIDSQAwRgIhAMuSq75BPBXXBWGtIMd57fhRqpKf3Yzl3ldDdoQsK2xEAiEA/dmWLMLiJPV3UzmQS5MUHtn611z0VlL/k3YAdaVJ51c=\n-----END CERTIFICATE-----\n",
"certificateData": {
"country": "US",
"commonName": "Example DS",
"crlDistributionPointUri": "https://iaca.example.com/crl"
}
}
```
**Defaults**:
- A local (`jwk`) secp256r1 key is automatically created.
- Validity defaults to 457 days, unless otherwise specified.
**Note:**
As we've used the local (`jwk`) key type, it's important to note that we need to save the returned values ourselves
for future reference. The API doesn't save any information about created keys.
**Note:**
Remember to save the value of the DS PEM-encoded X.509 certificate (`certificatePEM` field in the response) as
it is required for the next (issuance) step.
### Step 3: Issue the mDL
To issue mDLs:
1. Use the document signer's key (obtained previously).
2. Utilize the OID4VCI protocol to facilitate the issuance of the mDL from the document signer (issuer) to the holder.
3. Generate a credential offer URL that can be accepted by any compliant wallet to receive the credential.
**Note:**
The credential offer URL specifies the credentials to be issued. This includes details such as
the URL of the issuer and information about the credential's format and type.
#### mDL Issuance Request
**Option: CURL**
**Endpoint:**
`/openid4vc/mdoc/issue` | [API Reference](https://issuer.portal.test.waltid.cloud/swagger/index.html#/Credential%20Issuance/post_openid4vc_mdoc_issue)
**Example Request**
```bash
curl -X 'POST' \
'http://0.0.0.0:7002/openid4vc/mdoc/issue' \
-H 'accept: */*' \
-H 'statusCallbackUri: https://example.com/$id' \
-H 'Content-Type: application/json' \
-d '{
"issuerKey": {
"type": "jwk",
"jwk": {
"kty": "EC",
"d": "ZSHgIcRvbwV9s224kHUaFqkEPShCAdwXocGl_w3M42Q",
"crv": "P-256",
"kid": "pX99OZjL2iNqM7OMkE1r1rYyuAObvPntewcDHdc2bMM",
"x": "GWKpdL3jPoPJ5wKgSA-jxS2jgp-ZUDE6sIQbeB86vF0",
"y": "F3xAwH96_xVciV7mFQslU_eRQgP-5pSZiNf8bjMoGfo"
}
},
"credentialConfigurationId": "org.iso.18013.5.1.mDL",
"mdocData": {
"org.iso.18013.5.1": {
"family_name": "Doe",
"given_name": "John",
"birth_date": "1986-03-22",
"issue_date": "2019-10-20",
"expiry_date": "2024-10-20",
"issuing_country": "AT",
"issuing_authority": "AT DMV",
"document_number": "123456789",
"portrait": [ 141, 182 ],
"driving_privileges": [
{
"vehicle_category_code": "A",
"issue_date": "2018-08-09",
"expiry_date": "2024-10-20"
},
{
"vehicle_category_code": "B",
"issue_date": "2017-02-23",
"expiry_date": "2024-10-20"
}
],
"un_distinguishing_sign": "AT"
}
},
"x5Chain": [
"-----BEGIN CERTIFICATE-----\nMIICCDCCAa2gAwIBAgIUDo8kr194t6sttt6KL3YcnMtcaYYwCgYIKoZIzj0EAwIwJDELMAkGA1UEBhMCVVMxFTATBgNVBAMMDEV4YW1wbGUgSUFDQTAeFw0yNTA1MjkwNzE4MzlaFw0yNjA4MjkwNzE4MzlaMCIxCzAJBgNVBAYTAlVTMRMwEQYDVQQDDApFeGFtcGxlIERTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEGWKpdL3jPoPJ5wKgSA+jxS2jgp+ZUDE6sIQbeB86vF0XfEDAf3r/FVyJXuYVCyVT95FCA/7mlJmI1/xuMygZ+qOBvjCBuzAfBgNVHSMEGDAWgBSMIxGx+iVN4rkOzoyo5aPk3HTUFDAdBgNVHQ4EFgQU7S49LSeg/e0onfT44FVbL/rSKnswDgYDVR0PAQH/BAQDAgeAMCMGA1UdEgQcMBqGGGh0dHBzOi8vaWFjYS5leGFtcGxlLmNvbTAVBgNVHSUBAf8ECzAJBgcogYxdBQECMC0GA1UdHwQmMCQwIqAgoB6GHGh0dHBzOi8vaWFjYS5leGFtcGxlLmNvbS9jcmwwCgYIKoZIzj0EAwIDSQAwRgIhAMuSq75BPBXXBWGtIMd57fhRqpKf3Yzl3ldDdoQsK2xEAiEA/dmWLMLiJPV3UzmQS5MUHtn611z0VlL/k3YAdaVJ51c=\n-----END CERTIFICATE-----\n"
]
}'
```
**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`
or `https://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 id
- `type`: _String_ - the event type
- `data`: _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` with `jwt` being the issued jwt
- `sdjwt_issue` with `sdjwt` being the issued sdjwt
- `batch_jwt_issue` with `jwt` being the issued jwt
- `batch_sdjwt_issue` with `sdjwt` being the issued sdjwt
- `generated_mdoc` with `mdoc` 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**
```json
{
"issuerKey": {
"type": "jwk",
"jwk": {
"kty": "EC",
"d": "ZSHgIcRvbwV9s224kHUaFqkEPShCAdwXocGl_w3M42Q",
"crv": "P-256",
"kid": "pX99OZjL2iNqM7OMkE1r1rYyuAObvPntewcDHdc2bMM",
"x": "GWKpdL3jPoPJ5wKgSA-jxS2jgp-ZUDE6sIQbeB86vF0",
"y": "F3xAwH96_xVciV7mFQslU_eRQgP-5pSZiNf8bjMoGfo"
}
},
"credentialConfigurationId": "org.iso.18013.5.1.mDL",
"mdocData": {
"org.iso.18013.5.1": {
"family_name": "Doe",
"given_name": "John",
"birth_date": "1986-03-22",
"issue_date": "2019-10-20",
"expiry_date": "2024-10-20",
"issuing_country": "AT",
"issuing_authority": "AT DMV",
"document_number": "123456789",
"portrait": [ 141, 182 ],
"driving_privileges": [
{
"vehicle_category_code": "A",
"issue_date": "2018-08-09",
"expiry_date": "2024-10-20"
},
{
"vehicle_category_code": "B",
"issue_date": "2017-02-23",
"expiry_date": "2024-10-20"
}
],
"un_distinguishing_sign": "AT"
}
},
"x5Chain": [
"-----BEGIN CERTIFICATE-----\nMIICCDCCAa2gAwIBAgIUDo8kr194t6sttt6KL3YcnMtcaYYwCgYIKoZIzj0EAwIwJDELMAkGA1UEBhMCVVMxFTATBgNVBAMMDEV4YW1wbGUgSUFDQTAeFw0yNTA1MjkwNzE4MzlaFw0yNjA4MjkwNzE4MzlaMCIxCzAJBgNVBAYTAlVTMRMwEQYDVQQDDApFeGFtcGxlIERTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEGWKpdL3jPoPJ5wKgSA+jxS2jgp+ZUDE6sIQbeB86vF0XfEDAf3r/FVyJXuYVCyVT95FCA/7mlJmI1/xuMygZ+qOBvjCBuzAfBgNVHSMEGDAWgBSMIxGx+iVN4rkOzoyo5aPk3HTUFDAdBgNVHQ4EFgQU7S49LSeg/e0onfT44FVbL/rSKnswDgYDVR0PAQH/BAQDAgeAMCMGA1UdEgQcMBqGGGh0dHBzOi8vaWFjYS5leGFtcGxlLmNvbTAVBgNVHSUBAf8ECzAJBgcogYxdBQECMC0GA1UdHwQmMCQwIqAgoB6GHGh0dHBzOi8vaWFjYS5leGFtcGxlLmNvbS9jcmwwCgYIKoZIzj0EAwIDSQAwRgIhAMuSq75BPBXXBWGtIMd57fhRqpKf3Yzl3ldDdoQsK2xEAiEA/dmWLMLiJPV3UzmQS5MUHtn611z0VlL/k3YAdaVJ51c=\n-----END CERTIFICATE-----\n"
]
}
```
**Body Parameters**
- `issuerKey`: _JSON_ - A JWK or reference object to a key stored in an external KMS to sign the mDL with.
Supported algorithms: secp256r1. This must be the **Document Signer (DS) key** obtained from the [DS onboarding step](#step-2-onboard-a-document-signer) (`documentSignerKey` field in the response). The private key here must correspond to the public key embedded in the DS certificate provided in `x5Chain`.
**JWK Format:** `{"type": "jwk", "jwk": Here JSON Web Key Object}`. Can be provided as String Or JWK object to "
issuerKey".
**KMS Key**: Please refer to the [Key Management Section](https://docs.walt.id/community-stack/issuer/key-management/overview.md) and the KMS you want to
use for more details on the structure
of the reference object.
- `credentialConfigurationId`: _String_ - org.iso.18013.5.1.mDL
- `mdocData`: _JSON_ - Claims to be added to the mDL. In the provided example above, all mandatory claims are included.
- `x5Chain`: _JSON Array_ - Must contain a single entry that contains the PEM-encoded, JSON stringified X.509
certificate of the DS (as output by the respective onboarding endpoint).
---
**Example Response**
The issuer endpoint will respond with Credential Offer URL.
**Plain Response**
```
openid-credential-offer://issuer.portal.test.waltid.cloud/draft13/?credential_offer_uri=https%3A%2F%2Fissuer.portal.test.waltid.cloud%2Fdraft13%2FcredentialOffer%3Fid%3D52b19ff5-5b42-423a-ad16-63099760baea
```
**Decoded**
```json
{
"credential_issuer": "https://issuer.portal.test.waltid.cloud/draft13",
"grants": {
"urn:ietf:params:oauth:grant-type:pre-authorized_code": {
"pre-authorized_code": "eyJ0eXBlIjoiand0IiwiYWxnIjoiRVMyNTYiLCJraWQiOiJ0R3pBbUthdE0tbzlicDM1Y291aDZUUXhxZHpVeFgzQWwtcU9iVXhZelh3In0.eyJzdWIiOiI1MmIxOWZmNS01YjQyLTQyM2EtYWQxNi02MzA5OTc2MGJhZWEiLCJpc3MiOiJodHRwczovL2lzc3Vlci5wb3J0YWwudGVzdC53YWx0aWQuY2xvdWQvZHJhZnQxMyIsImF1ZCI6IlRPS0VOIn0.aAgMH0XTq2YVTpsSGtwPIkinTai6Hr0iJyBttDOljPD2WV2kJolZR35K9hiqpz4ZBwGqUbHjmpYaywQqxb8Wfg"
}
},
"credential_configuration_ids": [
"org.iso.18013.5.1.mDL"
]
}
```
### Step 4: Receive the Credential Offer
The created credential offer can be used in two ways:
- **Scan as a QR code** – embed the offer into a QR code for users to scan with their mobile wallet.
- **Paste manually** – paste the offer into the credential offer field of our [web wallet](https://wallet.demo.walt.id/).
To try it out with our web wallet:
1. Open the [web wallet](https://wallet.demo.walt.id/).
2. Log in.
3. Click the **request credential** button.
4. Paste the received Offer URL into the text field below the camera.
🎉 Congratulations, you've issued a mDL using OID4VCI! 🎉
To learn how to accept this credential offer programmatically using the Wallet API, see [How to Accept a Mobile Driver's License via OID4VCI](https://docs.walt.id/community-stack/wallet/credential-exchange/guides/accept-iso-18013-5-mdl-oid4vci.md).
--------------------------------------------------------------------------------
PAGE: Data Functions
URL: https://docs.walt.id/community-stack/issuer/credential-issuance/data-functions
RAW: https://docs.walt.id/community-stack/issuer/credential-issuance/data-functions.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Data Functions
The **walt.id issuer API** offers the use of **data functions** when issuing VCs. These functions ensure that the
credentials issued always contain the most current and accurate information.
**Note:**
Note: We now support data functions across all credential types: **W3C JWT VC**, **IETF SD-JWT VC** and **ISO mDoc** as part of our 0.20.0 release!
Video: https://youtu.be/4e-U_gkh09U
### How Data Functions Work
Data functions are not executed at the moment you create an OpenID credential offer URL via the walt.id issuer api.
Instead, they get executed at the point when the credential is actually claimed by the recipient. This means that the
data within the VC stays up-to-date, regardless of whether the recipient claims it immediately or after a
considerable period.
### The Importance Of Timing
This delayed execution is crucial because it guarantees the information's relevance and accuracy. For example,
a credential might be issued today but claimed a month later. The data functions ensure that the VC reflects
any changes that occurred during that interval, maintaining the credibility of the credential.
### How To Use Data Functions
When issuing a credential you have the option to provide a JSON Object called mapping that defines which keys in
the credential data structure should be replaced by the value returned of the provided data function. Data functions can
return any type of value from a String, Number of whole JSON Object. Therefore, as seen in the example below the degree
which is a nested object can be generated on the fly via the JSON webhook.
#### Example
**Credential To Issue**
```json
{
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1"
],
"id": "",
"type": [
"VerifiableCredential",
"UniversityDegreeCredential"
],
"issuer": {
"id": ""
},
"credentialSubject": {
"degree": ""
}
}
```
**Mapping Object With Data Functions**
```json
{
"id": "",
"issuer": {
"id": ""
},
"degree": ""
}
```
**Example Issuance API Call Body**
```json
{
...
"credentialData": {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1"
],
"id": "",
"type": [
"VerifiableCredential",
"UniversityDegreeCredential"
],
"issuer": {
"id": ""
},
"credentialSubject": {
"degree": ""
}
},
"mapping": {
"id": "",
"issuer": {
"id": ""
},
"degree": ""
}
...
}
```
**Issued credential**
```json
{
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1"
],
"id": "http://example.gov/credentials/3732",
// return value from data function
"type": [
"VerifiableCredential",
"UniversityDegreeCredential"
],
"issuer": {
"id": "did:web:vc.transmute.world"
// return value from data function
},
"credentialSubject": {
"degree": {
// return value from data function
"type": "BachelorDegree",
"name": "Bachelor of Science and Arts"
}
}
}
```
## Available Functions
These functions can be used within the mapping object of the issuance request. For details on issuance, you can
read [this guide](https://docs.walt.id/community-stack/issuer/credential-issuance/vc-oid4vc.md)
A list of functions with their correct syntax to be used in the mapping object below:
#### UUID
`` to get a unique identifier.
#### Subject Did
`` to get the recipient's DID once the credential gets claimed.
#### Issuer DID
`` to get the issuer's DID.
**Note:**
**Note:** This function is only available for **W3C JWT VC** issuance. It is not supported for IETF SD-JWT VC credentials.
#### Timestamp
`` to get the current timestamp in ISO 8601 format `YYYY-MM-DDThh:mm:ss.sssZ`
#### Timestamp Seconds
`` to get the Unix timestamp.
#### Timestamp In
`` to get a future date by providing the duration as an argument
(e.g., "" for one year from now). Returned timestamp will be in ISO 8601 format
`YYYY-MM-DDThh:mm:ss.sssZ`
#### Timestamp In Seconds
`` to get a future date by providing the duration as an argument
(e.g., "" for one year from now). Returned will be a Unix timestamp.
#### Timestamp Before
`` to get a date in the past by providing the duration as an argument
(e.g., "timestamp-before:365d" for one year ago from now). The returned timestamp will be in ISO 8601 format
`YYYY-MM-DDThh:mm:ss.sssZ`
#### Timestamp Before Seconds
`` to get a date in the past by providing the duration as an argument
(e.g., "timestamp-before-seconds:365d" for one year ago from now). The returned value will be a Unix timestamp.
#### Display
`` to get the display metadata of the credential. The display metadata is defined in the issuer service and can
be used to customize the appearance of the credential. The display metadata can include information such as the logo,
name,
description, and background color of the credential.The returned value will be a JSON object containing the display
metadata.
For example if we have the following mapping during credential issuance:
```json
{
"vc": {
...
"credentialSubject": {
"id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
"degree": {
"type": "BachelorDegree",
"name": "Bachelor of Science and Arts"
}
}
},
"mapping": {
...
"display": ""
...
}
}
```
And the display metadata is defined in the issuer service config as follows:
```json
{
"display": [
{
"name": "University Credential",
"locale": "en-US",
"logo": {
"uri": "https://university.example.edu/public/logo.png",
"alt_text": "a square logo of a university"
},
"description": "This credential represents the conferment of a university degree.",
"background_color": "#12107c",
"text_color": "#FFFFFF"
}
]
}
```
The issued credential will look like:
```json
{
...
"credentialSubject": {
"id": "did:key:z6MkuHcPf3jhFwGso9agCvbvpzsbn6gHtYYNudXxHjQW1zQd#z6MkuHcPf3jhFwGso9agCvbvpzsbn6gHtYYNudXxHjQW1zQd",
"degree": {
"type": "BachelorDegree",
"name": "Bachelor of Science and Arts"
}
},
"display": {
"name": "University Credential",
"locale": "en-US",
"logo": {
"uri": "https://university.example.edu/public/logo.png",
"alt_text": "a square logo of a university"
},
"description": "This credential represents the conferment of a university degree.",
"background_color": "#12107c",
"text_color": "#FFFFFF",
"background_image": {
"uri": "https://university.example.edu/public/background.png",
"alt_text": "a background image of a university"
}
},
...
}
```
#### Webhook
`` to make a request to a specific URL. It will insert the response of the request as a plain
text string. The webhook URL is provided as an argument (e.g., "webhook:https://example.com").
For example if we have the following mapping during credential issuance:
```json
{
"vc": {
...
"credentialSubject": {
"id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
"degree": {
"type": "BachelorDegree",
"name": "Bachelor of Science and Arts"
}
}
},
"mapping": {
...
"credentialSubject": {
"id": "",
"degree": {
"name": ""
}
},
...
}
}
```
And the webhook endpoint responds with a content-type of text/plain with "Test Data". The issued
credential will look like:
```json
{
...
"credentialSubject": {
"id": "did:key:z6MkuHcPf3jhFwGso9agCvbvpzsbn6gHtYYNudXxHjQW1zQd#z6MkuHcPf3jhFwGso9agCvbvpzsbn6gHtYYNudXxHjQW1zQd",
"degree": {
"type": "BachelorDegree",
"name": "Test Data"
}
},
...
}
```
#### Webhook JSON
`` to make a request to a specific URL. It will insert the response body as a nested JSON
object. The webhook URL is provided as an argument (e.g., "webhook-json:https://example.com").
For example if we have the following mapping during credential issuance:
```json
{
"vc": {
...
"credentialSubject": {
"id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
"degree": {
"type": "BachelorDegree",
"name": "Bachelor of Science and Arts"
}
}
},
"mapping": {
...
"credentialSubject": {
"id": "",
"degree": ""
},
...
}
}
```
And the webhook endpoint responds with a content-type of application/json and the following data:
```json
{
"type": "BachelorDegreeWebhook",
"name": "Bachelor of Science Webhook"
}
```
The issued credential will look like:
```json
{
...
"credentialSubject": {
"id": "did:key:z6MkuHcPf3jhFwGso9agCvbvpzsbn6gHtYYNudXxHjQW1zQd#z6MkuHcPf3jhFwGso9agCvbvpzsbn6gHtYYNudXxHjQW1zQd",
"degree": {
"type": "BachelorDegreeWebhook",
"name": "Bachelor of Science Webhook"
}
},
...
}
```
## EBSI Specific Functions
The [EBSI](https://ec.europa.eu/digital-building-blocks/sites/display/EBSI/Home) trust framework mandates a specific
timestamp format for issued credentials, which differs from the one
generated by other timestamp functions in walt.id. To ensure compliance, we have developed a set of timestamp data
functions specifically for issuing EBSI-compliant credentials which you can find below.
#### EBSI Timestamp
`` to get the current timestamp in ISO 8601 format `YYYY-MM-DDThh:mm:ssZ`
#### EBSI Timestamp In
`` to get a future date by providing the duration as an argument
(e.g., `` for one year from now). Returned timestamp will be in ISO 8601 format
`YYYY-MM-DDThh:mm:ssZ`
--------------------------------------------------------------------------------
PAGE: Ecosystems
URL: https://docs.walt.id/community-stack/issuer/ecosystems/overview
RAW: https://docs.walt.id/community-stack/issuer/ecosystems/overview.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Ecosystems
The walt.id issuer API enables you to issue credentials across various digital identity
ecosystems like EBSI. Find a list of supported ecosystems below.
### Supported Ecosystems
- [EBSI](https://docs.walt.id/community-stack/issuer/ecosystems/ebsi/overview.md) - European Blockchain Services Infrastructure
- [cheqd](https://cheqd.io/) - a public permissionless network built with Self-Sovereign Identity (SSI) and blockchain technologies.
--------------------------------------------------------------------------------
PAGE: Overview
URL: https://docs.walt.id/community-stack/issuer/ecosystems/ebsi/overview
RAW: https://docs.walt.id/community-stack/issuer/ecosystems/ebsi/overview.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Overview
[The European Blockchain Services Infrastructure (EBSI)](https://ec.europa.eu/digital-building-blocks/sites/display/EBSI/Home)
is a standardized blockchain framework initiated by the European
Commission and the European Blockchain Partnership to enhance digital identity and public services across Europe. Its use case families include Verifiable Credentials for secure identity verification, Track and Trace for traceability, and Trusted Data Exchange for tamper-proof information exchange.
**EBSI
leverages blockchain technology to improve interoperability, security, and trust in managing digital identities**,
ensuring these identities align with regulatory requirements and are universally recognized across member states.
EBSI Verifiable Credentials Family includes:
- VC Exchange Framework
- Trust Chain Model
- Trusted Registries
- Revocation Frameworks
- Specifications
Using the walt.id Issuer & Verifier APIs you can issue and verify EBSI v3 compliant credentials based on the W3C credential
data model.
## Requirements
### Accreditations and Trust Chains
Accreditations and Trust Chains are crucial components of the EBSI Trust Framework, ensuring the integrity and reliability of data exchanges within specific scopes. Entities looking to create or participate in EBSI trust chains must first register and get accreditation. We can help you to becoming trusted issuers, participating on trust chains or creating your own on the EBSI conformance / pilot enviroment.
## Issuance Of Credentials
To issue EBSI v3 compliant credentials, simply utilize our regular W3C issuance endpoints as outlined in [this guide](https://docs.walt.id/community-stack/issuer/credential-issuance/vc-oid4vc.md). Date-related credential
attributes, such as "issuanceDate", "issued", "validFrom", and "expirationDate", must follow the format `YYYY-MM-DDThh:mm:ssZ`. Generating timestamps with the required format can be easily done using our[ pre-built EBSI data functions](https://docs.walt.id/community-stack/issuer/credential-issuance/data-functions.md#ebsi-specific-functions). For more info on data functions in general, please go [here](https://docs.walt.id/community-stack/issuer/credential-issuance/data-functions.md). The following sub-sections describes the parameters that you need to set up based on your use-case
### Without Trust Framework
You can issue EBSI compliant credentials without specifying any trust chain or framework. You can simply use a did:key with jwk_jcs-pub multicodec.
- `issuerKey` must correspond to the did:key
- `issuerDid` must correspond to issuerKey
### Including Trust Framework
You must provide the did:ebsi and the correspoding issuerKey
- `issuerKey` must correspond to the key registered as a trusted issuer on the EBSI blockchain.
- `issuerDid` must correspond to issuerKey
Also you might want to specify the term of use and the schemas in the `credentialData` object
- `termsOfUse` must correspond to the the value of Accreditation.
- `credentialSchema` must correspond to the credential schema that you Issuer is accredited for
--------------------------------------------------------------------------------
PAGE: Feature Manager
URL: https://docs.walt.id/community-stack/issuer/configurations/feature-manager
RAW: https://docs.walt.id/community-stack/issuer/configurations/feature-manager.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Feature Manager
Use the feature manager to control which issuer API features are enabled at service startup.
This reduces complexity and the risk of misconfigurations.
## Feature Types
The feature manager offers two types **Base** and **Optional**.
### Base Features
Base features are generally always enabled and do not require additional configuration in most cases. They provide
critical functionalities expected to be available by default.
### Optional Features
Optional features can either be enabled or disabled based on requirements.
1. **Enabled by Default**: These features do not need additional configuration to be useful. E.g. Health Checks operate
without any need for specific configs.
2. **Disabled by Default**: These features require specific configuration to function. E.g. the Entra Integration needs
access credentials and API URLs.
**Note:**
Each feature may have zero, one, or multiple configurations associated with it.
## How the Feature Manager Works
The Feature Manager ensures efficient and error-free initiation of services through the following steps:
1. **Initialization with Feature Catalogs**: Services are passed one or more Feature Catalogs when initialized.
2. **Determination of Active Features**: The initializer identifies explicitly enabled or disabled features and
determines the default states of optional features.
3. **Loading Configurations**: Configurations for base features and explicitly enabled optional features are loaded,
including default-enabled optional features.
**This process results in:**
- Loading only necessary configurations, reducing mental complexity and avoiding unused features.
- Minimizing required configuration through defaults, easing the setup process.
- Preventing invalid configurations from reaching production by validating during initialization.
## Feature Configuration
Configure features by updating the `_features.conf` file as
explained [here](https://docs.walt.id/community-stack/issuer/configurations/config-files/features.md). A list of available features can be found
here.
--------------------------------------------------------------------------------
PAGE: Logging
URL: https://docs.walt.id/community-stack/issuer/configurations/logging
RAW: https://docs.walt.id/community-stack/issuer/configurations/logging.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Logging
Multiplatform-based, the issuer API logging service enables dynamic reconfigurations, multiple log outputs, and
integrations
with log aggregator systems supporting various log output formats. Next to that, structured logging instead of plain
messages and context login (similar to MDC) are supported.
## Multiplatform Logging
Our logging implementation utilizes multiplatform libraries that are aligned with the logging systems already in use
across different platforms. Below are the specific logging systems targeted:
- Android: `android.util.Log`
- macOS/iOS: Darwin log system
- Java/JVM: SLF4J
- JavaScript: Console logger
This ensures that the logging system remains consistent with the platform's native logging practices.
## Capability Overview
The logging service is provided by the service commons module in the identity lib, enabling the following
capabilities.
### Dynamic Reconfigurations
- **Multiple Log Outputs**: Logs can be sent to multiple destinations simultaneously.
- **Log Aggregators**: Logs can be pushed to various log aggregators including standard output/error, Seq, Graylog, and
Logstash (part of the ELK stack).
### Log Output Formats
The multi log output formats enable integrations with different tools and platforms. Supported formats include:
- CLEF: Compact JSON format.
- Elastic Common Schema: Standard format for logging data.
- Elastic Common Schema for .NET: Specialized format for .NET applications.
- GELF: JSON format optimized for Graylog.
- JSON: Format for Splunk HTTP Event Collector.
- Simple & ISO 8601 Rendering: Easy-to-read and ISO 8601 standard timestamp formats.
## Configuration & Setup
Configuring the logging system can be done in two main ways:
1. **Predefined Setups**: Select from simplified pre-defined logging setups curated by walt.id by setting a command line
argument. If no configuration is detected, a default logging setup is used to ensure logging
is active and functional out-of-the-box.
2. **Configuration File**: For maximum flexibility configuration is possible via a file in HOCON or JSON format.
### Predefined Setup
To use the predefined setups provide one of the following arguments on service startup or setup an environment variable
with the same name in the service environment.
```
--log-level=default
--log-level=debug
--log-level=trace
Switch logging type (different rendering):
--log-type=simple (no colors)
--log-type=ansi (colors ) (default)
--log-type=iso8601 (iso timestamp)
--log-type=clef (Compact Log Event Format)
--log-type=gelf (Graylog Extended Log Format)
--log-type=ecs (Elastic Common Schema)
--log-type=ecs_dotnet (Elastic Common Schema for .net)
```
### Configuration File
Setup a JSON (.json) or HOCON (.conf) config file, in [this format](https://github.com/walt-id/waltid-identity/blob/main/waltid-services/waltid-wallet-api/src/main/resources/example_klogging.conf):
some options are explained [here]( https://klogging.io/docs/configuration/json#configuration-object-names)
--------------------------------------------------------------------------------
PAGE: Distributed Architecture
URL: https://docs.walt.id/community-stack/issuer/configurations/distributed-architecture
RAW: https://docs.walt.id/community-stack/issuer/configurations/distributed-architecture.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Distributed Architecture
When the Community Stack is cloned and run out-of-the-box as described [here](https://github.com/walt-id/waltid-identity?tab=readme-ov-file#use-services-and-apps) there is only one instance of each service started. i.e One instance of the Issuer API, one instance of the Verifier API and one instance of the Wallet API is started.
```
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3172c547a1c9 waltid/waltid-web-wallet:0.5.0 "bun run server/inde…" 25 hours ago Up 30 seconds 7101/tcp docker-compose-waltid-web-wallet-1
a109275e76b6 waltid/wallet-api:0.5.0 "/waltid-wallet-api/…" 25 hours ago Up 31 seconds 7001/tcp docker-compose-wallet-api-1
03cf14477b29 waltid/verifier-api:0.5.0 "/waltid-verifier-ap…" 25 hours ago Up 37 seconds 7003/tcp docker-compose-verifier-api-1
993557a8de84 waltid/issuer-api:0.5.0 "/waltid-issuer-api/…" 25 hours ago Up 36 seconds 7002/tcp docker-compose-issuer-api-1
a7eea13f67bd waltid/portal:0.5.0 "docker-entrypoint.s…" 25 hours ago Up 37 seconds 7102/tcp docker-compose-web-portal-1
fcb83570a141 waltid/vc-repository:latest "/bin/sh -c 'node se…" 25 hours ago Up 37 seconds 3000/tcp docker-compose-vc-repo-1
9f1cfcc83bfc caddy:2 "caddy run --config …" 25 hours ago Up 37 seconds 80/tcp, 443/tcp, 0.0.0.0:7001-7003->7001-7003/tcp, 0.0.0.0:7101-7103->7101-7103/tcp, 2019/tcp, 443/udp, 0.0.0.0:8080->8080/tcp docker-compose-caddy-1
8b54fbba10cd postgres "docker-entrypoint.s…" 3 weeks ago Up 37 seconds (healthy) 0.0.0.0:5432->5432/tcp
```
## Multiple Instances
However, when it comes to a production setup, in order to achieve better scalability and high availability, multiple services instances are required.
As an example, let's do a very basic setup, with 2 instances of the Issuer API.
Clone the project and change to root directory:
```
git clone https://github.com/walt-id/waltid-identity.git && cd waltid-identity
```
Build the Issuer API service Docker image:
```bash
./gradlew :waltid-services:waltid-issuer-api:publishImageToLocalRegistry
# image: waltid/issuer-api:
```
If you want a multi-arch image push to your registry, use `publishImage` with the env-vars:
`DOCKER_USERNAME`/`DOCKER_PASSWORD` and update the image name in the `docker run` commands.
Before starting the instances, it might be a good idea to create a Docker Network to ease the inter-container communication.
```
docker network create waltid
```
And now, starting two instances of the Issuer API...
One instance, named `issuer1` will be listening at the port 7002.
```
docker run --rm --name issuer1 --net waltid -p 7002:7002 waltid/issuer-api -- --webPort=7002 --baseUrl=http://caddy:9092
```
The other one (`issuer2`), listening on the 8002.
```
docker run --rm --name issuer2 --net waltid -p 8002:8002 waltid/issuer-api -- --webPort=8002 --baseUrl=http://caddy:9092
```
Now, we have two Issuer API instances up and running.
```
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
317015641bae waltid/issuer-api "/waltid-issuer-api/…" 7 seconds ago Up 6 seconds 7002/tcp, 0.0.0.0:8002->8002/tcp issuer2
7ad9fee7cd3d waltid/issuer-api "/waltid-issuer-api/…" 15 seconds ago Up 14 seconds 0.0.0.0:7002->7002/tcp issuer1
```
# Load Balancing
To hide the complexity of the distributed architecture from the end user, it is necessary to add a network asset, usally a load balancer or a reverse proxy (software or hardware) that intermediates user calls to the multiple instances of redundant services.
```
┌─────────┐
│ │
┌--─►│ issuer1 │
│ │ │
│ └─────────┘
┌─────────────────┐ │
│ Load Balancer │ │
│ ├────────────┘
│ or │
│ ├────────────┐
│ Reverse Proxy │ │
└─────────────────┘ │
│ ┌─────────┐
│ │ │
└--─►│ issuer2 │
│ │
└─────────┘
```
Nowadays, there are several options that could play this role, like the Apache Web Server, nginx, lighthttpd, Traefik etc.
### Caddy setup
For the sake of simplicity, let's use [Caddy](https://caddyserver.com/) in our example. Using the oficial Docker image, start it up.
```
docker run --rm --net waltid --name caddy -p 9092:9092 -v $(pwd)/waltid-services/waltid-issuer-api/distributed-test.Caddyfile:/etc/caddy/Caddyfile caddy
```
The Caddy config file is quite simple:
```
http://localhost:9092 {
reverse_proxy {
to issuer1:7002
to issuer2:8002
lb_policy round_robin
}
}
http://localhost:9093 {
reverse_proxy {
to verifier1:7003
to verifier2:8003
lb_policy round_robin
}
}
```
In a nutshell, it defines two endpoints: `localhost:9092` and `localhost:9093`.
Both work as a reverse proxy that [balances the load](https://caddyserver.com/docs/caddyfile/directives/reverse_proxy#load-balancing) in a round robin strategy between 2 Issuer instances (`issuer1` and `issuer2`) and 2 Verifier instances (`verifier1` and `verifier2`).
```
┌──────────────┐
│ │
┌--─►│ issuer1:7002 │
│ │ │
│ └───────-───-──┘
┌─────────────────┐ │
│ │ │
│ localhost:9092 ├────────────┘
│ (Issuer API) ├────────────┐
│ | │
└─────────────────┘ │ ┌───────-──────┐
│ │ │
└--─►│ issuer2:8002 │
│ │
└──────────────┘
┌───────────────-┐
│ │
┌--─►│ verifier1:7003 │
│ │ │
│ └─────────-───-──┘
┌─────────────────┐ │
│ │ │
│ localhost:9093 ├────────────┘
│ (Verifier API) ├────────────┐
│ | │
└─────────────────┘ │
│ ┌───────-────────┐
│ │ │
└--─►│ verifier2:8003 │
│ │
└────────────────┘
```
Now, give it a try and open http://localhost:9092. The Swagger page containing the Issuer API documentation will be opened.
With the instances log terminals visible, refresh the page a couple of times and observe how the requests are distributed between both instances. Cool, isn't it? However, there is a problem here.
## Session Management
### Isolated sessions
When a OIDC credencial exchange flow is started with the `/openid4vc/jwt/issue` endpoint, for example, a credential offer URL like the one bellow is generated.
```
openid-credential-offer://caddy:9092/?credential_offer_uri=http%3A%2F%2Fcaddy%3A9092%2Fopenid4vc%2FcredentialOffer%3Fid%3D1765770d-2682-4e4e-95e9-183875d4a9de
```
This URL links to an issuance session initiated in one of the instances of the Issuer API, for example, the `issuer1` in the diagram above.
This credential offer URL is supposed to be used later on by the `/wallet-api/wallet/{wallet}/exchange/useOfferRequest` Wallet API endpoint.
However, when the Wallet calls the URL encoded in the `credential_offer_uri` parameter to request the offered credential, the request may not reach the same Issuer API instance where the issuance session was initiated and the result will be:
```
{"exception":true,"status":"Not Found","code":"404","message":"No active issuance session found by the given id"}
```
This happens because each Issuer API has an independent (in memory) session management system. They don't share information between them. So, if an issuance session is initiated in one instance, the other instances doesn't know about it.
```
┌─────────────-┐
│ │ ┌───────────┐
┌--─►│ issuer1:7002 │─────►| Session 1 |
│ │ │ └───────────┘
│ └──────────────┘
┌─────────────────┐ │
│ │ │
│ localhost:9092 ├────────────┘
│ (Issuer API) ├────────────┐
│ | │
└─────────────────┘ │ ┌───────-─────-┐
│ │ │ ┌───────────┐
└--─►│ issuer2:8002 │─────►| Session 2 |
│ │ └───────────┘
└──────────────┘
```
### Shared session
There are multiple strategies to solve this issue in the distributed computing context. For now, the only way to exchange session data in a distributed deployment architecture of the Walt.id Community Stack is through a data persistence layer that is shared between the various instances of the cluster.
```
┌───────────────┐
│ │
┌--─►│ issuer1:7002 │
│ │ │
│ └───────┬───────┘
│ │
│ ▼
┌─────────────────┐ │ ┌────────────────┐
│ │ │ │ │
│ localhost:9092 ├────────────┘ │ Shared Session │
│ (Issuer API) ├────────────┐ │ │
│ │ │ └────────────────┘
└─────────────────┘ │ ▲
│ │
│ ┌───────┴───────┐
│ │ │
└--─►│ issuer2:8002 │
│ │
└───────────────┘
```
#### Enabling shared session management
This feature is disabled by default in the Community Stack. So, the very first thing to do is to enable it in the `waltid-services/waltid-issuer-api/config/_features.conf` file.
```
enabledFeatures = [
persistence
]
disabledFeatures = [
# ...
]
```
The settings for the persistence layer can be found in the `waltid-services/waltid-issuer-api/config/persistence.conf` config file. Look at its content:
```
type = "memory"
// type = "redis"
// nodes = [{host = "127.0.0.1", port = 6379}]
// user: ""
// password: ""
```
By default, the session persistence is managed in memory by each instance independently, as shown before. Let's change it to a shared data store.
#### Shared data store setup
For the moment, we only support the use of [Redis](https://redis.io/) as a shared database to store distributed session data. The easiest way to get it up and running is with an official Docker image.
```
docker run --rm --net waltid --name redis-stack-server -p 6379:6379 redis/redis-stack-server:latest
```
Change the `persistence.conf` file to use the "redis" persistence mechanism and configure the other parameters accordingly.
```
// type = "memory"
type = "redis"
nodes = [{host = "redis-stack-server", port = 6379}]
user: "default"
password: ""
```
Worth observing the `redis-stack-server` container name was also used as the Redis server hostname in the config file.
To update the configuration changes just made,
1. Stop the running Issuer API instances
```
docker stop $(docker ps | grep issuer-api | awk '{print $1}')
```
The plain old Ctrl+C is probably enough :-)
2. Rebuild the Issuer API image
```bash
./gradlew :waltid-services:waltid-issuer-api:publishImageToLocalRegistry
# image: waltid/issuer-api:
```
3. Restart the instances.
```
docker run --rm --name issuer1 --net waltid -p 7002:7002 waltid/issuer-api -- --webPort=7002 --baseUrl=http://caddy:9092
```
```
docker run --rm --name issuer2 --net waltid -p 8002:8002 waltid/issuer-api -- --webPort=8002 --baseUrl=http://caddy:9092
```
Let's check if it works?
## Testing
1. Go back to the Issuer API Swagger page at http://localhost:9092.
2. Use the `/openid4vc/jwt/issue` endpoint to start an OIDC credential exchange. Get the result Offer URL.
3. Start a local instance of the Wallet API.
```
docker run --rm --name wallet --net waltid -p 7001:7001 -it -v $(pwd)/waltid-services/waltid-wallet-api/config:/waltid-wallet-api/config -v $(pwd)/waltid-services/waltid-wallet-api/data:/waltid-wallet-api/data -t waltid/wallet-api
```
4. Open the Wallet API Swagger page at http://localhost:7001.
5. Use the `/wallet-api/auth/login` endpoint to authenticate with the pre-registered credentials.
6. Get the wallet id of the authenticated user with the `/wallet-api/wallet/accounts/wallets` endpoint.
7. Use the obtained wallet id and the offer URL in the `/wallet-api/wallet/{wallet}/exchange/useOfferRequest` enpoint to claim for the offered credential.
Voilá. You've just created your first distributed architecture of the Walt.id Community Stack for issuing credentials.
--------------------------------------------------------------------------------
PAGE: Data Retention
URL: https://docs.walt.id/community-stack/issuer/configurations/data-retention
RAW: https://docs.walt.id/community-stack/issuer/configurations/data-retention.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Data Retention
## What is stored
The issuer service stores the following ojects during the issuance process:
#### IssuanceSession:
- Session data (ID, expiration, status)
- the credential to be issued. You first initiate the issuance request with the data (or endpoints you want the data to be fetched from).
## Retention policy
- **Default expiration**: 5 minutes
- **Storage backend**: Configurable (memory/Redis/Redis-cluster)
- **Automatic cleanup**: Yes, via TTL expiration
### How to configure the retention policy
#### Per-Request TTL Override
You can override the default TTL for individual sessions via HTTP headers:
Include the `sessionTtl` header in your issuance request:
```http
POST /openid4vc/mdoc/issue
sessionTtl: 600
Content-Type: application/json
Body: {...}
```
The `sessionTtl` value is in **seconds**. Example above sets TTL to 600 seconds (10 minutes).
#### Storage backend
This is useful for distributed architectures. You can configure the storage backend to use a shared database to store session data.
In the `waltid-services/waltid-issuer-api/config/persistence.conf` config file, you can configure the storage backend to use a shared database to store session data.
```
// Default storage backend is memory.
type = "memory"
// Using redis as a shared database to store session data.
type = "redis"
nodes = [{host = "127.0.0.1", port = 6379}]
user: ""
password: ""
```
--------------------------------------------------------------------------------
PAGE: Features
URL: https://docs.walt.id/community-stack/issuer/configurations/config-files/features
RAW: https://docs.walt.id/community-stack/issuer/configurations/config-files/features.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Features
With the `_features.conf` you can control which issuer API features are enabled. They are managed via
the [feature manager](https://docs.walt.id/community-stack/issuer/configurations/feature-manager.md).
## Available Features
**Base Features**
Please note that the base features should always be enabled to make full use of the issuer API.
- `web` - Web service
- `issuer-service` - Issuer service implementation
- `credential-types` - Configure credential types available in the issuer instance. Learn
more [here](https://docs.walt.id/community-stack/issuer/configurations/config-files/credential-issuer-metadata.md).
**Optional Features**
- `healthchecks` - Enables healthcheck endpoints.
- `debug-endpoints` - Enables various debug endpoints.
- `feature-flag-information-endpoint` - Enables endpoints related to showing information about available features on this service instance.
- `persistence` - Enables the persistence layer for shared session management in [multi-instances deployments](https://docs.walt.id/community-stack/issuer/configurations/distributed-architecture.md).
### Example Setup
```editorconfig[_features.conf]
enabledFeatures = [
entra
]
disabledFeatures = [
debug-endpoints,
healthchecks
]
```
--------------------------------------------------------------------------------
PAGE: Credential Issuer Metadata
URL: https://docs.walt.id/community-stack/issuer/configurations/config-files/credential-issuer-metadata
RAW: https://docs.walt.id/community-stack/issuer/configurations/config-files/credential-issuer-metadata.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Credential Issuer Metadata
The Credential Issuer Metadata, defined by
the [OID4VC spec](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-credential-issuer-metadata),
provides detailed information about the issuer's technical and operational capabilities,
supported credential types, interaction endpoints, encryption standards, and localization options for display
information.
Using the `waltid-issuer-api/config/credential-issuer-metadata.conf` file, you can customize the supported credential
types and their related cryptographic binding methods, credential signing alg values, and more.
This way you can enable the issuance of any custom credential type with specific options.
You can either simply add another row defining your custom credential to the `supportedCredentialTypes` array already
defined in the config file
or use the expanded syntax to specify other options next to the credential type.
**Note:**
Please make sure to restart the issuer API service for changes to take effect.
### 1. Custom Credential Type Simple
Use the blow syntax if you want to only specify the credential type, other options like
`cryptographic_binding_methods_supported`,
`credential_signing_alg_values_supported` and more will be automatically generated by the issuer API. If you want to
explicitly set those options, please refer to the next section "Custom Credential Type Expanded".
```
CustomCredential = [VerifiableCredential, CustomCredential]
```
- **key** The name of the credential
- **array**: Type of the credential. Every W3C credential type is represented by an array, where the first type is
always `VerifiableCredential` followed by your custom credential name.
### 2. Custom Credential Type Expanded
Using the expanded syntax you can specify the credential type that needs to be supported next to the:
- format
- cryptographic_binding_methods_supported
- credential_signing_alg_values_supported
- credential_definition
- doctype
- vct
- doctype
depending on the credential format. You can find examples for the different credential formats we support below.
**Option: W3C Example**
```
"BankId_jwt_vc_json" = {
format = "jwt_vc_json"
cryptographic_binding_methods_supported = ["jwk"]
credential_signing_alg_values_supported = ["ES256"]
credential_definition = {
types = ["VerifiableCredential", "BankId"]
}
}
```
**Option: SD-JWT VC Example**
```
"identity_credential_vc+sd-jwt" = {
format = "vc+sd-jwt",
cryptographic_binding_methods_supported = ["jwk"]
credential_signing_alg_values_supported = ["ES256"]
vct = "http://localhost:22222/identity_credential"
}
```
**Note:**
**Using External VCT URLs**: You can configure your own VCT URL by providing a fully qualified URL pointing to an externally hosted VCT metadata document (e.g., `https://schema.example.com/vct/identity_credential`). This is particularly useful for ecosystem setups where multiple issuers share the same schema repository. The credential data will be validated against the schema defined in your VCT during issuance. The VCT metadata document may contain either the `schema` or `schema_uri` properties.
**Option: mDL Example**
```
"org.iso.18013.5.1.mDL" = {
format = "mso_mdoc"
cryptographic_binding_methods_supported = ["cose_key"]
credential_signing_alg_values_supported = ["ES256"]
proof_types_supported = { cwt = { proof_signing_alg_values_supported = ["ES256"] } }
doctype = "org.iso.18013.5.1.mDL"
}
```
**Option: W3C Example with Credential Display**
```
"BankId_jwt_vc_json" = {
format = "jwt_vc_json"
cryptographic_binding_methods_supported = ["jwk"]
credential_signing_alg_values_supported = ["ES256"]
credential_definition = {
types = ["VerifiableCredential", "BankId"]
}
display = [{
name = "Bank ID",
locale = "en-US",
description = "This is a Bank ID credential",
logo = {
url = "https://example.com/logo.png",
altText = "Logo"
},
backgroundColor = "#FFFFFF",
textColor = "#000000",
backgroundImage = {
url = "https://example.com/background.png",
altText = "Background"
},
}],
}
```
### 3. Using External VCT URLs
For SD-JWT VC credentials, you can configure your own VCT (Verifiable Credential Type) URL instead of using a local or auto-generated one. This is particularly useful for:
- **Ecosystem setups**: When multiple issuers need to reference a shared schema repository
- **Compliance requirements**: When your organization requires using specific VCT URLs that conform to regulatory standards
- **Interoperability**: When working with partners who use standardized VCT schemas
To use an external VCT URL, simply provide a fully qualified URL in the `vct` field:
```
"identity_credential_vc+sd-jwt" = {
format = "vc+sd-jwt",
cryptographic_binding_methods_supported = ["jwk"]
credential_signing_alg_values_supported = ["ES256"]
vct = "https://schema.example.com/vct/identity_credential"
}
```
**Note:**
**Important**: When using external VCT URLs, ensure:
• The URL points to a valid VCT metadata document that conforms to the SD-JWT VC specification
• The document is accessible and contains a valid JSON schema
• The credential data you issue conforms to the schema defined in your VCT, as it will be validated during issuance
The VCT metadata document may contain either the `schema` or `schema_uri` properties. During credential issuance, the system will validate the credential data against the schema defined in your VCT.
### Example Config
You can also find it
in [walt.id repo](https://github.com/walt-id/waltid-identity/blob/main/waltid-services/waltid-issuer-api/config/credential-issuer-metadata.conf).
```[credential-issuer-metadata.conf]
supportedCredentialTypes = {
BankId = [VerifiableCredential, BankId],
KycChecksCredential = [VerifiableCredential, VerifiableAttestation, KycChecksCredential],
KycCredential = [VerifiableCredential, VerifiableAttestation, KycCredential],
KycDataCredential = [VerifiableCredential, VerifiableAttestation, KycDataCredential],
PassportCh = [VerifiableCredential, VerifiableAttestation, VerifiableId, PassportCh],
PND91Credential = [VerifiableCredential, PND91Credential],
MortgageEligibility = [VerifiableCredential, VerifiableAttestation, VerifiableId, MortgageEligibility],
PortableDocumentA1 = [VerifiableCredential, VerifiableAttestation, PortableDocumentA1],
OpenBadgeCredential = [VerifiableCredential, OpenBadgeCredential],
VaccinationCertificate = [VerifiableCredential, VerifiableAttestation, VaccinationCertificate],
WalletHolderCredential = [VerifiableCredential, WalletHolderCredential],
UniversityDegree = [VerifiableCredential, UniversityDegree],
VerifiableId = [VerifiableCredential, VerifiableAttestation, VerifiableId],
CTWalletSameAuthorisedInTime = [VerifiableCredential, VerifiableAttestation, CTWalletSameAuthorisedInTime],
CTWalletSameAuthorisedDeferred = [VerifiableCredential, VerifiableAttestation, CTWalletSameAuthorisedDeferred],
CTWalletSamePreAuthorisedInTime = [VerifiableCredential, VerifiableAttestation, CTWalletSamePreAuthorisedInTime],
CTWalletSamePreAuthorisedDeferred = [VerifiableCredential, VerifiableAttestation, CTWalletSamePreAuthorisedDeferred],
AlpsTourReservation = [VerifiableCredential, VerifiableAttestation, AlpsTourReservation],
EducationalID = [VerifiableCredential, VerifiableAttestation, EducationalID],
HotelReservation = [VerifiableCredential, VerifiableAttestation, HotelReservation],
Iso18013DriversLicenseCredential = [VerifiableCredential, VerifiableAttestation, Iso18013DriversLicenseCredential],
TaxReceipt = [VerifiableCredential, VerifiableAttestation, TaxReceipt],
VerifiablePortableDocumentA1 = [VerifiableCredential, VerifiableAttestation, VerifiablePortableDocumentA1],
Visa = [VerifiableCredential, VerifiableAttestation, Visa],
eID = [VerifiableCredential, VerifiableAttestation, eID],
NaturalPersonVerifiableID = [VerifiableCredential, VerifiableAttestation, NaturalPersonVerifiableID],
BoardingPass = [VerifiableCredential, VerifiableAttestation, BoardingPass]
"org.iso.18013.5.1.mDL" = {
format = "mso_mdoc"
cryptographic_binding_methods_supported = ["cose_key"]
credential_signing_alg_values_supported = ["ES256"]
proof_types_supported = { cwt = { proof_signing_alg_values_supported = ["ES256"] } }
doctype = "org.iso.18013.5.1.mDL"
}
"identity_credential_vc+sd-jwt" = {
format = "vc+sd-jwt",
cryptographic_binding_methods_supported = ["jwk"]
credential_signing_alg_values_supported = ["ES256"]
vct = "http://localhost:22222/identity_credential"
}
"BankId_jwt_vc_json" = {
format = "jwt_vc_json"
cryptographic_binding_methods_supported = ["jwk"]
credential_signing_alg_values_supported = ["ES256"]
credential_definition = {
types = ["VerifiableCredential", "BankId"]
}
}
"identity_credential_vc+sd-jwt" = {
format = "vc+sd-jwt",
cryptographic_binding_methods_supported = ["jwk"]
credential_signing_alg_values_supported = ["ES256"]
types = ["identity_credential_vc+sd-jwt"],
doctype = "identity_credential_vc+sd-jwt",
vct = "https://example.org/identity_credential",
display = [{
name = "Test Credential",
locale = "en-US",
description = "This is a test credential",
logo = {
url = "https://example.com/logo.png",
altText = "Logo"
},
backgroundColor = "#FFFFFF",
textColor = "#000000"
}],
sdJwtVcTypeMetadata = {
vct = "https://example.org/identity_credential",
name = "Identity Credential",
description = "The Identity Verifiable Credential"
}
}
}
```
### Top Level Display Options
You can also specify top level display options for the issuer service. This is useful if you want to display a logo and name for the issuer service itself.
```json
issuerDisplay = [
{
name = "My Organization Issuer"
locale = "en-US"
logo = {
url = "https://example.org/logo.png"
alt_text = "Issuer logo"
}
description = "My Organization Issuer"
}
]
supportedCredentialTypes = {
...
"MyOrganizationCredential": [VerifiableCredential, MyOrganizationCredential],
...
}
```
--------------------------------------------------------------------------------
PAGE: Issuer Service
URL: https://docs.walt.id/community-stack/issuer/configurations/config-files/issuer-service
RAW: https://docs.walt.id/community-stack/issuer/configurations/config-files/issuer-service.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Issuer Service
The `issuer-service.conf` file controls the core runtime settings of the walt.id Issuer API.
**Note:**
Please make sure to restart the issuer API service for changes to take effect.
## Example Configurations
### Local Development
```editorconfig[issuer-service.conf]
baseUrl = "http://localhost:7002"
```
### Docker / Docker Compose
```editorconfig[issuer-service.conf]
baseUrl = "http://${SERVICE_HOST}:${ISSUER_API_PORT}"
```
### Production (with pinned token key)
```editorconfig[issuer-service.conf]
baseUrl = "https://issuer.yourdomain.com"
ciTokenKey = "{\"type\":\"jwk\",\"jwk\":\"{\\"kty\\":\\"EC\\",\\"crv\\":\\"P-256\\",\\"d\\":\\"Tys3-CNQkQxRlZ6yMvH1n7k6o_xFBxzRMVGJNamRLdY\\",\\"x\\":\\"WqGl0DlGFfH9KRsLZW0v2RDWGXQT_JsMyW3A7L5Bq0A\\",\\"y\\":\\"1lzq3OO0PB9X_3Zs4c5GNd6eRxjXfLGP5YvMkZQ8V1Y\\"}\"}"
```
## Parameters
### baseUrl
**Required.** The public base URL at which the Issuer API is reachable.
The `baseUrl` is embedded into the OID4VCI artifacts the service produces at runtime, including the credential
issuer metadata, credential offer URIs, the `vct` claim in SD-JWT VCs, and the `iss` claim when no issuer DID
is configured. Make sure the URL is reachable by the wallet.
| Environment | Example value |
|---|---|
| Local development (browser-based wallet on same machine) | `http://localhost:7002` |
| Local development (mobile wallet on the same network) | `http://192.168.x.x:7002` — use your machine's LAN IP |
| Docker / Docker Compose | `http://${SERVICE_HOST}:${ISSUER_API_PORT}` |
| Production | `https://issuer.yourdomain.com` |
**Note:**
The port in `baseUrl` must match `webPort` in [`web.conf`](https://docs.walt.id/community-stack/issuer/configurations/config-files/web.md).
---
### ciTokenKey
**Optional.** A serialized EC key used to sign internal credential issuance session tokens (the short-lived tokens
exchanged between the issuer and wallet during the OID4VCI flow).
When `ciTokenKey` is not set, the service **auto-generates a fresh P-256 key on every startup**. This has two
important consequences:
- All active issuance sessions are **invalidated on every service restart.** Any wallet that received a credential
offer before the restart will no longer be able to claim it.
- In **multi-instance or rolling deployments**, each instance generates its own key. A session token issued by one
instance cannot be validated by another, causing random claim failures depending on which instance the wallet
reaches.
For any production or staging environment, you should configure a fixed, persistent key.
The value is a JSON object — serialized as a string — where the inner `jwk` field is itself a JSON-encoded
string. Generate a P-256 key pair using any standard tool (openssl, an online JWK generator, etc.), then
substitute your own `d`, `x`, and `y` values into the production example above.
**Note:**
**Replace the example key before deploying.** The value in the production example above is provided for
illustration only. Using it in production means anyone with access to these docs could forge session tokens for
your issuer.
--------------------------------------------------------------------------------
PAGE: Web
URL: https://docs.walt.id/community-stack/issuer/configurations/config-files/web
RAW: https://docs.walt.id/community-stack/issuer/configurations/config-files/web.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Web
With the `web.conf` you can configure the host and port for local network connections. You
can find an example below.
### Example
```editorconfig[web.conf]
webHost = "0.0.0.0"
webPort = "7002"
```
--------------------------------------------------------------------------------
PAGE: Overview
URL: https://docs.walt.id/community-stack/issuer/protocol-flows/overview
RAW: https://docs.walt.id/community-stack/issuer/protocol-flows/overview.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Overview
Sequence diagrams for common issuance flows. Each flow shows the full request lifecycle — from the portal initiating issuance through to the wallet storing the credential.
## Available Flows
- [mdoc Issuance via OID4VCI with Cloud KMS](https://docs.walt.id/community-stack/issuer/protocol-flows/mdoc-issuance-oid4vci-kms.md) — Full issuance flow for an mdoc credential signed via an external TSE/Vault KMS.
--------------------------------------------------------------------------------
PAGE: mdoc Issuance via OID4VCI with Cloud KMS
URL: https://docs.walt.id/community-stack/issuer/protocol-flows/mdoc-issuance-oid4vci-kms
RAW: https://docs.walt.id/community-stack/issuer/protocol-flows/mdoc-issuance-oid4vci-kms.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# mdoc Issuance via OID4VCI with Cloud KMS
This flow covers a portal initiating an mdoc credential offer, the wallet claiming it via OID4VCI, and the Issuer API delegating signing to an external Cloud KMS (TSE/Vault).
**Components involved:**
- Portal Application — initiates the credential offer and displays the QR code
- Wallet (Holder) — scans the QR code, completes the OID4VCI flow, stores the credential
- Issuer API — manages the issuance session and orchestrates signing
- Cloud KMS (TSE/Vault) — holds the issuer private key and performs signing
```mermaid
sequenceDiagram
participant Portal as Portal Application
participant Wallet as Wallet (Holder)
participant Issuer as Issuer API
participant KMS as Cloud KMS (TSE/Vault)
Portal->>Issuer: POST /openid4vc/mdoc/issue
activate Issuer
Issuer->>Issuer: Create credential offer URI and issuance session
Issuer-->>Portal: 200 OK {credentialOfferUri: "openid-credential-offer://..."}
deactivate Issuer
Portal->>Portal: Generate QR code with credential offer URI
Portal-->>Wallet: Display QR code (openid-credential-offer://...)
Wallet->>Wallet: Scan QR code
Wallet->>Wallet: Parse credential offer URI
Wallet->>Issuer: GET /.well-known/openid-credential-issuer
Issuer-->>Wallet: Provider metadata
Wallet->>Issuer: POST /token
Wallet->>Wallet: Generate proof of possession (sign with holder key)
Wallet->>Issuer: POST /credential
activate Issuer
Issuer->>Issuer: Resolve TSE key from issuerKey in request
Issuer->>KMS: Authenticate with Vault
activate KMS
KMS-->>Issuer: Authentication successful
deactivate KMS
Issuer->>Issuer: Build credential structure with credentialData
Issuer->>KMS: Request signature for credential
activate KMS
KMS->>KMS: Sign using private key
KMS-->>Issuer: Signature bytes
deactivate KMS
Issuer->>Issuer: Complete mdoc signing with signature from KMS
Issuer-->>Wallet: 200 OK
deactivate Issuer
Wallet->>Wallet: Store mdoc credential
```
## Related
- [Key Management — Hashicorp Vault](https://docs.walt.id/community-stack/issuer/key-management/hashicorp-vault.md)
- [mDL Issuance](https://docs.walt.id/community-stack/issuer/credential-issuance/mdl-oid4vc.md)
--------------------------------------------------------------------------------
PAGE: Overview
URL: https://docs.walt.id/community-stack/issuer/apps/portal/overview
RAW: https://docs.walt.id/community-stack/issuer/apps/portal/overview.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Overview
Video: https://youtu.be/9CzXmw0Fb_4
A list of credentials to issue and verify via a white-label UI. The credentials available via the portal are
fetched from the [walt.id credential repository](https://github.com/walt-id/waltid-credentials).
## Setup
1. Clone the identity repo
```bash
git clone https://github.com/walt-id/waltid-identity.git && cd waltid-identity
```
2. Running the project
**Option: Local**
1. Change directory
```bash
cd waltid-applications/waltid-web-portal/
```
**Note:**
Make sure you have the `NEXT_PUBLIC_VC_REPO` variable set in `.env`.
As default value `"https://credentials.walt.id"` can be used.
2. Run the development server
```bash
npm install
npm run dev
```
or
2. For production build
```bash
npm install
npm run build
```
**Option: Docker**
1. Build & run the image
```bash
docker build -t waltid/portal -f waltid-applications/waltid-web-portal/Dockerfile .
docker run -p 3000:3000 -e NEXT_PUBLIC_VC_REPO="https://credentials.walt.id" -i -t waltid/portal
```
**Note:**
Here we are using the public hosted VC repo **credentials.walt.id.** However, you can also run & configure it locally.
Learn more [here](https://github.com/walt-id/waltid-credentials).
2. Access the portal under [localhost:3000](http://localhost:3000/)
--------------------------------------------------------------------------------
PAGE: Full API Reference
URL: https://docs.walt.id/community-stack/issuer/full-api-reference
RAW: https://docs.walt.id/community-stack/issuer/full-api-reference.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Full API Reference
Find it [here](https://issuer.demo.walt.id/swagger/index.html)
--------------------------------------------------------------------------------
PAGE: FAQs
URL: https://docs.walt.id/community-stack/issuer/faq
RAW: https://docs.walt.id/community-stack/issuer/faq.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Issuer API FAQs
This page addresses common questions about the walt.id Issuer API for the Community Stack.
## General Questions
**Q: What is the difference between the Community Stack and Enterprise Stack Issuer APIs?**
A: The Community Stack Issuer API is stateless and typically deployed as a single instance via Docker. It's designed for simpler use cases where you manage your own infrastructure. The Enterprise Stack is stateful, supports multiple instances with shared databases, includes a tenant system for B2B scenarios, and provides built-in GUI management tools, lifecycle management, and credential revocation mechanisms.
**Q: What credential formats does the Issuer support?**
A: The Issuer supports multiple credential formats including:
- **SD-JWT VC (IETF)**: Selective Disclosure JSON Web Token Verifiable Credentials
- **W3C Verifiable Credentials**: Versions 1.1+ and 2.0 with JSON Web signatures
- **ISO 18013-5 mDL**: Mobile Driver's License format
- **Custom formats**: The system is extensible to support new or proprietary formats
**Q: Which OID4VCI draft versions are supported?**
A: The Issuer supports OID4VCI Draft 11 and Draft 13, with various user flows including Pre-Authorized Code Flow (with or without PIN) and Authorization Code Flow (with ID Token, VP Token, username/password authentication).
## Key Management & Security
**Q: How does key management work with the Issuer API?**
A: The Issuer API is KMS-agnostic and supports integration with external key management solutions like AWS KMS, Azure Key Vault, Google Cloud KMS, Hashicorp Vault, OCI KMS, and others. Private keys never leave the secure KMS environment - the system sends signing payloads to the KMS and receives signatures back. Raw key material is also supported for development and PoC environments.
**Q: What cryptographic algorithms are supported for credential signing?**
A: The Issuer supports multiple asymmetric key types including ed25519, secp256k1, secp256r1, and RSA. Only asymmetric keys are used for credential issuance to enable public-key cryptographic operations essential for decentralized trust.
**Q: How do I integrate with my existing KMS infrastructure?**
A: The system supports multiple authentication methods depending on your KMS provider. For cloud environments, you can use automatically assigned roles (like AWS IAM roles) for keyless authentication. API keys are also supported for other KMS providers.
## Credential Data & Lifecycle
**Q: How can I collect and manage credential data?**
A: There are three main approaches:
1. **Before credential offer creation**: Collect all data upfront from external sources (CRM, databases, registries)
2. **After offer creation, before signing**: Use [data functions](https://docs.walt.id/community-stack/issuer/credential-issuance/data-functions.md) to dynamically enrich credentials with timestamps, DIDs, or external API calls
3. **During user authentication**: Pull verified claims from identity providers during the OID4VCI authorization flow (Enterprise Stack only)
**Q: How do I handle credential revocation and status management?**
A: In the Community Stack, you can issue credentials with status information, but you're responsible for hosting and managing the status credentials externally. The Enterprise Stack includes built-in credential status management with support for standards like Bitstring Status List, Token Status List, and StatusList2021, with automatic hosting and publishing capabilities.
**Q: Can I set expiration dates on credentials?**
A: Yes, credentials can include expiration and "valid from" dates. These can be set explicitly during credential creation or generated dynamically using timestamp data functions. This is useful for time-limited credentials like event tickets, student IDs, or transportation passes.
**Q: Can I define a custom credential template?**
A: The walt.id issuer API is flexible regarding credential formats and types. This means you can use the API to sign any custom data structure without needing to create a "credential template" beforehand. The only requirement is to register your custom credential type (which includes the name of your credential and optional display properties) in the issuer metadata, as outlined [here](https://docs.walt.id/community-stack/issuer/configurations/config-files/credential-issuer-metadata.md).
## Integration & Deployment
**Q: How do I get started with the Issuer API?**
A: Start with the [getting started guide](https://docs.walt.id/community-stack/issuer/getting-started.md) which covers setup, key management, and basic credential issuance. You can also explore the [SDKs](https://docs.walt.id/community-stack/issuer/sdks/getting-started) for Kotlin/Java integration or the [white-label portal](https://docs.walt.id/community-stack/issuer/apps/portal/overview.md) for a GUI-based approach.
**Q: Can I use the Issuer API with my existing identity infrastructure?**
A: Yes, the Issuer API can integrate with external identity providers through the OID4VCI authorization code flow. This allows you to authenticate users through your existing identity systems (Azure AD, Google, Keycloak, etc.) and use verified claims for credential issuance (Enterprise Stack only)
**Q: How do I customize credential appearance in wallets?**
A: Credential branding can be configured in two ways:
1. **Issuer metadata**: Define consistent styling per credential type (background color, logo, description)
2. **Embedded branding**: Include visual attributes directly in individual credentials for personalized presentation
**Q: What if I need to support a new credential format not currently supported?**
A: The walt.id system is built for extensibility. New credential formats can be added as dedicated Kotlin libraries and integrated through the shared digital-credential abstraction layer. Reach out for more info to tamino@walt.id
## Compliance & Standards
**Q: Is the Issuer API compliant with eIDAS2 requirements?**
A: Yes, the Issuer is aligned with the EU's emerging digital ID ecosystem under eIDAS2, including ARF and implementing acts. This ensures credentials can be issued to different wallet, including EUDI wallets.
**Q: What compliance standards do the KMS integrations support?**
A: The supported KMS solutions employ hardware security modules (HSMs) validated under FIPS 140-2 (often Level 3) and help organizations meet standards including ISO 27001, PCI DSS, SOC 1/2/3, HIPAA, and GDPR. Specific certifications depend on your chosen KMS provider and deployment configuration.
--------------------------------------------------------------------------------
PAGE: Getting Started
URL: https://docs.walt.id/community-stack/verifier/getting-started
RAW: https://docs.walt.id/community-stack/verifier/getting-started.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Getting Started
Video: https://www.youtube.com/watch?v=RpAiWkbqb2c
The walt.id verifier API allows you to **verify various types of digital credentials (W3C, SD-JWT, mDL) via OID4VP**. The issuer and
subject can be identified by various types of DIDs (e.g. did:key, did:jwk, did:web, ...) or just a key. Use [policies](https://docs.walt.id/community-stack/verifier/credential-verification/policies/overview.md)
to customize the verification process.
## Supported Standards
| | |
|:-------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **Credential Formats:** | [SD-JWT VC (IETF)](https://docs.walt.id/concepts/digital-credentials/sd-jwt-vc.md), [W3C VC (v1.1+, v2.0)](https://docs.walt.id/concepts/digital-credentials/verifiable-credentials-w3c.md), [ISO 18013-5 mDL](https://docs.walt.id/concepts/digital-credentials/mdoc-mdl-iso.md) |
| **Credential Exchange:** | [OID4VP](https://docs.walt.id/concepts/data-exchange-protocols.md) (Draft 14/20, v1 available via Verifier2 - [learn more here](https://github.com/walt-id/waltid-identity/tree/main/waltid-services/waltid-verifier-api2) for more info), [ISO/IEC 18013-7](https://docs.walt.id/concepts/data-exchange-protocols.md) |
| **Signature Algorithms:** | ed25519, secp256k1, secp256r1, RSA |
## Setup
- [Open-Source](https://docs.walt.id/community-stack/verifier/setup.md) - Use the verifier API available via the [walt.id identity repo](https://github.com/walt-id/waltid-identity.git).
- [Docker compose quick start](https://docs.walt.id/community-stack/home.md#quick-start) - Spin up the verifier API locally together with the other services.
- [Full API reference](https://docs.walt.id/community-stack/verifier/full-api-reference.md) - Explore all available endpoints via Swagger.
## Features
- [Credential Verification](https://docs.walt.id/community-stack/verifier/credential-verification/overview.md) - Request and verify various digital credential types and formats ([W3C](https://docs.walt.id/community-stack/verifier/credential-verification/vc-oid4vc.md), [SD-JWT](https://docs.walt.id/community-stack/verifier/credential-verification/sd-jwt-vc-oid4vc.md), [mDL](https://docs.walt.id/community-stack/verifier/credential-verification/mdl-oid4vc.md)) via OID4VP.
- [Policies](https://docs.walt.id/community-stack/verifier/credential-verification/policies/overview.md) - Use policies to tailor validation behavior and checks to your use-case.
Checkout the full feature overview [here](https://docs.walt.id/community-stack/verifier/features).
## Ecosystems
- [EBSI](https://docs.walt.id/community-stack/verifier/ecosystems/ebsi/overview.md) - Verify credentials that are compliant with the European Blockchain Services Infrastructure (EBSI).
## Configurations
Use [the feature manager](https://docs.walt.id/community-stack/verifier/configurations/feature-manager.md) to enable or disable API functionality depending on your use-case.
## Guides
- [How to Verify a W3C Verifiable Credential (JWT / SD-JWT) via OID4VP](https://docs.walt.id/community-stack/verifier/credential-verification/vc-oid4vc.md)
- [How to Verify an SD-JWT-based Verifiable Credential (IETF | SD-JWT VC) via OID4VP](https://docs.walt.id/community-stack/verifier/credential-verification/sd-jwt-vc-oid4vc.md)
- [How to Verify Mobile Driver’s Licenses (ISO/IEC 18013-5 mDL) via OID4VP](https://docs.walt.id/community-stack/verifier/credential-verification/mdl-oid4vc.md)
- [Full API reference](https://docs.walt.id/community-stack/verifier/full-api-reference.md)
--------------------------------------------------------------------------------
PAGE: Overview
URL: https://docs.walt.id/community-stack/verifier/capabilities
RAW: https://docs.walt.id/community-stack/verifier/capabilities.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Overview
The Community Stack Verifier API is a stateless, lightweight REST API designed for building credential verification solutions.
## Credential Verification Capabilites
--------------------------------------------------------------------------------
PAGE: Credential Lifecycle
URL: https://docs.walt.id/community-stack/verifier/capabilities/credential-lifecycle
RAW: https://docs.walt.id/community-stack/verifier/capabilities/credential-lifecycle.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Credential Lifecycle
The Community Stack Verifier API validates credential lifecycle states—whether a credential is active, revoked, suspended, or expired—using globally adopted status mechanisms and validity timestamps.
**Note:**
The Verifier doesn't manage credential status itself—it verifies status by checking issuer-hosted status lists and credential validity fields during presentation verification.
## What's included
The Verifier capability supports lifecycle checks through dedicated verification policies:
#### Status list verification
Check whether a credential has been revoked or suspended by fetching and validating its entry in the issuer's status list.
**This means:**
- Verifiers automatically fetch status credentials from issuer-hosted locations (web servers, S3, Azure Blob, etc.)
- Supports industry-standard formats: Bitstring Status List v1.0, TokenStatusList, StatusList2021, RevocationList2020
- Works across credential types: W3C VC, SD-JWT VC, ISO mdoc
- Verifiers can check revocation, suspension, or custom status purposes
#### Validity window verification
Verify that credentials are being used within their intended validity period by checking `validFrom` (or `notBefore`) and `validUntil` (or `expiresAt`) timestamps.
**This means:**
- Prevents use of credentials before their activation date
- Rejects expired credentials automatically
- No external status list required—validity timestamps are embedded in the credential itself
- Verifiers can enforce strict temporal policies per use case
## FAQs
- **Which status list formats are supported?** — The Verifier supports W3C Bitstring Status List v1.0, TokenStatusList (IETF), StatusList2021, and RevocationList2020, covering all major standards.
- **Can I verify credentials offline?** — Validity timestamps can be verified offline, but status list checks require network access to fetch the current status credential. For offline scenarios, verifiers need a cached copy of the status list.
## Get Started
- **[Credential status verification policy](https://docs.walt.id/community-stack/verifier/credential-verification/policies/parameterized-verification-policies.md#credential-status)** – Learn how to verify credential status using status list policies.
- **[Expiration & validity verification policies](https://docs.walt.id/community-stack/verifier/credential-verification/policies/static-verification-policies.md#expiredn)** – Learn about built-in policies for expiration and validity checks.
--------------------------------------------------------------------------------
PAGE: Setup
URL: https://docs.walt.id/community-stack/verifier/setup
RAW: https://docs.walt.id/community-stack/verifier/setup.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Setup
## Installation & Running the Project
Make sure you have Docker installed on your machine.
### Option 1: Using Docker Pull
**1. Cloning The Identity-Repo**
First, clone the repository to your local machine:
```bash
git clone https://github.com/walt-id/waltid-identity.git && cd waltid-identity
```
**2. Pulling the Docker Image**
To quickly get started, you can pull the pre-built Docker image from Docker Hub:
```bash
docker pull waltid/verifier-api
```
**2. Running the Docker Image**
Once the image is pulled, you can run it using the following command:
```bash
docker run -p 7003:7003 \
-itv $(pwd)/waltid-services/waltid-verifier-api/config:/waltid-verifier-api/config \
-t waltid/verifier-api
```
### Option 2: Building the Image Yourself
**1. Cloning The Identity-Repo**
First, clone the repository to your local machine:
```bash
git clone https://github.com/walt-id/waltid-identity.git && cd waltid-identity
```
**2. Building the Docker Image**
If you prefer to build the Docker image yourself, use the Gradle Ktor/Jib tasks:
```bash
# Development (local Docker daemon, single-arch)
./gradlew :waltid-services:waltid-verifier-api:publishImageToLocalRegistry
# image: waltid/verifier-api:
```
```bash
# Production (multi-arch push to your registry)
export DOCKER_USERNAME=
export DOCKER_PASSWORD=
./gradlew :waltid-services:waltid-verifier-api:publishImage
# image: docker.io//verifier-api:
```
Note: multi-arch images require a registry push. Local tar output is single-arch only.
**3. Running the Docker Image**
After building the image, run it using (adjust the image name if you pushed to your registry):
```bash
docker run -p 7003:7003 \
-itv $(pwd)/waltid-services/waltid-verifier-api/config:/waltid-verifier-api/config \
-t waltid/verifier-api
```
## Start Verifying Credentials
Now that the verifier api is running, you can start verifying credentials.
- [How to Verify a W3C Verifiable Credential (JWT / SD-JWT) via OID4VP](https://docs.walt.id/community-stack/verifier/credential-verification/vc-oid4vc.md)
- [How to Verify an SD-JWT-based Verifiable Credential (IETF | SD-JWT VC) via OID4VP](https://docs.walt.id/community-stack/verifier/credential-verification/sd-jwt-vc-oid4vc.md)
- [How to Verify Mobile Driver’s Licenses (ISO/IEC 18013-5 mDL) via OID4VP](https://docs.walt.id/community-stack/verifier/credential-verification/mdl-oid4vc.md)
- [Full API reference](http://localhost:7003)
--------------------------------------------------------------------------------
PAGE: Overview
URL: https://docs.walt.id/community-stack/verifier/credential-verification/overview
RAW: https://docs.walt.id/community-stack/verifier/credential-verification/overview.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Overview
## How to request and verify different types of credentials
- [W3C VCs](https://docs.walt.id/community-stack/verifier/credential-verification/vc-oid4vc.md) - request & verify W3C Verifiable Credentials (JWT or SD-JWT signature).
- [SD-JWT VCs](https://docs.walt.id/community-stack/verifier/credential-verification/sd-jwt-vc-oid4vc.md) - request & verify IETF SD-JWT VC credentials.
- [mDLs](https://docs.walt.id/community-stack/verifier/credential-verification/mdl-oid4vc.md) - request & verify Mobile Driver’s License (ISO/IEC 18013-7 mDLs) credentials.
## Customize Verification Checks for Specific Use Cases
- [Policies](https://docs.walt.id/community-stack/verifier/credential-verification/policies/overview.md) - check credential data presented with static and parameterized verification policies.
--------------------------------------------------------------------------------
PAGE: How to Verify a W3C Verifiable Credential (JWT / SD-JWT) via OID4VP with walt.id
URL: https://docs.walt.id/community-stack/verifier/credential-verification/vc-oid4vc
RAW: https://docs.walt.id/community-stack/verifier/credential-verification/vc-oid4vc.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# How to Verify a W3C Verifiable Credential (JWT / SD-JWT) via OID4VP with walt.id
**TL;DR**
**What you’ll learn:**
- Configure an OID4VP compliant authorization request using the `/verify` endpoint.
- Apply VP/VC verification policies on received credentials.
- Use custom input descriptors and relational constraints to enforce issuer, holder, or subject relationships.
- Retrieve verification status, policy results, and inspect presented credentials via the verifier API.
**Relevant concepts:**
- [Verifiable Credentials: A Simple Guide to How They Work](https://docs.walt.id/concepts/digital-credentials/verifiable-credentials-w3c.md) – Learn more about the credential format we will be using.
- [What is OpenID4VP? The Developer's Guide (2025)](https://docs.walt.id/concepts/data-exchange-protocols/openid4vp.md) – Protocol for requesting and receiving verifiable presentations
Video: https://youtu.be/WjvCaZQ-OFU
This guide provides a comprehensive walkthrough for verifying a Verifiable Credential based on the W3C standard using
the walt.id verifier API. The verification process will utilize the OID4VCI protocol, an extension of OpenID,
facilitating secure and standardized communication between identities.
**Verifiable Credential (VC)**: A digital equivalent of physical credentials such as a driver's license or university
degree. VCs are cryptographically secure, privacy-respecting, and instantly verifiable.
**OID4VCI**: A protocol specifying how parties can issue VCs and present these credentials in a way that's consistent
and secure across platforms ensuring interoperability.
## Preparing the Verification
First, determine the type of Verifiable Credential to be verified. Although this guide focuses on a "UniversityDegree,"
you can use any credential type compliant with the W3C standard.
The verification process will be as follows:
1. Specify the credential type(s) to request from a user and the verification policies to be applied to the credential(
s).
2. Optionally provide a success and failure redirect URL, which the user will be redirected to after the verification
process is completed.
After you have provided the required information:
1. The API generates
a [Presentation Definition](https://identity.foundation/presentation-exchange/#presentation-definition).
2. API returns a URL which can passed to OIDC-compliant wallet to fulfill the request.
If you have provided a success or failure redirect URL, the user will be redirected to that URL. You can then access the
verification results by using the id of the verification session, which can be found in the URL generated by the API, as
well as in the query or path parameters of the redirect URL.
### Example Verification Request 1 - Basic
For this example, we will only do a simple verification, where we request one credential type, a University Degree, and
let the system apply the default signature policy to verify the validity of the signature of the presented credential.
Optionally we can provide a success and failure redirect URL, which the user will be redirected to after the
verification.
**Option: CURL**
```bash
curl -X 'POST' \
'http://0.0.0.0:7003/openid4vc/verify' \
-H 'accept: */*' \
-H 'authorizeBaseUrl: openid4vp://authorize' \
-H 'responseMode: direct_post' \
-H 'successRedirectUri: https://example.com/success?id=$id' \
-H 'errorRedirectUri: https://example.com/error?id=$id' \
-H 'statusCallbackUri: https://example.com/verificationResult' \
-H 'statusCallbackApiKey: myAPIKey' \
-H 'stateId: myUniqueStateValue' \
-H 'Content-Type: application/json' \
-d '{
"request_credentials": [
{ "type": "UniversityDegree", "format": "jwt_vc_json" }
]
}'
```
**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 here
`https://wallet.demo.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](#verification-status-policies-response)
- **statusCallbackApiKey** (optional) - If the endpoint you provide via `statusCallbackUri` is protected, you can use
the `statusCallbackApiKey` 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](https://docs.walt.id/community-stack/verifier/ecosystems/ebsi/overview.md).
- `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:
1. **W3C JWT Credential**
- **type** Specifies the type of credential (e.g., `VerifiableID`, `VerifiableDiploma`). This maps to the `type`
attribute in W3C credentials.
- **format**: Describes the format of the credential. For W3C JWT credentials, this would
be `jwt_vc_json`.
```json
{ "type": "ProofOfResidence", "format": "jwt_vc_json" }
```
2. **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`.
```json
{ "vct": "test.com/identity_credential", "format": "vc+sd-jwt" }
```
3. **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`.
```json
{ "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 optional `policies` and `id`
attribute
- **policies**: An array of policies to apply to the specified credential. A list of all policies can be
found [here](https://docs.walt.id/community-stack/verifier/credential-verification/policies/overview.md).
- **id**: Used to set a specific id for the
generated [Presentation Definition](https://identity.foundation/presentation-exchange/spec/v1.0.0/#presentation-definition).
If not set,
the verifier API auto-assigns a generated one.
**Full Examples**
```json
{
"type": "ProofOfResidence",
"format": "jwt_vc_json",
"policies": [
"schema",
{
"policy": "webhook",
"args": "https://example.org/abc/xyz"
}
],
"id": "test123"
}
```
### Example Verification Request 2 - Defining VC/VP Policies
In this example, we not only specify the types of credentials to verify, but also define the policies that should be
executed during verification:
- **VC policies (vc_policies)**: Applied to individual Verifiable Credentials (VCs).
- **VP policies (vp_policies)**: Applied to the Verifiable Presentation (VP) as a whole.
- **Default behavior**: The `signature` policy is applied by default to both VP and VC(s).
- **Policy catalog**: A list of available policies can be found
[here](https://docs.walt.id/community-stack/verifier/credential-verification/policies/overview.md).
How VC policies are applied:
- **Global scope**: Once defined, VC policies are applied globally to all requested credentials in the verification
session.
- **Per-credential override**: A later section shows how to customize and apply specific policies to individual
credentials when needed.
We specify policies in the verification request alongside the `request_credentials` parameter (previously explained),
using the `vc_policies` and `vp_policies` fields:
- **`vc_policies`** and **`vp_policies`** each contain a list of policies; either one or both can be provided.
- **Identical structure**: Both fields share the same structure.
- **Policy representation**:
- As a **string** if the policy does not require arguments.
- As an **object** if the policy requires arguments.
**Example Polices**
```json
[
"signature",
// policy without argument
"expired",
// policy without argument
"not-before",
// policy without argument
{
"policy": "webhook",
"args": "https://example.org/abc/xyz"
}
// policy with argument
]
```
**Initiation Request**
**Option: CURL**
```bash
curl -X 'POST' \
'http://0.0.0.0:7003/openid4vc/verify' \
-H 'accept: */*' \
-H 'authorizeBaseUrl: openid4vp://authorize' \
-H 'responseMode: direct_post' \
-H 'successRedirectUri: https://example.com/success?id=$id' \
-H 'errorRedirectUri: https://example.com/error?id=$id' \
-H 'statusCallbackUri: https://example.com/verificationResult' \
-H 'statusCallbackApiKey: myAPIKey' \
-H 'stateId: myUniqueStateValue' \
-H 'Content-Type: application/json' \
-d '{
"vp_policies": ["signature", "expired", "not-before"],
"vc_policies": ["signature", "expired", "not-before",
{"policy": "webhook", "args": "https://example.org/abc/xyz"}],
"request_credentials": [
{ "type": "UniversityDegree", "format": "jwt_vc_json" }
]
}'
```
**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 here
`https://wallet.demo.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](#verification-status-policies-response)
- **statusCallbackApiKey** (optional) - If the endpoint you provide via `statusCallbackUri` is protected, you can use
the `statusCallbackApiKey` 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](https://docs.walt.id/community-stack/verifier/ecosystems/ebsi/overview.md).
- `ISO_18013_7_MDOC`: For mdoc Openid4VP.
**Body Parameters**
- **vp_policies** - Policies applied to the Verifiable Presentation. A list of policies can be
found [here](https://docs.walt.id/community-stack/verifier/credential-verification/policies/overview.md)
- **vc_policies** - Policies applied to all requested credentials. A list of policies can be
found [here](https://docs.walt.id/community-stack/verifier/credential-verification/policies/overview.md).
- **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:
1. **W3C JWT Credential**
- **type** Specifies the type of credential (e.g., `VerifiableID`, `VerifiableDiploma`). This maps to the `type`
attribute in W3C credentials.
- **format**: Describes the format of the credential. For W3C JWT credentials, this would
be `jwt_vc_json`.
```json
{ "type": "ProofOfResidence", "format": "jwt_vc_json" }
```
2. **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`.
```json
{ "vct": "test.com/identity_credential", "format": "vc+sd-jwt" }
```
3. **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`.
```json
{ "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 optional `policies` and `id`
attribute
- **policies**: An array of policies to apply to the specified credential. A list of all policies can be
found [here](https://docs.walt.id/community-stack/verifier/credential-verification/policies/overview.md).
- **id**: Used to set a specific id for the
generated [Presentation Definition](https://identity.foundation/presentation-exchange/spec/v1.0.0/#presentation-definition).
If not set,
the verifier API auto-assigns a generated one.
**Full Examples**
```json
{
"type": "ProofOfResidence",
"format": "jwt_vc_json",
"policies": [
"schema",
{
"policy": "webhook",
"args": "https://example.org/abc/xyz"
}
],
"id": "test123"
}
```
Upon execution, the system generates a URL, which can be shared directly with users or displayed as a QR code.
### Example Verification Request 3: Applying Policies to Specific Credentials
In this example, which builds up on the previous ones, we'll demonstrate how to apply specific policies to a distinct
credential in the verification process. This is achieved by specifying next to the required credential type and format,
the policies that should be applied as an array in the credential specification object which goes into the
`requested_credentials` array. A list of supported policies can be
found [here](https://docs.walt.id/community-stack/verifier/credential-verification/policies/overview.md).
Here's how it looks in practice:
```json
{
"vp_policies": [
"signature",
"expired"
],
"vc_policies": [
"signature",
"expired"
],
"request_credentials": [
{
"type": "VerifiableId",
"format": "jwt_vc_json"
},
{
"type": "ProofOfResidence",
"format": "jwt_vc_json"
},
{
"type": "OpenBadgeCredential",
"format": "jwt_vc_json",
"policies": [
"signature",
{
"policy": "webhook",
"args": "https://example.org/abc/xyz"
}
]
}
]
}
```
In this code snippet, we extended the regular credential specification object that goes into `request_credentials`
to include policies that should be applied only to the "OpenBadgeCredential". The first policy "signature" doesn't
require any arguments and can therefore be provided as a string. The "webhook" policy does require an argument and is
therefore provided as an object. A list of different types of policies and their required arguments can be
found [here](https://docs.walt.id/community-stack/verifier/credential-verification/policies/overview.md).
Meanwhile, the other requested credentials, "VerifiableId" and "ProofOfResidence", don't specify and custom policies,
meaning they will be verified against the globally defined "vp_policies" and "vc_policies".
**Initiation Request**
**Option: CURL**
```bash
curl -X 'POST' \
'http://0.0.0.0:7003/openid4vc/verify' \
-H 'accept: */*' \
-H 'authorizeBaseUrl: openid4vp://authorize' \
-H 'responseMode: direct_post' \
-H 'successRedirectUri: https://example.com/success?id=$id' \
-H 'errorRedirectUri: https://example.com/error?id=$id' \
-H 'statusCallbackUri: https://example.com/verificationResult' \
-H 'statusCallbackApiKey: myAPIKey' \
-H 'stateId: myUniqueStateValue' \
-H 'Content-Type: application/json' \
-d '{
"vp_policies": [
"signature",
"expired"
],
"vc_policies": [
"signature",
"expired"
],
"request_credentials": [
{ "type": "VerifiableId", "format": "jwt_vc_json" },
{ "type": "ProofOfResidence", "format": "jwt_vc_json" },
{
"type": "OpenBadgeCredential",
"format": "jwt_vc_json"
"policies": [
"signature",
{
"policy": "webhook",
"args": "https://example.org/abc/xyz"
}
]
}
]
}'
```
**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 here
`https://wallet.demo.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](#verification-status-policies-response)
- **statusCallbackApiKey** (optional) - If the endpoint you provide via `statusCallbackUri` is protected, you can use
the `statusCallbackApiKey` 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](https://docs.walt.id/community-stack/verifier/ecosystems/ebsi/overview.md).
- `ISO_18013_7_MDOC`: For mdoc Openid4VP.
**Body Parameters**
- **vp_policies** - Policies applied to the Verifiable Presentation. A list of policies can be
found [here](https://docs.walt.id/community-stack/verifier/credential-verification/policies/overview.md)
- **vc_policies** - Policies applied to all requested credentials. A list of policies can be
found [here](https://docs.walt.id/community-stack/verifier/credential-verification/policies/overview.md).
- **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:
1. **W3C JWT Credential**
- **type** Specifies the type of credential (e.g., `VerifiableID`, `VerifiableDiploma`). This maps to the `type`
attribute in W3C credentials.
- **format**: Describes the format of the credential. For W3C JWT credentials, this would
be `jwt_vc_json`.
```json
{ "type": "ProofOfResidence", "format": "jwt_vc_json" }
```
2. **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`.
```json
{ "vct": "test.com/identity_credential", "format": "vc+sd-jwt" }
```
3. **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`.
```json
{ "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 optional `policies` and `id`
attribute
- **policies**: An array of policies to apply to the specified credential. A list of all policies can be
found [here](https://docs.walt.id/community-stack/verifier/credential-verification/policies/overview.md).
- **id**: Used to set a specific id for the
generated [Presentation Definition](https://identity.foundation/presentation-exchange/spec/v1.0.0/#presentation-definition).
If not set,
the verifier API auto-assigns a generated one.
**Full Examples**
```json
{
"type": "ProofOfResidence",
"format": "jwt_vc_json",
"policies": [
"schema",
{
"policy": "webhook",
"args": "https://example.org/abc/xyz"
}
],
"id": "test123"
}
```
Upon execution, the system generates a URL, which can be shared directly with users or displayed as a QR code.
### Example Verification Request 4: Implementing a Custom Presentation Definition
In this example, we'll explore how you can provide your own `input_descriptor` that will be merged with the autogenerated
presentation definition.
You can specify your custom `input_descriptor` for the [presentation definition](https://identity.foundation/presentation-exchange/#presentation-definition)
via an object in the `request_credentials` array, see example blow.
**Note:**
Important: Please also provide an id in the `input_descriptor` object.
**Example**
```json
{
"vp_policies": [
...
],
"vc_policies": [
...
],
"request_credentials": [
{
"type": "VerifiableId",
"format": "jwt_vc_json"
},
{
"format": "jwt_vc_json",
"type": "OpenBadgeCredential",
"input_descriptor": {
"id": "234",
"constraints": {
"fields": [
{
"path": [
"$.name"
],
"filter": {
"type": "string",
"pattern": ".*"
}
}
]
}
}
}
]
}
```
On execution the verifier API will auto-generate the input descriptor for the `VerifiableId` credential that we specified via
the regular object and merge it with our custom input descriptor of the `identity_credential` to form the final presentation definition.
**Note:**
When using the `input_descriptor` make sure to also provide the `presentation-definition` policy in the
`vp_policies` array to ensure the constraints specified are checked during verification.
### Example Verification Request 5: Using Relational Constraints
The `presentation-definition` policy also supports the
[relational constraint feature](https://identity.foundation/presentation-exchange/#relational-constraint-feature).
It allows you to express relations between issuer, holder and multiple credentials
directly within the `input_descriptor`.
#### subject_is_issuer
Use this constraint when you want to ensure that the credential was self issued
by the presenter. The verification will only succeed if the issuer DID matches the credential subject.
```json
{
"vp_policies": ["presentation-definition"],
"request_credentials": [
{
"input_descriptor": {
"id": "OpenBadgeCredential",
"format": { "jwt_vc_json": { "alg": ["EdDSA"] } },
"constraints": {
"subject_is_issuer": "required",
"fields": [
{
"path": ["$.vc.type"],
"filter": { "type": "string", "pattern": "OpenBadgeCredential" }
}
]
}
}
}
]
}
```
#### is_holder
The `is_holder` option checks that a credential was issued to the same entity
that is presenting the verifiable presentation. Use it to bind specific fields
to the holder's DID.
```json
{
"vp_policies": ["presentation-definition"],
"request_credentials": [
{
"input_descriptor": {
"id": "OpenBadgeCredential",
"format": { "jwt_vc_json": { "alg": ["EdDSA"] } },
"constraints": {
"is_holder": [
{ "field_id": ["achname"], "directive": "required" }
],
"fields": [
{
"path": ["$.vc.type"],
"filter": { "type": "string", "pattern": "OpenBadgeCredential" }
},
{
"id": "achname",
"path": ["$.vc.credentialSubject.achievement.name"]
}
]
}
}
}
]
}
```
#### same_subject
Use `same_subject` when multiple credentials should refer to the same
underlying subject. The verifier compares the listed fields and only accepts the
presentation if they share an identical subject DID.
```json
{
"vp_policies": ["presentation-definition"],
"request_credentials": [
{
"input_descriptor": {
"id": "OpenBadgeCredential",
"format": { "jwt_vc_json": { "alg": ["EdDSA"] } },
"constraints": {
"same_subject": [
{ "field_id": ["obc_achievement", "udc_degree"], "directive": "required" }
],
"fields": [
{
"path": ["$.vc.type"],
"filter": { "type": "string", "pattern": "OpenBadgeCredential" }
},
{
"id": "obc_achievement",
"path": ["$.vc.credentialSubject.achievement.name"]
}
]
}
}
},
{
"input_descriptor": {
"id": "UniversityDegreeCredential",
"format": { "jwt_vc_json": { "alg": ["EdDSA"] } },
"constraints": {
"fields": [
{
"path": ["$.vc.type"],
"filter": { "type": "string", "pattern": "UniversityDegreeCredential" }
},
{
"id": "udc_degree",
"path": ["$.vc.credentialSubject.degree.name"]
}
]
}
}
}
]
}
```
## Presenting the Credential via walt.id Wallet
Using the URL returned by the verification request, you can fulfill the request using the
[hosted wallet](https://wallet.demo.walt.id) by walt.id.
To present the credential:
1. Open the hosted wallet and click **"Scan to receive or present credentials"** on the credentials overview page in the
top right corner.
2. Choose how to provide the URL:
- **Scan a QR code** that encodes the URL with your device camera.
- **Paste the URL** directly into the text field below the camera.
To learn how to present credentials programmatically using the Wallet API, see [How to Present Digital Credentials via OID4VP](https://docs.walt.id/community-stack/wallet/credential-exchange/guides/present-digital-credentials-oid4vp.md).
## Retrieving the Verification Status
After the user presents the credential(s), you can retrieve the verification result using the `state` value from
the presentation request URL you shared with the user.
**Extracting the `state` value**
```
openid4vp://authorize?...&state=a07bdb17-7d87-4965-9296-1adefcaaddd9&...
```
**Option: CURL**
**Endpoint:** `GET /openid4vc/session/{state}` | [API Reference](https://verifier.demo.walt.id/swagger/index.html#/Verification/get_openid4vc_session__state_)
##### Example Request
```bash
curl -X 'GET' \
'http://0.0.0.0:7003/openid4vc/session/{state}' \
-H 'accept: application/json'
```
**Path Parameters**
- `state`: _String_ - The `state` value extracted from the presentation request URL.
---
##### Example Response
The response contains the status of the verification policies applied to the credential(s) presented by the user.
```json
{
"id": "X31C7TERsFzR",
"presentationDefinition": {
"id": "HbIdo1BMxEwf",
"input_descriptors": [
{
"id": "UniversityDegree",
"format": {
"jwt_vc_json": {
"alg": [
"EdDSA"
]
}
},
"constraints": {
"fields": [
{
"path": [
"$.vc.type"
],
"filter": {
"type": "string",
"pattern": "UniversityDegree"
}
}
]
}
}
],
"customParameters": {}
},
"tokenResponse": {
"vp_token": "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaVRIRlJiMWc1U0RJMk1GVkVXVkpSVlMxaE9HOW1VVlpxZDFWNFIyRk9UMHMxVW5Wc2FYVlVTeTFSYnlJc0luZ2lPaUpwTTBNdGFGOXRaelEyWTNaNVlVWmxaV05aVWtSeVVrSmlaMVp3WlhaaVMzSmxkbWxyVDNCd2FUUTBJbjAjMCIsInR5cCI6IkpXVCIsImFsZyI6IkVkRFNBIn0.eyJzdWIiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaVRIRlJiMWc1U0RJMk1GVkVXVkpSVlMxaE9HOW1VVlpxZDFWNFIyRk9UMHMxVW5Wc2FYVlVTeTFSYnlJc0luZ2lPaUpwTTBNdGFGOXRaelEyWTNaNVlVWmxaV05aVWtSeVVrSmlaMVp3WlhaaVMzSmxkbWxyVDNCd2FUUTBJbjAiLCJuYmYiOjE3NDcwMzQwMDAsImlhdCI6MTc0NzAzNDA2MCwianRpIjoiSGJJZG8xQk14RXdmIiwiaXNzIjoiZGlkOmp3azpleUpyZEhraU9pSlBTMUFpTENKamNuWWlPaUpGWkRJMU5URTVJaXdpYTJsa0lqb2lUSEZSYjFnNVNESTJNRlZFV1ZKUlZTMWhPRzltVVZacWQxVjRSMkZPVDBzMVVuVnNhWFZVU3kxUmJ5SXNJbmdpT2lKcE0wTXRhRjl0WnpRMlkzWjVZVVpsWldOWlVrUnlVa0ppWjFad1pYWmlTM0psZG1sclQzQndhVFEwSW4wIiwibm9uY2UiOiI2ZTZiN2UzYy1lN2ZkLTQxMzYtYWIyNS1iOTUyYWJiNDVmYzUiLCJhdWQiOiJodHRwczovL3ZlcmlmaWVyLmRlbW8ud2FsdC5pZC9vcGVuaWQ0dmMvdmVyaWZ5IiwidnAiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwidHlwZSI6WyJWZXJpZmlhYmxlUHJlc2VudGF0aW9uIl0sImlkIjoiSGJJZG8xQk14RXdmIiwiaG9sZGVyIjoiZGlkOmp3azpleUpyZEhraU9pSlBTMUFpTENKamNuWWlPaUpGWkRJMU5URTVJaXdpYTJsa0lqb2lUSEZSYjFnNVNESTJNRlZFV1ZKUlZTMWhPRzltVVZacWQxVjRSMkZPVDBzMVVuVnNhWFZVU3kxUmJ5SXNJbmdpT2lKcE0wTXRhRjl0WnpRMlkzWjVZVVpsWldOWlVrUnlVa0ppWjFad1pYWmlTM0psZG1sclQzQndhVFEwSW4wIiwidmVyaWZpYWJsZUNyZWRlbnRpYWwiOlsiZXlKcmFXUWlPaUprYVdRNmFuZHJPbVY1U25Ka1NHdHBUMmxLUmxGNVNYTkpiVTU1WkdsSk5rbHNRWFJOYWxVeVNXbDNhV0V5Ykd0SmFtOXBUVEZzVDFwRWJFZGlibWMxVTIxNE5WVkdXbHBrTW1SWVVtdFZlazR3VlhwU00yUktUVWRXU0dKRlRreFBTR1JIWWtaa05GSXlXbmRVVTBselNXNW5hVTlwU2tkaU0xcGFUV3BHVFZGVlJsQldSM2h1VEZjd2RGUnRWa3hXTW1oaFVsVjNNVmxWV25saWJFbDNaRmRPUzJGclVURldSWFIzVWpOV2JrbHBkMmxsVTBrMlNXdE9lVkpyY0cxU01WSnJWVVJKTlZOcmNHcFpNMEpTVjBoV05WUlZPSHBpTW1nd1pXNUtWV05XUWpaUmJFSkRVMVpTV21GcVFuWmFNRVZwWmxFak0xbE9aRGxHYm5nNVNteDVVRlpaZDJkWFJrVXpOMFV6UjNkSk1HVkhiRU5MT0hkR2JGZDRSMlp3VFNJc0luUjVjQ0k2SWtwWFZDSXNJbUZzWnlJNklrVlRNalUySW4wLmV5SnBjM01pT2lKa2FXUTZhbmRyT21WNVNuSmtTR3RwVDJsS1JsRjVTWE5KYlU1NVpHbEpOa2xzUVhSTmFsVXlTV2wzYVdFeWJHdEphbTlwVFRGc1QxcEViRWRpYm1jMVUyMTROVlZHV2xwa01tUllVbXRWZWs0d1ZYcFNNMlJLVFVkV1NHSkZUa3hQU0dSSFlrWmtORkl5V25kVVUwbHpTVzVuYVU5cFNrZGlNMXBhVFdwR1RWRlZSbEJXUjNodVRGY3dkRlJ0Vmt4V01taGhVbFYzTVZsVldubGliRWwzWkZkT1MyRnJVVEZXUlhSM1VqTldia2xwZDJsbFUwazJTV3RPZVZKcmNHMVNNVkpyVlVSSk5WTnJjR3BaTTBKU1YwaFdOVlJWT0hwaU1tZ3daVzVLVldOV1FqWlJiRUpEVTFaU1dtRnFRblphTUVWcFpsRWlMQ0p6ZFdJaU9pSmthV1E2YW5kck9tVjVTbkprU0d0cFQybEtVRk14UVdsTVEwcHFZMjVaYVU5cFNrWmFSRWt4VGxSRk5VbHBkMmxoTW14clNXcHZhVlJJUmxKaU1XYzFVMFJKTWsxR1ZrVlhWa3BTVmxNeGFFOUhPVzFWVmxweFpERldORkl5Ums5VU1ITXhWVzVXYzJGWVZsVlRlVEZTWW5sSmMwbHVaMmxQYVVwd1RUQk5kR0ZHT1hSYWVsRXlXVE5hTlZsVldteGFWMDVhVld0U2VWVnJTbWxhTVZwM1dsaGFhVk16U214a2JXeHlWRE5DZDJGVVVUQkpiakFpTENKMll5STZleUpBWTI5dWRHVjRkQ0k2V3lKb2RIUndjem92TDNkM2R5NTNNeTV2Y21jdk1qQXhPQzlqY21Wa1pXNTBhV0ZzY3k5Mk1TSXNJbWgwZEhCek9pOHZkM2QzTG5jekxtOXlaeTh5TURFNEwyTnlaV1JsYm5ScFlXeHpMMlY0WVcxd2JHVnpMM1l4SWwwc0ltbGtJam9pZFhKdU9uVjFhV1E2TkRNMU1qUmlZbVF0WWpRM09TMDBNMlJrTFdJNU5UY3RZV1EzWVRobE1USTFNamN4SWl3aWRIbHdaU0k2V3lKV1pYSnBabWxoWW14bFEzSmxaR1Z1ZEdsaGJDSXNJbFZ1YVhabGNuTnBkSGxFWldkeVpXVWlYU3dpYVhOemRXVnlJanA3SW1sa0lqb2laR2xrT21wM2F6cGxlVXB5WkVocmFVOXBTa1pSZVVselNXMU9lV1JwU1RaSmJFRjBUV3BWTWtscGQybGhNbXhyU1dwdmFVMHhiRTlhUkd4SFltNW5OVk50ZURWVlJscGFaREprV0ZKclZYcE9NRlY2VWpOa1NrMUhWa2hpUlU1TVQwaGtSMkpHWkRSU01scDNWRk5KYzBsdVoybFBhVXBIWWpOYVdrMXFSazFSVlVaUVZrZDRia3hYTUhSVWJWWk1WakpvWVZKVmR6RlpWVnA1WW14SmQyUlhUa3RoYTFFeFZrVjBkMUl6Vm01SmFYZHBaVk5KTmtsclRubFNhM0J0VWpGU2ExVkVTVFZUYTNCcVdUTkNVbGRJVmpWVVZUaDZZakpvTUdWdVNsVmpWa0kyVVd4Q1ExTldVbHBoYWtKMldqQkZhV1pSSW4wc0ltbHpjM1ZoYm1ObFJHRjBaU0k2SWpJd01qVXRNRFV0TVRKVU1EYzZNVE02TlRFdU9UYzNNek13T0RjeldpSXNJbU55WldSbGJuUnBZV3hUZFdKcVpXTjBJanA3SW1sa0lqb2laR2xrT21wM2F6cGxlVXB5WkVocmFVOXBTbEJUTVVGcFRFTkthbU51V1dsUGFVcEdXa1JKTVU1VVJUVkphWGRwWVRKc2EwbHFiMmxVU0VaU1lqRm5OVk5FU1RKTlJsWkZWMVpLVWxaVE1XaFBSemx0VlZaYWNXUXhWalJTTWtaUFZEQnpNVlZ1Vm5OaFdGWlZVM2t4VW1KNVNYTkpibWRwVDJsS2NFMHdUWFJoUmpsMFducFJNbGt6V2pWWlZWcHNXbGRPV2xWclVubFZhMHBwV2pGYWQxcFlXbWxUTTBwc1pHMXNjbFF6UW5kaFZGRXdTVzR3SWl3aVpHVm5jbVZsSWpwN0luUjVjR1VpT2lKQ1lXTm9aV3h2Y2tSbFozSmxaU0lzSW01aGJXVWlPaUpDWVdOb1pXeHZjaUJ2WmlCVFkybGxibU5sSUdGdVpDQkJjblJ6SW4xOWZTd2lhblJwSWpvaWRYSnVPblYxYVdRNk5ETTFNalJpWW1RdFlqUTNPUzAwTTJSa0xXSTVOVGN0WVdRM1lUaGxNVEkxTWpjeElpd2lhV0YwSWpveE56UTNNRE0wTURNeExDSnVZbVlpT2pFM05EY3dNelF3TXpGOS44eUl0UklvUVRUMm04X0RtVVpCQmNOaURLdHhEU3laaDlWak9UTHVxRmw5bDJlS0ZRMlZZQm5acWNJaU1HRG1hcGJaYWp4ZjM1NURsRjgtdUZ1dHhPdyJdfX0.orHu3ZkOQ1YAU0LuOhThByfZh2ZvvkieePfwlpPuwjOxL4g-EVX6AqT8RBbSpWwIRymnlf397fE7uPNk8DUTAQ",
"presentation_submission": {
"id": "HbIdo1BMxEwf",
"definition_id": "HbIdo1BMxEwf",
"descriptor_map": [
{
"id": "UniversityDegree",
"format": "jwt_vp",
"path": "$",
"path_nested": {
"id": "UniversityDegree",
"format": "jwt_vc_json",
"path": "$.verifiableCredential[0]",
"customParameters": {}
}
}
]
},
"state": "X31C7TERsFzR",
"customParameters": {}
},
"verificationResult": true,
"policyResults": {
"results": [
{
"credential": "VerifiablePresentation",
"policyResults": [
{
"policy": "signature",
"description": "Checks a JWT credential by verifying its cryptographic signature using the key referenced by the DID in `iss`.",
"is_success": true,
"result": {
"sub": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiTHFRb1g5SDI2MFVEWVJRVS1hOG9mUVZqd1V4R2FOT0s1UnVsaXVUSy1RbyIsIngiOiJpM0MtaF9tZzQ2Y3Z5YUZlZWNZUkRyUkJiZ1ZwZXZiS3JldmlrT3BwaTQ0In0",
"nbf": 1747034000,
"iat": 1747034060,
"jti": "HbIdo1BMxEwf",
"iss": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiTHFRb1g5SDI2MFVEWVJRVS1hOG9mUVZqd1V4R2FOT0s1UnVsaXVUSy1RbyIsIngiOiJpM0MtaF9tZzQ2Y3Z5YUZlZWNZUkRyUkJiZ1ZwZXZiS3JldmlrT3BwaTQ0In0",
"nonce": "6e6b7e3c-e7fd-4136-ab25-b952abb45fc5",
"aud": "https://verifier.demo.walt.id/openid4vc/verify",
"vp": {
"@context": [
"https://www.w3.org/2018/credentials/v1"
],
"type": [
"VerifiablePresentation"
],
"id": "HbIdo1BMxEwf",
"holder": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiTHFRb1g5SDI2MFVEWVJRVS1hOG9mUVZqd1V4R2FOT0s1UnVsaXVUSy1RbyIsIngiOiJpM0MtaF9tZzQ2Y3Z5YUZlZWNZUkRyUkJiZ1ZwZXZiS3JldmlrT3BwaTQ0In0",
"verifiableCredential": [
"eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pTTFsT1pEbEdibmc1U214NVVGWlpkMmRYUmtVek4wVXpSM2RKTUdWSGJFTkxPSGRHYkZkNFIyWndUU0lzSW5naU9pSkdiM1paTWpGTVFVRlBWR3huTFcwdFRtVkxWMmhhUlV3MVlVWnlibEl3ZFdOS2FrUTFWRXR3UjNWbklpd2llU0k2SWtOeVJrcG1SMVJrVURJNVNrcGpZM0JSV0hWNVRVOHpiMmgwZW5KVWNWQjZRbEJDU1ZSWmFqQnZaMEVpZlEjM1lOZDlGbng5Smx5UFZZd2dXRkUzN0UzR3dJMGVHbENLOHdGbFd4R2ZwTSIsInR5cCI6IkpXVCIsImFsZyI6IkVTMjU2In0.eyJpc3MiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pTTFsT1pEbEdibmc1U214NVVGWlpkMmRYUmtVek4wVXpSM2RKTUdWSGJFTkxPSGRHYkZkNFIyWndUU0lzSW5naU9pSkdiM1paTWpGTVFVRlBWR3huTFcwdFRtVkxWMmhhUlV3MVlVWnlibEl3ZFdOS2FrUTFWRXR3UjNWbklpd2llU0k2SWtOeVJrcG1SMVJrVURJNVNrcGpZM0JSV0hWNVRVOHpiMmgwZW5KVWNWQjZRbEJDU1ZSWmFqQnZaMEVpZlEiLCJzdWIiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaVRIRlJiMWc1U0RJMk1GVkVXVkpSVlMxaE9HOW1VVlpxZDFWNFIyRk9UMHMxVW5Wc2FYVlVTeTFSYnlJc0luZ2lPaUpwTTBNdGFGOXRaelEyWTNaNVlVWmxaV05aVWtSeVVrSmlaMVp3WlhaaVMzSmxkbWxyVDNCd2FUUTBJbjAiLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL2V4YW1wbGVzL3YxIl0sImlkIjoidXJuOnV1aWQ6NDM1MjRiYmQtYjQ3OS00M2RkLWI5NTctYWQ3YThlMTI1MjcxIiwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIlVuaXZlcnNpdHlEZWdyZWUiXSwiaXNzdWVyIjp7ImlkIjoiZGlkOmp3azpleUpyZEhraU9pSkZReUlzSW1OeWRpSTZJbEF0TWpVMklpd2lhMmxrSWpvaU0xbE9aRGxHYm5nNVNteDVVRlpaZDJkWFJrVXpOMFV6UjNkSk1HVkhiRU5MT0hkR2JGZDRSMlp3VFNJc0luZ2lPaUpHYjNaWk1qRk1RVUZQVkd4bkxXMHRUbVZMVjJoYVJVdzFZVVp5YmxJd2RXTktha1ExVkV0d1IzVm5JaXdpZVNJNklrTnlSa3BtUjFSa1VESTVTa3BqWTNCUldIVjVUVTh6YjJoMGVuSlVjVkI2UWxCQ1NWUlphakJ2WjBFaWZRIn0sImlzc3VhbmNlRGF0ZSI6IjIwMjUtMDUtMTJUMDc6MTM6NTEuOTc3MzMwODczWiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmp3azpleUpyZEhraU9pSlBTMUFpTENKamNuWWlPaUpGWkRJMU5URTVJaXdpYTJsa0lqb2lUSEZSYjFnNVNESTJNRlZFV1ZKUlZTMWhPRzltVVZacWQxVjRSMkZPVDBzMVVuVnNhWFZVU3kxUmJ5SXNJbmdpT2lKcE0wTXRhRjl0WnpRMlkzWjVZVVpsWldOWlVrUnlVa0ppWjFad1pYWmlTM0psZG1sclQzQndhVFEwSW4wIiwiZGVncmVlIjp7InR5cGUiOiJCYWNoZWxvckRlZ3JlZSIsIm5hbWUiOiJCYWNoZWxvciBvZiBTY2llbmNlIGFuZCBBcnRzIn19fSwianRpIjoidXJuOnV1aWQ6NDM1MjRiYmQtYjQ3OS00M2RkLWI5NTctYWQ3YThlMTI1MjcxIiwiaWF0IjoxNzQ3MDM0MDMxLCJuYmYiOjE3NDcwMzQwMzF9.8yItRIoQTT2m8_DmUZBBcNiDKtxDSyZh9VjOTLuqFl9l2eKFQ2VYBnZqcIiMGDmapbZajxf355DlF8-uFutxOw"
]
}
}
}
]
},
{
"credential": "UniversityDegree",
"policyResults": [
{
"policy": "signature",
"description": "Checks a JWT credential by verifying its cryptographic signature using the key referenced by the DID in `iss`.",
"is_success": true,
"result": {
"iss": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiM1lOZDlGbng5Smx5UFZZd2dXRkUzN0UzR3dJMGVHbENLOHdGbFd4R2ZwTSIsIngiOiJGb3ZZMjFMQUFPVGxnLW0tTmVLV2haRUw1YUZyblIwdWNKakQ1VEtwR3VnIiwieSI6IkNyRkpmR1RkUDI5SkpjY3BRWHV5TU8zb2h0enJUcVB6QlBCSVRZajBvZ0EifQ",
"sub": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiTHFRb1g5SDI2MFVEWVJRVS1hOG9mUVZqd1V4R2FOT0s1UnVsaXVUSy1RbyIsIngiOiJpM0MtaF9tZzQ2Y3Z5YUZlZWNZUkRyUkJiZ1ZwZXZiS3JldmlrT3BwaTQ0In0",
"vc": {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1"
],
"id": "urn:uuid:43524bbd-b479-43dd-b957-ad7a8e125271",
"type": [
"VerifiableCredential",
"UniversityDegree"
],
"issuer": {
"id": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiM1lOZDlGbng5Smx5UFZZd2dXRkUzN0UzR3dJMGVHbENLOHdGbFd4R2ZwTSIsIngiOiJGb3ZZMjFMQUFPVGxnLW0tTmVLV2haRUw1YUZyblIwdWNKakQ1VEtwR3VnIiwieSI6IkNyRkpmR1RkUDI5SkpjY3BRWHV5TU8zb2h0enJUcVB6QlBCSVRZajBvZ0EifQ"
},
"issuanceDate": "2025-05-12T07:13:51.977330873Z",
"credentialSubject": {
"id": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiTHFRb1g5SDI2MFVEWVJRVS1hOG9mUVZqd1V4R2FOT0s1UnVsaXVUSy1RbyIsIngiOiJpM0MtaF9tZzQ2Y3Z5YUZlZWNZUkRyUkJiZ1ZwZXZiS3JldmlrT3BwaTQ0In0",
"degree": {
"type": "BachelorDegree",
"name": "Bachelor of Science and Arts"
}
}
},
"jti": "urn:uuid:43524bbd-b479-43dd-b957-ad7a8e125271",
"iat": 1747034031,
"nbf": 1747034031
}
}
]
}
],
"time": "PT0.013413451S",
"policiesRun": 2
},
"customParameters": {}
}
```
**Response Fields**
- `verificationResult`: _Boolean_ - `true` if all policies were successful, otherwise `false`.
- `policyResults`: _Object_ - Container with the results of the policies applied to each credential.
- `credential`: _String_ - Name of the credential that a given set of policy results refers to.
- `policies`: _Array_ - Collection of individual policy evaluation results for the credential.
- `policy`: _String_ - Name of an individual policy.
- `is_success`: _Boolean_ - Result of the individual policy evaluation.
## Inspecting Presented Credentials
After a successful presentation session (`verificationResult` is `true`), you can get a decoded and formatted
view of all credentials presented by the holder.
### University Degree Without Any Disclosable Claims
In the following we provide an example that involves the presentation of a University Degree W3C Verifiable Credential
without any disclosable claims.
**Option: Simple**
**Endpoint:** `GET /openid4vc/session/{id}/presented-credentials` | [API Reference](https://verifier.demo.walt.id/swagger/index.html#/Credential%20Verification/get_openid4vc_session__id__presented_credentials)
##### Example Request
```bash
curl -X 'GET' \
'http://0.0.0.0:7003/openid4vc/session/{id}/presented-credentials' \
-H 'accept: application/json'
```
**Path Parameters**
- `id`: _String_ - The identifier of the presentation session whose credentials should be retrieved.
**Query Parameters**
- `viewMode`: _String (optional)_ - Controls how detailed the response will be. Accepted values:
- `simple` _(default)_ — Returns decoded `header` and `payload` for each credential. The go-to choice for most cases.
- `verbose` — Returns the full compact JWT (`raw`), decoded headers, full and undisclosed payloads, and selective disclosure maps. Useful for debugging and auditing.
---
##### Example Response
**Response Fields**
- `viewMode`: _String_ - Echoed request value (or the default if not specified).
- `credentialsByFormat`: _Object_ - A map where each key is a presentation format (`jwt_vc_json`, `sd_jwt_vc`, `mso_mdoc`) and the value is an array of decoded presented credentials. Each entry contains:
- `holder`: _String_ - The `holder` property of the W3C verifiable presentation.
- `verifiableCredentials`: _Array_ - Decoded credentials included in the presentation, each providing JWT `header` and `payload` objects.
```json
{
"credentialsByFormat": {
"jwt_vc_json": [
{
"type": "jwt_vc_json_view_simple",
"holder": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiZXY2a3BuS0l6akRLRTFZcXRONDJIMk9VMmxVZWVKamVpVmh4VEpRdVVJYyIsIngiOiJCdmt3bzh0Y0hLN3RsTy1lWUNZRjZLakNxTXNMeHdoTVJOQ3VtX0tJZUk4IiwieSI6IkQ3czc0WV9wdXVGdXJWNlJtWUtHRUpaTzlWdU1ycy1OdlNGeHdMd01SVVUifQ",
"verifiableCredentials": [
{
"header": {
"kid": "did:key:zDnaeqgdw7qN5J8qZbwo8PCu18he2bK7PSHfaQEhmTw4xrDCC#zDnaeqgdw7qN5J8qZbwo8PCu18he2bK7PSHfaQEhmTw4xrDCC",
"typ": "JWT",
"alg": "ES256"
},
"payload": {
"iss": "did:key:zDnaeqgdw7qN5J8qZbwo8PCu18he2bK7PSHfaQEhmTw4xrDCC",
"sub": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiZXY2a3BuS0l6akRLRTFZcXRONDJIMk9VMmxVZWVKamVpVmh4VEpRdVVJYyIsIngiOiJCdmt3bzh0Y0hLN3RsTy1lWUNZRjZLakNxTXNMeHdoTVJOQ3VtX0tJZUk4IiwieSI6IkQ3czc0WV9wdXVGdXJWNlJtWUtHRUpaTzlWdU1ycy1OdlNGeHdMd01SVVUifQ",
"vc": {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1"
],
"id": "urn:uuid:ca31b7d7-2db6-458e-b776-c6213ee82190",
"type": [
"VerifiableCredential",
"UniversityDegree"
],
"issuer": {
"id": "did:key:zDnaeqgdw7qN5J8qZbwo8PCu18he2bK7PSHfaQEhmTw4xrDCC"
},
"issuanceDate": "2025-07-16T06:38:06.202581908Z",
"credentialSubject": {
"id": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiZXY2a3BuS0l6akRLRTFZcXRONDJIMk9VMmxVZWVKamVpVmh4VEpRdVVJYyIsIngiOiJCdmt3bzh0Y0hLN3RsTy1lWUNZRjZLakNxTXNMeHdoTVJOQ3VtX0tJZUk4IiwieSI6IkQ3czc0WV9wdXVGdXJWNlJtWUtHRUpaTzlWdU1ycy1OdlNGeHdMd01SVVUifQ",
"degree": {
"type": "BachelorDegree",
"name": "Bachelor of Science and Arts"
}
},
"issuerDid": "did:key:zDnaeqgdw7qN5J8qZbwo8PCu18he2bK7PSHfaQEhmTw4xrDCC",
"expirationDate": "2026-07-16T06:38:06.202607447Z"
},
"jti": "urn:uuid:ca31b7d7-2db6-458e-b776-c6213ee82190",
"exp": 1784183886,
"iat": 1752647886,
"nbf": 1752647886
}
}
]
}
]
},
"viewMode": "simple"
}
```
**Option: Verbose**
**Endpoint:** `GET /openid4vc/session/{id}/presented-credentials` | [API Reference](https://verifier.demo.walt.id/swagger/index.html#/Credential%20Verification/get_openid4vc_session__id__presented_credentials)
##### Example Request
```bash
curl -X 'GET' \
'http://0.0.0.0:7003/openid4vc/session/{id}/presented-credentials?viewMode=verbose' \
-H 'accept: application/json'
```
**Path Parameters**
- `id`: _String_ - The identifier of the presentation session whose credentials should be retrieved.
**Query Parameters**
- `viewMode`: _String (optional)_ - Controls how detailed the response will be. Accepted values:
- `simple` _(default)_ — Returns decoded `header` and `payload` for each credential. The go-to choice for most cases.
- `verbose` — Returns the full compact JWT (`raw`), decoded headers, full and undisclosed payloads, and selective disclosure maps. Useful for debugging and auditing.
---
##### Example Response
**Response Fields**
- `viewMode`: _String_ - Echoed request value (or the default if not specified).
- `credentialsByFormat`: _Object_ - A map where each key is a presentation format (`jwt_vc_json`, `sd_jwt_vc`, `mso_mdoc`) and the value is an array of decoded presented credentials. Each entry contains:
- `vp`: _Object_ - Verbosely decoded verifiable presentation containing `raw` (compact JWT), `header`, and `payload`.
- `verifiableCredentials`: _Array_ - Verbose decoded credentials, each containing:
- `raw`: _String_ - The compact serialized verifiable credential JWT.
- `header`: _Object_ - The JWT header of the verifiable credential.
- `fullPayload`: _Object_ - JWT payload with all disclosures substituted.
- `undisclosedPayload`: _Object_ - JWT payload without any disclosures substituted.
- `disclosures`: _Object (optional)_ - Map of hashed selective disclosure values to their decoded representation. Only present when disclosable claims were part of the presentation.
```json
{
"credentialsByFormat": {
"jwt_vc_json": [
{
"type": "jwt_vc_json_view_verbose",
"vp": {
"raw": "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pYUVoV2R6ZzVYMVl6TkVnelUxcG9VbnBPTkZCU2RHaElXa000VmpGWVVHbE1WbWR4ZFRSMmJHdFVieUlzSW5naU9pSlRUbTlEYWpVeWQwSnpYMHBoZHpSSGFrb3hRV2wwVld0VlNIZGZaemMwYlc5UVdUQnlXbXBHYzBwSklpd2llU0k2SWpBeldXNUVjVGhzTjNKblZVVlpUMEpOUm1sbU5YRmFUbEprU0VoSWVVNVRiMGN0TlRCUFZXWndjSGNpZlEjMCIsInR5cCI6IkpXVCIsImFsZyI6IkVTMjU2In0.eyJzdWIiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pYUVoV2R6ZzVYMVl6TkVnelUxcG9VbnBPTkZCU2RHaElXa000VmpGWVVHbE1WbWR4ZFRSMmJHdFVieUlzSW5naU9pSlRUbTlEYWpVeWQwSnpYMHBoZHpSSGFrb3hRV2wwVld0VlNIZGZaemMwYlc5UVdUQnlXbXBHYzBwSklpd2llU0k2SWpBeldXNUVjVGhzTjNKblZVVlpUMEpOUm1sbU5YRmFUbEprU0VoSWVVNVRiMGN0TlRCUFZXWndjSGNpZlEiLCJuYmYiOjE3NTI2NDgxNDIsImlhdCI6MTc1MjY0ODIwMiwianRpIjoibTJHdmNRU0dyZUpFIiwiaXNzIjoiZGlkOmp3azpleUpyZEhraU9pSkZReUlzSW1OeWRpSTZJbEF0TWpVMklpd2lhMmxrSWpvaWFFaFdkemc1WDFZek5FZ3pVMXBvVW5wT05GQlNkR2hJV2tNNFZqRllVR2xNVm1keGRUUjJiR3RVYnlJc0luZ2lPaUpUVG05RGFqVXlkMEp6WDBwaGR6Ukhha294UVdsMFZXdFZTSGRmWnpjMGJXOVFXVEJ5V21wR2MwcEpJaXdpZVNJNklqQXpXVzVFY1Roc04zSm5WVVZaVDBKTlJtbG1OWEZhVGxKa1NFaEllVTVUYjBjdE5UQlBWV1p3Y0hjaWZRIiwibm9uY2UiOiJiZmJhMjZlYS1jNGQxLTRiZTUtODI1OC1jNWVkODhjZDQwYzIiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjIyMjIyL29wZW5pZDR2Yy92ZXJpZnkiLCJ2cCI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVQcmVzZW50YXRpb24iXSwiaWQiOiJtMkd2Y1FTR3JlSkUiLCJob2xkZXIiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pYUVoV2R6ZzVYMVl6TkVnelUxcG9VbnBPTkZCU2RHaElXa000VmpGWVVHbE1WbWR4ZFRSMmJHdFVieUlzSW5naU9pSlRUbTlEYWpVeWQwSnpYMHBoZHpSSGFrb3hRV2wwVld0VlNIZGZaemMwYlc5UVdUQnlXbXBHYzBwSklpd2llU0k2SWpBeldXNUVjVGhzTjNKblZVVlpUMEpOUm1sbU5YRmFUbEprU0VoSWVVNVRiMGN0TlRCUFZXWndjSGNpZlEiLCJ2ZXJpZmlhYmxlQ3JlZGVudGlhbCI6WyJleUpyYVdRaU9pSmthV1E2YTJWNU9ucEVibUZsVnpkd09WRkZjM1oxZEVORmNGZGxkR2R5WTNWVWQweG9WbUpOYlRsSVZFUlZSVkJVYWtvMWVYWmFPV0lqZWtSdVlXVlhOM0E1VVVWemRuVjBRMFZ3VjJWMFozSmpkVlIzVEdoV1lrMXRPVWhVUkZWRlVGUnFTalY1ZGxvNVlpSXNJblI1Y0NJNklrcFhWQ0lzSW1Gc1p5STZJa1ZUTWpVMkluMC5leUpwYzNNaU9pSmthV1E2YTJWNU9ucEVibUZsVnpkd09WRkZjM1oxZEVORmNGZGxkR2R5WTNWVWQweG9WbUpOYlRsSVZFUlZSVkJVYWtvMWVYWmFPV0lpTENKemRXSWlPaUprYVdRNmFuZHJPbVY1U25Ka1NHdHBUMmxLUmxGNVNYTkpiVTU1WkdsSk5rbHNRWFJOYWxVeVNXbDNhV0V5Ykd0SmFtOXBZVVZvVjJSNlp6VllNVmw2VGtWbmVsVXhjRzlWYm5CUFRrWkNVMlJIYUVsWGEwMDBWbXBHV1ZWSGJFMVdiV1I0WkZSU01tSkhkRlZpZVVselNXNW5hVTlwU2xSVWJUbEVZV3BWZVdRd1NucFlNSEJvWkhwU1NHRnJiM2hSVjJ3d1ZsZDBWbE5JWkdaYWVtTXdZbGM1VVZkVVFubFhiWEJIWXpCd1NrbHBkMmxsVTBrMlNXcEJlbGRYTlVWalZHaHpUak5LYmxaVlZscFVNRXBPVW0xc2JVNVlSbUZVYkVwclUwVm9TV1ZWTlZSaU1HTjBUbFJDVUZaWFduZGpTR05wWmxFaUxDSjJZeUk2ZXlKQVkyOXVkR1Y0ZENJNld5Sm9kSFJ3Y3pvdkwzZDNkeTUzTXk1dmNtY3ZNakF4T0M5amNtVmtaVzUwYVdGc2N5OTJNU0lzSW1oMGRIQnpPaTh2ZDNkM0xuY3pMbTl5Wnk4eU1ERTRMMk55WldSbGJuUnBZV3h6TDJWNFlXMXdiR1Z6TDNZeElsMHNJbWxrSWpvaWRYSnVPblYxYVdRNk1UZzVaREZrWVRndFlXUTJaUzAwTnpjeExXSmhORE10T0dOaVlqQTJZbU5sTkRCbElpd2lkSGx3WlNJNld5SldaWEpwWm1saFlteGxRM0psWkdWdWRHbGhiQ0lzSWxWdWFYWmxjbk5wZEhsRVpXZHlaV1VpWFN3aWFYTnpkV1Z5SWpwN0ltbGtJam9pWkdsa09tdGxlVHA2Ukc1aFpWYzNjRGxSUlhOMmRYUkRSWEJYWlhSbmNtTjFWSGRNYUZaaVRXMDVTRlJFVlVWUVZHcEtOWGwyV2psaUluMHNJbWx6YzNWaGJtTmxSR0YwWlNJNklqSXdNalV0TURjdE1UWlVNRFk2TkRNNk1qRXVOelExTURBek5UZ3dXaUlzSW1OeVpXUmxiblJwWVd4VGRXSnFaV04wSWpwN0ltbGtJam9pWkdsa09tcDNhenBsZVVweVpFaHJhVTlwU2taUmVVbHpTVzFPZVdScFNUWkpiRUYwVFdwVk1rbHBkMmxoTW14clNXcHZhV0ZGYUZka2VtYzFXREZaZWs1RlozcFZNWEJ2Vlc1d1QwNUdRbE5rUjJoSlYydE5ORlpxUmxsVlIyeE5WbTFrZUdSVVVqSmlSM1JWWW5sSmMwbHVaMmxQYVVwVVZHMDVSR0ZxVlhsa01FcDZXREJ3YUdSNlVraGhhMjk0VVZkc01GWlhkRlpUU0dSbVducGpNR0pYT1ZGWFZFSjVWMjF3UjJNd2NFcEphWGRwWlZOSk5rbHFRWHBYVnpWRlkxUm9jMDR6U201V1ZWWmFWREJLVGxKdGJHMU9XRVpoVkd4S2ExTkZhRWxsVlRWVVlqQmpkRTVVUWxCV1YxcDNZMGhqYVdaUklpd2laR1ZuY21WbElqcDdJblI1Y0dVaU9pSkNZV05vWld4dmNrUmxaM0psWlNJc0ltNWhiV1VpT2lKQ1lXTm9aV3h2Y2lCdlppQlRZMmxsYm1ObElHRnVaQ0JCY25SekluMTlMQ0pwYzNOMVpYSkVhV1FpT2lKa2FXUTZhMlY1T25wRWJtRmxWemR3T1ZGRmMzWjFkRU5GY0ZkbGRHZHlZM1ZVZDB4b1ZtSk5iVGxJVkVSVlJWQlVha28xZVhaYU9XSWlMQ0psZUhCcGNtRjBhVzl1UkdGMFpTSTZJakl3TWpZdE1EY3RNVFpVTURZNk5ETTZNakV1TnpRMU1ESXlOek0yV2lKOUxDSnFkR2tpT2lKMWNtNDZkWFZwWkRveE9EbGtNV1JoT0MxaFpEWmxMVFEzTnpFdFltRTBNeTA0WTJKaU1EWmlZMlUwTUdVaUxDSmxlSEFpT2pFM09EUXhPRFF5TURFc0ltbGhkQ0k2TVRjMU1qWTBPREl3TVN3aWJtSm1Jam94TnpVeU5qUTRNakF4ZlEuWHRpYVg4SGdxLTE2dURhWGhVaGV6Sk00QWxEVHF6UWh6ZFBaaGQxbmlhS0lyZU4xVFlDRTFlQ3NoWWZ0VTdFcVdLUU1RcGkxeE54QzdlcWo5UEtWbVEiXX19.Xm0c6E7tSLvr0EJuNN3rJMaYDsYXJFp3U1rTU5X8aYp4ofiZc-ps9Lu-J1FR5lYCafZVxEKuzRrbdSGkKA3vtw",
"header": {
"kid": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiaEhWdzg5X1YzNEgzU1poUnpONFBSdGhIWkM4VjFYUGlMVmdxdTR2bGtUbyIsIngiOiJTTm9DajUyd0JzX0phdzRHakoxQWl0VWtVSHdfZzc0bW9QWTByWmpGc0pJIiwieSI6IjAzWW5EcThsN3JnVUVZT0JNRmlmNXFaTlJkSEhIeU5Tb0ctNTBPVWZwcHcifQ#0",
"typ": "JWT",
"alg": "ES256"
},
"payload": {
"sub": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiaEhWdzg5X1YzNEgzU1poUnpONFBSdGhIWkM4VjFYUGlMVmdxdTR2bGtUbyIsIngiOiJTTm9DajUyd0JzX0phdzRHakoxQWl0VWtVSHdfZzc0bW9QWTByWmpGc0pJIiwieSI6IjAzWW5EcThsN3JnVUVZT0JNRmlmNXFaTlJkSEhIeU5Tb0ctNTBPVWZwcHcifQ",
"nbf": 1752648142,
"iat": 1752648202,
"jti": "m2GvcQSGreJE",
"iss": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiaEhWdzg5X1YzNEgzU1poUnpONFBSdGhIWkM4VjFYUGlMVmdxdTR2bGtUbyIsIngiOiJTTm9DajUyd0JzX0phdzRHakoxQWl0VWtVSHdfZzc0bW9QWTByWmpGc0pJIiwieSI6IjAzWW5EcThsN3JnVUVZT0JNRmlmNXFaTlJkSEhIeU5Tb0ctNTBPVWZwcHcifQ",
"nonce": "bfba26ea-c4d1-4be5-8258-c5ed88cd40c2",
"aud": "http://localhost:22222/openid4vc/verify",
"vp": {
"@context": [
"https://www.w3.org/2018/credentials/v1"
],
"type": [
"VerifiablePresentation"
],
"id": "m2GvcQSGreJE",
"holder": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiaEhWdzg5X1YzNEgzU1poUnpONFBSdGhIWkM4VjFYUGlMVmdxdTR2bGtUbyIsIngiOiJTTm9DajUyd0JzX0phdzRHakoxQWl0VWtVSHdfZzc0bW9QWTByWmpGc0pJIiwieSI6IjAzWW5EcThsN3JnVUVZT0JNRmlmNXFaTlJkSEhIeU5Tb0ctNTBPVWZwcHcifQ",
"verifiableCredential": [
"eyJraWQiOiJkaWQ6a2V5OnpEbmFlVzdwOVFFc3Z1dENFcFdldGdyY3VUd0xoVmJNbTlIVERVRVBUako1eXZaOWIjekRuYWVXN3A5UUVzdnV0Q0VwV2V0Z3JjdVR3TGhWYk1tOUhURFVFUFRqSjV5dlo5YiIsInR5cCI6IkpXVCIsImFsZyI6IkVTMjU2In0.eyJpc3MiOiJkaWQ6a2V5OnpEbmFlVzdwOVFFc3Z1dENFcFdldGdyY3VUd0xoVmJNbTlIVERVRVBUako1eXZaOWIiLCJzdWIiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pYUVoV2R6ZzVYMVl6TkVnelUxcG9VbnBPTkZCU2RHaElXa000VmpGWVVHbE1WbWR4ZFRSMmJHdFVieUlzSW5naU9pSlRUbTlEYWpVeWQwSnpYMHBoZHpSSGFrb3hRV2wwVld0VlNIZGZaemMwYlc5UVdUQnlXbXBHYzBwSklpd2llU0k2SWpBeldXNUVjVGhzTjNKblZVVlpUMEpOUm1sbU5YRmFUbEprU0VoSWVVNVRiMGN0TlRCUFZXWndjSGNpZlEiLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL2V4YW1wbGVzL3YxIl0sImlkIjoidXJuOnV1aWQ6MTg5ZDFkYTgtYWQ2ZS00NzcxLWJhNDMtOGNiYjA2YmNlNDBlIiwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIlVuaXZlcnNpdHlEZWdyZWUiXSwiaXNzdWVyIjp7ImlkIjoiZGlkOmtleTp6RG5hZVc3cDlRRXN2dXRDRXBXZXRncmN1VHdMaFZiTW05SFREVUVQVGpKNXl2WjliIn0sImlzc3VhbmNlRGF0ZSI6IjIwMjUtMDctMTZUMDY6NDM6MjEuNzQ1MDAzNTgwWiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmp3azpleUpyZEhraU9pSkZReUlzSW1OeWRpSTZJbEF0TWpVMklpd2lhMmxrSWpvaWFFaFdkemc1WDFZek5FZ3pVMXBvVW5wT05GQlNkR2hJV2tNNFZqRllVR2xNVm1keGRUUjJiR3RVYnlJc0luZ2lPaUpUVG05RGFqVXlkMEp6WDBwaGR6Ukhha294UVdsMFZXdFZTSGRmWnpjMGJXOVFXVEJ5V21wR2MwcEpJaXdpZVNJNklqQXpXVzVFY1Roc04zSm5WVVZaVDBKTlJtbG1OWEZhVGxKa1NFaEllVTVUYjBjdE5UQlBWV1p3Y0hjaWZRIiwiZGVncmVlIjp7InR5cGUiOiJCYWNoZWxvckRlZ3JlZSIsIm5hbWUiOiJCYWNoZWxvciBvZiBTY2llbmNlIGFuZCBBcnRzIn19LCJpc3N1ZXJEaWQiOiJkaWQ6a2V5OnpEbmFlVzdwOVFFc3Z1dENFcFdldGdyY3VUd0xoVmJNbTlIVERVRVBUako1eXZaOWIiLCJleHBpcmF0aW9uRGF0ZSI6IjIwMjYtMDctMTZUMDY6NDM6MjEuNzQ1MDIyNzM2WiJ9LCJqdGkiOiJ1cm46dXVpZDoxODlkMWRhOC1hZDZlLTQ3NzEtYmE0My04Y2JiMDZiY2U0MGUiLCJleHAiOjE3ODQxODQyMDEsImlhdCI6MTc1MjY0ODIwMSwibmJmIjoxNzUyNjQ4MjAxfQ.XtiaX8Hgq-16uDaXhUhezJM4AlDTqzQhzdPZhd1niaKIreN1TYCE1eCshYftU7EqWKQMQpi1xNxC7eqj9PKVmQ"
]
}
}
},
"verifiableCredentials": [
{
"raw": "eyJraWQiOiJkaWQ6a2V5OnpEbmFlVzdwOVFFc3Z1dENFcFdldGdyY3VUd0xoVmJNbTlIVERVRVBUako1eXZaOWIjekRuYWVXN3A5UUVzdnV0Q0VwV2V0Z3JjdVR3TGhWYk1tOUhURFVFUFRqSjV5dlo5YiIsInR5cCI6IkpXVCIsImFsZyI6IkVTMjU2In0.eyJpc3MiOiJkaWQ6a2V5OnpEbmFlVzdwOVFFc3Z1dENFcFdldGdyY3VUd0xoVmJNbTlIVERVRVBUako1eXZaOWIiLCJzdWIiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pYUVoV2R6ZzVYMVl6TkVnelUxcG9VbnBPTkZCU2RHaElXa000VmpGWVVHbE1WbWR4ZFRSMmJHdFVieUlzSW5naU9pSlRUbTlEYWpVeWQwSnpYMHBoZHpSSGFrb3hRV2wwVld0VlNIZGZaemMwYlc5UVdUQnlXbXBHYzBwSklpd2llU0k2SWpBeldXNUVjVGhzTjNKblZVVlpUMEpOUm1sbU5YRmFUbEprU0VoSWVVNVRiMGN0TlRCUFZXWndjSGNpZlEiLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL2V4YW1wbGVzL3YxIl0sImlkIjoidXJuOnV1aWQ6MTg5ZDFkYTgtYWQ2ZS00NzcxLWJhNDMtOGNiYjA2YmNlNDBlIiwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIlVuaXZlcnNpdHlEZWdyZWUiXSwiaXNzdWVyIjp7ImlkIjoiZGlkOmtleTp6RG5hZVc3cDlRRXN2dXRDRXBXZXRncmN1VHdMaFZiTW05SFREVUVQVGpKNXl2WjliIn0sImlzc3VhbmNlRGF0ZSI6IjIwMjUtMDctMTZUMDY6NDM6MjEuNzQ1MDAzNTgwWiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmp3azpleUpyZEhraU9pSkZReUlzSW1OeWRpSTZJbEF0TWpVMklpd2lhMmxrSWpvaWFFaFdkemc1WDFZek5FZ3pVMXBvVW5wT05GQlNkR2hJV2tNNFZqRllVR2xNVm1keGRUUjJiR3RVYnlJc0luZ2lPaUpUVG05RGFqVXlkMEp6WDBwaGR6Ukhha294UVdsMFZXdFZTSGRmWnpjMGJXOVFXVEJ5V21wR2MwcEpJaXdpZVNJNklqQXpXVzVFY1Roc04zSm5WVVZaVDBKTlJtbG1OWEZhVGxKa1NFaEllVTVUYjBjdE5UQlBWV1p3Y0hjaWZRIiwiZGVncmVlIjp7InR5cGUiOiJCYWNoZWxvckRlZ3JlZSIsIm5hbWUiOiJCYWNoZWxvciBvZiBTY2llbmNlIGFuZCBBcnRzIn19LCJpc3N1ZXJEaWQiOiJkaWQ6a2V5OnpEbmFlVzdwOVFFc3Z1dENFcFdldGdyY3VUd0xoVmJNbTlIVERVRVBUako1eXZaOWIiLCJleHBpcmF0aW9uRGF0ZSI6IjIwMjYtMDctMTZUMDY6NDM6MjEuNzQ1MDIyNzM2WiJ9LCJqdGkiOiJ1cm46dXVpZDoxODlkMWRhOC1hZDZlLTQ3NzEtYmE0My04Y2JiMDZiY2U0MGUiLCJleHAiOjE3ODQxODQyMDEsImlhdCI6MTc1MjY0ODIwMSwibmJmIjoxNzUyNjQ4MjAxfQ.XtiaX8Hgq-16uDaXhUhezJM4AlDTqzQhzdPZhd1niaKIreN1TYCE1eCshYftU7EqWKQMQpi1xNxC7eqj9PKVmQ",
"header": {
"kid": "did:key:zDnaeW7p9QEsvutCEpWetgrcuTwLhVbMm9HTDUEPTjJ5yvZ9b#zDnaeW7p9QEsvutCEpWetgrcuTwLhVbMm9HTDUEPTjJ5yvZ9b",
"typ": "JWT",
"alg": "ES256"
},
"fullPayload": {
"iss": "did:key:zDnaeW7p9QEsvutCEpWetgrcuTwLhVbMm9HTDUEPTjJ5yvZ9b",
"sub": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiaEhWdzg5X1YzNEgzU1poUnpONFBSdGhIWkM4VjFYUGlMVmdxdTR2bGtUbyIsIngiOiJTTm9DajUyd0JzX0phdzRHakoxQWl0VWtVSHdfZzc0bW9QWTByWmpGc0pJIiwieSI6IjAzWW5EcThsN3JnVUVZT0JNRmlmNXFaTlJkSEhIeU5Tb0ctNTBPVWZwcHcifQ",
"vc": {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1"
],
"id": "urn:uuid:189d1da8-ad6e-4771-ba43-8cbb06bce40e",
"type": [
"VerifiableCredential",
"UniversityDegree"
],
"issuer": {
"id": "did:key:zDnaeW7p9QEsvutCEpWetgrcuTwLhVbMm9HTDUEPTjJ5yvZ9b"
},
"issuanceDate": "2025-07-16T06:43:21.745003580Z",
"credentialSubject": {
"id": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiaEhWdzg5X1YzNEgzU1poUnpONFBSdGhIWkM4VjFYUGlMVmdxdTR2bGtUbyIsIngiOiJTTm9DajUyd0JzX0phdzRHakoxQWl0VWtVSHdfZzc0bW9QWTByWmpGc0pJIiwieSI6IjAzWW5EcThsN3JnVUVZT0JNRmlmNXFaTlJkSEhIeU5Tb0ctNTBPVWZwcHcifQ",
"degree": {
"type": "BachelorDegree",
"name": "Bachelor of Science and Arts"
}
},
"issuerDid": "did:key:zDnaeW7p9QEsvutCEpWetgrcuTwLhVbMm9HTDUEPTjJ5yvZ9b",
"expirationDate": "2026-07-16T06:43:21.745022736Z"
},
"jti": "urn:uuid:189d1da8-ad6e-4771-ba43-8cbb06bce40e",
"exp": 1784184201,
"iat": 1752648201,
"nbf": 1752648201
},
"undisclosedPayload": {
"iss": "did:key:zDnaeW7p9QEsvutCEpWetgrcuTwLhVbMm9HTDUEPTjJ5yvZ9b",
"sub": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiaEhWdzg5X1YzNEgzU1poUnpONFBSdGhIWkM4VjFYUGlMVmdxdTR2bGtUbyIsIngiOiJTTm9DajUyd0JzX0phdzRHakoxQWl0VWtVSHdfZzc0bW9QWTByWmpGc0pJIiwieSI6IjAzWW5EcThsN3JnVUVZT0JNRmlmNXFaTlJkSEhIeU5Tb0ctNTBPVWZwcHcifQ",
"vc": {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1"
],
"id": "urn:uuid:189d1da8-ad6e-4771-ba43-8cbb06bce40e",
"type": [
"VerifiableCredential",
"UniversityDegree"
],
"issuer": {
"id": "did:key:zDnaeW7p9QEsvutCEpWetgrcuTwLhVbMm9HTDUEPTjJ5yvZ9b"
},
"issuanceDate": "2025-07-16T06:43:21.745003580Z",
"credentialSubject": {
"id": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiaEhWdzg5X1YzNEgzU1poUnpONFBSdGhIWkM4VjFYUGlMVmdxdTR2bGtUbyIsIngiOiJTTm9DajUyd0JzX0phdzRHakoxQWl0VWtVSHdfZzc0bW9QWTByWmpGc0pJIiwieSI6IjAzWW5EcThsN3JnVUVZT0JNRmlmNXFaTlJkSEhIeU5Tb0ctNTBPVWZwcHcifQ",
"degree": {
"type": "BachelorDegree",
"name": "Bachelor of Science and Arts"
}
},
"issuerDid": "did:key:zDnaeW7p9QEsvutCEpWetgrcuTwLhVbMm9HTDUEPTjJ5yvZ9b",
"expirationDate": "2026-07-16T06:43:21.745022736Z"
},
"jti": "urn:uuid:189d1da8-ad6e-4771-ba43-8cbb06bce40e",
"exp": 1784184201,
"iat": 1752648201,
"nbf": 1752648201
}
}
]
}
]
},
"viewMode": "verbose"
}
```
### University Degree With Two Disclosable Claims
In the following we provide an example that involves the presentation of a University Degree W3C Verifiable Credential
with two disclosable claims, namely, the `issuanceDate` and `credentialSubject.degree.name` properties.
**Option: Simple**
**Endpoint:** `GET /openid4vc/session/{id}/presented-credentials` | [API Reference](https://verifier.demo.walt.id/swagger/index.html#/Credential%20Verification/get_openid4vc_session__id__presented_credentials)
##### Example Request
```bash
curl -X 'GET' \
'http://0.0.0.0:7003/openid4vc/session/{id}/presented-credentials' \
-H 'accept: application/json'
```
**Path Parameters**
- `id`: _String_ - The identifier of the presentation session whose credentials should be retrieved.
**Query Parameters**
- `viewMode`: _String (optional)_ - Controls how detailed the response will be. Accepted values:
- `simple` _(default)_ — Returns decoded `header` and `payload` for each credential. The go-to choice for most cases.
- `verbose` — Returns the full compact JWT (`raw`), decoded headers, full and undisclosed payloads, and selective disclosure maps. Useful for debugging and auditing.
---
##### Example Response
**Response Fields**
- `viewMode`: _String_ - Echoed request value (or the default if not specified).
- `credentialsByFormat`: _Object_ - A map where each key is a presentation format (`jwt_vc_json`, `sd_jwt_vc`, `mso_mdoc`) and the value is an array of decoded presented credentials. Each entry contains:
- `holder`: _String_ - The `holder` property of the W3C verifiable presentation.
- `verifiableCredentials`: _Array_ - Decoded credentials included in the presentation, each providing JWT `header` and `payload` objects.
```json
{
"credentialsByFormat": {
"jwt_vc_json": [
{
"type": "jwt_vc_json_view_simple",
"holder": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiVkp6Qlc2bXVVMV8ydGFzcDdSTFh6N2tod0xWb3E2NTBtU0pWNGxtVzJmTSIsIngiOiI0bUx6N2RyMkxpT0JvaHEyaTI0Wk9qLWVUVWlCdC0xdEVtSm9mbFBBdzNrIiwieSI6Il9mS0JNc05WTnJ4ZUw0M1V1dVR0NVFMaUYwcUlvSEFUSkt0d1dyYXBhYlkifQ",
"verifiableCredentials": [
{
"header": {
"kid": "did:key:zDnaeqPNFZ4YdURZczMiLp5cNpP3SurXZA5b8w461xRw19shB#zDnaeqPNFZ4YdURZczMiLp5cNpP3SurXZA5b8w461xRw19shB",
"typ": "JWT",
"alg": "ES256"
},
"payload": {
"iss": "did:key:zDnaeqPNFZ4YdURZczMiLp5cNpP3SurXZA5b8w461xRw19shB",
"sub": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiVkp6Qlc2bXVVMV8ydGFzcDdSTFh6N2tod0xWb3E2NTBtU0pWNGxtVzJmTSIsIngiOiI0bUx6N2RyMkxpT0JvaHEyaTI0Wk9qLWVUVWlCdC0xdEVtSm9mbFBBdzNrIiwieSI6Il9mS0JNc05WTnJ4ZUw0M1V1dVR0NVFMaUYwcUlvSEFUSkt0d1dyYXBhYlkifQ",
"vc": {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1"
],
"id": "urn:uuid:1d037cf4-8489-4bb8-8c20-8f2e2d13292d",
"type": [
"VerifiableCredential",
"UniversityDegree"
],
"issuer": {
"id": "did:key:zDnaeqPNFZ4YdURZczMiLp5cNpP3SurXZA5b8w461xRw19shB"
},
"credentialSubject": {
"id": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiVkp6Qlc2bXVVMV8ydGFzcDdSTFh6N2tod0xWb3E2NTBtU0pWNGxtVzJmTSIsIngiOiI0bUx6N2RyMkxpT0JvaHEyaTI0Wk9qLWVUVWlCdC0xdEVtSm9mbFBBdzNrIiwieSI6Il9mS0JNc05WTnJ4ZUw0M1V1dVR0NVFMaUYwcUlvSEFUSkt0d1dyYXBhYlkifQ",
"degree": {
"type": "BachelorDegree",
"name": "Bachelor of Science and Arts"
}
},
"issuerDid": "did:key:zDnaeqPNFZ4YdURZczMiLp5cNpP3SurXZA5b8w461xRw19shB",
"expirationDate": "2026-07-16T06:52:03.063864637Z",
"issuanceDate": "2025-07-16T06:52:03.063843966Z"
},
"jti": "urn:uuid:1d037cf4-8489-4bb8-8c20-8f2e2d13292d",
"exp": 1784184723,
"iat": 1752648723,
"nbf": 1752648723
}
}
]
}
]
},
"viewMode": "simple"
}
```
**Option: Verbose**
**Endpoint:** `GET /openid4vc/session/{id}/presented-credentials` | [API Reference](https://verifier.demo.walt.id/swagger/index.html#/Credential%20Verification/get_openid4vc_session__id__presented_credentials)
##### Example Request
```bash
curl -X 'GET' \
'http://0.0.0.0:7003/openid4vc/session/{id}/presented-credentials?viewMode=verbose' \
-H 'accept: application/json'
```
**Path Parameters**
- `id`: _String_ - The identifier of the presentation session whose credentials should be retrieved.
**Query Parameters**
- `viewMode`: _String (optional)_ - Controls how detailed the response will be. Accepted values:
- `simple` _(default)_ — Returns decoded `header` and `payload` for each credential. The go-to choice for most cases.
- `verbose` — Returns the full compact JWT (`raw`), decoded headers, full and undisclosed payloads, and selective disclosure maps. Useful for debugging and auditing.
---
##### Example Response
**Response Fields**
- `viewMode`: _String_ - Echoed request value (or the default if not specified).
- `credentialsByFormat`: _Object_ - A map where each key is a presentation format (`jwt_vc_json`, `sd_jwt_vc`, `mso_mdoc`) and the value is an array of decoded presented credentials. Each entry contains:
- `vp`: _Object_ - Verbosely decoded verifiable presentation containing `raw` (compact JWT), `header`, and `payload`.
- `verifiableCredentials`: _Array_ - Verbose decoded credentials, each containing:
- `raw`: _String_ - The compact serialized verifiable credential JWT.
- `header`: _Object_ - The JWT header of the verifiable credential.
- `fullPayload`: _Object_ - JWT payload with all disclosures substituted.
- `undisclosedPayload`: _Object_ - JWT payload without any disclosures substituted.
- `disclosures`: _Object (optional)_ - Map of hashed selective disclosure values to their decoded representation. Only present when disclosable claims were part of the presentation.
```json
{
"credentialsByFormat": {
"jwt_vc_json": [
{
"type": "jwt_vc_json_view_verbose",
"vp": {
"raw": "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pVmtwNlFsYzJiWFZWTVY4eWRHRnpjRGRTVEZoNk4ydG9kMHhXYjNFMk5UQnRVMHBXTkd4dFZ6Sm1UU0lzSW5naU9pSTBiVXg2TjJSeU1reHBUMEp2YUhFeWFUSTBXazlxTFdWVVZXbENkQzB4ZEVWdFNtOW1iRkJCZHpOcklpd2llU0k2SWw5bVMwSk5jMDVXVG5KNFpVdzBNMVYxZFZSME5WRk1hVVl3Y1VsdlNFRlVTa3QwZDFkeVlYQmhZbGtpZlEjMCIsInR5cCI6IkpXVCIsImFsZyI6IkVTMjU2In0.eyJzdWIiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pVmtwNlFsYzJiWFZWTVY4eWRHRnpjRGRTVEZoNk4ydG9kMHhXYjNFMk5UQnRVMHBXTkd4dFZ6Sm1UU0lzSW5naU9pSTBiVXg2TjJSeU1reHBUMEp2YUhFeWFUSTBXazlxTFdWVVZXbENkQzB4ZEVWdFNtOW1iRkJCZHpOcklpd2llU0k2SWw5bVMwSk5jMDVXVG5KNFpVdzBNMVYxZFZSME5WRk1hVVl3Y1VsdlNFRlVTa3QwZDFkeVlYQmhZbGtpZlEiLCJuYmYiOjE3NTI2NDg2NjMsImlhdCI6MTc1MjY0ODcyMywianRpIjoiOWtZQWFnTjVMSFBxIiwiaXNzIjoiZGlkOmp3azpleUpyZEhraU9pSkZReUlzSW1OeWRpSTZJbEF0TWpVMklpd2lhMmxrSWpvaVZrcDZRbGMyYlhWVk1WOHlkR0Z6Y0RkU1RGaDZOMnRvZDB4V2IzRTJOVEJ0VTBwV05HeHRWekptVFNJc0luZ2lPaUkwYlV4Nk4yUnlNa3hwVDBKdmFIRXlhVEkwV2s5cUxXVlVWV2xDZEMweGRFVnRTbTltYkZCQmR6TnJJaXdpZVNJNklsOW1TMEpOYzA1V1RuSjRaVXcwTTFWMWRWUjBOVkZNYVVZd2NVbHZTRUZVU2t0MGQxZHlZWEJoWWxraWZRIiwibm9uY2UiOiI1OTYzODg2ZS1iMmMyLTQ5NmMtYjM3My0yNzA5MjZhOTQ2NDIiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjIyMjIyL29wZW5pZDR2Yy92ZXJpZnkiLCJ2cCI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVQcmVzZW50YXRpb24iXSwiaWQiOiI5a1lBYWdONUxIUHEiLCJob2xkZXIiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pVmtwNlFsYzJiWFZWTVY4eWRHRnpjRGRTVEZoNk4ydG9kMHhXYjNFMk5UQnRVMHBXTkd4dFZ6Sm1UU0lzSW5naU9pSTBiVXg2TjJSeU1reHBUMEp2YUhFeWFUSTBXazlxTFdWVVZXbENkQzB4ZEVWdFNtOW1iRkJCZHpOcklpd2llU0k2SWw5bVMwSk5jMDVXVG5KNFpVdzBNMVYxZFZSME5WRk1hVVl3Y1VsdlNFRlVTa3QwZDFkeVlYQmhZbGtpZlEiLCJ2ZXJpZmlhYmxlQ3JlZGVudGlhbCI6WyJleUpyYVdRaU9pSmthV1E2YTJWNU9ucEVibUZsY1ZCT1JsbzBXV1JWVWxwamVrMXBUSEExWTA1d1VETlRkWEpZV2tFMVlqaDNORFl4ZUZKM01UbHphRUlqZWtSdVlXVnhVRTVHV2pSWlpGVlNXbU42VFdsTWNEVmpUbkJRTTFOMWNsaGFRVFZpT0hjME5qRjRVbmN4T1hOb1FpSXNJblI1Y0NJNklrcFhWQ0lzSW1Gc1p5STZJa1ZUTWpVMkluMC5leUpwYzNNaU9pSmthV1E2YTJWNU9ucEVibUZsY1ZCT1JsbzBXV1JWVWxwamVrMXBUSEExWTA1d1VETlRkWEpZV2tFMVlqaDNORFl4ZUZKM01UbHphRUlpTENKemRXSWlPaUprYVdRNmFuZHJPbVY1U25Ka1NHdHBUMmxLUmxGNVNYTkpiVTU1WkdsSk5rbHNRWFJOYWxVeVNXbDNhV0V5Ykd0SmFtOXBWbXR3TmxGc1l6SmlXRlpXVFZZNGVXUkhSbnBqUkdSVFZFWm9OazR5ZEc5a01IaFhZak5GTWs1VVFuUlZNSEJYVGtkNGRGWjZTbTFVVTBselNXNW5hVTlwU1RCaVZYZzJUakpTZVUxcmVIQlVNRXAyWVVoRmVXRlVTVEJYYXpseFRGZFdWVlpYYkVOa1F6QjRaRVZXZEZOdE9XMWlSa0pDWkhwT2NrbHBkMmxsVTBrMlNXdzViVk13U2s1ak1EVlhWRzVLTkZwVmR6Qk5NVll4WkZaU01FNVdSazFoVlZsM1kxVnNkbE5GUmxWVGEzUXdaREZrZVZsWVFtaFpiR3RwWmxFaUxDSjJZeUk2ZXlKQVkyOXVkR1Y0ZENJNld5Sm9kSFJ3Y3pvdkwzZDNkeTUzTXk1dmNtY3ZNakF4T0M5amNtVmtaVzUwYVdGc2N5OTJNU0lzSW1oMGRIQnpPaTh2ZDNkM0xuY3pMbTl5Wnk4eU1ERTRMMk55WldSbGJuUnBZV3h6TDJWNFlXMXdiR1Z6TDNZeElsMHNJbWxrSWpvaWRYSnVPblYxYVdRNk1XUXdNemRqWmpRdE9EUTRPUzAwWW1JNExUaGpNakF0T0dZeVpUSmtNVE15T1RKa0lpd2lkSGx3WlNJNld5SldaWEpwWm1saFlteGxRM0psWkdWdWRHbGhiQ0lzSWxWdWFYWmxjbk5wZEhsRVpXZHlaV1VpWFN3aWFYTnpkV1Z5SWpwN0ltbGtJam9pWkdsa09tdGxlVHA2Ukc1aFpYRlFUa1phTkZsa1ZWSmFZM3BOYVV4d05XTk9jRkF6VTNWeVdGcEJOV0k0ZHpRMk1YaFNkekU1YzJoQ0luMHNJbU55WldSbGJuUnBZV3hUZFdKcVpXTjBJanA3SW1sa0lqb2laR2xrT21wM2F6cGxlVXB5WkVocmFVOXBTa1pSZVVselNXMU9lV1JwU1RaSmJFRjBUV3BWTWtscGQybGhNbXhyU1dwdmFWWnJjRFpSYkdNeVlsaFdWazFXT0hsa1IwWjZZMFJrVTFSR2FEWk9NblJ2WkRCNFYySXpSVEpPVkVKMFZUQndWMDVIZUhSV2VrcHRWRk5KYzBsdVoybFBhVWt3WWxWNE5rNHlVbmxOYTNod1ZEQktkbUZJUlhsaFZFa3dWMnM1Y1V4WFZsVldWMnhEWkVNd2VHUkZWblJUYlRsdFlrWkNRbVI2VG5KSmFYZHBaVk5KTmtsc09XMVRNRXBPWXpBMVYxUnVTalJhVlhjd1RURldNV1JXVWpCT1ZrWk5ZVlZaZDJOVmJIWlRSVVpWVTJ0ME1HUXhaSGxaV0VKb1dXeHJhV1pSSWl3aVpHVm5jbVZsSWpwN0luUjVjR1VpT2lKQ1lXTm9aV3h2Y2tSbFozSmxaU0lzSWw5elpDSTZXeUptWW14M2FFSnNWa3huYm1OdlQwVlFjMDVvVEU5RGJqRTFMVVZOTUMxcGFtdGpjakJRTlVoRFVHaG5JbDE5ZlN3aWFYTnpkV1Z5Ukdsa0lqb2laR2xrT210bGVUcDZSRzVoWlhGUVRrWmFORmxrVlZKYVkzcE5hVXh3TldOT2NGQXpVM1Z5V0ZwQk5XSTRkelEyTVhoU2R6RTVjMmhDSWl3aVpYaHdhWEpoZEdsdmJrUmhkR1VpT2lJeU1ESTJMVEEzTFRFMlZEQTJPalV5T2pBekxqQTJNemcyTkRZek4xb2lMQ0pmYzJRaU9sc2lObVV4WmpjeVZHWlZjRVZXVGtONVQzQkVTekpaZDFSeU4xSTBWVTFEYUZkU2EybEhUMHBDTW0xNFJTSmRmU3dpYW5ScElqb2lkWEp1T25WMWFXUTZNV1F3TXpkalpqUXRPRFE0T1MwMFltSTRMVGhqTWpBdE9HWXlaVEprTVRNeU9USmtJaXdpWlhod0lqb3hOemcwTVRnME56SXpMQ0pwWVhRaU9qRTNOVEkyTkRnM01qTXNJbTVpWmlJNk1UYzFNalkwT0RjeU0zMC5jbTJNdVc2eXFTQXpja0h3ekZzWVZnb0J1MWN3VnI4N3djcVBnWWFzSTZqVXZhYno2RHhJaHhNWGJNdVZ3MVpWWlN1eUZla1dfTFlDRVRmLVdkbUlRZ35XeUk1U3pGU01Gb3pORTF0VWsxbFVGUkdkWGxxYURkQklpd2lhWE56ZFdGdVkyVkVZWFJsSWl3aU1qQXlOUzB3TnkweE5sUXdOam8xTWpvd015NHdOak00TkRNNU5qWmFJbDB-V3lKWGMxcHFVbWRaWkc5NWRVdzVWVmhUUjJaU1NUbDNJaXdpYm1GdFpTSXNJa0poWTJobGJHOXlJRzltSUZOamFXVnVZMlVnWVc1a0lFRnlkSE1pWFEiXX19.XFz-Dio7lbM0BDgRJMKZ4yq9QooEFLzh-9dByeZcz9xLw4GUzb8hyFsaXYg6Mg7GZ3srzp52R-X4M6wFFewItg",
"header": {
"kid": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiVkp6Qlc2bXVVMV8ydGFzcDdSTFh6N2tod0xWb3E2NTBtU0pWNGxtVzJmTSIsIngiOiI0bUx6N2RyMkxpT0JvaHEyaTI0Wk9qLWVUVWlCdC0xdEVtSm9mbFBBdzNrIiwieSI6Il9mS0JNc05WTnJ4ZUw0M1V1dVR0NVFMaUYwcUlvSEFUSkt0d1dyYXBhYlkifQ#0",
"typ": "JWT",
"alg": "ES256"
},
"payload": {
"sub": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiVkp6Qlc2bXVVMV8ydGFzcDdSTFh6N2tod0xWb3E2NTBtU0pWNGxtVzJmTSIsIngiOiI0bUx6N2RyMkxpT0JvaHEyaTI0Wk9qLWVUVWlCdC0xdEVtSm9mbFBBdzNrIiwieSI6Il9mS0JNc05WTnJ4ZUw0M1V1dVR0NVFMaUYwcUlvSEFUSkt0d1dyYXBhYlkifQ",
"nbf": 1752648663,
"iat": 1752648723,
"jti": "9kYAagN5LHPq",
"iss": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiVkp6Qlc2bXVVMV8ydGFzcDdSTFh6N2tod0xWb3E2NTBtU0pWNGxtVzJmTSIsIngiOiI0bUx6N2RyMkxpT0JvaHEyaTI0Wk9qLWVUVWlCdC0xdEVtSm9mbFBBdzNrIiwieSI6Il9mS0JNc05WTnJ4ZUw0M1V1dVR0NVFMaUYwcUlvSEFUSkt0d1dyYXBhYlkifQ",
"nonce": "5963886e-b2c2-496c-b373-270926a94642",
"aud": "http://localhost:22222/openid4vc/verify",
"vp": {
"@context": [
"https://www.w3.org/2018/credentials/v1"
],
"type": [
"VerifiablePresentation"
],
"id": "9kYAagN5LHPq",
"holder": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiVkp6Qlc2bXVVMV8ydGFzcDdSTFh6N2tod0xWb3E2NTBtU0pWNGxtVzJmTSIsIngiOiI0bUx6N2RyMkxpT0JvaHEyaTI0Wk9qLWVUVWlCdC0xdEVtSm9mbFBBdzNrIiwieSI6Il9mS0JNc05WTnJ4ZUw0M1V1dVR0NVFMaUYwcUlvSEFUSkt0d1dyYXBhYlkifQ",
"verifiableCredential": [
"eyJraWQiOiJkaWQ6a2V5OnpEbmFlcVBORlo0WWRVUlpjek1pTHA1Y05wUDNTdXJYWkE1Yjh3NDYxeFJ3MTlzaEIjekRuYWVxUE5GWjRZZFVSWmN6TWlMcDVjTnBQM1N1clhaQTViOHc0NjF4UncxOXNoQiIsInR5cCI6IkpXVCIsImFsZyI6IkVTMjU2In0.eyJpc3MiOiJkaWQ6a2V5OnpEbmFlcVBORlo0WWRVUlpjek1pTHA1Y05wUDNTdXJYWkE1Yjh3NDYxeFJ3MTlzaEIiLCJzdWIiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pVmtwNlFsYzJiWFZWTVY4eWRHRnpjRGRTVEZoNk4ydG9kMHhXYjNFMk5UQnRVMHBXTkd4dFZ6Sm1UU0lzSW5naU9pSTBiVXg2TjJSeU1reHBUMEp2YUhFeWFUSTBXazlxTFdWVVZXbENkQzB4ZEVWdFNtOW1iRkJCZHpOcklpd2llU0k2SWw5bVMwSk5jMDVXVG5KNFpVdzBNMVYxZFZSME5WRk1hVVl3Y1VsdlNFRlVTa3QwZDFkeVlYQmhZbGtpZlEiLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL2V4YW1wbGVzL3YxIl0sImlkIjoidXJuOnV1aWQ6MWQwMzdjZjQtODQ4OS00YmI4LThjMjAtOGYyZTJkMTMyOTJkIiwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIlVuaXZlcnNpdHlEZWdyZWUiXSwiaXNzdWVyIjp7ImlkIjoiZGlkOmtleTp6RG5hZXFQTkZaNFlkVVJaY3pNaUxwNWNOcFAzU3VyWFpBNWI4dzQ2MXhSdzE5c2hCIn0sImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmp3azpleUpyZEhraU9pSkZReUlzSW1OeWRpSTZJbEF0TWpVMklpd2lhMmxrSWpvaVZrcDZRbGMyYlhWVk1WOHlkR0Z6Y0RkU1RGaDZOMnRvZDB4V2IzRTJOVEJ0VTBwV05HeHRWekptVFNJc0luZ2lPaUkwYlV4Nk4yUnlNa3hwVDBKdmFIRXlhVEkwV2s5cUxXVlVWV2xDZEMweGRFVnRTbTltYkZCQmR6TnJJaXdpZVNJNklsOW1TMEpOYzA1V1RuSjRaVXcwTTFWMWRWUjBOVkZNYVVZd2NVbHZTRUZVU2t0MGQxZHlZWEJoWWxraWZRIiwiZGVncmVlIjp7InR5cGUiOiJCYWNoZWxvckRlZ3JlZSIsIl9zZCI6WyJmYmx3aEJsVkxnbmNvT0VQc05oTE9DbjE1LUVNMC1pamtjcjBQNUhDUGhnIl19fSwiaXNzdWVyRGlkIjoiZGlkOmtleTp6RG5hZXFQTkZaNFlkVVJaY3pNaUxwNWNOcFAzU3VyWFpBNWI4dzQ2MXhSdzE5c2hCIiwiZXhwaXJhdGlvbkRhdGUiOiIyMDI2LTA3LTE2VDA2OjUyOjAzLjA2Mzg2NDYzN1oiLCJfc2QiOlsiNmUxZjcyVGZVcEVWTkN5T3BESzJZd1RyN1I0VU1DaFdSa2lHT0pCMm14RSJdfSwianRpIjoidXJuOnV1aWQ6MWQwMzdjZjQtODQ4OS00YmI4LThjMjAtOGYyZTJkMTMyOTJkIiwiZXhwIjoxNzg0MTg0NzIzLCJpYXQiOjE3NTI2NDg3MjMsIm5iZiI6MTc1MjY0ODcyM30.cm2MuW6yqSAzckHwzFsYVgoBu1cwVr87wcqPgYasI6jUvabz6DxIhxMXbMuVw1ZVZSuyFekW_LYCETf-WdmIQg~WyI5SzFSMFozNE1tUk1lUFRGdXlqaDdBIiwiaXNzdWFuY2VEYXRlIiwiMjAyNS0wNy0xNlQwNjo1MjowMy4wNjM4NDM5NjZaIl0~WyJXc1pqUmdZZG95dUw5VVhTR2ZSSTl3IiwibmFtZSIsIkJhY2hlbG9yIG9mIFNjaWVuY2UgYW5kIEFydHMiXQ"
]
}
}
},
"verifiableCredentials": [
{
"raw": "eyJraWQiOiJkaWQ6a2V5OnpEbmFlcVBORlo0WWRVUlpjek1pTHA1Y05wUDNTdXJYWkE1Yjh3NDYxeFJ3MTlzaEIjekRuYWVxUE5GWjRZZFVSWmN6TWlMcDVjTnBQM1N1clhaQTViOHc0NjF4UncxOXNoQiIsInR5cCI6IkpXVCIsImFsZyI6IkVTMjU2In0.eyJpc3MiOiJkaWQ6a2V5OnpEbmFlcVBORlo0WWRVUlpjek1pTHA1Y05wUDNTdXJYWkE1Yjh3NDYxeFJ3MTlzaEIiLCJzdWIiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pVmtwNlFsYzJiWFZWTVY4eWRHRnpjRGRTVEZoNk4ydG9kMHhXYjNFMk5UQnRVMHBXTkd4dFZ6Sm1UU0lzSW5naU9pSTBiVXg2TjJSeU1reHBUMEp2YUhFeWFUSTBXazlxTFdWVVZXbENkQzB4ZEVWdFNtOW1iRkJCZHpOcklpd2llU0k2SWw5bVMwSk5jMDVXVG5KNFpVdzBNMVYxZFZSME5WRk1hVVl3Y1VsdlNFRlVTa3QwZDFkeVlYQmhZbGtpZlEiLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL2V4YW1wbGVzL3YxIl0sImlkIjoidXJuOnV1aWQ6MWQwMzdjZjQtODQ4OS00YmI4LThjMjAtOGYyZTJkMTMyOTJkIiwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIlVuaXZlcnNpdHlEZWdyZWUiXSwiaXNzdWVyIjp7ImlkIjoiZGlkOmtleTp6RG5hZXFQTkZaNFlkVVJaY3pNaUxwNWNOcFAzU3VyWFpBNWI4dzQ2MXhSdzE5c2hCIn0sImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmp3azpleUpyZEhraU9pSkZReUlzSW1OeWRpSTZJbEF0TWpVMklpd2lhMmxrSWpvaVZrcDZRbGMyYlhWVk1WOHlkR0Z6Y0RkU1RGaDZOMnRvZDB4V2IzRTJOVEJ0VTBwV05HeHRWekptVFNJc0luZ2lPaUkwYlV4Nk4yUnlNa3hwVDBKdmFIRXlhVEkwV2s5cUxXVlVWV2xDZEMweGRFVnRTbTltYkZCQmR6TnJJaXdpZVNJNklsOW1TMEpOYzA1V1RuSjRaVXcwTTFWMWRWUjBOVkZNYVVZd2NVbHZTRUZVU2t0MGQxZHlZWEJoWWxraWZRIiwiZGVncmVlIjp7InR5cGUiOiJCYWNoZWxvckRlZ3JlZSIsIl9zZCI6WyJmYmx3aEJsVkxnbmNvT0VQc05oTE9DbjE1LUVNMC1pamtjcjBQNUhDUGhnIl19fSwiaXNzdWVyRGlkIjoiZGlkOmtleTp6RG5hZXFQTkZaNFlkVVJaY3pNaUxwNWNOcFAzU3VyWFpBNWI4dzQ2MXhSdzE5c2hCIiwiZXhwaXJhdGlvbkRhdGUiOiIyMDI2LTA3LTE2VDA2OjUyOjAzLjA2Mzg2NDYzN1oiLCJfc2QiOlsiNmUxZjcyVGZVcEVWTkN5T3BESzJZd1RyN1I0VU1DaFdSa2lHT0pCMm14RSJdfSwianRpIjoidXJuOnV1aWQ6MWQwMzdjZjQtODQ4OS00YmI4LThjMjAtOGYyZTJkMTMyOTJkIiwiZXhwIjoxNzg0MTg0NzIzLCJpYXQiOjE3NTI2NDg3MjMsIm5iZiI6MTc1MjY0ODcyM30.cm2MuW6yqSAzckHwzFsYVgoBu1cwVr87wcqPgYasI6jUvabz6DxIhxMXbMuVw1ZVZSuyFekW_LYCETf-WdmIQg~WyI5SzFSMFozNE1tUk1lUFRGdXlqaDdBIiwiaXNzdWFuY2VEYXRlIiwiMjAyNS0wNy0xNlQwNjo1MjowMy4wNjM4NDM5NjZaIl0~WyJXc1pqUmdZZG95dUw5VVhTR2ZSSTl3IiwibmFtZSIsIkJhY2hlbG9yIG9mIFNjaWVuY2UgYW5kIEFydHMiXQ",
"header": {
"kid": "did:key:zDnaeqPNFZ4YdURZczMiLp5cNpP3SurXZA5b8w461xRw19shB#zDnaeqPNFZ4YdURZczMiLp5cNpP3SurXZA5b8w461xRw19shB",
"typ": "JWT",
"alg": "ES256"
},
"fullPayload": {
"iss": "did:key:zDnaeqPNFZ4YdURZczMiLp5cNpP3SurXZA5b8w461xRw19shB",
"sub": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiVkp6Qlc2bXVVMV8ydGFzcDdSTFh6N2tod0xWb3E2NTBtU0pWNGxtVzJmTSIsIngiOiI0bUx6N2RyMkxpT0JvaHEyaTI0Wk9qLWVUVWlCdC0xdEVtSm9mbFBBdzNrIiwieSI6Il9mS0JNc05WTnJ4ZUw0M1V1dVR0NVFMaUYwcUlvSEFUSkt0d1dyYXBhYlkifQ",
"vc": {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1"
],
"id": "urn:uuid:1d037cf4-8489-4bb8-8c20-8f2e2d13292d",
"type": [
"VerifiableCredential",
"UniversityDegree"
],
"issuer": {
"id": "did:key:zDnaeqPNFZ4YdURZczMiLp5cNpP3SurXZA5b8w461xRw19shB"
},
"credentialSubject": {
"id": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiVkp6Qlc2bXVVMV8ydGFzcDdSTFh6N2tod0xWb3E2NTBtU0pWNGxtVzJmTSIsIngiOiI0bUx6N2RyMkxpT0JvaHEyaTI0Wk9qLWVUVWlCdC0xdEVtSm9mbFBBdzNrIiwieSI6Il9mS0JNc05WTnJ4ZUw0M1V1dVR0NVFMaUYwcUlvSEFUSkt0d1dyYXBhYlkifQ",
"degree": {
"type": "BachelorDegree",
"name": "Bachelor of Science and Arts"
}
},
"issuerDid": "did:key:zDnaeqPNFZ4YdURZczMiLp5cNpP3SurXZA5b8w461xRw19shB",
"expirationDate": "2026-07-16T06:52:03.063864637Z",
"issuanceDate": "2025-07-16T06:52:03.063843966Z"
},
"jti": "urn:uuid:1d037cf4-8489-4bb8-8c20-8f2e2d13292d",
"exp": 1784184723,
"iat": 1752648723,
"nbf": 1752648723
},
"undisclosedPayload": {
"iss": "did:key:zDnaeqPNFZ4YdURZczMiLp5cNpP3SurXZA5b8w461xRw19shB",
"sub": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiVkp6Qlc2bXVVMV8ydGFzcDdSTFh6N2tod0xWb3E2NTBtU0pWNGxtVzJmTSIsIngiOiI0bUx6N2RyMkxpT0JvaHEyaTI0Wk9qLWVUVWlCdC0xdEVtSm9mbFBBdzNrIiwieSI6Il9mS0JNc05WTnJ4ZUw0M1V1dVR0NVFMaUYwcUlvSEFUSkt0d1dyYXBhYlkifQ",
"vc": {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1"
],
"id": "urn:uuid:1d037cf4-8489-4bb8-8c20-8f2e2d13292d",
"type": [
"VerifiableCredential",
"UniversityDegree"
],
"issuer": {
"id": "did:key:zDnaeqPNFZ4YdURZczMiLp5cNpP3SurXZA5b8w461xRw19shB"
},
"credentialSubject": {
"id": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiVkp6Qlc2bXVVMV8ydGFzcDdSTFh6N2tod0xWb3E2NTBtU0pWNGxtVzJmTSIsIngiOiI0bUx6N2RyMkxpT0JvaHEyaTI0Wk9qLWVUVWlCdC0xdEVtSm9mbFBBdzNrIiwieSI6Il9mS0JNc05WTnJ4ZUw0M1V1dVR0NVFMaUYwcUlvSEFUSkt0d1dyYXBhYlkifQ",
"degree": {
"type": "BachelorDegree",
"_sd": [
"fblwhBlVLgncoOEPsNhLOCn15-EM0-ijkcr0P5HCPhg"
]
}
},
"issuerDid": "did:key:zDnaeqPNFZ4YdURZczMiLp5cNpP3SurXZA5b8w461xRw19shB",
"expirationDate": "2026-07-16T06:52:03.063864637Z",
"_sd": [
"6e1f72TfUpEVNCyOpDK2YwTr7R4UMChWRkiGOJB2mxE"
]
},
"jti": "urn:uuid:1d037cf4-8489-4bb8-8c20-8f2e2d13292d",
"exp": 1784184723,
"iat": 1752648723,
"nbf": 1752648723
},
"disclosures": {
"6e1f72TfUpEVNCyOpDK2YwTr7R4UMChWRkiGOJB2mxE": {
"disclosure": "WyI5SzFSMFozNE1tUk1lUFRGdXlqaDdBIiwiaXNzdWFuY2VEYXRlIiwiMjAyNS0wNy0xNlQwNjo1MjowMy4wNjM4NDM5NjZaIl0",
"salt": "9K1R0Z34MmRMePTFuyjh7A",
"key": "issuanceDate",
"value": "2025-07-16T06:52:03.063843966Z"
},
"fblwhBlVLgncoOEPsNhLOCn15-EM0-ijkcr0P5HCPhg": {
"disclosure": "WyJXc1pqUmdZZG95dUw5VVhTR2ZSSTl3IiwibmFtZSIsIkJhY2hlbG9yIG9mIFNjaWVuY2UgYW5kIEFydHMiXQ",
"salt": "WsZjRgYdoyuL9UXSGfRI9w",
"key": "name",
"value": "Bachelor of Science and Arts"
}
}
}
]
}
]
},
"viewMode": "verbose"
}
```
--------------------------------------------------------------------------------
PAGE: How to Verify an SD-JWT-based Verifiable Credential (IETF | SD-JWT VC) via OID4VP with walt.id
URL: https://docs.walt.id/community-stack/verifier/credential-verification/sd-jwt-vc-oid4vc
RAW: https://docs.walt.id/community-stack/verifier/credential-verification/sd-jwt-vc-oid4vc.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# How to Verify an SD-JWT-based Verifiable Credential (IETF | SD-JWT VC) via OID4VP with walt.id
**TL;DR**
**What you:**
- Create verification requests for SD-JWT VC credentials via OID4VP
- Configure VC and VP policies for credential verification
- Implement custom presentation definitions with field constraints
- Retrieve and inspect verification results and presented credentials
**Relevant concepts:**
- [SD-JWT VC](https://docs.walt.id/concepts/digital-credentials/sd-jwt-vc.md) – Intro to the IETF standard for selective disclosure credentials
- [OpenID4VP](https://docs.walt.id/concepts/data-exchange-protocols/openid4vp.md) – Protocol for requesting and receiving verifiable presentations
Video: https://youtu.be/N6pZTs-Jr9M
This guide provides a comprehensive walkthrough for verifying a SD-JWT VC credentials based on
the [IETF standard](https://datatracker.ietf.org/doc/draft-ietf-oauth-sd-jwt-vc/) using
the walt.id verifier API. The verification process will utilize the OID4VCI protocol, an extension of OpenID,
facilitating secure and standardized communication between identities.
**SD-JWT VC**: A digital equivalent of physical credentials such as a driver's license or university
degree. VCs are cryptographically secure, privacy-respecting, and instantly verifiable.
**OID4VCI**: A protocol specifying how parties can issue VCs and present these credentials in a way that's consistent
and secure across platforms ensuring interoperability.
## Preparing the Verification
First, determine the type of SD-JWT VC Credential to be verified. Although this guide focuses on an `identity_credential` — the default credential issued in the [SD-JWT VC issuance guide](https://docs.walt.id/community-stack/issuer/credential-issuance/sd-jwt-vc-oid4vc.md) — you can use any credential type compliant with the IETF standard.
The verification process will be as follows:
1. Specify the credential type(s) to request from a user and the verification policies to be applied to the credential(
s).
2. Optionally provide a success and failure redirect URL, which the user will be redirected to after the verification
process is completed.
After you have provided the required information:
1. The API generates
a [Presentation Definition](https://identity.foundation/presentation-exchange/#presentation-definition).
2. API returns a URL which can passed to OIDC-compliant wallet to fulfill the request.
If you have provided a success or failure redirect URL, the user will be redirected to that URL. You can then access the
verification results by using the id of the verification session, which can be found in the URL generated by the API, as
well as in the query or path parameters of the redirect URL.
### Example Verification Request
In this example, we specify the credential we want to verify by supplying the type.
**Option: CURL**
```bash
curl -X 'POST' \
'http://0.0.0.0:7003/openid4vc/verify' \
-H 'accept: */*' \
-H 'authorizeBaseUrl: openid4vp://authorize' \
-H 'responseMode: direct_post' \
-H 'successRedirectUri: https://example.com/success?id=$id' \
-H 'errorRedirectUri: https://example.com/error?id=$id' \
-H 'statusCallbackUri: https://example.com/verificationResult' \
-H 'statusCallbackApiKey: myAPIKey' \
-H 'stateId: myUniqueStateValue' \
-H 'Content-Type: application/json' \
-d '{
"request_credentials": [
{ "vct": "https://issuer.demo.walt.id/draft13/identity_credential", "format": "vc+sd-jwt" }
]
}'
```
**Note:**
The `vct` value is the Verifiable Credential Type identifier for the SD-JWT VC you want to request. It is a URL that uniquely identifies the credential type and resolves to its type metadata. You can find the correct `vct` value for a credential in the credential that was issued — see the [SD-JWT VC issuance guide](https://docs.walt.id/community-stack/issuer/credential-issuance/sd-jwt-vc-oid4vc.md).
**Header Parameters**
- **authorizeBaseUrl** - is used to modify the start of the OID4VC request URL. The default value is `openid4vp://authorize` which
is fine for most use-cases. If you however are required to use the HAIP OID4VC profile, you need to update the value as follows: `haip://`
- **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](#verification-status-policies-response)
- **statusCallbackApiKey** (optional) - If the endpoint you provide via `statusCallbackUri` is protected, you can use
the `statusCallbackApiKey` 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:
- `HAIP`: For the OID4VC HAIP profile.
- `EBSIV3`: For EBSI V3 compliant VP. Learn more [here](https://docs.walt.id/community-stack/verifier/ecosystems/ebsi/overview.md).
- `ISO_18013_7_MDOC`: For mdoc Openid4VP.
**Body Parameters**
- **vp_policies** - Policies applied to the Verifiable Presentation. A list of policies can be
found [here](https://docs.walt.id/community-stack/verifier/credential-verification/policies/overview.md)
- **vc_policies** - Policies applied to all requested credentials. A list of policies can be
found [here](https://docs.walt.id/community-stack/verifier/credential-verification/policies/overview.md).
- **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:
1. **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`.
```json
{ "vct": "test.com/identity_credential", "format": "vc+sd-jwt" }
```
1. **W3C JWT Credential**
- **type** Specifies the type of credential (e.g., `VerifiableID`, `VerifiableDiploma`). This maps to the `type`
attribute in W3C credentials.
- **format**: Describes the format of the credential. For W3C JWT credentials, this would
be `jwt_vc_json`.
```json
{ "type": "ProofOfResidence", "format": "jwt_vc_json" }
```
2. **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`.
```json
{ "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 optional `policies` and `id`
attribute
- **policies**: An array of policies to apply to the specified credential. A list of all policies can be
found [here](https://docs.walt.id/community-stack/verifier/credential-verification/policies/overview.md).
- **id**: Used to set a specific id for the
generated [Presentation Definition](https://identity.foundation/presentation-exchange/spec/v1.0.0/#presentation-definition).
If not set,
the verifier API auto-assigns a generated one.
**Full Examples**
```json
{
"vct": "ProofOfResidence",
"format": "vc+sd-jwt",
"policies": [
"schema",
{
"policy": "webhook",
"args": "https://example.org/abc/xyz"
}
],
"id": "test123"
}
```
### Example Verification Request 2 - Defining VC/VP Policies
**Error:**
Please note that if you are using the HAIP profile, you cannot specify custom policies at this point.
In this example, we not only specify the types of credentials to verify, but also define the policies that should be
executed during verification:
- **VC policies (vc_policies)**: Applied to individual Verifiable Credentials (VCs).
- **VP policies (vp_policies)**: Applied to the Verifiable Presentation (VP) as a whole.
- **Default behavior**: The `signature` policy is applied by default to both VP and VC(s).
- **Policy catalog**: A list of available policies can be found
[here](https://docs.walt.id/community-stack/verifier/credential-verification/policies/overview.md).
How VC policies are applied:
- **Global scope**: Once defined, VC policies are applied globally to all requested credentials in the verification
session.
- **Per-credential override**: A later section shows how to customize and apply specific policies to individual
credentials when needed.
We specify policies in the verification request alongside the `request_credentials` parameter (previously explained),
using the `vc_policies` and `vp_policies` fields:
- **`vc_policies`** and **`vp_policies`** each contain a list of policies; either one or both can be provided.
- **Identical structure**: Both fields share the same structure.
- **Policy representation**:
- As a **string** if the policy does not require arguments.
- As an **object** if the policy requires arguments.
**Example Polices**
```json
[
"signature",
// policy without argument
"expired",
// policy without argument
"not-before",
// policy without argument
{
"policy": "webhook",
"args": "https://example.org/abc/xyz"
}
// policy with argument
]
```
**Initiation Request**
**Option: CURL**
```bash
curl -X 'POST' \
'http://0.0.0.0:7003/openid4vc/verify' \
-H 'accept: */*' \
-H 'authorizeBaseUrl: openid4vp://authorize' \
-H 'responseMode: direct_post' \
-H 'successRedirectUri: https://example.com/success?id=$id' \
-H 'errorRedirectUri: https://example.com/error?id=$id' \
-H 'statusCallbackUri: https://example.com/verificationResult' \
-H 'statusCallbackApiKey: myAPIKey' \
-H 'stateId: myUniqueStateValue' \
-H 'Content-Type: application/json' \
-d '{
"vp_policies": ["signature", "expired", "not-before"],
"vc_policies": ["signature", "expired", "not-before",
{"policy": "webhook", "args": "https://example.org/abc/xyz"}],
"request_credentials": [
{ "vct": "https://issuer.demo.walt.id/draft13/identity_credential", "format": "vc+sd-jwt" }
]
}'
```
**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 here
`https://wallet.demo.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](#verification-status-policies-response)
- **statusCallbackApiKey** (optional) - If the endpoint you provide via `statusCallbackUri` is protected, you can use
the `statusCallbackApiKey` 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:
- `HAIP`: For the OID4VC HAIP profile.
- `EBSIV3`: For EBSI V3 compliant VP. Learn more [here](https://docs.walt.id/community-stack/verifier/ecosystems/ebsi/overview.md).
- `ISO_18013_7_MDOC`: For mdoc Openid4VP.
**Body Parameters**
- **vp_policies** - Policies applied to the Verifiable Presentation. A list of policies can be
found [here](https://docs.walt.id/community-stack/verifier/credential-verification/policies/overview.md)
- **vc_policies** - Policies applied to all requested credentials. A list of policies can be
found [here](https://docs.walt.id/community-stack/verifier/credential-verification/policies/overview.md).
- **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:
1. **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`.
```json
{ "vct": "test.com/identity_credential", "format": "vc+sd-jwt" }
```
2. **W3C JWT Credential**
- **type** Specifies the type of credential (e.g., `VerifiableID`, `VerifiableDiploma`). This maps to the `type`
attribute in W3C credentials.
- **format**: Describes the format of the credential. For W3C JWT credentials, this would
be `jwt_vc_json`.
```json
{ "type": "ProofOfResidence", "format": "jwt_vc_json" }
```
3. **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`.
```json
{ "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 optional `policies` and `id`
attribute
- **policies**: An array of policies to apply to the specified credential. A list of all policies can be
found [here](https://docs.walt.id/community-stack/verifier/credential-verification/policies/overview.md).
- **id**: Used to set a specific id for the
generated [Presentation Definition](https://identity.foundation/presentation-exchange/spec/v1.0.0/#presentation-definition).
If not set,
the verifier API auto-assigns a generated one.
**Full Examples**
```json
{
"vct": "ProofOfResidence",
"format": "vc+sd-jwt",
"policies": [
"schema",
{
"policy": "webhook",
"args": "https://example.org/abc/xyz"
}
],
"id": "test123"
}
```
Upon execution, the system generates a URL, which can be shared directly with users or displayed as a QR code.
### Example Verification Request 3: Applying Policies to Specific Credentials
**Error:**
Please note that if you are using the HAIP profile, you cannot specify custom policies at this point.
In this example, which builds on the previous ones, we apply specific policies to a distinct credential in the
verification process.
To do this:
1. Specify the required credential type and format in the `request_credentials` array.
2. Add a `policies` array to the same credential object to define policies that apply **only** to that credential.
3. Choose supported policy names and optional arguments from the list of policies
[here](https://docs.walt.id/community-stack/verifier/credential-verification/policies/overview.md).
Here's how it looks in practice:
```json
{
"vp_policies": [
"signature",
"expired"
],
"vc_policies": [
"signature",
"expired"
],
"request_credentials": [
{
"vct": "VerifiableId",
"format": "vc+sd-jwt"
},
{
"vct": "ProofOfResidence",
"format": "vc+sd-jwt"
},
{
"vct": "OpenBadgeCredential",
"format": "vc+sd-jwt",
"policies": [
"signature",
{
"policy": "webhook",
"args": "https://example.org/abc/xyz"
}
]
}
]
}
```
Key points from this example:
- **Per-credential policies**: The `OpenBadgeCredential` object in `request_credentials` is extended with a `policies`
array so that additional checks apply only to this credential.
- **String vs. object policies**:
- `"signature"` does not require arguments and is therefore provided as a **string**.
- `"webhook"` requires an argument and is therefore provided as an **object** with a `policy` and `args` field.
- **Policy reference**: A list of different types of policies and their required arguments can be found
[here](https://docs.walt.id/community-stack/verifier/credential-verification/policies/overview.md).
- **Global policies remain in effect**: The other requested credentials, `"VerifiableId"` and `"ProofOfResidence"`,
do not define custom policies and are verified against the globally defined `vp_policies` and `vc_policies`.
**Initiation Request**
**Option: CURL**
```bash
curl -X 'POST' \
'http://0.0.0.0:7003/openid4vc/verify' \
-H 'accept: */*' \
-H 'authorizeBaseUrl: openid4vp://authorize' \
-H 'responseMode: direct_post' \
-H 'successRedirectUri: https://example.com/success?id=$id' \
-H 'errorRedirectUri: https://example.com/error?id=$id' \
-H 'statusCallbackUri: https://example.com/verificationResult' \
-H 'statusCallbackApiKey: myAPIKey' \
-H 'stateId: myUniqueStateValue' \
-H 'Content-Type: application/json' \
-d '{
"vp_policies": [
"signature",
"expired"
],
"vc_policies": [
"signature",
"expired"
],
"request_credentials": [
{
"vct": "VerifiableId",
"format": "vc+sd-jwt"
},
{
"vct": "ProofOfResidence",
"format": "vc+sd-jwt"
},
{
"vct": "OpenBadgeCredential",
"format": "vc+sd-jwt"
"policies": [
"signature",
{
"policy": "webhook",
"args": "https://example.org/abc/xyz"
}
]
}
]
}'
```
**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 here
`https://wallet.demo.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](#verification-status-policies-response)
- **statusCallbackApiKey** (optional) - If the endpoint you provide via `statusCallbackUri` is protected, you can use
the `statusCallbackApiKey` 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:
- `HAIP`: For the OID4VC HAIP profile.
- `EBSIV3`: For EBSI V3 compliant VP. Learn more [here](https://docs.walt.id/community-stack/verifier/ecosystems/ebsi/overview.md).
- `ISO_18013_7_MDOC`: For mdoc Openid4VP.
**Body Parameters**
- **vp_policies** - Policies applied to the Verifiable Presentation. A list of policies can be
found [here](https://docs.walt.id/community-stack/verifier/credential-verification/policies/overview.md)
- **vc_policies** - Policies applied to all requested credentials. A list of policies can be
found [here](https://docs.walt.id/community-stack/verifier/credential-verification/policies/overview.md).
- **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:
1. **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`.
```json
{ "vct": "test.com/identity_credential", "format": "vc+sd-jwt" }
```
2. **W3C JWT Credential**
- **type** Specifies the type of credential (e.g., `VerifiableID`, `VerifiableDiploma`). This maps to the `type`
attribute in W3C credentials.
- **format**: Describes the format of the credential. For W3C JWT credentials, this would
be `jwt_vc_json`.
```json
{ "type": "ProofOfResidence", "format": "jwt_vc_json" }
```
3. **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`.
```json
{ "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 optional `policies` and `id`
attribute
- **policies**: An array of policies to apply to the specified credential. A list of all policies can be
found [here](https://docs.walt.id/community-stack/verifier/credential-verification/policies/overview.md).
- **id**: Used to set a specific id for the
generated [Presentation Definition](https://identity.foundation/presentation-exchange/spec/v1.0.0/#presentation-definition).
If not set,
the verifier API auto-assigns a generated one.
**Full Examples**
```json
{
"vct": "ProofOfResidence",
"format": "jwt_vc_json",
"policies": [
"schema",
{
"policy": "webhook",
"args": "https://example.org/abc/xyz"
}
],
"id": "test123"
}
```
Upon execution, the system generates a URL, which can be shared directly with users or displayed as a QR code.
### Example Verification Request 4: Implementing a Custom Presentation Definition
In this example, we explore how you can provide your own `input_descriptor` that will be merged with the autogenerated
presentation definition.
To implement a custom presentation definition:
1. Create a custom `input_descriptor` for the
[presentation definition](https://identity.foundation/presentation-exchange/#presentation-definition).
2. Add this `input_descriptor` as part of an object in the `request_credentials` array so it can be merged with the
auto-generated definition (see example below).
**Note:**
Important: Please also provide an id in the `input_descriptor` object.
**Example**
```json
{
"vp_policies": [
...
],
"vc_policies": [
...
],
"request_credentials": [
{ "vct": "VerifiableId", "format": "vc+sd-jwt" },
{
"format": "vc+sd-jwt",
"vct": "https://issuer.com/identity_credential",
"input_descriptor": {
"id": "234",
"constraints": {
"fields": [
{
"path": [
"$.birthdate"
],
"filter": {
"type": "string",
"pattern": ".*"
}
}
]
}
}
}
]
}
```
On execution the verifier API will auto-generate the input descriptor for the `VerifiableId` credential that we specified via
the regular object and merge it with our custom input descriptor of the `identity_credential` to form the final presentation definition.
**Note:**
When using the `input_descriptor` make sure to also provide the `presentation-definition` policy in the
`vp_policies` array to ensure the constraints specified are checked during verification.
### Example Verification Request 5: Using Relational Constraints
The `presentation-definition` policy also implements the
[relational constraint feature](https://identity.foundation/presentation-exchange/#relational-constraint-feature).
These constraints let you define relations between issuer, holder and multiple credentials
within the `input_descriptor`.
#### subject_is_issuer
Use this constraint when you want to ensure that the credential was self issued by the presenter. The verification will only succeed if the issuer DID matches the credential subject.
```json
{
"vp_policies": ["presentation-definition"],
"request_credentials": [
{
"input_descriptor": {
"id": "OpenBadgeCredential",
"format": { "jwt_vc_json": { "alg": ["EdDSA"] } },
"constraints": {
"subject_is_issuer": "required",
"fields": [
{
"path": ["$.vc.type"],
"filter": { "type": "string", "pattern": "OpenBadgeCredential" }
}
]
}
}
}
]
}
```
#### is_holder
`is_holder` ensures the presenter actually holds the credential referenced by
the selected field(s). It checks that the credential was issued to the holder
who is submitting the presentation.
```json
{
"vp_policies": ["presentation-definition"],
"request_credentials": [
{
"input_descriptor": {
"id": "OpenBadgeCredential",
"format": { "jwt_vc_json": { "alg": ["EdDSA"] } },
"constraints": {
"is_holder": [
{ "field_id": ["achname"], "directive": "required" }
],
"fields": [
{
"path": ["$.vc.type"],
"filter": { "type": "string", "pattern": "OpenBadgeCredential" }
},
{
"id": "achname",
"path": ["$.vc.credentialSubject.achievement.name"]
}
]
}
}
}
]
}
```
#### same_subject
This constraint enforces that the fields referenced across several credentials
all relate to the same subject DID. Use it when multiple credentials must refer
to one entity.
```json
{
"vp_policies": ["presentation-definition"],
"request_credentials": [
{
"input_descriptor": {
"id": "OpenBadgeCredential",
"format": { "jwt_vc_json": { "alg": ["EdDSA"] } },
"constraints": {
"same_subject": [
{ "field_id": ["obc_achievement", "udc_degree"], "directive": "required" }
],
"fields": [
{
"path": ["$.vc.type"],
"filter": { "type": "string", "pattern": "OpenBadgeCredential" }
},
{
"id": "obc_achievement",
"path": ["$.vc.credentialSubject.achievement.name"]
}
]
}
}
},
{
"input_descriptor": {
"id": "UniversityDegreeCredential",
"format": { "jwt_vc_json": { "alg": ["EdDSA"] } },
"constraints": {
"fields": [
{
"path": ["$.vc.type"],
"filter": { "type": "string", "pattern": "UniversityDegreeCredential" }
},
{
"id": "udc_degree",
"path": ["$.vc.credentialSubject.degree.name"]
}
]
}
}
}
]
}
```
## Presenting the Credential via walt.id Wallet
Using the URL returned by the verification request, we can fulfill the request using the
[hosted wallet](https://wallet.demo.walt.id) by walt.id. Either show the URL as QR code and scan it with a camera or provide
the URL as is in the
text field below the camera once you click on "Scan to receive or present credentials" in the web wallet credentials
overview page in the top right corner.
To fulfill the presentation request programmatically via the Wallet API, see the guide on
[how to present digital credentials via OID4VP](https://docs.walt.id/community-stack/wallet/credential-exchange/guides/present-digital-credentials-oid4vp.md).
## Retrieving the Verification Status
After the user presents the credential(s), you can retrieve the verification result using the `state` value from
the presentation request URL you shared with the user.
**Extracting the `state` value**
```
openid4vp://authorize?...&state=a07bdb17-7d87-4965-9296-1adefcaaddd9&...
```
**Option: CURL**
**Endpoint:** `GET /openid4vc/session/{state}` | [API Reference](https://verifier.demo.walt.id/swagger/index.html#/Verification/get_openid4vc_session__state_)
##### Example Request
```bash
curl -X 'GET' \
'http://0.0.0.0:7003/openid4vc/session/{state}' \
-H 'accept: application/json'
```
**Path Parameters**
- `state`: _String_ – The `state` value extracted from the presentation request URL.
---
##### Example Response
The response contains the status of the verification policies applied to the credential(s) presented by the user.
- **`verificationResult`** – `true` if all policies were successful, otherwise `false`.
- **`policyResults`** – Container with the results of the policies applied to each credential.
- **`credential`** – Name of the credential that a given set of policy results refers to.
- **`policies`** – Collection of individual policy evaluation results for the credential.
- **`policy`** – Name of an individual policy.
- **`is_success`** – Boolean result of the policy.
```json
{
"id": "5ef9bdg5OvW4",
"presentationDefinition": {
"id": "8ZhlGXPnXaQx",
"input_descriptors": [
{
"id": "https://issuer.demo.walt.id/draft13/identity_credential",
"format": {
"vc+sd-jwt": {}
},
"constraints": {
"fields": [
{
"path": [
"$.vct"
],
"filter": {
"type": "string",
"pattern": "https://issuer.demo.walt.id/draft13/identity_credential"
}
}
],
"limit_disclosure": "required"
}
}
]
},
"tokenResponse": {
"vp_token": "eyJraWQiOiJkaWQ6a2V5Ono2TWtqb1JocTFqU05KZExpcnVTWHJGRnhhZ3FyenRaYVhIcUhHVVRLSmJjTnl3cCN6Nk1ram9SaHExalNOSmRMaXJ1U1hyRkZ4YWdxcnp0WmFYSHFIR1VUS0piY055d3AiLCJ0eXAiOiJ2YytzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJnaXZlbl9uYW1lIjoiSm9obiIsImZhbWlseV9uYW1lIjoiRG9lIiwiZW1haWwiOiJqb2huZG9lQGV4YW1wbGUuY29tIiwicGhvbmVfbnVtYmVyIjoiKzEtMjAyLTU1NS0wMTAxIiwiYWRkcmVzcyI6eyJzdHJlZXRfYWRkcmVzcyI6IjEyMyBNYWluIFN0IiwibG9jYWxpdHkiOiJBbnl0b3duIiwicmVnaW9uIjoiQW55c3RhdGUiLCJjb3VudHJ5IjoiVVMifSwiaXNfb3Zlcl8xOCI6dHJ1ZSwiaXNfb3Zlcl8yMSI6dHJ1ZSwiaXNfb3Zlcl82NSI6dHJ1ZSwiaWQiOiJ1cm46dXVpZDphMGZjM2VmMi1kZDI2LTQ1NTAtOWI0Ni01ZjY1OTM0ZDEzYjAiLCJpYXQiOjE3Nzc0NDIzMTAsIm5iZiI6MTc3NzQ0MjMxMCwiZXhwIjoxODA4OTc4MzEwLCJfc2RfYWxnIjoic2hhLTI1NiIsImlzcyI6ImRpZDprZXk6ejZNa2pvUmhxMWpTTkpkTGlydVNYckZGeGFncXJ6dFphWEhxSEdVVEtKYmNOeXdwIiwiY25mIjp7Imp3ayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoib05kaHFwWlV5TnZRSHdOLS1IOWFxQmJuTnRPYWpSN056bVJzNl8wdWtqZyIsIngiOiJtZTdKbWE3S05hejNMM3N6OVNlRTdqbDkxYW1UbTktd1Y4dXBJcllJYVJRIiwieSI6ImxFQWVDNHRkcnl4MnBCUmJleHlQcE52ejdxSk9wWFBVS3ZKSnZoQkh6QUEifX0sInZjdCI6Imh0dHBzOi8vaXNzdWVyLmRlbW8ud2FsdC5pZC9kcmFmdDEzL2lkZW50aXR5X2NyZWRlbnRpYWwiLCJfc2QiOlsicjVYMDg4RGZYUWxsYlVSbHlmSnRwWVBXbldxWmgyeUgzX3NpUUw3T2JONCJdfQ.q5nb1t_ei5KyVJ-88h7clze-EAmKh8VFIKg-i51GZ0J0aiomLmtGibXs0Sf0yoG4u_PN2uHEwdEz7C64e0khBw~eyJraWQiOiJvTmRocXBaVXlOdlFId04tLUg5YXFCYm5OdE9halI3TnptUnM2XzB1a2pnIiwidHlwIjoia2Irand0IiwiYWxnIjoiRVMyNTYifQ.eyJpYXQiOjE3Nzc0NTA1MzYsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3Q6NzAwMy9vcGVuaWQ0dmMvdmVyaWZ5Iiwibm9uY2UiOiI5Y2UwMTZiYS01YzQxLTRiNjctOWVlMC04MzlhNzYyODVmOTgiLCJzZF9oYXNoIjoiNVNYWGN1TVpUM3RJSFRwRW1wTjV1NEFhX3FVZEgwWnBHdndLOGdhT0lIRSJ9.2BH7psl5N3LqFHlfBsFArHgVlOFvqdSNwrgTcNpCd_P_yUESbIrWGXu-CI840xmgFFkjISUz413W2IpaYre1-A",
"presentation_submission": {
"id": "8ZhlGXPnXaQx",
"definition_id": "8ZhlGXPnXaQx",
"descriptor_map": [
{
"id": "8ZhlGXPnXaQx",
"format": "vc+sd-jwt",
"path": "$"
}
]
},
"state": "5ef9bdg5OvW4"
},
"verificationResult": true,
"policyResults": {
"results": [
{
"credential": "eyJraWQiOiJkaWQ6a2V5Ono2TWtqb1JocTFqU05KZExpcnVTWHJGRnhhZ3FyenRaYVhIcUhHVVRLSmJjTnl3cCN6Nk1ram9SaHExalNOSmRMaXJ1U1hyRkZ4YWdxcnp0WmFYSHFIR1VUS0piY055d3AiLCJ0eXAiOiJ2YytzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJnaXZlbl9uYW1lIjoiSm9obiIsImZhbWlseV9uYW1lIjoiRG9lIiwiZW1haWwiOiJqb2huZG9lQGV4YW1wbGUuY29tIiwicGhvbmVfbnVtYmVyIjoiKzEtMjAyLTU1NS0wMTAxIiwiYWRkcmVzcyI6eyJzdHJlZXRfYWRkcmVzcyI6IjEyMyBNYWluIFN0IiwibG9jYWxpdHkiOiJBbnl0b3duIiwicmVnaW9uIjoiQW55c3RhdGUiLCJjb3VudHJ5IjoiVVMifSwiaXNfb3Zlcl8xOCI6dHJ1ZSwiaXNfb3Zlcl8yMSI6dHJ1ZSwiaXNfb3Zlcl82NSI6dHJ1ZSwiaWQiOiJ1cm46dXVpZDphMGZjM2VmMi1kZDI2LTQ1NTAtOWI0Ni01ZjY1OTM0ZDEzYjAiLCJpYXQiOjE3Nzc0NDIzMTAsIm5iZiI6MTc3NzQ0MjMxMCwiZXhwIjoxODA4OTc4MzEwLCJfc2RfYWxnIjoic2hhLTI1NiIsImlzcyI6ImRpZDprZXk6ejZNa2pvUmhxMWpTTkpkTGlydVNYckZGeGFncXJ6dFphWEhxSEdVVEtKYmNOeXdwIiwiY25mIjp7Imp3ayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoib05kaHFwWlV5TnZRSHdOLS1IOWFxQmJuTnRPYWpSN056bVJzNl8wdWtqZyIsIngiOiJtZTdKbWE3S05hejNMM3N6OVNlRTdqbDkxYW1UbTktd1Y4dXBJcllJYVJRIiwieSI6ImxFQWVDNHRkcnl4MnBCUmJleHlQcE52ejdxSk9wWFBVS3ZKSnZoQkh6QUEifX0sInZjdCI6Imh0dHBzOi8vaXNzdWVyLmRlbW8ud2FsdC5pZC9kcmFmdDEzL2lkZW50aXR5X2NyZWRlbnRpYWwiLCJfc2QiOlsicjVYMDg4RGZYUWxsYlVSbHlmSnRwWVBXbldxWmgyeUgzX3NpUUw3T2JONCJdfQ.q5nb1t_ei5KyVJ-88h7clze-EAmKh8VFIKg-i51GZ0J0aiomLmtGibXs0Sf0yoG4u_PN2uHEwdEz7C64e0khBw~eyJraWQiOiJvTmRocXBaVXlOdlFId04tLUg5YXFCYm5OdE9halI3TnptUnM2XzB1a2pnIiwidHlwIjoia2Irand0IiwiYWxnIjoiRVMyNTYifQ.eyJpYXQiOjE3Nzc0NTA1MzYsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3Q6NzAwMy9vcGVuaWQ0dmMvdmVyaWZ5Iiwibm9uY2UiOiI5Y2UwMTZiYS01YzQxLTRiNjctOWVlMC04MzlhNzYyODVmOTgiLCJzZF9oYXNoIjoiNVNYWGN1TVpUM3RJSFRwRW1wTjV1NEFhX3FVZEgwWnBHdndLOGdhT0lIRSJ9.2BH7psl5N3LqFHlfBsFArHgVlOFvqdSNwrgTcNpCd_P_yUESbIrWGXu-CI840xmgFFkjISUz413W2IpaYre1-A",
"policyResults": [
{
"policy": "signature_sd-jwt-vc",
"description": "Checks a SD-JWT-VC credential by verifying its cryptographic signature using the key referenced by the DID in `iss`.",
"is_success": true,
"result": {
"given_name": "John",
"family_name": "Doe",
"email": "johndoe@example.com",
"phone_number": "+1-202-555-0101",
"address": {
"street_address": "123 Main St",
"locality": "Anytown",
"region": "Anystate",
"country": "US"
},
"is_over_18": true,
"is_over_21": true,
"is_over_65": true,
"id": "urn:uuid:a0fc3ef2-dd26-4550-9b46-5f65934d13b0",
"iat": 1777442310,
"nbf": 1777442310,
"exp": 1808978310,
"_sd_alg": "sha-256",
"iss": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp",
"cnf": {
"jwk": {
"kty": "EC",
"crv": "P-256",
"kid": "oNdhqpZUyNvQHwN--H9aqBbnNtOajR7NzmRs6_0ukjg",
"x": "me7Jma7KNaz3L3sz9SeE7jl91amTm9-wV8upIrYIaRQ",
"y": "lEAeC4tdryx2pBRbexyPpNvz7qJOpXPUKvJJvhBHzAA"
}
},
"vct": "https://issuer.demo.walt.id/draft13/identity_credential",
"_sd": [
"r5X088DfXQllbURlyfJtpYPWnWqZh2yH3_siQL7ObN4"
]
}
}
]
}
],
"time": "PT0.028667167S",
"policiesRun": 1
}
}
```
## Inspecting Presented Credentials
After a successful presentation session (`verificationResult` is `true`), you can retrieve a decoded and formatted
view of all credentials presented by the holder.
You can choose between two view modes:
- **`simple`**: Recommended default view; provides a concise, human-friendly representation suitable for most use cases.
- **`verbose`**: Provides additional technical detail useful for debugging, auditing, and in-depth inspection.
### Identity Credential With Two Disclosable Claims
In the following we provide an example that involves the presentation of an IETF SD-JWT-VC Identity Credential
with two disclosable claims, namely, the `family_name` and `birthdate` properties.
**Option: Simple**
**Endpoint:** `GET /openid4vc/session/{id}/presented-credentials` | [API Reference](https://verifier.demo.walt.id/swagger/index.html#/Credential%20Verification/get_openid4vc_session__id__presented_credentials)
##### Example Request
```bash
curl -X 'GET' \
'http://0.0.0.0:7003/openid4vc/session/{id}/presented-credentials' \
-H 'accept: application/json'
```
**Path Parameters**
- `id`: _String_ – The identifier of the presentation session whose credentials should be retrieved.
**Query Parameters**
- `viewMode`: _String (optional)_ – Controls how detailed the response will be. Accepted values:
- `simple` _(default)_ — Returns decoded `header` and `payload` for each credential. The go-to choice for most cases.
- `verbose` — Returns the full compact JWT (`raw`), decoded headers, full and undisclosed payloads, and selective disclosure maps. Useful for debugging and auditing.
---
##### Example Response
```json
{
"credentialsByFormat": {
"vc+sd-jwt": [
{
"type": "sd_jwt_vc_view_simple",
"vc": {
"header": {
"x5c": [
"-----BEGIN CERTIFICATE-----\nMIIBeTCCAR8CFHrWgrGl5KdefSvRQhR+aoqdf48+MAoGCCqGSM49BAMCMBcxFTATBgNVBAMMDE1ET0MgUk9PVCBDQTAgFw0yNTA1MTQxNDA4MDlaGA8yMDc1MDUwMjE0MDgwOVowZTELMAkGA1UEBhMCQVQxDzANBgNVBAgMBlZpZW5uYTEPMA0GA1UEBwwGVmllbm5hMRAwDgYDVQQKDAd3YWx0LmlkMRAwDgYDVQQLDAd3YWx0LmlkMRAwDgYDVQQDDAd3YWx0LmlzMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEG0RINBiF+oQUD3d5DGnegQuXenI29JDaMGoMvioKRBN53d4UazakS2unu8BnsEtxutS2kqRhYBPYk9RAriU3gTAKBggqhkjOPQQDAgNIADBFAiAOMwM7hH7q9Di+mT6qCi4LvB+kH8OxMheIrZ2eRPxtDQIhALHzTxwvN8Udt0Z2Cpo8JBihqacfeXkIxVAO8XkxmXhB\n-----END CERTIFICATE-----"
],
"kid": "9vuaJyUxRx4KmHyoZ9kjJxMs_mjpnnf-mPM9nPMG51A",
"typ": "vc+sd-jwt",
"alg": "ES256"
},
"payload": {
"given_name": "John",
"email": "johndoe@example.com",
"phone_number": "+1-202-555-0101",
"address": {
"street_address": "123 Main St",
"locality": "Anytown",
"region": "Anystate",
"country": "US"
},
"is_over_18": true,
"is_over_21": true,
"is_over_65": true,
"id": "urn:uuid:f4411ed9-d3eb-4c5d-971a-ffb2872b616d",
"iat": 1752648454,
"nbf": 1752648454,
"exp": 1784184454,
"iss": "http://localhost:22222/draft13",
"cnf": {
"jwk": {
"kty": "EC",
"crv": "P-256",
"kid": "pE7QRzzJ3utoKynTja7JyeAzcQefzZO24rEdWwkGtT0",
"x": "oRIMe5XVIcJGDSW10r19w5jym8W-btkxChzecsuXUhc",
"y": "XQrS0p2BJf_WXIcn-RAdFoX17DJsefpED3Ct4t4W-X0"
}
},
"vct": "http://localhost:22222/identity_credential",
"display": [],
"family_name": "Doe",
"birthdate": "1940-01-01"
}
},
"keyBinding": {
"header": {
"kid": "pE7QRzzJ3utoKynTja7JyeAzcQefzZO24rEdWwkGtT0",
"typ": "kb+jwt",
"alg": "ES256"
},
"payload": {
"iat": 1752648455,
"aud": "http://localhost:22222/openid4vc/verify",
"nonce": "196b37da-77d0-463f-897c-5a0c1c6d5623",
"sd_hash": "iywnYcva2cWTVcclB1gECoI-x-y3IGz07snR_4QiWyE"
}
}
}
]
},
"viewMode": "simple"
}
```
**Response Fields**
- `viewMode`: _String_ – Echoed request value (or the default if not specified).
- `credentialsByFormat`: _Object_ – A map where each key is a presentation format (`vc+sd-jwt`, `jwt_vc_json`, `mso_mdoc`) and the value is an array of decoded presented credentials. Each `sd_jwt_vc` entry in `simple` view contains:
- `vc`: _Object_ – Decoded IETF SD-JWT-VC credential:
- `header`: _Object_ – The JWT `header` section.
- `payload`: _Object_ – The JWT `payload` with all disclosures substituted.
- `keyBinding`: _Object_ – Decoded Key Binding JWT (if requested/provided):
- `header`: _Object_ – The JWT `header` section.
- `payload`: _Object_ – The JWT `payload` section.
**Option: Verbose**
**Endpoint:** `GET /openid4vc/session/{id}/presented-credentials` | [API Reference](https://verifier.demo.walt.id/swagger/index.html#/Credential%20Verification/get_openid4vc_session__id__presented_credentials)
##### Example Request
```bash
curl -X 'GET' \
'http://0.0.0.0:7003/openid4vc/session/{id}/presented-credentials?viewMode=verbose' \
-H 'accept: application/json'
```
**Path Parameters**
- `id`: _String_ – The identifier of the presentation session whose credentials should be retrieved.
**Query Parameters**
- `viewMode`: _String (optional)_ – Controls how detailed the response will be. Accepted values:
- `simple` _(default)_ — Returns decoded `header` and `payload` for each credential. The go-to choice for most cases.
- `verbose` — Returns the full compact JWT (`raw`), decoded headers, full and undisclosed payloads, and selective disclosure maps. Useful for debugging and auditing.
---
##### Example Response
```json
{
"credentialsByFormat": {
"vc+sd-jwt": [
{
"type": "sd_jwt_vc_view_verbose",
"raw": "eyJ4NWMiOlsiLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tXG5NSUlCZVRDQ0FSOENGSHJXZ3JHbDVLZGVmU3ZSUWhSK2FvcWRmNDgrTUFvR0NDcUdTTTQ5QkFNQ01CY3hGVEFUQmdOVkJBTU1ERTFFVDBNZ1VrOVBWQ0JEUVRBZ0Z3MHlOVEExTVRReE5EQTRNRGxhR0E4eU1EYzFNRFV3TWpFME1EZ3dPVm93WlRFTE1Ba0dBMVVFQmhNQ1FWUXhEekFOQmdOVkJBZ01CbFpwWlc1dVlURVBNQTBHQTFVRUJ3d0dWbWxsYm01aE1SQXdEZ1lEVlFRS0RBZDNZV3gwTG1sa01SQXdEZ1lEVlFRTERBZDNZV3gwTG1sa01SQXdEZ1lEVlFRRERBZDNZV3gwTG1sek1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRUcwUklOQmlGK29RVUQzZDVER25lZ1F1WGVuSTI5SkRhTUdvTXZpb0tSQk41M2Q0VWF6YWtTMnVudThCbnNFdHh1dFMya3FSaFlCUFlrOVJBcmlVM2dUQUtCZ2dxaGtqT1BRUURBZ05JQURCRkFpQU9Nd003aEg3cTlEaSttVDZxQ2k0THZCK2tIOE94TWhlSXJaMmVSUHh0RFFJaEFMSHpUeHd2TjhVZHQwWjJDcG84SkJpaHFhY2ZlWGtJeFZBTzhYa3htWGhCXG4tLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tIl0sImtpZCI6Ijl2dWFKeVV4Ung0S21IeW9aOWtqSnhNc19tanBubmYtbVBNOW5QTUc1MUEiLCJ0eXAiOiJ2YytzZC1qd3QiLCJhbGciOiJFUzI1NiJ9.eyJnaXZlbl9uYW1lIjoiSm9obiIsImVtYWlsIjoiam9obmRvZUBleGFtcGxlLmNvbSIsInBob25lX251bWJlciI6IisxLTIwMi01NTUtMDEwMSIsImFkZHJlc3MiOnsic3RyZWV0X2FkZHJlc3MiOiIxMjMgTWFpbiBTdCIsImxvY2FsaXR5IjoiQW55dG93biIsInJlZ2lvbiI6IkFueXN0YXRlIiwiY291bnRyeSI6IlVTIn0sImlzX292ZXJfMTgiOnRydWUsImlzX292ZXJfMjEiOnRydWUsImlzX292ZXJfNjUiOnRydWUsImlkIjoidXJuOnV1aWQ6ZjQ0MTFlZDktZDNlYi00YzVkLTk3MWEtZmZiMjg3MmI2MTZkIiwiaWF0IjoxNzUyNjQ4NDU0LCJuYmYiOjE3NTI2NDg0NTQsImV4cCI6MTc4NDE4NDQ1NCwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDoyMjIyMi9kcmFmdDEzIiwiY25mIjp7Imp3ayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoicEU3UVJ6ekozdXRvS3luVGphN0p5ZUF6Y1FlZnpaTzI0ckVkV3drR3RUMCIsIngiOiJvUklNZTVYVkljSkdEU1cxMHIxOXc1anltOFctYnRreENoemVjc3VYVWhjIiwieSI6IlhRclMwcDJCSmZfV1hJY24tUkFkRm9YMTdESnNlZnBFRDNDdDR0NFctWDAifX0sInZjdCI6Imh0dHA6Ly9sb2NhbGhvc3Q6MjIyMjIvaWRlbnRpdHlfY3JlZGVudGlhbCIsImRpc3BsYXkiOltdLCJfc2QiOlsiV0NzTVZhMWxtUlBuRE1FSXVtSkg1UjJ3MDdLek5sRXlmLXFibS1sdlJlQSIsInZ5Q2trOG5rTlpjU05CcXpMeWhTekRwMUF1d25TOTExdWtyRmdtRlpQRkkiXX0.c2smAamo4kc6YxE5-SqF5ZIVIabdm5TZxHcESC3MjpoKaEF3ZL6HO8XeOid-0wD0XuPHscqjilmyiqFkk6yuaw~WyJjdFdidkpYeGJmbVRzMlJ3UFBXZ2d3IiwiZmFtaWx5X25hbWUiLCJEb2UiXQ~WyJ6cWRyTmZSUHp4WWl2YnZaZXZFaDF3IiwiYmlydGhkYXRlIiwiMTk0MC0wMS0wMSJd~eyJraWQiOiJwRTdRUnp6SjN1dG9LeW5UamE3SnllQXpjUWVmelpPMjRyRWRXd2tHdFQwIiwidHlwIjoia2Irand0IiwiYWxnIjoiRVMyNTYifQ.eyJpYXQiOjE3NTI2NDg0NTUsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3Q6MjIyMjIvb3BlbmlkNHZjL3ZlcmlmeSIsIm5vbmNlIjoiMTk2YjM3ZGEtNzdkMC00NjNmLTg5N2MtNWEwYzFjNmQ1NjIzIiwic2RfaGFzaCI6Iml5d25ZY3ZhMmNXVFZjY2xCMWdFQ29JLXgteTNJR3owN3NuUl80UWlXeUUifQ.XOZ_Y5n8OVuVS7OqkJz6O26ppf767ryIt8sVxxpQKFqYgLEMRd--WsVepvs6JQWiccavm4iGwlAokbw1ESPl6Q",
"vc": {
"header": {
"x5c": [
"-----BEGIN CERTIFICATE-----\nMIIBeTCCAR8CFHrWgrGl5KdefSvRQhR+aoqdf48+MAoGCCqGSM49BAMCMBcxFTATBgNVBAMMDE1ET0MgUk9PVCBDQTAgFw0yNTA1MTQxNDA4MDlaGA8yMDc1MDUwMjE0MDgwOVowZTELMAkGA1UEBhMCQVQxDzANBgNVBAgMBlZpZW5uYTEPMA0GA1UEBwwGVmllbm5hMRAwDgYDVQQKDAd3YWx0LmlkMRAwDgYDVQQLDAd3YWx0LmlkMRAwDgYDVQQDDAd3YWx0LmlzMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEG0RINBiF+oQUD3d5DGnegQuXenI29JDaMGoMvioKRBN53d4UazakS2unu8BnsEtxutS2kqRhYBPYk9RAriU3gTAKBggqhkjOPQQDAgNIADBFAiAOMwM7hH7q9Di+mT6qCi4LvB+kH8OxMheIrZ2eRPxtDQIhALHzTxwvN8Udt0Z2Cpo8JBihqacfeXkIxVAO8XkxmXhB\n-----END CERTIFICATE-----"
],
"kid": "9vuaJyUxRx4KmHyoZ9kjJxMs_mjpnnf-mPM9nPMG51A",
"typ": "vc+sd-jwt",
"alg": "ES256"
},
"fullPayload": {
"given_name": "John",
"email": "johndoe@example.com",
"phone_number": "+1-202-555-0101",
"address": {
"street_address": "123 Main St",
"locality": "Anytown",
"region": "Anystate",
"country": "US"
},
"is_over_18": true,
"is_over_21": true,
"is_over_65": true,
"id": "urn:uuid:f4411ed9-d3eb-4c5d-971a-ffb2872b616d",
"iat": 1752648454,
"nbf": 1752648454,
"exp": 1784184454,
"iss": "http://localhost:22222/draft13",
"cnf": {
"jwk": {
"kty": "EC",
"crv": "P-256",
"kid": "pE7QRzzJ3utoKynTja7JyeAzcQefzZO24rEdWwkGtT0",
"x": "oRIMe5XVIcJGDSW10r19w5jym8W-btkxChzecsuXUhc",
"y": "XQrS0p2BJf_WXIcn-RAdFoX17DJsefpED3Ct4t4W-X0"
}
},
"vct": "http://localhost:22222/identity_credential",
"display": [],
"family_name": "Doe",
"birthdate": "1940-01-01"
},
"undisclosedPayload": {
"given_name": "John",
"email": "johndoe@example.com",
"phone_number": "+1-202-555-0101",
"address": {
"street_address": "123 Main St",
"locality": "Anytown",
"region": "Anystate",
"country": "US"
},
"is_over_18": true,
"is_over_21": true,
"is_over_65": true,
"id": "urn:uuid:f4411ed9-d3eb-4c5d-971a-ffb2872b616d",
"iat": 1752648454,
"nbf": 1752648454,
"exp": 1784184454,
"iss": "http://localhost:22222/draft13",
"cnf": {
"jwk": {
"kty": "EC",
"crv": "P-256",
"kid": "pE7QRzzJ3utoKynTja7JyeAzcQefzZO24rEdWwkGtT0",
"x": "oRIMe5XVIcJGDSW10r19w5jym8W-btkxChzecsuXUhc",
"y": "XQrS0p2BJf_WXIcn-RAdFoX17DJsefpED3Ct4t4W-X0"
}
},
"vct": "http://localhost:22222/identity_credential",
"display": [],
"_sd": [
"WCsMVa1lmRPnDMEIumJH5R2w07KzNlEyf-qbm-lvReA",
"vyCkk8nkNZcSNBqzLyhSzDp1AuwnS911ukrFgmFZPFI"
]
},
"disclosures": {
"WCsMVa1lmRPnDMEIumJH5R2w07KzNlEyf-qbm-lvReA": {
"disclosure": "WyJjdFdidkpYeGJmbVRzMlJ3UFBXZ2d3IiwiZmFtaWx5X25hbWUiLCJEb2UiXQ",
"salt": "ctWbvJXxbfmTs2RwPPWggw",
"key": "family_name",
"value": "Doe"
},
"vyCkk8nkNZcSNBqzLyhSzDp1AuwnS911ukrFgmFZPFI": {
"disclosure": "WyJ6cWRyTmZSUHp4WWl2YnZaZXZFaDF3IiwiYmlydGhkYXRlIiwiMTk0MC0wMS0wMSJd",
"salt": "zqdrNfRPzxYivbvZevEh1w",
"key": "birthdate",
"value": "1940-01-01"
}
}
},
"keyBinding": {
"header": {
"kid": "pE7QRzzJ3utoKynTja7JyeAzcQefzZO24rEdWwkGtT0",
"typ": "kb+jwt",
"alg": "ES256"
},
"payload": {
"iat": 1752648455,
"aud": "http://localhost:22222/openid4vc/verify",
"nonce": "196b37da-77d0-463f-897c-5a0c1c6d5623",
"sd_hash": "iywnYcva2cWTVcclB1gECoI-x-y3IGz07snR_4QiWyE"
}
}
}
]
},
"viewMode": "verbose"
}
```
**Response Fields**
- `viewMode`: _String_ – Echoed request value (or the default if not specified).
- `credentialsByFormat`: _Object_ – A map where each key is a presentation format (`vc+sd-jwt`, `jwt_vc_json`, `mso_mdoc`) and the value is an array of decoded presented credentials. Each `sd_jwt_vc` entry in `verbose` view contains:
- `raw`: _String_ – The compact serialized IETF SD-JWT-VC.
- `vc`: _Object_ – Verbosely decoded IETF SD-JWT-VC:
- `header`: _Object_ – The JWT `header` section.
- `fullPayload`: _Object_ – The JWT `payload` with all disclosures substituted.
- `undisclosedPayload`: _Object_ – The JWT `payload` without any disclosures substituted.
- `disclosures`: _Object_ – A map between selective disclosure (hashed) values and their decoded representation. Only present if disclosable claims were provided.
- `keyBinding`: _Object_ – Decoded Key Binding JWT (if requested/provided):
- `header`: _Object_ – The JWT `header` section.
- `payload`: _Object_ – The JWT `payload` section.
--------------------------------------------------------------------------------
PAGE: How to Verify Mobile Driver’s Licenses (ISO/IEC 18013-7 mDL) via OID4VP with walt.id
URL: https://docs.walt.id/community-stack/verifier/credential-verification/mdl-oid4vc
RAW: https://docs.walt.id/community-stack/verifier/credential-verification/mdl-oid4vc.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# How to Verify Mobile Driver's Licenses (ISO/IEC 18013-7 mDL) via OID4VP with walt.id
**TL;DR**
**What you:**
- Create verification requests for mDL credentials with specific claim requirements
- Configure trusted IACA root certificates for mDL verification
- Retrieve verification status, policy results and inspect presented mDL credentials
**Relevant concepts:**
- [Mobile Driver's License (mDL)](https://docs.walt.id/concepts/digital-credentials/mdoc-mdl-iso.md) – ISO/IEC 18013-5/7 standard for mobile driver's licenses
- [OpenID4VP](https://docs.walt.id/concepts/data-exchange-protocols/openid4vp.md) – Protocol for requesting and receiving verifiable presentations
This guide provides a comprehensive walkthrough for verifying an mDL based on
the [ISO/IEC 18103-7 standard](https://www.iso.org/standard/91154.html) using
the walt.id Verifier API. The verification process will utilize the OID4VP protocol.
**mDL**: A digital equivalent of physical driver's license.
**OID4VP**: A protocol specifying how parties can present verifiable credentials in a way that's consistent
and secure across platforms ensuring interoperability.
## Verification Process
1. Set mDL as the credential type to request from a user.
2. Specify the claims from the mDL that are subject to presentation.
3. Optionally provide a success and failure redirect URL, which the user will be redirected to after the verification
process is completed.
After you have provided the required information, the Verifier API:
1. Generates an appropriate [Presentation Definition](https://identity.foundation/presentation-exchange/#presentation-definition).
2. Returns a URL which can passed to a compliant wallet to fulfill the request.
If you have provided a success or failure redirect URL, the user will be redirected to that URL. You can then access the
verification results by using the id of the verification session, which can be found in the URL generated by the API, as
well as in the query or path parameters of the redirect URL.
### Example Verification Request
**Option: CURL**
```bash
curl -X 'POST' \
'https://verifier.portal.test.waltid.cloud/openid4vc/verify' \
-H 'accept: */*' \
-H 'authorizeBaseUrl: mdoc-openid4vp://' \
-H 'responseMode: direct_post.jwt' \
-H 'successRedirectUri: https://example.com/success?id=$id' \
-H 'errorRedirectUri: https://example.com/error?id=$id' \
-H 'statusCallbackUri: https://example.com/verificationResult' \
-H 'statusCallbackApiKey: myAPIKey' \
-H 'stateId: myUniqueStateValue' \
-H 'Content-Type: application/json' \
-d '{
"request_credentials": [
{
"id": "mDL-request",
"input_descriptor": {
"id": "org.iso.18013.5.1.mDL",
"format": {
"mso_mdoc": {
"alg": [
"ES256"
]
}
},
"constraints": {
"fields": [
{
"path": [
"$['org.iso.18013.5.1']['birth_date']"
],
"intent_to_retain": false
},
{
"path": [
"$['org.iso.18013.5.1']['issue_date']"
],
"intent_to_retain": false
},
{
"path": [
"$['org.iso.18013.5.1']['expiry_date']"
],
"intent_to_retain": false
}
],
"limit_disclosure": "required"
}
}
}
],
"trusted_root_cas": [
"-----BEGIN CERTIFICATE-----\nMIIBtDCCAVmgAwIBAgIUAOXLkeu9penFRno6oDcOBgT1odYwCgYIKoZIzj0EAwIwKDELMAkGA1UEBhMCQVQxGTAXBgNVBAMMEFdhbHRpZCBUZXN0IElBQ0EwHhcNMjUwNjAyMDYzOTQ0WhcNNDAwNTI5MDYzOTQ0WjAoMQswCQYDVQQGEwJBVDEZMBcGA1UEAwwQV2FsdGlkIFRlc3QgSUFDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAZGrRN7Oeanhn7MOaGU6HhaCt8ZMySk/nRHefLbRq8lChr+PS6JqpCJ503sEvByXzPDgPsp0urKg/y0E+F7q9+jYTBfMB0GA1UdDgQWBBTxCn2nWMrE70qXb614U14BweY2azASBgNVHRMBAf8ECDAGAQH/AgEAMBoGA1UdEgQTMBGGD2h0dHBzOi8vd2FsdC5pZDAOBgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwIDSQAwRgIhAOM37BjC48KhsSlU6mdJwlTLrad9VzlXVKc1GmjoCNm1AiEAkFRJalpz62QCOby9l7Vkq0LAdWVKiFMd0DmSxjsdT2U=\n-----END CERTIFICATE-----\n"
],
"openid_profile": "ISO_18013_7_MDOC"
}'
```
**Header Parameters**
- **authorizeBaseUrl** - should be set `mdoc-openid4vp://`.
- **responseMode** - must be set to `direct_post.jwt`.
- **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](#verification-status-policies-response)
- **statusCallbackApiKey** (optional) - If the endpoint you provide via `statusCallbackUri` is protected, you can use
the `statusCallbackApiKey` 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.
**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:
1. **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`.
```json
{ "doc_type": "org.iso.18013.5.1.mDL", "format": "mso_mdoc" }
```
2. **W3C JWT Credential**
- **type** Specifies the type of credential (e.g., `VerifiableID`, `VerifiableDiploma`). This maps to the `type`
attribute in W3C credentials.
- **format**: Describes the format of the credential. For W3C JWT credentials, this would
be `jwt_vc_json`.
```json
{ "type": "ProofOfResidence", "format": "jwt_vc_json" }
```
3. **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`.
```json
{ "vct": "test.com/identity_credential", "format": "vc+sd-jwt" }
```
**Optional Parameters**
Next to describing the type and format of the credential, the objects also take an optional `policies` and `id`
attribute
- **policies**: An array of policies to apply to the specified credential. A list of all policies can be
found [here](https://docs.walt.id/community-stack/verifier/credential-verification/policies/overview.md).
- **id**: Used to set a specific id for the
generated [Presentation Definition](https://identity.foundation/presentation-exchange/spec/v1.0.0/#presentation-definition).
If not set,
the verifier API auto-assigns a generated one.
**Full Examples**
```json
{
"type": "ProofOfResidence",
"format": "jwt_vc_json",
"policies": [
"schema",
{
"policy": "webhook",
"args": "https://example.org/abc/xyz"
}
],
"id": "test123"
}
```
- **trusted_root_cas** - List of trusted IACA root certificates used to verify the authenticity of the presented mDL
credential.
- **openid_profile** - should be set to `ISO_18013_7_MDOC`.
Once the Verifier API receives the presented mDL from the holder, it will apply the following checks:
1. Validate the certificate of the document signer (credential issuer) included in the MSO header.
2. Validate that the certificate of the document signer is issued by one of the designated trusted IACAs.
3. Perform various other validity checks involving the certificates (e.g., validity period).
4. Verify the digital signature of the `IssuerAuth` structure.
5. Verify the digital signature of the `DeviceAuth` structure.
6. Verify that every `IssuerSignedItem` received from the holder has not been tampered with.
7. Verify the `doc_type`.
8. Validate the elements in the MSO's `ValidityInfo` structure.
## Presenting the mDL via walt.id Wallet
Using the URL returned by the verification request, you can fulfill the request using the
[hosted wallet](https://wallet.demo.walt.id) by walt.id.
To present the mDL via the walt.id Wallet:
1. Open the hosted wallet and click **"Scan to receive or present credentials"** in the top-right corner of the credentials
overview page.
2. Choose one of the following options:
- Show the URL as a QR code and scan it with a camera.
- Paste the URL into the text field below the camera.
## Retrieving the Verification Status
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=a07bdb17-7d87-4965-9296-1adefcaaddd9...
```
Making the call to receive the verification result
```bash
curl -X 'GET' \
'https://verifier.portal.test.waltid.cloud/openid4vc/session/$state' \
-H 'accept: */*'
```
## Verification Status Response
The response will contain, among others, the status of the verification following the submission of the
presentation from the mDL holder. A sample verification status response (based on the sample presentation request
introduced above) is as follows:
```json
{
"id": "6V0d0knr5cEk",
"presentationDefinition": {
"id": "2nQOQaPb7bi",
"input_descriptors": [
{
"id": "org.iso.18013.5.1.mDL",
"format": {
"mso_mdoc": {
"alg": [
"ES256"
]
}
},
"constraints": {
"fields": [
{
"path": [
"$['org.iso.18013.5.1']['birth_date']"
],
"intent_to_retain": false
},
{
"path": [
"$['org.iso.18013.5.1']['issue_date']"
],
"intent_to_retain": false
},
{
"path": [
"$['org.iso.18013.5.1']['expiry_date']"
],
"intent_to_retain": false
}
],
"limit_disclosure": "required"
}
}
],
"customParameters": {}
},
"tokenResponse": {
"vp_token": "o2d2ZXJzaW9uYzEuMGlkb2N1bWVudHOBo2dkb2NUeXBldW9yZy5pc28uMTgwMTMuNS4xLm1ETGxpc3N1ZXJTaWduZWSiam5hbWVTcGFjZXOhcW9yZy5pc28uMTgwMTMuNS4xg9gYWFikaGRpZ2VzdElEAmZyYW5kb21Qw62gF77o6MEYazSd6hmS43FlbGVtZW50SWRlbnRpZmllcmpiaXJ0aF9kYXRlbGVsZW1lbnRWYWx1ZWoxOTg2LTAzLTIy2BhYWKRoZGlnZXN0SUQDZnJhbmRvbVBQWS2bHMQpgo6sQEcXyEGhcWVsZW1lbnRJZGVudGlmaWVyamlzc3VlX2RhdGVsZWxlbWVudFZhbHVlajIwMTktMTAtMjDYGFhZpGhkaWdlc3RJRARmcmFuZG9tUNSMY-SYGOzgghW85RIykexxZWxlbWVudElkZW50aWZpZXJrZXhwaXJ5X2RhdGVsZWxlbWVudFZhbHVlajIwMjQtMTAtMjBqaXNzdWVyQXV0aIRDoQEmoRghWQINMIICCTCCAbCgAwIBAgIUfqyiArJZoX7M61_473UAVi2_UpgwCgYIKoZIzj0EAwIwKDELMAkGA1UEBhMCQVQxGTAXBgNVBAMMEFdhbHRpZCBUZXN0IElBQ0EwHhcNMjUwNjAyMDY0MTEzWhcNMjYwOTAyMDY0MTEzWjAzMQswCQYDVQQGEwJBVDEkMCIGA1UEAwwbV2FsdGlkIFRlc3QgRG9jdW1lbnQgU2lnbmVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPzp6eVSAdXERqAp8q8OuDEhl2ILGAaoaQXTJ2sD2g5Xp3CFQDMrMpR_SQ0jt_jTOqExk1PRzjQ79aKpIsJM1mqOBrDCBqTAfBgNVHSMEGDAWgBTxCn2nWMrE70qXb614U14BweY2azAdBgNVHQ4EFgQUx5qkOLC4lpl1xpYZGmF9HLxtp0gwDgYDVR0PAQH_BAQDAgeAMBoGA1UdEgQTMBGGD2h0dHBzOi8vd2FsdC5pZDAVBgNVHSUBAf8ECzAJBgcogYxdBQECMCQGA1UdHwQdMBswGaAXoBWGE2h0dHBzOi8vd2FsdC5pZC9jcmwwCgYIKoZIzj0EAwIDRwAwRAIgHTap3c6yCUNhDVfZWBPMKj9dCWZbrME03kh9NJTbw1ECIAvVvuGll9O21eR16SkJHHAA1pPcovhcTvF9fz9cc66MWQLb2BhZAtamZ3ZlcnNpb25jMS4wb2RpZ2VzdEFsZ29yaXRobWdTSEEtMjU2bHZhbHVlRGlnZXN0c6Fxb3JnLmlzby4xODAxMy41LjGrAFggNl8eLVqR74wd3QOna5iBSVtgkxUy1evZYyf-kzBAhUIBWCBqENuJTWRajrLgO4G0wCg_zbGVkYYngwbSuCM6pBDPEQJYIDgC-m4uZJFhvUiZqgSyjKZSnEg9xfKnOGiSq3kcH8EcA1ggjFwxgydoUg3FIablekLv1KPHumKUxnjSZDN4h8RgUZQEWCBfdbAm68joDWrtS97zTBSyOHSX6L8LNGycJxiKQWmWnQVYICX7ygfWVlNg5BffKs0oSWNPF1bXTB56p-bGJvo7cYEZBlgg7v7IxOCReToypDwxgg2Q_w7rXrU78o9p5sHajsne0NUHWCDChJxVL8ls1PB_WSUpY0LMGb4MfnaSwXsDQy5_TnKRoghYIJt90wUKMxmz1OctNn-YjFhg02-QomCoSJ2M_IPgh4aSCVggqgkamVdQM2GwLUCu8T-pTWKo9NhiHuMGPco3seU3SnQKWCBJgG1p_b_JO_CcXoi4VpIToNDZCKvO6m88jiMFK_4RJW1kZXZpY2VLZXlJbmZvoWlkZXZpY2VLZXmkAQIgASFYIAGMglQiByggcIffBlQbNiRFfsGh3vmPhekEgmpTsdpqIlggG10cWsZDdmK14nPpItsrs65J-EMMRlbXzDnCERQ2Ut1nZG9jVHlwZXVvcmcuaXNvLjE4MDEzLjUuMS5tRExsdmFsaWRpdHlJbmZvo2ZzaWduZWTAeB4yMDI1LTA2LTAzVDA1OjU0OjQxLjg2MTkwMzM3N1ppdmFsaWRGcm9twHgeMjAyNS0wNi0wM1QwNTo1NDo0MS44NjE5MDM5MThaanZhbGlkVW50aWzAeB4yMDI2LTA2LTAzVDA1OjU0OjQxLjg2MTkwMzk2OFpYQORvhnlGP5xw6xK8T-9fdlgerENfFDjiUwwaCH-AmXGq_kjqAF4LJo8nvkfJ9VzTinkPJO0SZpN7LmoNbL7bIitsZGV2aWNlU2lnbmVkompuYW1lU3BhY2Vz2BhBoGpkZXZpY2VBdXRooW9kZXZpY2VTaWduYXR1cmWEQ6EBJqEYIYD2WEAZqNCCNuDJDUlWUrjRzo9C5UylYEYpcdDmmQwgQetrwRTj14uDyw1jEYOFKdtGj1YPpdw30oM0fewG9StfAPEDZnN0YXR1cwA=",
"presentation_submission": {
"id": "2nQOQaPb7bi",
"definition_id": "2nQOQaPb7bi",
"descriptor_map": [
{
"id": "org.iso.18013.5.1.mDL",
"format": "mso_mdoc",
"path": "$",
"path_nested": {
"id": "org.iso.18013.5.1.mDL",
"format": "mso_mdoc",
"path": "$.documents[0]",
"customParameters": {}
}
}
]
},
"state": "6V0d0knr5cEk"
},
"verificationResult": true
}
```
**Body Parameters**
- **id** - Unique identifier for the presentation session.
- **presentationDefinition** - The requirements that the holder must satisfy (e.g., credential types, fields,
algorithms) in the context of this presentation session.
- **vp_token** - The submitted verifiable presentation.
- **presentation_submission** - Metadata showing how the presented credential maps to the expected structure..
- **verificationResult** - Indicates whether the presentation was successfully verified.
The `verificationResult` field will be `true` if all verifications were successful, otherwise it will be `false`.
## Inspecting Presented mDL Claims
After a successful presentation session (`verificationResult` is `true`), you can retrieve a decoded and formatted
view of all mDLs presented by the holder.
Two view modes are available:
- **`simple`** - Go-to choice for most cases.
- **`verbose`** - Useful for debugging, auditing, and other detailed technical information.
**Endpoint:** `/openid4vc/session/{id}/presented-credentials` | [API Reference](https://verifier.demo.walt.id/swagger/index.html#/Credential%20Verification/get_openid4vc_session__id__presented_credentials)
### Request
**Path Parameters**
- **`id`** (required)- The identifier of the presentation session whose credentials should be retrieved.
**Query Parameters**
- **`viewMode`** (optional) - Controls how detailed the response will be. Available values are `simple` and `verbose`
(defaults to `simple`).
### Response
**Response Body**
- **`viewMode`** - Echoed request value (or the default if it was not specified).
- **`credentialsByFormat`** : A map where each key is a presentation format (`jwt_vc_json`, `sd_jwt_vc`, `mso_mdoc`)
and the value is an array of decoded presented credentials, formatted as JSON objects and whose properties vary
according to the value of `viewMode`:
- For `mso_mdoc` in `simple` view mode:
- **`version`** - The version of the device response structure.
- **`status`** - Integer value where a value of `0` corresponds to the `OK` status message.
- **`documents`** - An array of decoded mDocs (only one entry in this case that contains the mDL) where each entry
is a JSON object that is composed of the following properties:
- **`docType`** - The document type of the mDoc, which in this case will have a value of `org.iso.18013.5.1.mDL`.
- **`nameSpaces`** - An associative array between the requested data elements and the namespaces (identifiers)
they belong to.
- **`certificateChain`** - The `x5chain` element in the header of the `IssuerAuth` Cose_Sign1 structure.
- **`validityInfo`** - JSON object that contains information related to the validity of the Mobile Security
Object (MSO) and its signature. It is composed of the following properties:
- **`signed`** - Timestamp of when the MSO was signed.
- **`validFrom`** - Timestamp before which the MSO should not be considered valid.
- **`validUntil`** - Timestamp after which the MSO should not be considered valid.
- **`expectedUpdate`** - Optionally included timestamp by the document signer (issuer) at which the MSO is
expected to be re-signed and the data elements will be potentially updated.
- **`deviceKey`** - JWK-formatted device key.
- For `mso_mdoc` in `verbose` view mode:
- **`raw`** - The base64 URL-encoded device response structure.
- **`version`** - The version of the device response structure.
- **`status`** - Integer value where a value of `0` corresponds to the `OK` status message.
- **`documents`** - An array of verbosely decoded mDocs (only one entry in this case that contains the mDL) where
each entry is a JSON object that is composed of the following properties:
- **`docType`** - The document type of the mDoc, which in this case will have a value of `org.iso.18013.5.1.mDL`.
- **`issuerSigned`** - JSON object that is composed of the following properties:
- **`nameSpaces`** - An associative array between the requested data elements and the namespaces (identifiers)
they belong to.
- **`issuerAuth`** - JSON object that is composed of the following properties:
- **`x5c`** - The `x5chain` element in the header of the `IssuerAuth` Cose_Sign1 structure.
- **`algorithm`** - The COSE Algorithm ID.
- **`protectedHeader`** - The protected header of the `IssuerAuth` Cose_Sign1 structure.
- **`payload`** - JSON object that provides a verbosely decoded version of the MSO and is composed of the
following properties:
- **`docType`** - The document type of the mDoc, which in this case will have a value of `org.iso.18013.5.1.mDL`.
- **`version`** - The version of the mobile security object structure.
- **`digestAlgorithm`** - Identifier of the digest algorithm used.
- **`valueDigests`** - Digests of all data elements per namespace.
- **`validityInfo`** - JSON object that contains information related to the validity of the Mobile Security
Object (MSO) and its signature (defined above).
- **`deviceKeyInfo`** - JSON object that contains information related to the device key and is composed of
the following properties:
- **`deviceKey`** - JWK-formatted device key.
- **`keyInfo`** - Optional structure that may contain extra information about the key.
- **`keyAuthorizations`** - Contains all the elements the device key may sign or MAC (optional field).
- **`status`** - Optional structure that may be used to provide MSO revocation information that is compared
to the information in an externally hosted MSO revocation list. This structure is provided as a JSON
object.
- **`deviceSigned`** - JSON object that is composed of the following properties:
- **`nameSpaces`** - Contains the returned data elements as part of their corresponding namespaces.
- **`deviceAuth`** - Contains either the device signature or the device MAC element.
### mDL With All Mandatory Claims Requested/Presented
In the following we provide an example that involves the presentation of all mandatory claims of an mDL.
**Option: Simple**
**Example Request**
```bash
curl -X 'GET' \
'http://0.0.0.0:7003/openid4vc/session/{id}/presented-credentials' \
-H 'accept: application/json'
```
**Example Response**
```json
{
"credentialsByFormat": {
"mso_mdoc": [
{
"type": "mso_mdoc_view_simple",
"version": "1.0",
"status": 0,
"documents": [
{
"docType": "org.iso.18013.5.1.mDL",
"nameSpaces": {
"org.iso.18013.5.1": {
"family_name": "Doe",
"given_name": "John",
"birth_date": "1986-03-22",
"issue_date": "2019-10-20",
"expiry_date": "2024-10-20",
"issuing_country": "AT",
"issuing_authority": "AT DMV",
"document_number": 123456789,
"portrait": [
141,
182,
121,
111,
238,
50,
120,
94,
54,
111,
113,
13,
241,
12,
12
],
"driving_privileges": [
{
"vehicle_category_code": "A",
"issue_date": "2018-08-09",
"expiry_date": "2024-10-20"
},
{
"vehicle_category_code": "B",
"issue_date": "2017-02-23",
"expiry_date": "2024-10-20"
}
],
"un_distinguishing_sign": "AT"
}
},
"certificateChain": [
"-----BEGIN CERTIFICATE-----\nMIICCTCCAbCgAwIBAgIUfqyiArJZoX7M61/473UAVi2/UpgwCgYIKoZIzj0EAwIwKDELMAkGA1UEBhMCQVQxGTAXBgNVBAMMEFdhbHRpZCBUZXN0IElBQ0EwHhcNMjUwNjAyMDY0MTEzWhcNMjYwOTAyMDY0MTEzWjAzMQswCQYDVQQGEwJBVDEkMCIGA1UEAwwbV2FsdGlkIFRlc3QgRG9jdW1lbnQgU2lnbmVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPzp6eVSAdXERqAp8q8OuDEhl2ILGAaoaQXTJ2sD2g5Xp3CFQDMrMpR/SQ0jt/jTOqExk1PRzjQ79aKpIsJM1mqOBrDCBqTAfBgNVHSMEGDAWgBTxCn2nWMrE70qXb614U14BweY2azAdBgNVHQ4EFgQUx5qkOLC4lpl1xpYZGmF9HLxtp0gwDgYDVR0PAQH/BAQDAgeAMBoGA1UdEgQTMBGGD2h0dHBzOi8vd2FsdC5pZDAVBgNVHSUBAf8ECzAJBgcogYxdBQECMCQGA1UdHwQdMBswGaAXoBWGE2h0dHBzOi8vd2FsdC5pZC9jcmwwCgYIKoZIzj0EAwIDRwAwRAIgHTap3c6yCUNhDVfZWBPMKj9dCWZbrME03kh9NJTbw1ECIAvVvuGll9O21eR16SkJHHAA1pPcovhcTvF9fz9cc66M\n-----END CERTIFICATE-----"
],
"validityInfo": {
"signed": "2025-07-16T07:28:10.396597247Z",
"validFrom": "2025-07-16T07:28:10.396597357Z",
"validUntil": "2026-07-16T07:28:10.396597407Z"
},
"deviceKey": {
"kty": "EC",
"crv": "P-256",
"x": "wDAbXvruEhxgchNp_M-lCaTU-oVbk4iz90wBah3RoN0",
"y": "WHY287BApRNQkUrNQVEDUZUID5W0D8gi01EZpHG7e6I"
}
}
]
}
]
},
"viewMode": "simple"
}
```
**Option: Verbose**
**Example Request**
```bash
curl -X 'GET' \
'http://0.0.0.0:7003/openid4vc/session/{id}/presented-credentials?viewMode=verbose' \
-H 'accept: application/json'
```
**Example Response**
```json
{
"credentialsByFormat": {
"mso_mdoc": [
{
"type": "mso_mdoc_view_verbose",
"raw": "o2d2ZXJzaW9uYzEuMGlkb2N1bWVudHOBo2dkb2NUeXBldW9yZy5pc28uMTgwMTMuNS4xLm1ETGxpc3N1ZXJTaWduZWSiam5hbWVTcGFjZXOhcW9yZy5pc28uMTgwMTMuNS4xi9gYWFKkaGRpZ2VzdElEAGZyYW5kb21Q6jtBNq_iT1tw9ackPuQNcHFlbGVtZW50SWRlbnRpZmllcmtmYW1pbHlfbmFtZWxlbGVtZW50VmFsdWVjRG9l2BhYUqRoZGlnZXN0SUQBZnJhbmRvbVAF97buvW0vtdvvyidYa3qQcWVsZW1lbnRJZGVudGlmaWVyamdpdmVuX25hbWVsZWxlbWVudFZhbHVlZEpvaG7YGFhYpGhkaWdlc3RJRAJmcmFuZG9tUIp69etSESKQMr4A3-eNsL9xZWxlbWVudElkZW50aWZpZXJqYmlydGhfZGF0ZWxlbGVtZW50VmFsdWVqMTk4Ni0wMy0yMtgYWFikaGRpZ2VzdElEA2ZyYW5kb21QHo_HvY6nN-lD-4p9Q2BvznFlbGVtZW50SWRlbnRpZmllcmppc3N1ZV9kYXRlbGVsZW1lbnRWYWx1ZWoyMDE5LTEwLTIw2BhYWaRoZGlnZXN0SUQEZnJhbmRvbVC3Us6PvyqxANfy7WZrwR-bcWVsZW1lbnRJZGVudGlmaWVya2V4cGlyeV9kYXRlbGVsZW1lbnRWYWx1ZWoyMDI0LTEwLTIw2BhYVaRoZGlnZXN0SUQFZnJhbmRvbVBQDUUmVFjsYhQcK7WZ4ApycWVsZW1lbnRJZGVudGlmaWVyb2lzc3VpbmdfY291bnRyeWxlbGVtZW50VmFsdWViQVTYGFhbpGhkaWdlc3RJRAZmcmFuZG9tUP1DNM51--gzzLH8I5Bjx7VxZWxlbWVudElkZW50aWZpZXJxaXNzdWluZ19hdXRob3JpdHlsZWxlbWVudFZhbHVlZkFUIERNVtgYWFekaGRpZ2VzdElEB2ZyYW5kb21QFgjaYCngk7V46uUeXTBX-XFlbGVtZW50SWRlbnRpZmllcm9kb2N1bWVudF9udW1iZXJsZWxlbWVudFZhbHVlGgdbzRXYGFhnpGhkaWdlc3RJRAhmcmFuZG9tUA-b-wZHW3ERzIb6hs3HAapxZWxlbWVudElkZW50aWZpZXJocG9ydHJhaXRsZWxlbWVudFZhbHVljxiNGLYYeRhvGO4YMhh4GF4YNhhvGHENGPEMDNgYWOKkaGRpZ2VzdElECWZyYW5kb21QGFSyeNBXnznFVEBHHpqZlXFlbGVtZW50SWRlbnRpZmllcnJkcml2aW5nX3ByaXZpbGVnZXNsZWxlbWVudFZhbHVlgqN1dmVoaWNsZV9jYXRlZ29yeV9jb2RlYUFqaXNzdWVfZGF0ZWoyMDE4LTA4LTA5a2V4cGlyeV9kYXRlajIwMjQtMTAtMjCjdXZlaGljbGVfY2F0ZWdvcnlfY29kZWFCamlzc3VlX2RhdGVqMjAxNy0wMi0yM2tleHBpcnlfZGF0ZWoyMDI0LTEwLTIw2BhYXKRoZGlnZXN0SUQKZnJhbmRvbVC7AKxX3rE-D3-Y0_MtrznwcWVsZW1lbnRJZGVudGlmaWVydnVuX2Rpc3Rpbmd1aXNoaW5nX3NpZ25sZWxlbWVudFZhbHVlYkFUamlzc3VlckF1dGiEQ6EBJqEYIVkCDTCCAgkwggGwoAMCAQICFH6sogKyWaF-zOtf-O91AFYtv1KYMAoGCCqGSM49BAMCMCgxCzAJBgNVBAYTAkFUMRkwFwYDVQQDDBBXYWx0aWQgVGVzdCBJQUNBMB4XDTI1MDYwMjA2NDExM1oXDTI2MDkwMjA2NDExM1owMzELMAkGA1UEBhMCQVQxJDAiBgNVBAMMG1dhbHRpZCBUZXN0IERvY3VtZW50IFNpZ25lcjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD86enlUgHVxEagKfKvDrgxIZdiCxgGqGkF0ydrA9oOV6dwhUAzKzKUf0kNI7f40zqhMZNT0c40O_WiqSLCTNZqjgawwgakwHwYDVR0jBBgwFoAU8Qp9p1jKxO9Kl2-teFNeAcHmNmswHQYDVR0OBBYEFMeapDiwuJaZdcaWGRphfRy8badIMA4GA1UdDwEB_wQEAwIHgDAaBgNVHRIEEzARhg9odHRwczovL3dhbHQuaWQwFQYDVR0lAQH_BAswCQYHKIGMXQUBAjAkBgNVHR8EHTAbMBmgF6AVhhNodHRwczovL3dhbHQuaWQvY3JsMAoGCCqGSM49BAMCA0cAMEQCIB02qd3OsglDYQ1X2VgTzCo_XQlmW6zBNN5IfTSU28NRAiAL1b7hpZfTttXkdekpCRxwANaT3KL4XE7xfX8_XHOujFkC29gYWQLWpmd2ZXJzaW9uYzEuMG9kaWdlc3RBbGdvcml0aG1nU0hBLTI1Nmx2YWx1ZURpZ2VzdHOhcW9yZy5pc28uMTgwMTMuNS4xqwBYIEhnRHmvup8TlsQ9hkEF4kH5KRKVxfjOpdadkGxgAqTUAVgg2PkypU9XEsKInFxUYaLdY95CQuR9WEx_uTKKMSakfDICWCCMWA5TlOdlv9OCYOPsUL7jvPQkOM8g6BQfZv6zcnE8AANYII5xio1q4XtRNUKeD-r3mD4rFVKeXzyJe_QHoDs2HcaPBFgg-Not5xT8jU-MMwi__otHq-A1HwvZto7K6gcNMHFe5NwFWCBXW91nys2OEHiJYr8TKTw0P9idVx0N9Xf384fmGnIKWQZYIBqK5uyNOCXvmTzRbl_SQSRapF5DIRtFxza-RfhKR14lB1ggbgAfyHWnOWRB0SCUFAj9He6XUdoJFYMVm-3cHEX-PjkIWCA-j1UB92LJnlr6BSdC3ZQ2pkzYz1zykCk28Baas-tShglYILn7TvLzLvTAFkS85WVyKnD74IW9CjNgaQn0KGAhfQ_uClggjOrfc9Ue1XVquGt2AUhlJrd1sUwrP2No5UZQi_ti5KBtZGV2aWNlS2V5SW5mb6FpZGV2aWNlS2V5pAECIAEhWCDAMBte-u4SHGByE2n8z6UJpNT6hVuTiLP3TAFqHdGg3SJYIFh2NvOwQKUTUJFKzUFRA1GVCA-VtA_IItNRGaRxu3uiZ2RvY1R5cGV1b3JnLmlzby4xODAxMy41LjEubURMbHZhbGlkaXR5SW5mb6Nmc2lnbmVkwHgeMjAyNS0wNy0xNlQwNzoyODoxMC4zOTY1OTcyNDdaaXZhbGlkRnJvbcB4HjIwMjUtMDctMTZUMDc6Mjg6MTAuMzk2NTk3MzU3Wmp2YWxpZFVudGlswHgeMjAyNi0wNy0xNlQwNzoyODoxMC4zOTY1OTc0MDdaWEDiemZef8Z-tVwEMtNPde_6ySjS37D5VCHm3WqbsnwGi87Y4H1HLrLQdkvonitJqjmP8WT5Vqt-wdAml82wlSmPbGRldmljZVNpZ25lZKJqbmFtZVNwYWNlc9gYQaBqZGV2aWNlQXV0aKFvZGV2aWNlU2lnbmF0dXJlhEOhASahGCGA9lhAU3IJNfF-abBEzwzEH0U1zJinbx_wbxXs0oaI3mPQJ_hWdb4WUO4GN1aLpPQ7McqNIozYxY7bQTmPbArqzSAIEWZzdGF0dXMA",
"version": "1.0",
"status": 0,
"documents": [
{
"docType": "org.iso.18013.5.1.mDL",
"issuerSigned": {
"nameSpaces": {
"org.iso.18013.5.1": {
"family_name": "Doe",
"given_name": "John",
"birth_date": "1986-03-22",
"issue_date": "2019-10-20",
"expiry_date": "2024-10-20",
"issuing_country": "AT",
"issuing_authority": "AT DMV",
"document_number": 123456789,
"portrait": [
141,
182,
121,
111,
238,
50,
120,
94,
54,
111,
113,
13,
241,
12,
12
],
"driving_privileges": [
{
"vehicle_category_code": "A",
"issue_date": "2018-08-09",
"expiry_date": "2024-10-20"
},
{
"vehicle_category_code": "B",
"issue_date": "2017-02-23",
"expiry_date": "2024-10-20"
}
],
"un_distinguishing_sign": "AT"
}
},
"issuerAuth": {
"x5c": [
"-----BEGIN CERTIFICATE-----\nMIICCTCCAbCgAwIBAgIUfqyiArJZoX7M61/473UAVi2/UpgwCgYIKoZIzj0EAwIwKDELMAkGA1UEBhMCQVQxGTAXBgNVBAMMEFdhbHRpZCBUZXN0IElBQ0EwHhcNMjUwNjAyMDY0MTEzWhcNMjYwOTAyMDY0MTEzWjAzMQswCQYDVQQGEwJBVDEkMCIGA1UEAwwbV2FsdGlkIFRlc3QgRG9jdW1lbnQgU2lnbmVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPzp6eVSAdXERqAp8q8OuDEhl2ILGAaoaQXTJ2sD2g5Xp3CFQDMrMpR/SQ0jt/jTOqExk1PRzjQ79aKpIsJM1mqOBrDCBqTAfBgNVHSMEGDAWgBTxCn2nWMrE70qXb614U14BweY2azAdBgNVHQ4EFgQUx5qkOLC4lpl1xpYZGmF9HLxtp0gwDgYDVR0PAQH/BAQDAgeAMBoGA1UdEgQTMBGGD2h0dHBzOi8vd2FsdC5pZDAVBgNVHSUBAf8ECzAJBgcogYxdBQECMCQGA1UdHwQdMBswGaAXoBWGE2h0dHBzOi8vd2FsdC5pZC9jcmwwCgYIKoZIzj0EAwIDRwAwRAIgHTap3c6yCUNhDVfZWBPMKj9dCWZbrME03kh9NJTbw1ECIAvVvuGll9O21eR16SkJHHAA1pPcovhcTvF9fz9cc66M\n-----END CERTIFICATE-----"
],
"algorithm": -7,
"protectedHeader": {
"1": -7
},
"payload": {
"docType": "org.iso.18013.5.1.mDL",
"version": "1.0",
"digestAlgorithm": "SHA-256",
"valueDigests": {
"org.iso.18013.5.1": {
"0": [
72,
103,
68,
121,
-81,
-70,
-97,
19,
-106,
-60,
61,
-122,
65,
5,
-30,
65,
-7,
41,
18,
-107,
-59,
-8,
-50,
-91,
-42,
-99,
-112,
108,
96,
2,
-92,
-44
],
"1": [
-40,
-7,
50,
-91,
79,
87,
18,
-62,
-120,
-100,
92,
84,
97,
-94,
-35,
99,
-34,
66,
66,
-28,
125,
88,
76,
127,
-71,
50,
-118,
49,
38,
-92,
124,
50
],
"2": [
-116,
88,
14,
83,
-108,
-25,
101,
-65,
-45,
-126,
96,
-29,
-20,
80,
-66,
-29,
-68,
-12,
36,
56,
-49,
32,
-24,
20,
31,
102,
-2,
-77,
114,
113,
60,
0
],
"3": [
-114,
113,
-118,
-115,
106,
-31,
123,
81,
53,
66,
-98,
15,
-22,
-9,
-104,
62,
43,
21,
82,
-98,
95,
60,
-119,
123,
-12,
7,
-96,
59,
54,
29,
-58,
-113
],
"4": [
-8,
-38,
45,
-25,
20,
-4,
-115,
79,
-116,
51,
8,
-65,
-2,
-117,
71,
-85,
-32,
53,
31,
11,
-39,
-74,
-114,
-54,
-22,
7,
13,
48,
113,
94,
-28,
-36
],
"5": [
87,
91,
-35,
103,
-54,
-51,
-114,
16,
120,
-119,
98,
-65,
19,
41,
60,
52,
63,
-40,
-99,
87,
29,
13,
-11,
119,
-9,
-13,
-121,
-26,
26,
114,
10,
89
],
"6": [
26,
-118,
-26,
-20,
-115,
56,
37,
-17,
-103,
60,
-47,
110,
95,
-46,
65,
36,
90,
-92,
94,
67,
33,
27,
69,
-57,
54,
-66,
69,
-8,
74,
71,
94,
37
],
"7": [
110,
0,
31,
-56,
117,
-89,
57,
100,
65,
-47,
32,
-108,
20,
8,
-3,
29,
-18,
-105,
81,
-38,
9,
21,
-125,
21,
-101,
-19,
-36,
28,
69,
-2,
62,
57
],
"8": [
62,
-113,
85,
1,
-9,
98,
-55,
-98,
90,
-6,
5,
39,
66,
-35,
-108,
54,
-90,
76,
-40,
-49,
92,
-14,
-112,
41,
54,
-16,
22,
-102,
-77,
-21,
82,
-122
],
"9": [
-71,
-5,
78,
-14,
-13,
46,
-12,
-64,
22,
68,
-68,
-27,
101,
114,
42,
112,
-5,
-32,
-123,
-67,
10,
51,
96,
105,
9,
-12,
40,
96,
33,
125,
15,
-18
],
"10": [
-116,
-22,
-33,
115,
-43,
30,
-43,
117,
106,
-72,
107,
118,
1,
72,
101,
38,
-73,
117,
-79,
76,
43,
63,
99,
104,
-27,
70,
80,
-117,
-5,
98,
-28,
-96
]
}
},
"validityInfo": {
"signed": "2025-07-16T07:28:10.396597247Z",
"validFrom": "2025-07-16T07:28:10.396597357Z",
"validUntil": "2026-07-16T07:28:10.396597407Z"
},
"deviceKeyInfo": {
"deviceKey": {
"kty": "EC",
"crv": "P-256",
"x": "wDAbXvruEhxgchNp_M-lCaTU-oVbk4iz90wBah3RoN0",
"y": "WHY287BApRNQkUrNQVEDUZUID5W0D8gi01EZpHG7e6I"
}
}
}
}
},
"deviceSigned": {
"nameSpaces": {},
"deviceAuth": {
"deviceSignature": [
[
-95,
1,
38
],
{
"33": []
},
null,
[
83,
114,
9,
53,
-15,
126,
105,
-80,
68,
-49,
12,
-60,
31,
69,
53,
-52,
-104,
-89,
111,
31,
-16,
111,
21,
-20,
-46,
-122,
-120,
-34,
99,
-48,
39,
-8,
86,
117,
-66,
22,
80,
-18,
6,
55,
86,
-117,
-92,
-12,
59,
49,
-54,
-115,
34,
-116,
-40,
-59,
-114,
-37,
65,
57,
-113,
108,
10,
-22,
-51,
32,
8,
17
]
]
}
}
}
]
}
]
},
"viewMode": "verbose"
}
```
--------------------------------------------------------------------------------
PAGE: Overview
URL: https://docs.walt.id/community-stack/verifier/credential-verification/policies/overview
RAW: https://docs.walt.id/community-stack/verifier/credential-verification/policies/overview.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Policies
For verification of verifiable credentials, the walt.id verifier api offers a range of predefined static,
parameterized and dynamic verification policies, which are ready-to-use and are designed for common use cases.
Walt.id verifier api offers the following verification policies:
- [Static Verification Policies](https://docs.walt.id/community-stack/verifier/credential-verification/policies/static-verification-policies.md)
- [Parameterized Verification Policies](https://docs.walt.id/community-stack/verifier/credential-verification/policies/parameterized-verification-policies.md)
- [Dynamic Verification Policies](https://docs.walt.id/community-stack/verifier/credential-verification/policies/dynamic-verification-policies.md)
--------------------------------------------------------------------------------
PAGE: Static Verification Policies
URL: https://docs.walt.id/community-stack/verifier/credential-verification/policies/static-verification-policies
RAW: https://docs.walt.id/community-stack/verifier/credential-verification/policies/static-verification-policies.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Static Verification Policies
## Static Verification Policies
Predefined_ and covering a variety of common use cases, enabling developers to verify credentials easily.
### Signature
Used as `signature`, it verifies the signature of the W3C VC.
### Expired
Used as `expired`, it verifies the VC based on the expiration date.
### Not Before
Used as `not-before`, it verifies VC based on the valid-from date.
### Schema
Used as `schema`, it verifies against the associated JSON schema. Note that the attribute credentialSchema must be
set and the JSON schema must be accessible by the http URL.
### Revoked Status List
Used as `revoked-status-list`, it verifies VC status based on Verifiable Credentials
[Status List v2021](https://www.w3.org/TR/2023/WD-vc-status-list-20230427) standard, against the following values:
* purpose = revocation (case-sensitive)
* expected valid value = 0x0 (unset)
### Holder Binding
Used as `holder-binding`, it check that presenter (Issuer of Verifiable Presentation) is the correct subject throughout
the credentials contained within the presentation; so that you can't just present a credential of a different user you
ve found somewhere within your Verifiable Presentation.
--------------------------------------------------------------------------------
PAGE: Parameterized Verification Policies
URL: https://docs.walt.id/community-stack/verifier/credential-verification/policies/parameterized-verification-policies
RAW: https://docs.walt.id/community-stack/verifier/credential-verification/policies/parameterized-verification-policies.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Parameterized Verification Policies
## Parameterized Verification Policies
Parameterized policies are a type of policy that requires certain parameters or arguments for their execution.
- [Allowed Issuer](#allowed-issuer)
- [Webhook](#webhook)
- [Credential Status](#credential-status)
- [VP required credentials](#vp-required-credentials)
### Allowed Issuer
Used as `allowed-issuer`, it verifies that the issuer of the presented VC(s) is one of the allowed issuers provided as argument
to the policy.
**Example**
```json
{
"policy": "allowed-issuer",
"args": ["did:key:z6MkveAavXuA9JCEUjWGB9FQp1H3tuYYmxkjz84H9CVBKsuV", "did:key:z6MknfS1FxdFrgZYgM2HUUFJnHST1JX4yLZdLmXxZBShCB1Z"]
}
```
### Webhook
Used as `webhook`, the policy is specified as an object and expects the URL which should be called on issuance as
argument.
**Example:**
```json
{
"policy": "webhook",
"args": "https://example.org/abc/xyz"
}
```
The request sent to the provided webhook endpoint during verification will vary depending on where the policy is placed.
If the
policy is included in the `vp_policies` list, the request to the webhook will contain the entire verifiable
presentation. If the policy is provided in the `vc_policies` list or on the credential level, the request received by
the webhook will only include one credential, along with issuer and subject information.
Please find a list of examples for each scenario below.
#### Example of Request if Policy Provided via VC Policy list or Directly Provided on Credential Level.
Please note that if the policy is applied via `vc_policies` and the verification requests asked for multiple
credentials, the system sends one request per credential.
```json
{
"iss": "did:key:z6MkoabA7LmtjeeAAGKqqcpmhsda6Bs2ZayVS6LRay2gbXRJ",
"sub": "did:key:z6MkuHcPf3jhFwGso9agCvbvpzsbn6gHtYYNudXxHjQW1zQd#z6MkuHcPf3jhFwGso9agCvbvpzsbn6gHtYYNudXxHjQW1zQd",
"vc": {
"@context": ["https://www.w3.org/2018/credentials/v1", "https://purl.imsglobal.org/spec/ob/v3p0/context.json"],
"id": "urn:uuid:410b3681-e185-4356-bed6-7ee5812324c0",
"type": ["VerifiableCredential", "OpenBadgeCredential"],
"name": "JFF x vc-edu PlugFest 3 Interoperability",
"issuer": {
"type": ["SomeType"],
"id": "did:key:z6MkoabA7LmtjeeAAGKqqcpmhsda6Bs2ZayVS6LRay2gbXRJ",
"name": "Jobs for the Future (JFF)",
"url": "https://www.jff.org/",
"image": "https://w3c-ccg.github.io/vc-ed/plugfest-1-2022/images/JFF_LogoLockup.png"
},
"issuanceDate": "2023-11-02T07:12:20.254559481Z",
"expirationDate": "2024-11-01T07:12:20.254633964Z",
"credentialSubject": {
"id": "did:key:z6MkuHcPf3jhFwGso9agCvbvpzsbn6gHtYYNudXxHjQW1zQd#z6MkuHcPf3jhFwGso9agCvbvpzsbn6gHtYYNudXxHjQW1zQd",
"type": ["SomeType"],
"achievement": [
{
"achievementKey": "achievementValue"
}
]
}
},
"jti": "urn:uuid:410b3681-e185-4356-bed6-7ee5812324c0",
"exp": 1730445140,
"iat": 1698909140,
"nbf": 1698909050
}
```
#### Example of Request if Policy Provided via VP Policy List
```json
{
"sub": "did:key:z6MkuHcPf3jhFwGso9agCvbvpzsbn6gHtYYNudXxHjQW1zQd",
"nbf": 1699289947,
"iat": 1699290007,
"jti": "urn:uuid:35e7a4a5-ca50-4ac7-bf82-c8750b2f543f",
"iss": "did:key:z6MkuHcPf3jhFwGso9agCvbvpzsbn6gHtYYNudXxHjQW1zQd",
"nonce": "",
"vp": {
"@context": ["https://www.w3.org/2018/credentials/v1"],
"type": ["VerifiablePresentation"],
"id": "urn:uuid:f65c458d-73ec-4dd4-ae4c-d3fda1459afa",
"holder": "did:key:z6MkuHcPf3jhFwGso9agCvbvpzsbn6gHtYYNudXxHjQW1zQd",
"verifiableCredential": [
"eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6ImRpZDprZXk6ejZNa29hYkE3TG10amVlQUFHS3FxY3BtaHNkYTZCczJaYXlWUzZMUmF5MmdiWFJKIn0.eyJpc3MiOiJkaWQ6a2V5Ono2TWtvYWJBN0xtdGplZUFBR0txcWNwbWhzZGE2QnMyWmF5VlM2TFJheTJnYlhSSiIsInN1YiI6ImRpZDprZXk6ejZNa3VIY1BmM2poRndHc285YWdDdmJ2cHpzYm42Z0h0WVlOdWRYeEhqUVcxelFkI3o2TWt1SGNQZjNqaEZ3R3NvOWFnQ3ZidnB6c2JuNmdIdFlZTnVkWHhIalFXMXpRZCIsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIl0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJWZXJpZmlhYmxlQXR0ZXN0YXRpb24iLCJWZXJpZmlhYmxlSWQiXSwiY3JlZGVudGlhbFNjaGVtYSI6eyJpZCI6Imh0dHBzOi8vYXBpLnByZXByb2QuZWJzaS5ldS90cnVzdGVkLXNjaGVtYXMtcmVnaXN0cnkvdjEvc2NoZW1hcy8weGI3N2Y4NTE2YTk2NTYzMWI0ZjE5N2FkNTRjNjVhOWUyZjk5MzZlYmZiNzZiYWU0OTA2ZDMzNzQ0ZGJjYzYwYmEiLCJ0eXBlIjoiRnVsbEpzb25TY2hlbWFWYWxpZGF0b3IyMDIxIn0sImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImN1cnJlbnRBZGRyZXNzIjpbIjEgQm91bGV2YXJkIGRlIGxhIExpYmVydMOpLCA1OTgwMCBMaWxsZSJdLCJkYXRlT2ZCaXJ0aCI6IjE5OTMtMDQtMDgiLCJmYW1pbHlOYW1lIjoiRE9FIiwiZmlyc3ROYW1lIjoiSmFuZSIsImdlbmRlciI6IkZFTUFMRSIsImlkIjoiZGlkOmtleTp6Nk1rdUhjUGYzamhGd0dzbzlhZ0N2YnZwenNibjZnSHRZWU51ZFh4SGpRVzF6UWQjejZNa3VIY1BmM2poRndHc285YWdDdmJ2cHpzYm42Z0h0WVlOdWRYeEhqUVcxelFkIiwibmFtZUFuZEZhbWlseU5hbWVBdEJpcnRoIjoiSmFuZSBET0UiLCJwZXJzb25hbElkZW50aWZpZXIiOiIwOTA0MDA4MDg0SCIsInBsYWNlT2ZCaXJ0aCI6IkxJTExFLCBGUkFOQ0UifSwiZXZpZGVuY2UiOlt7ImRvY3VtZW50UHJlc2VuY2UiOlsiUGh5c2ljYWwiXSwiZXZpZGVuY2VEb2N1bWVudCI6WyJQYXNzcG9ydCJdLCJzdWJqZWN0UHJlc2VuY2UiOiJQaHlzaWNhbCIsInR5cGUiOlsiRG9jdW1lbnRWZXJpZmljYXRpb24iXSwidmVyaWZpZXIiOiJkaWQ6ZWJzaToyQTlCWjlTVWU2QmF0YWNTcHZzMVY1Q2RqSHZMcFE3YkVzaTJKYjZMZEhLblF4YU4ifV0sImlkIjoidXJuOnV1aWQ6NjM1N2FmMzEtMjBhNS00YzY1LTlkOWYtNWUyNmZkZmEwYjI5IiwiaXNzdWVkIjoiMjAyMS0wOC0zMVQwMDowMDowMFoiLCJpc3N1ZXIiOiJkaWQ6a2V5Ono2TWtvYWJBN0xtdGplZUFBR0txcWNwbWhzZGE2QnMyWmF5VlM2TFJheTJnYlhSSiIsInZhbGlkRnJvbSI6IjIwMjEtMDgtMzFUMDA6MDA6MDBaIiwiaXNzdWFuY2VEYXRlIjoiMjAyMy0xMS0wMlQwNzoxODowNC4yODM3ODk4MTlaIn0sImp0aSI6InVybjp1dWlkOjYzNTdhZjMxLTIwYTUtNGM2NS05ZDlmLTVlMjZmZGZhMGIyOSIsImlhdCI6MTY5ODkwOTQ4NCwibmJmIjoxNjk4OTA5Mzk0fQ.gxoeshSOpFjL53iue--vKTHAnI_w1cMW6-LmAF3dRtXBjlhhMJryZLmsL8_OrxiNRbm_2kTyjmhJapBT973eAA"
]
}
}
```
The webhook endpoint that receives the request can perform any checks based on the body provided. After the checks are
done, it can either respond with a success code (200-299), indicating the policy has been passed, or with any other
code, which signals a failure. Although this outcome (success or failure) does not impact the execution of other
policies (as they will be executed regardless), if any policy fails, it results in the overall verification process
being marked as a failure.
Here is an example of what a detailed verification response could look like, where the webhook policy failed but the
rest passed:
```json
{
"id": "96200fd3-05f2-429a-aa19-d3f8a19c52cc",
"presentationDefinition": {
"input_descriptors": [
{
"id": "VerifiableId",
"format": {
"jwt_vc_json": {
"alg": ["EdDSA"]
}
},
"constraints": {
"fields": [
{
"path": ["$.type"],
"filter": {
"type": "string",
"pattern": "VerifiableId"
}
}
]
}
}
]
},
"tokenResponse": {
"vp_token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6ImRpZDprZXk6ejZNa3VIY1BmM2poRndHc285YWdDdmJ2cHpzYm42Z0h0WVlOdWRYeEhqUVcxelFkI3o2TWt1SGNQZjNqaEZ3R3NvOWFnQ3ZidnB6c2JuNmdIdFlZTnVkWHhIalFXMXpRZCJ9.eyJzdWIiOiJkaWQ6a2V5Ono2TWt1SGNQZjNqaEZ3R3NvOWFnQ3ZidnB6c2JuNmdIdFlZTnVkWHhIalFXMXpRZCIsIm5iZiI6MTY5ODkwOTQzOCwiaWF0IjoxNjk4OTA5NDk4LCJqdGkiOiJ1cm46dXVpZDpkYTVjNGQ0OC1lNjllLTRkOWUtYTQ3Ny03MzRlNjA2OGQyMDYiLCJpc3MiOiJkaWQ6a2V5Ono2TWt1SGNQZjNqaEZ3R3NvOWFnQ3ZidnB6c2JuNmdIdFlZTnVkWHhIalFXMXpRZCIsIm5vbmNlIjoiIiwidnAiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwidHlwZSI6WyJWZXJpZmlhYmxlUHJlc2VudGF0aW9uIl0sImlkIjoidXJuOnV1aWQ6MzQ1Y2M3ZTYtODlhYi00OTEyLWE2MzItOGRjZGMwOTg5NjQ3IiwiaG9sZGVyIjoiZGlkOmtleTp6Nk1rdUhjUGYzamhGd0dzbzlhZ0N2YnZwenNibjZnSHRZWU51ZFh4SGpRVzF6UWQiLCJ2ZXJpZmlhYmxlQ3JlZGVudGlhbCI6WyJleUpoYkdjaU9pSkZaRVJUUVNJc0luUjVjQ0k2SWtwWFZDSXNJbXRwWkNJNkltUnBaRHByWlhrNmVqWk5hMjloWWtFM1RHMTBhbVZsUVVGSFMzRnhZM0J0YUhOa1lUWkNjekphWVhsV1V6Wk1VbUY1TW1kaVdGSktJbjAuZXlKcGMzTWlPaUprYVdRNmEyVjVPbm8yVFd0dllXSkJOMHh0ZEdwbFpVRkJSMHR4Y1dOd2JXaHpaR0UyUW5NeVdtRjVWbE0yVEZKaGVUSm5ZbGhTU2lJc0luTjFZaUk2SW1ScFpEcHJaWGs2ZWpaTmEzVklZMUJtTTJwb1JuZEhjMjg1WVdkRGRtSjJjSHB6WW00MlowaDBXVmxPZFdSWWVFaHFVVmN4ZWxGa0kzbzJUV3QxU0dOUVpqTnFhRVozUjNOdk9XRm5RM1ppZG5CNmMySnVObWRJZEZsWlRuVmtXSGhJYWxGWE1YcFJaQ0lzSW5aaklqcDdJa0JqYjI1MFpYaDBJanBiSW1oMGRIQnpPaTh2ZDNkM0xuY3pMbTl5Wnk4eU1ERTRMMk55WldSbGJuUnBZV3h6TDNZeElsMHNJblI1Y0dVaU9sc2lWbVZ5YVdacFlXSnNaVU55WldSbGJuUnBZV3dpTENKV1pYSnBabWxoWW14bFFYUjBaWE4wWVhScGIyNGlMQ0pXWlhKcFptbGhZbXhsU1dRaVhTd2lZM0psWkdWdWRHbGhiRk5qYUdWdFlTSTZleUpwWkNJNkltaDBkSEJ6T2k4dllYQnBMbkJ5WlhCeWIyUXVaV0p6YVM1bGRTOTBjblZ6ZEdWa0xYTmphR1Z0WVhNdGNtVm5hWE4wY25rdmRqRXZjMk5vWlcxaGN5OHdlR0kzTjJZNE5URTJZVGsyTlRZek1XSTBaakU1TjJGa05UUmpOalZoT1dVeVpqazVNelpsWW1aaU56WmlZV1UwT1RBMlpETXpOelEwWkdKall6WXdZbUVpTENKMGVYQmxJam9pUm5Wc2JFcHpiMjVUWTJobGJXRldZV3hwWkdGMGIzSXlNREl4SW4wc0ltTnlaV1JsYm5ScFlXeFRkV0pxWldOMElqcDdJbU4xY25KbGJuUkJaR1J5WlhOeklqcGJJakVnUW05MWJHVjJZWEprSUdSbElHeGhJRXhwWW1WeWRNT3BMQ0ExT1Rnd01DQk1hV3hzWlNKZExDSmtZWFJsVDJaQ2FYSjBhQ0k2SWpFNU9UTXRNRFF0TURnaUxDSm1ZVzFwYkhsT1lXMWxJam9pUkU5Rklpd2labWx5YzNST1lXMWxJam9pU21GdVpTSXNJbWRsYm1SbGNpSTZJa1pGVFVGTVJTSXNJbWxrSWpvaVpHbGtPbXRsZVRwNk5rMXJkVWhqVUdZemFtaEdkMGR6YnpsaFowTjJZblp3ZW5OaWJqWm5TSFJaV1U1MVpGaDRTR3BSVnpGNlVXUWplalpOYTNWSVkxQm1NMnBvUm5kSGMyODVZV2REZG1KMmNIcHpZbTQyWjBoMFdWbE9kV1JZZUVocVVWY3hlbEZrSWl3aWJtRnRaVUZ1WkVaaGJXbHNlVTVoYldWQmRFSnBjblJvSWpvaVNtRnVaU0JFVDBVaUxDSndaWEp6YjI1aGJFbGtaVzUwYVdacFpYSWlPaUl3T1RBME1EQTRNRGcwU0NJc0luQnNZV05sVDJaQ2FYSjBhQ0k2SWt4SlRFeEZMQ0JHVWtGT1EwVWlmU3dpWlhacFpHVnVZMlVpT2x0N0ltUnZZM1Z0Wlc1MFVISmxjMlZ1WTJVaU9sc2lVR2g1YzJsallXd2lYU3dpWlhacFpHVnVZMlZFYjJOMWJXVnVkQ0k2V3lKUVlYTnpjRzl5ZENKZExDSnpkV0pxWldOMFVISmxjMlZ1WTJVaU9pSlFhSGx6YVdOaGJDSXNJblI1Y0dVaU9sc2lSRzlqZFcxbGJuUldaWEpwWm1sallYUnBiMjRpWFN3aWRtVnlhV1pwWlhJaU9pSmthV1E2WldKemFUb3lRVGxDV2psVFZXVTJRbUYwWVdOVGNIWnpNVlkxUTJScVNIWk1jRkUzWWtWemFUSktZalpNWkVoTGJsRjRZVTRpZlYwc0ltbGtJam9pZFhKdU9uVjFhV1E2TmpNMU4yRm1NekV0TWpCaE5TMDBZelkxTFRsa09XWXROV1V5Tm1aa1ptRXdZakk1SWl3aWFYTnpkV1ZrSWpvaU1qQXlNUzB3T0Mwek1WUXdNRG93TURvd01Gb2lMQ0pwYzNOMVpYSWlPaUprYVdRNmEyVjVPbm8yVFd0dllXSkJOMHh0ZEdwbFpVRkJSMHR4Y1dOd2JXaHpaR0UyUW5NeVdtRjVWbE0yVEZKaGVUSm5ZbGhTU2lJc0luWmhiR2xrUm5KdmJTSTZJakl3TWpFdE1EZ3RNekZVTURBNk1EQTZNREJhSWl3aWFYTnpkV0Z1WTJWRVlYUmxJam9pTWpBeU15MHhNUzB3TWxRd056b3hPRG93TkM0eU9ETTNPRGs0TVRsYUluMHNJbXAwYVNJNkluVnlianAxZFdsa09qWXpOVGRoWmpNeExUSXdZVFV0TkdNMk5TMDVaRGxtTFRWbE1qWm1aR1poTUdJeU9TSXNJbWxoZENJNk1UWTVPRGt3T1RRNE5Dd2libUptSWpveE5qazRPVEE1TXprMGZRLmd4b2VzaFNPcEZqTDUzaXVlLS12S1RIQW5JX3cxY01XNi1MbUFGM2RSdFhCamxoaE1KcnlaTG1zTDhfT3J4aU5SYm1fMmtUeWptaEphcEJUOTczZUFBIl19fQ.7tbc1bQWcp3y8uZaIF0lrdwQqUMz86B_dYwqz0X0Uy1n8ddcH2r9kUcgSOpOOyZhbpgHQD5yzl-OCVbinclDCQ",
"presentation_submission": {
"id": "submission 1",
"definition_id": "1",
"descriptor_map": [
{
"id": "VerifiableId",
"format": "jwt_vp_json",
"path": "$[0]",
"path_nested": {
"format": "jwt_vc_json",
"path": "$.vp.verifiableCredential[0]"
}
}
]
},
"state": "96200fd3-05f2-429a-aa19-d3f8a19c52cc"
},
"verificationResult": false,
"policyResults": {
"results": [
{
"credential": "VerifiablePresentation",
"policies": [
{
"policy": "signature",
"description": "Checks a JWT credential by verifying its cryptographic signature using the key referenced by the DID in `iss`.",
"is_success": true,
"result": {
"sub": "did:key:z6MkuHcPf3jhFwGso9agCvbvpzsbn6gHtYYNudXxHjQW1zQd",
"nbf": 1698909438,
"iat": 1698909498,
"jti": "urn:uuid:da5c4d48-e69e-4d9e-a477-734e6068d206",
"iss": "did:key:z6MkuHcPf3jhFwGso9agCvbvpzsbn6gHtYYNudXxHjQW1zQd",
"nonce": "",
"vp": {
"@context": ["https://www.w3.org/2018/credentials/v1"],
"type": ["VerifiablePresentation"],
"id": "urn:uuid:345cc7e6-89ab-4912-a632-8dcdc0989647",
"holder": "did:key:z6MkuHcPf3jhFwGso9agCvbvpzsbn6gHtYYNudXxHjQW1zQd",
"verifiableCredential": [
"eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6ImRpZDprZXk6ejZNa29hYkE3TG10amVlQUFHS3FxY3BtaHNkYTZCczJaYXlWUzZMUmF5MmdiWFJKIn0.eyJpc3MiOiJkaWQ6a2V5Ono2TWtvYWJBN0xtdGplZUFBR0txcWNwbWhzZGE2QnMyWmF5VlM2TFJheTJnYlhSSiIsInN1YiI6ImRpZDprZXk6ejZNa3VIY1BmM2poRndHc285YWdDdmJ2cHpzYm42Z0h0WVlOdWRYeEhqUVcxelFkI3o2TWt1SGNQZjNqaEZ3R3NvOWFnQ3ZidnB6c2JuNmdIdFlZTnVkWHhIalFXMXpRZCIsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIl0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJWZXJpZmlhYmxlQXR0ZXN0YXRpb24iLCJWZXJpZmlhYmxlSWQiXSwiY3JlZGVudGlhbFNjaGVtYSI6eyJpZCI6Imh0dHBzOi8vYXBpLnByZXByb2QuZWJzaS5ldS90cnVzdGVkLXNjaGVtYXMtcmVnaXN0cnkvdjEvc2NoZW1hcy8weGI3N2Y4NTE2YTk2NTYzMWI0ZjE5N2FkNTRjNjVhOWUyZjk5MzZlYmZiNzZiYWU0OTA2ZDMzNzQ0ZGJjYzYwYmEiLCJ0eXBlIjoiRnVsbEpzb25TY2hlbWFWYWxpZGF0b3IyMDIxIn0sImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImN1cnJlbnRBZGRyZXNzIjpbIjEgQm91bGV2YXJkIGRlIGxhIExpYmVydMOpLCA1OTgwMCBMaWxsZSJdLCJkYXRlT2ZCaXJ0aCI6IjE5OTMtMDQtMDgiLCJmYW1pbHlOYW1lIjoiRE9FIiwiZmlyc3ROYW1lIjoiSmFuZSIsImdlbmRlciI6IkZFTUFMRSIsImlkIjoiZGlkOmtleTp6Nk1rdUhjUGYzamhGd0dzbzlhZ0N2YnZwenNibjZnSHRZWU51ZFh4SGpRVzF6UWQjejZNa3VIY1BmM2poRndHc285YWdDdmJ2cHpzYm42Z0h0WVlOdWRYeEhqUVcxelFkIiwibmFtZUFuZEZhbWlseU5hbWVBdEJpcnRoIjoiSmFuZSBET0UiLCJwZXJzb25hbElkZW50aWZpZXIiOiIwOTA0MDA4MDg0SCIsInBsYWNlT2ZCaXJ0aCI6IkxJTExFLCBGUkFOQ0UifSwiZXZpZGVuY2UiOlt7ImRvY3VtZW50UHJlc2VuY2UiOlsiUGh5c2ljYWwiXSwiZXZpZGVuY2VEb2N1bWVudCI6WyJQYXNzcG9ydCJdLCJzdWJqZWN0UHJlc2VuY2UiOiJQaHlzaWNhbCIsInR5cGUiOlsiRG9jdW1lbnRWZXJpZmljYXRpb24iXSwidmVyaWZpZXIiOiJkaWQ6ZWJzaToyQTlCWjlTVWU2QmF0YWNTcHZzMVY1Q2RqSHZMcFE3YkVzaTJKYjZMZEhLblF4YU4ifV0sImlkIjoidXJuOnV1aWQ6NjM1N2FmMzEtMjBhNS00YzY1LTlkOWYtNWUyNmZkZmEwYjI5IiwiaXNzdWVkIjoiMjAyMS0wOC0zMVQwMDowMDowMFoiLCJpc3N1ZXIiOiJkaWQ6a2V5Ono2TWtvYWJBN0xtdGplZUFBR0txcWNwbWhzZGE2QnMyWmF5VlM2TFJheTJnYlhSSiIsInZhbGlkRnJvbSI6IjIwMjEtMDgtMzFUMDA6MDA6MDBaIiwiaXNzdWFuY2VEYXRlIjoiMjAyMy0xMS0wMlQwNzoxODowNC4yODM3ODk4MTlaIn0sImp0aSI6InVybjp1dWlkOjYzNTdhZjMxLTIwYTUtNGM2NS05ZDlmLTVlMjZmZGZhMGIyOSIsImlhdCI6MTY5ODkwOTQ4NCwibmJmIjoxNjk4OTA5Mzk0fQ.gxoeshSOpFjL53iue--vKTHAnI_w1cMW6-LmAF3dRtXBjlhhMJryZLmsL8_OrxiNRbm_2kTyjmhJapBT973eAA"
]
}
}
}
]
},
{
"credential": "VerifiableId",
"policies": [
{
"policy": "signature",
"description": "Checks a JWT credential by verifying its cryptographic signature using the key referenced by the DID in `iss`.",
"is_success": true,
"result": {
"iss": "did:key:z6MkoabA7LmtjeeAAGKqqcpmhsda6Bs2ZayVS6LRay2gbXRJ",
"sub": "did:key:z6MkuHcPf3jhFwGso9agCvbvpzsbn6gHtYYNudXxHjQW1zQd#z6MkuHcPf3jhFwGso9agCvbvpzsbn6gHtYYNudXxHjQW1zQd",
"vc": {
"@context": ["https://www.w3.org/2018/credentials/v1"],
"type": ["VerifiableCredential", "VerifiableAttestation", "VerifiableId"],
"credentialSchema": {
"id": "https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xb77f8516a965631b4f197ad54c65a9e2f9936ebfb76bae4906d33744dbcc60ba",
"type": "FullJsonSchemaValidator2021"
},
"credentialSubject": {
"currentAddress": ["1 Boulevard de la Liberté, 59800 Lille"],
"dateOfBirth": "1993-04-08",
"familyName": "DOE",
"firstName": "Jane",
"gender": "FEMALE",
"id": "did:key:z6MkuHcPf3jhFwGso9agCvbvpzsbn6gHtYYNudXxHjQW1zQd#z6MkuHcPf3jhFwGso9agCvbvpzsbn6gHtYYNudXxHjQW1zQd",
"nameAndFamilyNameAtBirth": "Jane DOE",
"personalIdentifier": "0904008084H",
"placeOfBirth": "LILLE, FRANCE"
},
"evidence": [
{
"documentPresence": ["Physical"],
"evidenceDocument": ["Passport"],
"subjectPresence": "Physical",
"type": ["DocumentVerification"],
"verifier": "did:ebsi:2A9BZ9SUe6BatacSpvs1V5CdjHvLpQ7bEsi2Jb6LdHKnQxaN"
}
],
"id": "urn:uuid:6357af31-20a5-4c65-9d9f-5e26fdfa0b29",
"issued": "2021-08-31T00:00:00Z",
"issuer": "did:key:z6MkoabA7LmtjeeAAGKqqcpmhsda6Bs2ZayVS6LRay2gbXRJ",
"validFrom": "2021-08-31T00:00:00Z",
"issuanceDate": "2023-11-02T07:18:04.283789819Z"
},
"jti": "urn:uuid:6357af31-20a5-4c65-9d9f-5e26fdfa0b29",
"iat": 1698909484,
"nbf": 1698909394
}
},
{
"policy": "webhook",
"description": "Sends the credential data to an webhook URL as HTTP POST, and returns the verified status based on the webhooks set status code (success = 200 - 299).",
"args": "https://7625-2a02-8388-1781-e900-50a9-7528-a196-dc8a.ngrok-free.app/api/hello",
"is_success": false,
"error": {
"type": "WebhookPolicyException",
"response": {
"test": "failed"
}
}
}
]
}
],
"success": false,
"time": "0.2485s",
"policies_run": 3,
"policies_failed": 1,
"policies_succeeded": 2
}
}
```
### Credential Status
Used as `credential-status`, it verifies that the status entry of the presented VC(s) resolves to values that are
provided as argument to the policy.
**_Note!_** When the VC contains no status entry, the policy succeeds by default.
Currently, verification of the following status types is supported:
- [BitstringStatusList](https://www.w3.org/TR/vc-bitstring-status-list)
- [StatusList2021](https://www.w3.org/TR/2023/WD-vc-status-list-20230427)
- [RevocationList2020](https://w3c-ccg.github.io/vc-status-rl-2020)
- [TokenStatusList](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-status-list-08)
**Example**
**Option: BitstringStatusList**
**Option: Single**
```json
{
"policy": "credential-status",
"args":
{
"discriminator": "w3c",
"value": 0,
"purpose": "revocation",
"type": "BitstringStatusList"
}
}
```
**Option: Multiple**
```json
{
"policy": "credential-status",
"args":
{
"discriminator": "w3c-list",
"list":
[
{
"value": 0,
"purpose": "revocation",
"type": "BitstringStatusList"
},
{
"value": 0,
"purpose": "suspension",
"type": "BitstringStatusList"
}
]
}
}
```
**Option: StatusList2021**
```json
{
"policy": "credential-status",
"args":
{
"discriminator": "w3c",
"value": 0,
"purpose": "revocation",
"type": "StatusList2021"
}
}
```
**Option: RevocationList2020**
```json
{
"policy": "credential-status",
"args":
{
"discriminator": "w3c",
"value": 0,
"purpose": "revocation",
"type": "RevocationList2020"
}
}
```
**Option: TokenStatusList**
```json
{
"policy": "credential-status",
"args":
{
"discriminator": "ietf",
"value": 0
}
}
```
where:
- value - the expected value to be valid, takes an `Int` value
- purpose - the status purpose (only for W3C credentials), case-sensitive
- type - the status credential standard (only for W3C credentials)
- BitstringStatusList
- StatusList2021
- RevocationList2020
- discriminator - the payload type, used in parsing
- w3c - for BitstringStatusList, StatusList2021, RevocationList2020
- ietf - for TokenStatusList
### VP required credentials
Used as `vp_required_credentials`, the policy defines exactly which **W3C credentials** a Verifiable Presentation must contain for the exchange to be considered valid. It lets you specify mandatory credential types as well as groups of interchangeable alternatives.
```json
{
"policy": "vp_required_credentials",
"args": {
"required": [
{ "credential_type": "gx:Issuer" },
{ "credential_type": "gx:LegalPerson" },
{
"any_of": ["gx:EORI", "gx:LeiCode", "gx:VatID"]
}
]
}
}
```
It ensures:
- A credential of type `gx:Issuer` must be present.
- A credential of type `gx:LegalPerson` must be present.
- At least one credential from this list must be included:
* `gx:EORI`
* `gx:LeiCode`
* `gx:VatID`
In other words: the VP must contain the two mandatory credentials plus at least one identifier credential.
--------------------------------------------------------------------------------
PAGE: Dynamic Verification Policies
URL: https://docs.walt.id/community-stack/verifier/credential-verification/policies/dynamic-verification-policies
RAW: https://docs.walt.id/community-stack/verifier/credential-verification/policies/dynamic-verification-policies.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Dynamic Verification Policies with OPA and Rego
This guide provides comprehensive documentation for integrating **Open Policy Agent (OPA)** and **Rego policies** into a Verifier API. Learn how to define dynamic verification rules for verifiable credentials (VCs) using declarative policies.
---
## Table of Contents
1. [Introduction](#introduction)
2. [Prerequisites](#prerequisites)
3. [Installation](#installation)
4. [Policy Management](#policy-management)
5. [Integration with Verifier API](#integration-with-verifier-api)
6. [Examples](#examples)
7. [Troubleshooting](#troubleshooting)
8. [Conclusion](#conclusion)
---
### What is OPA?
**Open Policy Agent (OPA)** is an open-source policy engine that enables context-aware, dynamic authorization decisions. It uses the **Rego language** to define policies as code.
### Key Concepts
- **Rego**: Declarative query language for defining policies.
- **Verifiable Credentials (VCs)**: Digitally signed credentials that can be programmatically verified.
- **Dynamic Policies**: Rules evaluated at runtime to verify credentials based on custom logic (e.g., checking specific claims).
---
## Prerequisites
- Basic understanding of JSON and REST APIs.
- OPA server installed ([Installation Guide](#installation)).
- Access to a Verifier API instance.
---
## Installation
### Step 1: Install OPA
Download and run OPA locally:
```bash
# For Linux
wget https://openpolicyagent.org/downloads/v0.46.1/opa_linux_amd64 -O opa
chmod +x opa
# For macOS (Homebrew)
brew install opa
# For Windows (Docker)
docker run -p 8181:8181 openpolicyagent/opa run --server
```
### Step 2: Start OPA Server
```bash
./opa run --server
```
- Default endpoint: `http://localhost:8181`
### Step 3: Verify Installation
```bash
curl http://localhost:8181
```
---
## Policy Management
### Policy Structure
Policies are defined in Rego and stored in OPA. A policy consists of:
- **`package`**: Namespace for the policy (e.g., `vc.verification.test`).
- **`default allow`**: Default decision if no rules match.
- **Rules**: Conditions that evaluate to `true`/`false`.
### Example Policy
```rego
package vc.verification.example
default allow = false
# Allow if credential name matches parameter
allow if {
input.parameter.name == input.credentialData.credentialSubject.achievement.name
}
```
---
## Integration with Verifier API
### Policy Configuration
Include policies in the Verifier API request under `request_credentials.policies`:
| Field | Description |
|-----------------|-----------------------------------------------------------------------------|
| `policy` | Set to `"dynamic"` for OPA-based policies. |
| `policy_name` | Unique identifier for the policy. |
| `rules.rego` | Inline Rego policy (or `policy_url` for remote policies). |
| `argument` | Key-value pairs passed to the policy for evaluation. |
| `opa_server` | OPA server URL (default: `http://localhost:8181`). |
| `policy_query` | OPA data query path (e.g., `data.test.allow`). |
### Workflow
1. Verifier API sends the credential data and arguments to OPA.
2. OPA evaluates the policy against the input.
3. OPA returns `allow: true` or `allow: false`.
4. Verifier API enforces the decision.
---
## Examples
### 1. Single Condition
**Goal**: Verify the credential's name matches a specific value.
**Request**:
```json
{
"request_credentials": [
{
"format": "jwt_vc_json",
"type": "OpenBadgeCredential",
"policies": [
{
"policy": "dynamic",
"args": {
"policy_name": "name_check",
"rules": {
"rego": "package vc.verification\n\ndefault allow = false\n\nallow if input.parameter.name == input.credentialData.credentialSubject.name"
},
"argument": {
"name": "JFF x vc-edu PlugFest 3 Interoperability"
}
}
}
]
}
]
}
```
---
### 2. Multiple Conditions (AND Logic)
**Goal**: Require both `name` and `id` to match.
**Rego**:
```rego
package vc.verification
default allow = false
allow if {
input.parameter.name == input.credentialData.credentialSubject.name
input.parameter.id == input.credentialData.credentialSubject.id
}
```
**Request**:
```json
{
"args": {
"argument": {
"name": "PlugFest 3",
"id": "urn:uuid:ac254bd5-8fad-4bb1-9d29-efd938536926"
}
}
}
```
---
### 3. Remote Policy URL
**Goal**: Fetch a policy from a remote server.
**Request**:
```json
{
"args": {
"policy_url": "http://example.com/policies/name_check.rego",
"opa_server": "http://opa.example.com"
}
}
```
**Full example**:
```json
{
"request_credentials": [
{
"format": "jwt_vc_json",
"type": "OpenBadgeCredential",
"policies": [
{
"policy": "dynamic",
"args": {
"policy_name": "waltid",
"opa_server": "http://opa.example.com",
"policy_query": "data",
"rules": {
"policy_url": "http://example.com/policies/name_check"
},
"argument": {
"name": "JFF x vc-edu PlugFest 3 Interoperability"
}
}
}
]
}
]
}
```
---
For advanced use cases, explore [OPA’s documentation](https://www.openpolicyagent.org/docs/latest/).
--------------------------------------------------------------------------------
PAGE: Overview
URL: https://docs.walt.id/community-stack/verifier/ecosystems/ebsi/overview
RAW: https://docs.walt.id/community-stack/verifier/ecosystems/ebsi/overview.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Overview
[The European Blockchain Services Infrastructure (EBSI)](https://ec.europa.eu/digital-building-blocks/sites/display/EBSI/Home)
is a standardized blockchain framework initiated by the European
Commission and the European Blockchain Partnership to enhance digital identity and public services across Europe. **EBSI
leverages blockchain technology to improve interoperability, security, and trust in managing digital identities**,
ensuring these identities align with regulatory requirements and are universally recognized across member states.
Using the walt.id Issuer & Verifier APIs you can issue and verify EBSI v3 compliant credentials based on the W3C
credential
data model.
## Verification Of EBSI Credentials
To verify EBSI v3 compliant credentials, simply utilize our regular W3C verify endpoints as outlined
in [this guide](https://docs.walt.id/community-stack/issuer/credential-issuance/vc-oid4vc.md). To ensure that the API creates the correct request for wallets to present the
credential, provide in the `openId4VPProfile` parameter of the request, `EBSIV3` as value. You can also see it described
in the swagger
docs [here](https://verifier.demo.walt.id/swagger/index.html#/Credential%20Verification/post_openid4vc_verify).
During the verification, the verifier will then validate the EBSI DID of the credential via the EBSI Trusted Registries.
Additionally, if you want to ensure the validity and the status of a Credential using the Verifiable Credentials Status
List v2021 method, you can include the policy `revoked-status-list` as
defined [here](https://docs.walt.id/community-stack/verifier/credential-verification/policies/static-verification-policies.md#revoked-status-list).
--------------------------------------------------------------------------------
PAGE: Feature Manger
URL: https://docs.walt.id/community-stack/verifier/configurations/feature-manager
RAW: https://docs.walt.id/community-stack/verifier/configurations/feature-manager.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Feature Manger
Use the feature manager to control which verifier API features are enabled at service startup.
This reduces complexity and the risk of misconfigurations.
## Feature Types
The feature manager offers two types **Base** and **Optional**.
### Base Features
Base features are generally always enabled and do not require additional configuration in most cases. They provide
critical functionalities expected to be available by default.
### Optional Features
Optional features can either be enabled or disabled based on requirements.
1. **Enabled by Default**: These features do not need additional configuration to be useful. E.g. Health Checks operate
without any need for specific configs.
2. **Disabled by Default**: These features require specific configuration to function. E.g. the Entra Integration needs
access credentials and API URLs.
**Note:**
Each feature may have zero, one, or multiple configurations associated with it.
## How the Feature Manager Works
The Feature Manager ensures efficient and error-free initiation of services through the following steps:
1. **Initialization with Feature Catalogs**: Services are passed one or more Feature Catalogs when initialized.
2. **Determination of Active Features**: The initializer identifies explicitly enabled or disabled features and
determines the default states of optional features.
3. **Loading Configurations**: Configurations for base features and explicitly enabled optional features are loaded,
including default-enabled optional features.
**This process results in:**
- Loading only necessary configurations, reducing mental complexity and avoiding unused features.
- Minimizing required configuration through defaults, easing the setup process.
- Preventing invalid configurations from reaching production by validating during initialization.
## Feature Configuration
Configure features by updating the `_features.conf` file as
explained [here](https://docs.walt.id/community-stack/verifier/configurations/config-files/features.md). A list of available features can be found
here.
--------------------------------------------------------------------------------
PAGE: Logging
URL: https://docs.walt.id/community-stack/verifier/configurations/logging
RAW: https://docs.walt.id/community-stack/verifier/configurations/logging.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Logging
Multiplatform-based, the issuer API logging service enables dynamic reconfigurations, multiple log outputs, and
integrations
with log aggregator systems supporting various log output formats. Next to that, structured logging instead of plain
messages and context login (similar to MDC) are supported.
## Multiplatform Logging
Our logging implementation utilizes multiplatform libraries that are aligned with the logging systems already in use
across different platforms. Below are the specific logging systems targeted:
- Android: `android.util.Log`
- macOS/iOS: Darwin log system
- Java/JVM: SLF4J
- JavaScript: Console logger
This ensures that the logging system remains consistent with the platform's native logging practices.
## Capability Overview
The logging service is provided by the service commons module in the identity lib, enabling the following
capabilities.
### Dynamic Reconfigurations
- **Multiple Log Outputs**: Logs can be sent to multiple destinations simultaneously.
- **Log Aggregators**: Logs can be pushed to various log aggregators including standard output/error, Seq, Graylog, and
Logstash (part of the ELK stack).
### Log Output Formats
The multi log output formats enable integrations with different tools and platforms. Supported formats include:
- CLEF: Compact JSON format.
- Elastic Common Schema: Standard format for logging data.
- Elastic Common Schema for .NET: Specialized format for .NET applications.
- GELF: JSON format optimized for Graylog.
- JSON: Format for Splunk HTTP Event Collector.
- Simple & ISO 8601 Rendering: Easy-to-read and ISO 8601 standard timestamp formats.
## Configuration & Setup
Configuring the logging system can be done in two main ways:
1. **Predefined Setups**: Select from simplified pre-defined logging setups curated by walt.id by setting a command line
argument. If no configuration is detected, a default logging setup is used to ensure logging
is active and functional out-of-the-box.
2. **Configuration File**: For maximum flexibility configuration is possible via a file in HOCON or JSON format.
### Predefined Setup
To use the predefined setups provide one of the following arguments on service startup or setup an environment variable
with the same name in the service environment.
```
--log-level=default
--log-level=debug
--log-level=trace
Switch logging type (different rendering):
--log-type=simple (no colors)
--log-type=ansi (colors ) (default)
--log-type=iso8601 (iso timestamp)
--log-type=clef (Compact Log Event Format)
--log-type=gelf (Graylog Extended Log Format)
--log-type=ecs (Elastic Common Schema)
--log-type=ecs_dotnet (Elastic Common Schema for .net)
```
### Configuration File
Setup a JSON (.json) or HOCON (.conf) config file, in [this format](https://github.com/walt-id/waltid-identity/blob/main/waltid-services/waltid-wallet-api/src/main/resources/example_klogging.conf):
some options are explained [here]( https://klogging.io/docs/configuration/json#configuration-object-names)
--------------------------------------------------------------------------------
PAGE: Distributed Architecture
URL: https://docs.walt.id/community-stack/verifier/configurations/distributed-architecture
RAW: https://docs.walt.id/community-stack/verifier/configurations/distributed-architecture.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Distributed Architecture
When the Community Stack is cloned and run out-of-the-box as described [here](https://github.com/walt-id/waltid-identity?tab=readme-ov-file#use-services-and-apps) there is only one instance of each service started. i.e One instance of the Issuer API, one instance of the Verifier API and one instance of the Wallet API is started.
```
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3172c547a1c9 waltid/waltid-web-wallet:0.5.0 "bun run server/inde…" 25 hours ago Up 30 seconds 7101/tcp docker-compose-waltid-web-wallet-1
a109275e76b6 waltid/wallet-api:0.5.0 "/waltid-wallet-api/…" 25 hours ago Up 31 seconds 7001/tcp docker-compose-wallet-api-1
03cf14477b29 waltid/verifier-api:0.5.0 "/waltid-verifier-ap…" 25 hours ago Up 37 seconds 7003/tcp docker-compose-verifier-api-1
993557a8de84 waltid/issuer-api:0.5.0 "/waltid-issuer-api/…" 25 hours ago Up 36 seconds 7002/tcp docker-compose-issuer-api-1
a7eea13f67bd waltid/portal:0.5.0 "docker-entrypoint.s…" 25 hours ago Up 37 seconds 7102/tcp docker-compose-web-portal-1
fcb83570a141 waltid/vc-repository:latest "/bin/sh -c 'node se…" 25 hours ago Up 37 seconds 3000/tcp docker-compose-vc-repo-1
9f1cfcc83bfc caddy:2 "caddy run --config …" 25 hours ago Up 37 seconds 80/tcp, 443/tcp, 0.0.0.0:7001-7003->7001-7003/tcp, 0.0.0.0:7101-7103->7101-7103/tcp, 2019/tcp, 443/udp, 0.0.0.0:8080->8080/tcp docker-compose-caddy-1
8b54fbba10cd postgres "docker-entrypoint.s…" 3 weeks ago Up 37 seconds (healthy) 0.0.0.0:5432->5432/tcp
```
## Multiple Instances
However, when it comes to a production setup, in order to achieve better scalability and high availability, multiple services instances are required.
As an example, let's do a very basic setup, with 2 instances of the Verifier API.
Clone the project and change to root directory:
```
git clone https://github.com/walt-id/waltid-identity.git && cd waltid-identity
```
Build the Verifier API service Docker image:
```bash
./gradlew :waltid-services:waltid-verifier-api:publishImageToLocalRegistry
# image: waltid/verifier-api:
```
If you want a multi-arch image push to your registry, use `publishImage` with the env-vars:
`DOCKER_USERNAME`/`DOCKER_PASSWORD` and update the image name in the `docker run` commands.
Before starting the instances, it might be a good idea to create a Docker Network to ease the inter-container communication.
```
docker network create waltid
```
And now, starting two instances of the Verifier API...
One instance, named `verifier1` will be listening at the port 7003.
```
docker run --rm --name verifier1 --net waltid -p 7003:7003 waltid/verifier-api -- --webPort=7003 --baseUrl=http://caddy:9093
```
The other one (`verifier2`), listening on the 8003.
```
docker run --rm --name verifier2 --net waltid -p 8003:8003 waltid/verifier-api -- --webPort=8003 --baseUrl=http://caddy:9093
```
Now, we have two Verifier API instances up and running.
```
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
317015641bae waltid/verifier-api "/waltid-verifier-ap…" 7 seconds ago Up 6 seconds 7003/tcp, 0.0.0.0:8003->8003/tcp verifier2
7ad9fee7cd3d waltid/verifier-api "/waltid-verifier-ap…" 15 seconds ago Up 14 seconds 0.0.0.0:7003->7003/tcp verifier1
```
# Load Balancing
To hide the complexity of the distributed architecture from the end user, it is necessary to add a network asset, usally a load balancer or a reverse proxy (software or hardware) that intermediates user calls to the multiple instances of redundant services.
```
┌─────────┐
│ │
┌--─►│verifier1│
│ │ │
│ └─────────┘
┌─────────────────┐ │
│ Load Balancer │ │
│ ├────────────┘
│ or │
│ ├────────────┐
│ Reverse Proxy │ │
└─────────────────┘ │
│ ┌─────────┐
│ │ │
└--─►│verifier2│
│ │
└─────────┘
```
Nowadays, there are several options that could play this role, like the Apache Web Server, nginx, lighthttpd, Traefik etc.
### Caddy setup
For the sake of simplicity, let's use [Caddy](https://caddyserver.com/) in our example. Using the oficial Docker image, start it up.
```
docker run --rm --net waltid --name caddy -p 9093:9093 -v $(pwd)/waltid-services/waltid-verifier-api/distributed-test.Caddyfile:/etc/caddy/Caddyfile caddy
```
The Caddy config file is quite simple:
```
http://localhost:9093 {
reverse_proxy {
to verifier1:7003
to verifier2:8003
lb_policy round_robin
}
}
```
In a nutshell, it defines one endpoint: `localhost:9093`.
It works as a reverse proxy that [balances the load](https://caddyserver.com/docs/caddyfile/directives/reverse_proxy#load-balancing) in a round robin strategy between 2 Verifier instances (`verifier1` and `verifier2`).
```
┌───────────────-┐
│ │
┌--─►│ verifier1:7003 │
│ │ │
│ └─────────-───-──┘
┌─────────────────┐ │
│ │ │
│ localhost:9093 ├────────────┘
│ (Verifier API) ├────────────┐
│ | │
└─────────────────┘ │
│ ┌───────-────────┐
│ │ │
└--─►│ verifier2:8003 │
│ │
└────────────────┘
```
Now, give it a try and open http://localhost:9093. The Swagger page containing the Verifier API documentation will be opened.
With the instances log terminals visible, refresh the page a couple of times and observe how the requests are distributed between both instances. Cool, isn't it? However, there is a problem here.
## Session Management
### Isolated sessions
When an OIDC credential verification flow is started with the `/openid4vc/authorize` endpoint, for example, an authorization request URL like the one below is generated.
```
openid4vp://caddy:9093/?authorization_request_uri=http%3A%2F%2Fcaddy%3A9093%2Fopenid4vc%2FauthorizationRequest%3Fid%3D1765770d-2682-4e4e-95e9-183875d4a9de
```
This URL links to a verification session initiated in one of the instances of the Verifier API, for example, the `verifier1` in the diagram above.
This authorization request URL is supposed to be used later on by the wallet to submit credentials for verification.
However, when the Wallet calls the URL encoded in the `authorization_request_uri` parameter to submit credentials, the request may not reach the same Verifier API instance where the verification session was initiated and the result will be:
```
{"exception":true,"status":"Not Found","code":"404","message":"No active verification session found by the given id"}
```
This happens because each Verifier API has an independent (in memory) session management system. They don't share information between them. So, if a verification session is initiated in one instance, the other instances doesn't know about it.
```
┌─────────────-┐
│ │ ┌───────────┐
┌--─►│verifier1:7003│─────►| Session 1 |
│ │ │ └───────────┘
│ └──────────────┘
┌─────────────────┐ │
│ │ │
│ localhost:9093 ├────────────┘
│ (Verifier API) ├────────────┐
│ | │
└─────────────────┘ │ ┌───────-─────-┐
│ │ │ ┌───────────┐
└--─►│verifier2:8003│─────►| Session 2 |
│ │ └───────────┘
└──────────────┘
```
### Shared session
There are multiple strategies to solve this issue in the distributed computing context. For now, the only way to exchange session data in a distributed deployment architecture of the Walt.id Community Stack is through a data persistence layer that is shared between the various instances of the cluster.
```
┌───────────────┐
│ │
┌--─►│ verifier1:7003│
│ │ │
│ └───────┬───────┘
│ │
│ ▼
┌─────────────────┐ │ ┌────────────────┐
│ │ │ │ │
│ localhost:9093 ├────────────┘ │ Shared Session │
│ (Verifier API) ├────────────┐ │ │
│ │ │ └────────────────┘
└─────────────────┘ │ ▲
│ │
│ ┌───────┴───────┐
│ │ │
└--─►│ verifier2:8003│
│ │
└───────────────┘
```
#### Enabling shared session management
This feature is disabled by default in the Community Stack. So, the very first thing to do is to enable it in the `waltid-services/waltid-verifier-api/config/_features.conf` file.
```
enabledFeatures = [
persistence
]
disabledFeatures = [
# ...
]
```
The settings for the persistence layer can be found in the `waltid-services/waltid-verifier-api/config/persistence.conf` config file. Look at its content:
```
type = "memory"
// type = "redis"
// nodes = [{host = "127.0.0.1", port = 6379}]
// user: ""
// password: ""
```
By default, the session persistence is managed in memory by each instance independently, as shown before. Let's change it to a shared data store.
#### Shared data store setup
For the moment, we only support the use of [Redis](https://redis.io/) as a shared database to store distributed session data. The easiest way to get it up and running is with an official Docker image.
```
docker run --rm --net waltid --name redis-stack-server -p 6379:6379 redis/redis-stack-server:latest
```
Change the `persistence.conf` file to use the "redis" persistence mechanism and configure the other parameters accordingly.
```
// type = "memory"
type = "redis"
nodes = [{host = "redis-stack-server", port = 6379}]
user: "default"
password: ""
```
Worth observing the `redis-stack-server` container name was also used as the Redis server hostname in the config file.
To update the configuration changes just made,
1. Stop the running Verifier API instances
```
docker stop $(docker ps | grep verifier-api | awk '{print $1}')
```
The plain old Ctrl+C is probably enough :-)
2. Rebuild the Verifier API image
```bash
./gradlew :waltid-services:waltid-verifier-api:publishImageToLocalRegistry
# image: waltid/verifier-api:
```
3. Restart the instances.
```
docker run --rm --name verifier1 --net waltid -p 7003:7003 waltid/verifier-api -- --webPort=7003 --baseUrl=http://caddy:9093
```
```
docker run --rm --name verifier2 --net waltid -p 8003:8003 waltid/verifier-api -- --webPort=8003 --baseUrl=http://caddy:9093
```
Let's check if it works?
## Testing
1. Go back to the Verifier API Swagger page at http://localhost:9093.
2. Use the `/openid4vc/verify` endpoint to start an OIDC credential verification flow. Get the result presentation request URL.
3. Start a local instance of the Wallet API.
```
docker run --rm --name wallet --net waltid -p 7001:7001 -it -v $(pwd)/waltid-services/waltid-wallet-api/config:/waltid-wallet-api/config -v $(pwd)/waltid-services/waltid-wallet-api/data:/waltid-wallet-api/data -t waltid/wallet-api
```
4. Open the Wallet API Swagger page at http://localhost:7001.
5. Use the `/wallet-api/auth/login` endpoint to authenticate with the pre-registered credentials.
6. Get the wallet id of the authenticated user with the `/wallet-api/wallet/accounts/wallets` endpoint.
7. Use the obtained wallet id and the presentation request URL to submit credentials for verification using the `/wallet-api/wallet/{wallet}/exchange/usePresentationRequest` endpoint.
Voilá. You've just created your first distributed architecture of the Walt.id Community Stack for verifying credentials.
--------------------------------------------------------------------------------
PAGE: Data Retention
URL: https://docs.walt.id/community-stack/verifier/configurations/data-retention
RAW: https://docs.walt.id/community-stack/verifier/configurations/data-retention.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Data Retention
## What is stored
The verifier service stores the following ojects during the verification process:
#### PresentationSession
session metadata and auth state
#### SessionVerificationInformation
verification policies and redirect URIs
#### PolicyResults
verification results (potentially contains PII)
## Retention policy
- **Default expiration**: 5 minutes
- **Storage backend**: Configurable (memory/Redis/Redis-cluster)
- **Automatic cleanup**: Yes, via TTL expiration
### How to configure the retention policy
#### Per-Request TTL Override
You can override the default TTL for individual sessions via HTTP headers:
Include the `sessionTtl` header in your issuance request:
```http
POST /openid4vc/verify
sessionTtl: 600
Content-Type: application/json
Body: {...}
```
The `sessionTtl` value is in **seconds**. Example above sets TTL to 600 seconds (10 minutes).
#### Storage backend
This is useful for distributed architectures. You can configure the storage backend to use a shared database to store session data.
In the `waltid-services/waltid-issuer-api/config/persistence.conf` config file, you can configure the storage backend to use a shared database to store session data.
```
// Default storage backend is memory.
type = "memory"
// Using redis as a shared database to store session data.
type = "redis"
nodes = [{host = "127.0.0.1", port = 6379}]
user: ""
password: ""
```
--------------------------------------------------------------------------------
PAGE: Features
URL: https://docs.walt.id/community-stack/verifier/configurations/config-files/features
RAW: https://docs.walt.id/community-stack/verifier/configurations/config-files/features.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Features
With the `_features.conf` you can control which verifier API features are enabled. They are managed via
the [feature manager](https://docs.walt.id/community-stack/verifier/configurations/feature-manager.md).
## Available Features
**Base Features**
Please note that the base features should always be enabled to make full use of the verifier API.
- `web` - Web service
- `verifier-service` - verifier service implementation
**Optional Features**
- `entra` - Start issuing credential via the Microsoft Entra Verified ID platform. Learn
more [here](https://docs.walt.id/community-stack/verifier/integrations/entra/overview).
- `healthchecks` - Enables healthcheck endpoints.
- `debug-endpoints` - Enables various debug endpoints.
- `feature-flag-information-endpoint` - Enables endpoints related to showing information about available features on
this service instance.
### Example Setup
```editorconfig[_features.conf]
enabledFeatures = [
entra
]
disabledFeatures = [
debug-endpoints,
healthchecks
]
```
--------------------------------------------------------------------------------
PAGE: Verifier Service
URL: https://docs.walt.id/community-stack/verifier/configurations/config-files/verifier-service
RAW: https://docs.walt.id/community-stack/verifier/configurations/config-files/verifier-service.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Verifier Service
## verifier Service
With the `verifier-service.conf`, you can configure the external URL and port for accessing the service from outside the local
environment. You can find an example below.
### Example
```editorconfig[verifier-service.conf]
baseUrl = "http://localhost:7003"
```
--------------------------------------------------------------------------------
PAGE: Web
URL: https://docs.walt.id/community-stack/verifier/configurations/config-files/web
RAW: https://docs.walt.id/community-stack/verifier/configurations/config-files/web.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Web
With the `web.conf` you can configure the host and port for local network connections. You
can find an example below.
### Example
```editorconfig[web.conf]
webHost = "0.0.0.0"
webPort = "7003"
```
--------------------------------------------------------------------------------
PAGE: Protocol Flows
URL: https://docs.walt.id/community-stack/verifier/protocol-flows
RAW: https://docs.walt.id/community-stack/verifier/protocol-flows.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Protocol Flows
Sequence diagrams for common verification flows. Each flow shows the full request lifecycle — from the portal initiating a verification session through to the Verifier API returning the result.
## Available Flows
- [mdoc Verification via OID4VP](https://docs.walt.id/community-stack/verifier/protocol-flows/mdoc-verification-oid4vp.md) — Full verification flow for an mdoc credential presented by a wallet via OID4VP.
--------------------------------------------------------------------------------
PAGE: mdoc Verification via OID4VP
URL: https://docs.walt.id/community-stack/verifier/protocol-flows/mdoc-verification-oid4vp
RAW: https://docs.walt.id/community-stack/verifier/protocol-flows/mdoc-verification-oid4vp.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# mdoc Verification via OID4VP
This flow covers a portal initiating a verification session, the wallet scanning the QR code and presenting an mdoc credential, and the Verifier API validating it.
**Components involved:**
- Portal Application — initiates the verification session and displays the QR code
- Wallet (Holder) — scans the QR code, selects and presents the matching credential
- Verifier API — creates the authorization request, validates the presentation, and returns the result
```mermaid
sequenceDiagram
participant Portal as Portal Application
participant Wallet as Wallet (Holder)
participant Verifier as Verifier API
Portal->>Verifier: POST /openid4vc/verify
activate Verifier
Verifier->>Verifier: Create verification session and authorization request
Verifier-->>Portal: 200 OK {authorizationUrl: "openid4vp://..."}
deactivate Verifier
Portal->>Portal: Generate QR code with authorization URL
Portal-->>Wallet: Display QR code (openid4vp://...)
Wallet->>Wallet: Scan QR code
Wallet->>Wallet: Parse authorization request and match credentials
Wallet->>Wallet: User selects mdoc to present
Wallet->>Wallet: Generate DeviceResponse with mdoc credential
Wallet->>Verifier: POST /openid4vc/verify/{state}
activate Verifier
Verifier->>Verifier: Parse DeviceResponse Extract mdoc document
Verifier->>Verifier: Verify mdoc: - Issuer signature (x5Chain) - Device signature - Validity period - Session transcript
Verifier->>Verifier: Extract credential claims from mdoc namespaces
Verifier-->>Wallet: 200 OK
deactivate Verifier
```
## Related
- [mDL Verification](https://docs.walt.id/community-stack/verifier/credential-verification/mdl-oid4vc.md)
- [Verification Policies](https://docs.walt.id/community-stack/verifier/credential-verification/policies/overview.md)
--------------------------------------------------------------------------------
PAGE: Overview
URL: https://docs.walt.id/community-stack/verifier/apps/portal/overview
RAW: https://docs.walt.id/community-stack/verifier/apps/portal/overview.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Overview
Video: https://youtu.be/9CzXmw0Fb_4
A list of credentials to issue and verify via a white-label UI. The credentials available via the portal are
fetched from the [walt.id credential repository](https://github.com/walt-id/waltid-credentials).
## Setup
1. Clone the identity repo
```bash
git clone https://github.com/walt-id/waltid-identity.git && cd waltid-identity
```
2. Running the project
**Option: Local**
1. Change directory
```bash
cd waltid-applications/waltid-web-portal/
```
**Note:**
Make sure you have the `NEXT_PUBLIC_VC_REPO` variable set in `.env`.
As default value `"https://credentials.walt.id"` can be used.
2. Run the development server
```bash
npm install
npm run dev
```
or
2. For production build
```bash
npm install
npm run build
```
**Option: Docker**
1. Build & run the image
```bash
docker build -t waltid/portal -f waltid-applications/waltid-web-portal/Dockerfile .
docker run -p 3000:3000 -e NEXT_PUBLIC_VC_REPO="https://credentials.walt.id" -i -t waltid/portal
```
**Note:**
Here we are using the public hosted VC repo **credentials.walt.id.** However, you can also run & configure it locally.
Learn more [here](https://github.com/walt-id/waltid-credentials).
2. Access the portal under [localhost:3000](http://localhost:3000/)
--------------------------------------------------------------------------------
PAGE: Full API Reference
URL: https://docs.walt.id/community-stack/verifier/full-api-reference
RAW: https://docs.walt.id/community-stack/verifier/full-api-reference.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Full API Reference
Find it [here](https://verifier.demo.walt.id/swagger/index.html)
--------------------------------------------------------------------------------
PAGE: FAQs
URL: https://docs.walt.id/community-stack/verifier/faq
RAW: https://docs.walt.id/community-stack/verifier/faq.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Verifier API FAQs
This page addresses common questions about the walt.id Verifier API for the Community Stack.
## General Questions
**Q: What is the difference between the Community Stack and Enterprise Stack Verifier APIs?**
A: The Community Stack Verifier API is stateless and typically deployed as a single instance via Docker. It's designed for simpler use cases where you manage your own infrastructure. The Enterprise Stack is stateful, supports multiple instances with shared databases, includes a tenant system for B2B scenarios, and provides built-in GUI management tools, verification session management, and analytics dashboards.
**Q: What credential formats does the Verifier support?**
A: The Verifier supports multiple credential formats including:
- **SD-JWT VC (IETF)**: Selective Disclosure JSON Web Token Verifiable Credentials
- **W3C Verifiable Credentials**: Versions 1.1+ and 2.0 with JWT or SDD-JWT signatures
- **ISO 18013-5 mDL**: Mobile Driver's License format
**Q: Which OID4VP draft versions are supported?**
A: The Verifier supports OID4VP Draft 14 and Draft 20, with support for DCQL (Draft 28) coming soon. The authorization request can be expressed using either the Presentation Definition protocol or DCQL language.
## Credential Request & Exchange
**Q: How do I define what credentials to request from a wallet?**
A: There are three main approaches for defining credential requests:
1. **Simple**: Combine credential types and formats in a basic request
2. **Presentation Exchange (2.1.1)**: Use the Presentation Definition protocol for full flexibility with various filters and constraints
3. **DCQL (OpenID4VP Draft 28)**: Formulate requests using the DCQL language for maximum flexibility - coming soon
**Q: How does the credential exchange process work?**
A: The process involves:
1. Creating an authorization request that defines required credentials and attributes
2. Generating a standard-compliant OID4VP authorization request URL
3. Sharing the URL with the credential holder via link or QR code
4. The wallet responds with a VP token containing the requested credentials
5. The verifier validates signatures, applies policies, and creates a verification report
**Q: Can I request multiple credentials from different issuers in a single verification session?**
A: Yes, the verifier is designed to handle verifiable presentations that include multiple credentials, including "combined credentials" from different authorities and trust chains. Each credential is validated individually.
## Credential Validation & Policies
**Q: What types of validation policies are available?**
A: The verifier supports three types of policies:
1. **Static policies**: Address common use cases including signature verification, presentation definitions, validity periods, and schema compliance
2. **Parameterized policies**: Offer flexibility with inputs like "allow-issuer" and "webhook" options for external system delegation
3. **Custom policies**: Written in REGO language and evaluated by Open Policy Agent (OPA) for flexible checks on any attributes
**Q: How do webhook policies work?**
A: Webhook policies allow complete delegation of specific checks to external systems. The external system receives the full Verifiable Presentation (VP) token, including both the Verifiable Presentation and the Presented Credentials, and can perform any necessary checks before returning a validation result.
**Q: Can I validate credentials against external systems?**
A: Yes, you can use webhook policies to delegate validation to external systems, or use parameterized policies to integrate with your existing infrastructure. The verifier also supports custom REGO-based policies for complex validation logic.
## Key Management & Security
**Q: What cryptographic algorithms are supported for credential verification?**
A: The verifier supports multiple asymmetric key types including ed25519, secp256k1, secp256r1, and RSA for signature verification of credentials.
## Integration & Deployment
**Q: How do I get started with the Verifier API?**
A: Start with the [getting started guide](https://docs.walt.id/community-stack/verifier/getting-started.md) which covers setup and basic credential verification. You can also try the [white-label portal](https://docs.walt.id/community-stack/verifier/apps/portal/overview.md) for a GUI-based approach.
**Q: Can I integrate the Verifier with my existing identity infrastructure?**
A: Yes, the Verifier API can integrate with external systems through webhook policies and parameterized policies. This allows you to leverage your existing identity infrastructure and validation logic while using the verifier for credential exchange and basic validation.
**Q: How do I handle verification sessions and results?**
A: In the Community Stack, verification session information is stored in memory. You can access verification reports via API calls. The Enterprise Stack provides GUI tools to view and manage verification sessions, view verification results based on applied policies, and trigger credential requests with a multi-tenant setup.
## Standards & Compliance
**Q: What standards does the Verifier support?**
A: The Verifier supports multiple standards including:
- **OID4VP**: Draft 14 and 20, with DCQL (Draft 28) coming soon
- **ISO/IEC 18013-7**: For mDL verification
- **Presentation Exchange 2.1.1**: For flexible credential request definitions
- **REGO/Open Policy Agent**: For custom validation policies
**Q: How does the Verifier handle different credential formats?**
A: The verifier uses a unified architecture powered by core libraries (crypto, did, openid4vc, policies, digital-credentials, dif-definitions-parser, dcql-parser) that abstract away different credential formats. This allows validation of heterogeneous credential sets within a single verification session.
**Q: Can I extend the Verifier to support new credential formats?**
A: Yes, the verifier system is built for extensibility. New credential formats can be integrated through the digital-credential abstraction layer, and custom validation logic can be implemented through REGO policies or webhook integrations. Reach out for more info to tamino@walt.id
**Q: Is the Verifier API compliant with eIDAS2 requirements?**
A: Yes, the Verifier is aligned with the EU's emerging digital ID ecosystem under eIDAS2, including ARF and implementing acts. This ensures credentials can be requested & verified from different wallets, including EUDI wallets.
--------------------------------------------------------------------------------
PAGE: Getting Started
URL: https://docs.walt.id/community-stack/wallet/getting-started
RAW: https://docs.walt.id/community-stack/wallet/getting-started.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Getting Started
## Getting Started
Video: https://www.youtube.com/watch?v=ILaSAxjoHbw
The Wallet-API is designed to provide a broad range of API endpoints that let you offer identity wallets to users
capable of handling different keys, DIDs, credential types and facilitate the receipt and presentation of credentials
from various issuers and verifiers using the OIDC4VC protocol standard. Alongside digital identity capabilities, it also
supports the integration of web3 wallets. This feature enables your users to view their tokens from different blockchain
ecosystems like Ethereum, Polygon, and more.
## Supported Standards
| | |
|:-------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **Credential Formats:** | [SD-JWT VC (IETF)](https://docs.walt.id/concepts/digital-credentials/sd-jwt-vc.md), [W3C VC (v1.1+, v2.0)](https://docs.walt.id/concepts/digital-credentials/verifiable-credentials-w3c.md), [ISO 18013-5 mDL](https://docs.walt.id/concepts/digital-credentials/mdoc-mdl-iso.md) |
| **Credential Exchange:** | [OID4VCI (Draft 11, 13) & OID4VP (Draft 14, 20)](https://docs.walt.id/concepts/data-exchange-protocols.md) , [ISO/IEC 18013-7](https://docs.walt.id/concepts/data-exchange-protocols.md) |
| **Credential Status:** | [StatusList2021, Bitstring Status List, Token Status List](https://docs.walt.id/concepts/credential-lifecycle/credential-status-and-revocation.md) |
|**Signing Algorithms:** | ed25519, secp256k1, secp256r1, RSA |
## Setup
- [Open-Source](https://docs.walt.id/community-stack/wallet/setup.md) - Use the wallet API available via the walt.id identity repository.
- [Docker compose quick start](https://docs.walt.id/community-stack/home.md#quick-start) - Spin up the wallet API locally together with the other services.
- [Full API reference](https://docs.walt.id/community-stack/wallet/full-api-reference.md) - Explore all available endpoints via Swagger.
## Core Features and Components
- [User Accounts & Authentication](https://docs.walt.id/community-stack/wallet/authentication/overview.md) - Manage wallets with username/password or web3 login. Each call requires the wallet ID. Supported blockchain ecosystems are listed [here](https://docs.walt.id/community-stack/wallet/authentication/web3-auth.md).
- [Key Management](https://docs.walt.id/community-stack/wallet/key-management/overview.md) - Create and manage keys using RSA, ECDSA and EdDSA algorithms.
- [DID Management](https://docs.walt.id/community-stack/wallet/did-management/overview.md) - Create and manage DIDs.
- [Credential Exchange](https://docs.walt.id/community-stack/wallet/credential-exchange/overview.md) - Receive, store and present W3C JWT, SD-JWT VC and mDL credentials.
Checkout the full feature overview [here](https://docs.walt.id/community-stack/wallet/features).
## Configurations
Use [the feature manager](https://docs.walt.id/community-stack/wallet/configurations/feature-manager.md) to enable or disable API functionality depending on your use-case.
## Guides
- [Receiving a Credential via OIDC4VCI](https://docs.walt.id/community-stack/wallet/credential-exchange/guides/accept-w3c-vc-oid4vci.md)
- [Presenting a Requested Credential via OIDC4VP](https://docs.walt.id/community-stack/wallet/credential-exchange/guides/present-digital-credentials-oid4vp.md)
--------------------------------------------------------------------------------
PAGE: Overview
URL: https://docs.walt.id/community-stack/wallet/capabilities
RAW: https://docs.walt.id/community-stack/wallet/capabilities.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Overview
The Community Stack Wallet API is a stateful, REST API designed for building custodial digital ID wallet solutions. It provides all the core capabilities needed to manage keys, DIDs, credentials, and perform receive/present flows across multiple standards and formats.
## Wallet Capabilites
--------------------------------------------------------------------------------
PAGE: Setup
URL: https://docs.walt.id/community-stack/wallet/setup
RAW: https://docs.walt.id/community-stack/wallet/setup.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Setup
## Installation & Running the Project
Make sure you have Docker installed on your machine.
### Option 1: Using Docker Pull
**1. Cloning The Identity-Repo**
First, clone the repository to your local machine:
```bash
git clone https://github.com/walt-id/waltid-identity.git && cd waltid-identity
```
**2. Pulling the Docker Image**
To quickly get started, you can pull the pre-built Docker image from Docker Hub:
```bash
docker pull waltid/wallet-api
```
**2. Running the Docker Image**
Once the image is pulled, you can run it using the following command:
```bash
docker run \
-p 7001:7001 \
-itv $(pwd)/waltid-services/waltid-wallet-api/config:/waltid-wallet-api/config \
-itv $(pwd)/waltid-services/waltid-wallet-api/data:/waltid-wallet-api/data \
-t waltid/wallet-api
```
### Option 2: Building the Image Yourself
**1. Cloning The Identity-Repo**
First, clone the repository to your local machine:
```bash
git clone https://github.com/walt-id/waltid-identity.git && cd waltid-identity
```
**2. Building the Docker Image**
If you prefer to build the Docker image yourself, use the Gradle Ktor/Jib tasks:
```bash
# Development (local Docker daemon, single-arch)
./gradlew :waltid-services:waltid-wallet-api:publishImageToLocalRegistry
# image: waltid/wallet-api:
```
```bash
# Production (multi-arch push to your registry)
export DOCKER_USERNAME=
export DOCKER_PASSWORD=
./gradlew :waltid-services:waltid-wallet-api:publishImage
# image: docker.io//wallet-api:
```
Note: multi-arch images require a registry push. Local tar output is single-arch only.
**3. Running the Docker Image**
After building the image, run it using (adjust the image name if you pushed to your registry):
```bash
docker run \
-p 7001:7001 \
-itv $(pwd)/waltid-services/waltid-wallet-api/config:/waltid-wallet-api/config \
-itv $(pwd)/waltid-services/waltid-wallet-api/data:/waltid-wallet-api/data \
-t waltid/wallet-api
```
## Create and Manage Wallets From Your Users
The wallet-api is up and running, start creating user wallets and to receive and present
credentials.
- [Onboard Users](https://docs.walt.id/community-stack/wallet/authentication/overview.md)
- [Receive Credentials](https://docs.walt.id/community-stack/wallet/credential-exchange/guides/accept-w3c-vc-oid4vci.md)
- [Present Credentials](https://docs.walt.id/community-stack/wallet/credential-exchange/guides/present-digital-credentials-oid4vp.md)
--------------------------------------------------------------------------------
PAGE: Overview
URL: https://docs.walt.id/community-stack/wallet/authentication/overview
RAW: https://docs.walt.id/community-stack/wallet/authentication/overview.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# User Authentication and Session Management
**Info:**
We have recently added a new authentication strategy to the wallet which leverages our own [`waltid-ktor-authnz` library](https://github.com/walt-id/waltid-identity/tree/main/waltid-libraries/auth/waltid-ktor-authnz).
We recommend using this new authentication strategy if possible for all future developlemts. It offers a more production ready solution of our open source wallet backend including authentication methods for:
* Email/Password Auth
* OIDC Auth
You can find more information about the new authentication strategy [here](https://docs.walt.id/community-stack/wallet/authentication/new-auth.md).
The wallet API offers various endpoints for managing user authentication and sessions. Users can create accounts using a
standard email and password, an OIDC provider such as Keycloak, or Web3 wallets like Ethereum, Tezos, Flow, Near,
Polkadot, and Algorand. Additionally, if you have a custom OIDC authentication solution that provides JWT session
tokens,
you can use those tokens directly to log in users without having to use any of the previously mentioned onboarding
options.
## Storage
The wallet api uses a local database to store user accounts and session information. Secrets
are encrypted using Argon2.
## Methods of Authentication
The Wallet API by default employees cookie-based authentication, as it simplifies the user experience by automatically
handling the creation and renewal of cookies.
In specific scenarios where cookies may not be practical or permitted, the Wallet API also supports the Bearer token
authentication method. However, here you need to manage acquiring, storing, and appending tokens
to every request yourself.
We strongly advise using cookie-based authentication wherever feasible and only use Bearer tokens when necessary.
### Cookie-based Authentication
Cookie-based authentication is the default authentication method for the Wallet API. It is the simplest and most
convenient method of authentication, as it automatically handles the creation and renewal of cookies.
By calling the `/auth/login` endpoint, the Wallet API will create a cookie containing a session token. This token is
then used to authenticate the user for all subsequent requests.
By calling the `/auth/logout` endpoint, the Wallet API will delete the cookie containing the session token. Therefore,
the user will no longer be authenticated for subsequent requests.
You can find a full list of endpoints for user authentication and session
management [here](https://wallet.demo.walt.id/swagger/index.html#/Authentication).
### Bearer Token Authentication
Bearer token authentication is an alternative authentication method for the Wallet API. It is more complex than
cookie-based authentication, as it requires you to manage acquiring, storing, and appending tokens to every request
yourself.
By calling the `/auth/login` endpoint, the Wallet API will return a session token. This token is then used to
authenticate the user for all subsequent requests.
**Response**
```json
{
...
"token": "C6H7v0fjls5diXT2WX5JL7KnsEZnAysZqFjDirsNoIY",
...
}
```
By all subsequent requests, you need to append the token to the Authorization header. The token is appended as follows:
```json
{
"header": {
"Authorization": "Bearer "
// or
"waltid-authorization": "Bearer "
}
}
```
You can find a full list of endpoints for user authentication and session
management [here](https://wallet.demo.walt.id/swagger/index.html#/Authentication).
## Authentication Strategies
Below you find a list of all the different authentication options you can enable for your users to secure their
wallet accounts.
- [Email/Password Auth](https://docs.walt.id/community-stack/wallet/authentication/email-password-auth.md) - Classical email password authentication.
- [OIDC Provider Auth](https://docs.walt.id/community-stack/wallet/authentication/jwt-oidc/oidc.md) - Connect your OIDC provider, e.g. Keycloak or Auth0 , to authenticate users.
- [OIDC Unique Subject Auth](https://docs.walt.id/community-stack/wallet/authentication/jwt-oidc/oidc-unique-sub-auth.md) - Self-manage authentication, only provide a valid JWT for auth.
- [Web3 Auth](https://docs.walt.id/community-stack/wallet/authentication/web3-auth.md) - Authentication via web3 accounts from Ethereum, Tezos, Flow, Polkadot and others.
--------------------------------------------------------------------------------
PAGE: Email/Password Auth
URL: https://docs.walt.id/community-stack/wallet/authentication/email-password-auth
RAW: https://docs.walt.id/community-stack/wallet/authentication/email-password-auth.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Email/Password Auth
To create a new user account, you need to send a `POST` request to the following endpoint:
## Create User Account
**Option: CURL**
[API Reference](https://wallet.demo.walt.id/swagger/index.html#/Authentication/post_wallet_api_auth_create)
Each created account receives a default "did:key" and private key (Ed25519).
```bash
curl -X 'POST' \
'http://0.0.0.0:7001/wallet-api/auth/register' \
-H 'accept: */*' \
-H 'Content-Type: application/json' \
-d '{
"name": "Max Mustermann",
"email": "test@email.com",
"password": "password",
"type": "email"
}'
```
**Body Parameters**
```json
{
"name": "string",
"email": "string",
"password": "string",
"type": "{type}"
}
```
- `name`: _[string]_ - The name of the user.
- `email`: _[string]_ - The email of the user.
- `password`: _[string]_ - The password of the user.
- `type`: _[string]_ - The type of the user account. `email` or `address` (for web3 login).
## Login
**Option: CURL**
[API Reference](https://wallet.demo.walt.id/swagger/index.html#/Authentication/post_wallet_api_auth_login)
```bash
curl -X 'POST' \
'http://0.0.0.0:7001/wallet-api/auth/login' \
-H 'accept: */*' \
-H 'Content-Type: application/json' \
-d '{
"type": "email",
"email": "user@email.com",
"password": "password"
}'
```
**Body Parameters**
```json
{
"type": "{type}",
"email": "string",
"password": "string"
}
```
- `type`: _[string]_ - The type of the user account. `email` or `address` (for web3 login).
- `email`: _[string]_ - The email of the user.
- `password`: _[string]_ - The password of the user.
**Example Response**
Now a session is automatically created for cookie-based authentication. For Bearer Token Authentication, the `token`
returned
must be provided in the header for each request that needs authentication. Refer to
the [overview section](https://docs.walt.id/community-stack/wallet/authentication/overview.md#cookie-based-authentication) for more details.
```json
{
"token": "KL-a_dk1qO8moCX4gxaGfb7_TS8RK-JWVKZk9BBP0-s",
"id": "018045e5-942c-4362-b535-658c4dd581ef",
"username": "user@email.com"
}
```
## Logout
**Option: CURL**
[API Reference](https://wallet.demo.walt.id/swagger/index.html#/Authentication/post_wallet_api_auth_logout)
Deletes the session/invalidates the token.
```bash
curl -X 'POST' \
'http://0.0.0.0:7001/wallet-api/auth/logout' \
-H 'accept: */*' \
-d ''
```
--------------------------------------------------------------------------------
PAGE: Web3 Auth
URL: https://docs.walt.id/community-stack/wallet/authentication/web3-auth
RAW: https://docs.walt.id/community-stack/wallet/authentication/web3-auth.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Web3 Authentication
The wallet-api lets users create and secure their account by leveraging web3 authentication.
Based on the latest standards, like [Login With Ethereum](https://eips.ethereum.org/EIPS/eip-4361).
### Supported Blockchains
- Ethereum
### Enabling Web3 Authentication feature in the wallet-api
To enable the web3 authentication feature in the wallet-api, you need to set the following features in the
`_features.conf` file :
```bash
enabledFeatures = [
# external-signature-endpoints,
# trusted-ca,
# entra,
ktor-authnz,
# dev-mode
# ...
]
disabledFeatures = [
auth # legacy auth
]
```
### Configure the auth flow in `ktor-authnz.conf` file :
```bash
# Configure the Auth Flow (refer to: waltid-ktor-authnz)
authFlow = {
method: web3
expiration: "7d" # optional: Set expiration time for login tokens, e.g. a week
ok: true # Auth flow ends successfuly with this step
```
## Authentication Flow
1. **Client Requests Nonce**
- Client calls GET `/web3/nonce`
- Server generates a random nonce and returns it as a JWT
2. **Client Signs Message**
- Client uses their Ethereum wallet to sign the nonce
- Message is formatted according to EIP-191 standard
3. **Client Submits Signature**
- Client sends the signed message , the challenge and public key to POST `/web3/signed`
- Server verifies the signature and authenticates the user
4. **Server Authentication**
- Server recovers the public key from the signature
- Verifies the recovered address matches the provided address
- Creates or retrieves user account
- Issues session token
## Generate a new nonce for web3 authentication
To login with a web3 wallet, you need to generate a nonce first. The nonce is a random string that is used to sign a
message and verify the signature.:
**Option: CURL**
[API Reference](https://wallet.demo.walt.id/swagger/index.html#/Authentication/get_wallet_api_auth_account_web3_nonce)
```bash
curl -X 'POST' \
'http://0.0.0.0:7001/wallet-api/auth/account/web3/nonce' \
-H 'accept: */*'
```
**Example Response**
```bash
eyJhbGciOiJFZERTQSJ9.eyJub25jZSI6IjB4YjI1NzJhZWE2ODEyYmM4NjE2MDgyN2M3NmRmMjA3ZWVlNWNjMGVjNzQ4MmRlZjFhMmYxMTFiZTYxZGIzNjI3NyIsImV4cCI6MTczNzA2MjAyMH0.bQYGos3ZYA0POd5Dibvg7-NDZldVyReIW_0BsdDjVDpu0Tz1DJR9ZTpAuQlanC5hPgLB1yRua9_FPJ-0lokZCw
```
## Login
**Option: CURL**
[API Reference](https://wallet.demo.walt.id/swagger/index.html#/Authentication/post_wallet_api_auth_account_web3_signed)
```bash
curl -X 'POST' \
'http://0.0.0.0:7001/wallet-api/auth/account/web3/signed' \
-H 'accept: */*' \
-H 'Content-Type: application/json' \
-d '{
"challenge": "eyJhbGciOiJFZERTQSJ9.eyJub25jZSI6IjB4YTgzNzI1M2U3ZDdmNjhkMGI5ODM2ZWMzNjlkZWE0MmMyYTFmMDg0ZWU3NDhjYTM0YTc4YTI2OTY4YTJiY2Q1NiIsImV4cCI6MTczNzA2MjE5OX0.DJAb5AgZO-Qgxe7nTkkt7qVWlM_bhQcpM7ZkD9HSqIQ81DAIm-OpOjv0Y56R6lmOVy82sVpHg4g7RUofTzieCg",
"signed": "0x132463eab627a8ba10f0fc87871b48cb193ada7cab01ff68ed3627ab5b7268c64d3fe7172f58634ecb1412ff0084c70611dbe30c0cdc99528e2e06437a4056ae1c",
"publicKey": "0xec57a39eb7e46728c143f8c1a0eb6d0fb77c814d"
}'
```
**Body Parameters**
```json
{
"challenge": "string",
"signed": "string",
"publicKey": "string"
}
```
- `challenge`: _String_ - The nonce generated in the previous step.
- `signed`: _String_ - The signed message by the user.
- `publicKey`: _String_ - The public key of the user's wallet.
**Example Response**
Now a session is automatically created for web3 authentication. For Bearer Token Authentication, the `token`
returned
must be provided in the header for each request that needs authentication. Refer to
the [overview section](https://docs.walt.id/community-stack/wallet/authentication/overview.md#cookie-based-authentication) for more details.
```json
{
"session_id": "0211b04c-cb5b-4b56-8a49-eaa63b4fdfe8",
"status": "OK",
"token": "eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiI1MmIyZDQ2Zi0wNzQ0LTQ1YTgtOTIwNS1jZjdjYTMyODhkMzEiLCJzZXNzaW9uIjoiMDIxMWIwNGMtY2I1Yi00YjU2LThhNDktZWFhNjNiNGZkZmU4IiwiZXhwIjoxNzM3NjY2ODkxfQ.34-eydmcP7Vc8GJTsvqm8ofEnp2Ch_ZaZJHqYJO8edeBBvAoLB06YtWCLz2eDbLAxPyBkQwJOtJ6DPv0j1b3Cg",
"expiration": "2025-01-23T21:14:51.542977200Z"
}
```
--------------------------------------------------------------------------------
PAGE: X.509 Certificate
URL: https://docs.walt.id/community-stack/wallet/authentication/x.509-certificate
RAW: https://docs.walt.id/community-stack/wallet/authentication/x.509-certificate.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# X.509 Certificate
## Setup
1. Populate the trusted CA conf with the trusted certificates. Learn
more [here](https://docs.walt.id/community-stack/wallet/configurations/config-files/trusted-ca.md).
2. Enable the feature called `trusted-ca`, because it's disabled by default, via the feature manager. Learn
more [here](https://docs.walt.id/community-stack/wallet/configurations/config-files/features.md). Once the feature is enabled, a new section of API
endpoints will be visible in the swagger docs of the Wallet API.
## Usage
### Create Account
To create a new user account, you need to send a `POST` request to the following endpoint:
**Option: CURL**
[API Reference](https://wallet.demo.walt.id/swagger/index.html#/Authentication/post_wallet_api_auth_create)
```bash
curl -X 'POST' \
'http://0.0.0.0:7001/wallet-api/auth/x5c/register' \
-H 'accept: */*' \
-H 'Content-Type: application/json' \
-d '{
"type": "x5c",
"token": "eyJ4NWMiOlsiTUlJRElUQ0NBUW1nQXdJQkFnSUdBWklldzg3aE1BMEdDU3FHU0liM0RRRUJDd1VBTUJFeER6QU5CZ05WQkFNTUJsSnZiM1JEUVRBZUZ3MHlOREE1TWpJeE1qQXpNakZhRncweU5qQTVNak14TWpBek1qRmFNQll4RkRBU0JnTlZCQU1NQzFOdmJXVlRkV0pxWldOME1JR2ZNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0R05BRENCaVFLQmdRREVNRlh0VzR0b3lrZlcrWGJTRTVmTFg2NURoWnpneFlkbE14VGxtMEZRY25sZ1hBLzdpRytjeTN2MjRyOFJ5andsUnpWRFNoL3lJSUpuald6RkNCaFIxZ2oxMXdPMG9ZTHlRa29PRXR3RkFTRzR0cTk1bzhaS094MkJ2b0NWWWo3bW8xeE92N1VySHNQRllGUTlDOFEyd3FrTVYzNUxVNWMybXVBOHJpR3lxUUlEQVFBQk1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQ0FRQnM2dkQybDY1RUVDUmxMUE5NQ2RRZGJhUG5HN3ljVDljZEdIWi9Id3RGNk1MTUFxZWk1aXRUYTNibVJIR2s3aVYxU2pVbmZKTS9EQmQrZ2hWNy90cFZzblZ0a29EZEpQUS9XUDBPdUNTRnVTTk82TlY1dzBUZnlFVmVXNFJ5V05nYzJscHhjZjlJb2lZSU9NQW9iQ0ZNU285eXZSVXR4eENUV0x1UUlVNjEyZzVPaUF2SUl3LzJMbGs2TzlkTEM0aWFhR2ZRVm84Q3M3UnRZdUwzdFZkenVaU3RKMU1WVHNYa2VQdExncEUzc1BaR1pnSzVwbGRkanJ4WHc1K2F1NGVBaDV3RWlGYjdIck1RQndvbWtKeG5yUWRVU05mSDJEM2gzdVdEQ0hVc3pndmhsZUhhREl2eDh1VTVHbjg2elQ0SzJHUElTYSs5bDlFSlpMK3BjNXhQTmRaZjJubFFoY05MRmFvdGdWRnlwMkFWK2FBVkZmeWFKcG92YWYzL2lmdkhIR0lvU05HZzhDb1AzVUk5YVFOVktVS0JCWXRrUjFRQXlBb1l6NThpcUdNUzQxVUdHNmwydEN3YmtrM21KckxoZlB6eFVtSjdUVGRMVkhmQmFCd3NwdW91ZXN3MEU1REVvdittVXlIQ2Z2VU1ROXBjMldhSHNEekxKZHpPSlV5WFJBTjZUcm1wbjlnb0I1MVJOeTJqK3FOck1NOTJNR3Z6RzJPb0NNNWNTOHBGdWhhNisvMG1Sc20ydE16d1l6Mzl0bkFobEZSUWsxTmpNelE4bUh0andyTmhCOUFGb0o5cGVHRHBMYnYzVWc1alQ5Tzl0d0NLcjhwcWJLWlZ3WUhDektyUXV2SUVIaEZTdWRjRGxwWmpEUnEzNGFoWjNtTkIzMzZidUVZa2VBPT0iXSwiYWxnIjoiUlMyNTYifQ.e30.EApsGy5IPghUjIq7rTLr0UI6-glQjRcdCRecOlKRCrye-_icACNDX7HTRy9pKSqBJrzvhe8AiHadIeAbsbTXsd0E0QsHSQPeGYGYwOO7KgKXynB8ESlavaJRjMQj8F2Uuajxdas6J3MefeaA6REUt4RdVhBpUXJNW--uSYzb0Lg"
}'
```
**Body Parameters**
```json
{
"type": "x5c",
"token": "eyJ4NWMiOlsiTUlJRElUQ0NBUW1nQXdJQkFnSUdBWklldzg3aE1BMEdDU3FHU0liM0RRRUJDd1VBTUJFeER6QU5CZ05WQkFNTUJsSnZiM1JEUVRBZUZ3MHlOREE1TWpJeE1qQXpNakZhRncweU5qQTVNak14TWpBek1qRmFNQll4RkRBU0JnTlZCQU1NQzFOdmJXVlRkV0pxWldOME1JR2ZNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0R05BRENCaVFLQmdRREVNRlh0VzR0b3lrZlcrWGJTRTVmTFg2NURoWnpneFlkbE14VGxtMEZRY25sZ1hBLzdpRytjeTN2MjRyOFJ5andsUnpWRFNoL3lJSUpuald6RkNCaFIxZ2oxMXdPMG9ZTHlRa29PRXR3RkFTRzR0cTk1bzhaS094MkJ2b0NWWWo3bW8xeE92N1VySHNQRllGUTlDOFEyd3FrTVYzNUxVNWMybXVBOHJpR3lxUUlEQVFBQk1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQ0FRQnM2dkQybDY1RUVDUmxMUE5NQ2RRZGJhUG5HN3ljVDljZEdIWi9Id3RGNk1MTUFxZWk1aXRUYTNibVJIR2s3aVYxU2pVbmZKTS9EQmQrZ2hWNy90cFZzblZ0a29EZEpQUS9XUDBPdUNTRnVTTk82TlY1dzBUZnlFVmVXNFJ5V05nYzJscHhjZjlJb2lZSU9NQW9iQ0ZNU285eXZSVXR4eENUV0x1UUlVNjEyZzVPaUF2SUl3LzJMbGs2TzlkTEM0aWFhR2ZRVm84Q3M3UnRZdUwzdFZkenVaU3RKMU1WVHNYa2VQdExncEUzc1BaR1pnSzVwbGRkanJ4WHc1K2F1NGVBaDV3RWlGYjdIck1RQndvbWtKeG5yUWRVU05mSDJEM2gzdVdEQ0hVc3pndmhsZUhhREl2eDh1VTVHbjg2elQ0SzJHUElTYSs5bDlFSlpMK3BjNXhQTmRaZjJubFFoY05MRmFvdGdWRnlwMkFWK2FBVkZmeWFKcG92YWYzL2lmdkhIR0lvU05HZzhDb1AzVUk5YVFOVktVS0JCWXRrUjFRQXlBb1l6NThpcUdNUzQxVUdHNmwydEN3YmtrM21KckxoZlB6eFVtSjdUVGRMVkhmQmFCd3NwdW91ZXN3MEU1REVvdittVXlIQ2Z2VU1ROXBjMldhSHNEekxKZHpPSlV5WFJBTjZUcm1wbjlnb0I1MVJOeTJqK3FOck1NOTJNR3Z6RzJPb0NNNWNTOHBGdWhhNisvMG1Sc20ydE16d1l6Mzl0bkFobEZSUWsxTmpNelE4bUh0andyTmhCOUFGb0o5cGVHRHBMYnYzVWc1alQ5Tzl0d0NLcjhwcWJLWlZ3WUhDektyUXV2SUVIaEZTdWRjRGxwWmpEUnEzNGFoWjNtTkIzMzZidUVZa2VBPT0iXSwiYWxnIjoiUlMyNTYifQ.e30.EApsGy5IPghUjIq7rTLr0UI6-glQjRcdCRecOlKRCrye-_icACNDX7HTRy9pKSqBJrzvhe8AiHadIeAbsbTXsd0E0QsHSQPeGYGYwOO7KgKXynB8ESlavaJRjMQj8F2Uuajxdas6J3MefeaA6REUt4RdVhBpUXJNW--uSYzb0Lg"
}
```
- `type`: _[string]_ - specifying the auth method used for authentication. In our case `x5c`.
- `token`: _[string]_ - the auth token of the user that should be authenticated. The `x5c` header should be set based on
the [JSON Web Signature (JWS) of the JOSE family specifications](https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.6).
## Login
**Option: CURL**
[API Reference](https://wallet.demo.walt.id/swagger/index.html#/Authentication/post_wallet_api_auth_login)
```bash
curl -X 'POST' \
'http://0.0.0.0:7001/wallet-api/auth/x5c/login' \
-H 'accept: */*' \
-H 'Content-Type: application/json' \
-d '{
"type": "x5c",
"token": "eyJ4NWMiOlsiTUlJRElUQ0NBUW1nQXdJQkFnSUdBWklldzg3aE1BMEdDU3FHU0liM0RRRUJDd1VBTUJFeER6QU5CZ05WQkFNTUJsSnZiM1JEUVRBZUZ3MHlOREE1TWpJeE1qQXpNakZhRncweU5qQTVNak14TWpBek1qRmFNQll4RkRBU0JnTlZCQU1NQzFOdmJXVlRkV0pxWldOME1JR2ZNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0R05BRENCaVFLQmdRREVNRlh0VzR0b3lrZlcrWGJTRTVmTFg2NURoWnpneFlkbE14VGxtMEZRY25sZ1hBLzdpRytjeTN2MjRyOFJ5andsUnpWRFNoL3lJSUpuald6RkNCaFIxZ2oxMXdPMG9ZTHlRa29PRXR3RkFTRzR0cTk1bzhaS094MkJ2b0NWWWo3bW8xeE92N1VySHNQRllGUTlDOFEyd3FrTVYzNUxVNWMybXVBOHJpR3lxUUlEQVFBQk1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQ0FRQnM2dkQybDY1RUVDUmxMUE5NQ2RRZGJhUG5HN3ljVDljZEdIWi9Id3RGNk1MTUFxZWk1aXRUYTNibVJIR2s3aVYxU2pVbmZKTS9EQmQrZ2hWNy90cFZzblZ0a29EZEpQUS9XUDBPdUNTRnVTTk82TlY1dzBUZnlFVmVXNFJ5V05nYzJscHhjZjlJb2lZSU9NQW9iQ0ZNU285eXZSVXR4eENUV0x1UUlVNjEyZzVPaUF2SUl3LzJMbGs2TzlkTEM0aWFhR2ZRVm84Q3M3UnRZdUwzdFZkenVaU3RKMU1WVHNYa2VQdExncEUzc1BaR1pnSzVwbGRkanJ4WHc1K2F1NGVBaDV3RWlGYjdIck1RQndvbWtKeG5yUWRVU05mSDJEM2gzdVdEQ0hVc3pndmhsZUhhREl2eDh1VTVHbjg2elQ0SzJHUElTYSs5bDlFSlpMK3BjNXhQTmRaZjJubFFoY05MRmFvdGdWRnlwMkFWK2FBVkZmeWFKcG92YWYzL2lmdkhIR0lvU05HZzhDb1AzVUk5YVFOVktVS0JCWXRrUjFRQXlBb1l6NThpcUdNUzQxVUdHNmwydEN3YmtrM21KckxoZlB6eFVtSjdUVGRMVkhmQmFCd3NwdW91ZXN3MEU1REVvdittVXlIQ2Z2VU1ROXBjMldhSHNEekxKZHpPSlV5WFJBTjZUcm1wbjlnb0I1MVJOeTJqK3FOck1NOTJNR3Z6RzJPb0NNNWNTOHBGdWhhNisvMG1Sc20ydE16d1l6Mzl0bkFobEZSUWsxTmpNelE4bUh0andyTmhCOUFGb0o5cGVHRHBMYnYzVWc1alQ5Tzl0d0NLcjhwcWJLWlZ3WUhDektyUXV2SUVIaEZTdWRjRGxwWmpEUnEzNGFoWjNtTkIzMzZidUVZa2VBPT0iXSwiYWxnIjoiUlMyNTYifQ.e30.EApsGy5IPghUjIq7rTLr0UI6-glQjRcdCRecOlKRCrye-_icACNDX7HTRy9pKSqBJrzvhe8AiHadIeAbsbTXsd0E0QsHSQPeGYGYwOO7KgKXynB8ESlavaJRjMQj8F2Uuajxdas6J3MefeaA6REUt4RdVhBpUXJNW--uSYzb0Lg"
}'
```
**Body Parameters**
```json
{
"type": "x5c",
"token": "eyJ4NWMiOlsiTUlJRElUQ0NBUW1nQXdJQkFnSUdBWklldzg3aE1BMEdDU3FHU0liM0RRRUJDd1VBTUJFeER6QU5CZ05WQkFNTUJsSnZiM1JEUVRBZUZ3MHlOREE1TWpJeE1qQXpNakZhRncweU5qQTVNak14TWpBek1qRmFNQll4RkRBU0JnTlZCQU1NQzFOdmJXVlRkV0pxWldOME1JR2ZNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0R05BRENCaVFLQmdRREVNRlh0VzR0b3lrZlcrWGJTRTVmTFg2NURoWnpneFlkbE14VGxtMEZRY25sZ1hBLzdpRytjeTN2MjRyOFJ5andsUnpWRFNoL3lJSUpuald6RkNCaFIxZ2oxMXdPMG9ZTHlRa29PRXR3RkFTRzR0cTk1bzhaS094MkJ2b0NWWWo3bW8xeE92N1VySHNQRllGUTlDOFEyd3FrTVYzNUxVNWMybXVBOHJpR3lxUUlEQVFBQk1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQ0FRQnM2dkQybDY1RUVDUmxMUE5NQ2RRZGJhUG5HN3ljVDljZEdIWi9Id3RGNk1MTUFxZWk1aXRUYTNibVJIR2s3aVYxU2pVbmZKTS9EQmQrZ2hWNy90cFZzblZ0a29EZEpQUS9XUDBPdUNTRnVTTk82TlY1dzBUZnlFVmVXNFJ5V05nYzJscHhjZjlJb2lZSU9NQW9iQ0ZNU285eXZSVXR4eENUV0x1UUlVNjEyZzVPaUF2SUl3LzJMbGs2TzlkTEM0aWFhR2ZRVm84Q3M3UnRZdUwzdFZkenVaU3RKMU1WVHNYa2VQdExncEUzc1BaR1pnSzVwbGRkanJ4WHc1K2F1NGVBaDV3RWlGYjdIck1RQndvbWtKeG5yUWRVU05mSDJEM2gzdVdEQ0hVc3pndmhsZUhhREl2eDh1VTVHbjg2elQ0SzJHUElTYSs5bDlFSlpMK3BjNXhQTmRaZjJubFFoY05MRmFvdGdWRnlwMkFWK2FBVkZmeWFKcG92YWYzL2lmdkhIR0lvU05HZzhDb1AzVUk5YVFOVktVS0JCWXRrUjFRQXlBb1l6NThpcUdNUzQxVUdHNmwydEN3YmtrM21KckxoZlB6eFVtSjdUVGRMVkhmQmFCd3NwdW91ZXN3MEU1REVvdittVXlIQ2Z2VU1ROXBjMldhSHNEekxKZHpPSlV5WFJBTjZUcm1wbjlnb0I1MVJOeTJqK3FOck1NOTJNR3Z6RzJPb0NNNWNTOHBGdWhhNisvMG1Sc20ydE16d1l6Mzl0bkFobEZSUWsxTmpNelE4bUh0andyTmhCOUFGb0o5cGVHRHBMYnYzVWc1alQ5Tzl0d0NLcjhwcWJLWlZ3WUhDektyUXV2SUVIaEZTdWRjRGxwWmpEUnEzNGFoWjNtTkIzMzZidUVZa2VBPT0iXSwiYWxnIjoiUlMyNTYifQ.e30.EApsGy5IPghUjIq7rTLr0UI6-glQjRcdCRecOlKRCrye-_icACNDX7HTRy9pKSqBJrzvhe8AiHadIeAbsbTXsd0E0QsHSQPeGYGYwOO7KgKXynB8ESlavaJRjMQj8F2Uuajxdas6J3MefeaA6REUt4RdVhBpUXJNW--uSYzb0Lg"
}
```
- `type`: _[string]_ - specifying the auth method used for authentication. In our case `x5c`.
- `token`: _[string]_ - the auth token of the user that should be authenticated. The `x5c` header should be set based on
the [JSON Web Signature (JWS) of the JOSE family specifications](https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.6).
**Example Response**
Now a session is automatically created for cookie-based authentication. For Bearer Token Authentication, the `token`
returned
must be provided in the header for each request that needs authentication. Refer to
the [overview section](https://docs.walt.id/community-stack/wallet/authentication/overview.md#cookie-based-authentication) for more details.
```json
{
"id": "b372a51b-0374-4cd0-b7d3-3c9ab5d09bdf",
"token": "eyJhbGciOiJIUzI1NiJ9.eyJuYmYiOjE3MjcwOTMzMzIsImV4cCI6MTcyOTY4NTMzMiwiaWF0IjoxNzI3MDkzMzMyLCJqdGkiOiJhM2QxM2NmOC0wNzRkLTQ0MTYtOGEwZS1kMzRjMmQxZmNlNDAiLCJpc3MiOiJpc3N1ZXIgdmFsdWUiLCJhdWQiOiJhdWRpZW5jZSB2YWx1ZSIsInN1YiI6ImIzNzJhNTFiLTAzNzQtNGNkMC1iN2QzLTNjOWFiNWQwOWJkZiJ9.PyvS6AHSSj7AdPRNZhrfq089RgxvYwWkgHgFmQfQbzg"
}
```
## Logout
**Option: CURL**
[API Reference](https://wallet.demo.walt.id/swagger/index.html#/Authentication/post_wallet_api_auth_logout)
Deletes the session/invalidates the token.
```bash
curl -X 'POST' \
'http://0.0.0.0:7001/wallet-api/auth/x5c/logout' \
-H 'accept: */*' \
-d ''
```
--------------------------------------------------------------------------------
PAGE: New and Improved Authentication Strategy
URL: https://docs.walt.id/community-stack/wallet/authentication/new-auth
RAW: https://docs.walt.id/community-stack/wallet/authentication/new-auth.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# New and Improved Authentication Strategy
We recently added a new authentication strategy to the wallet which leverages our own [`waltid-ktor-authnz` library](https://github.com/walt-id/waltid-identity/tree/main/waltid-libraries/auth/waltid-ktor-authnz)
We recommend using this new authentication strategy if possible for all future developlemts. It offers a more production ready solution of our open source wallet backend including authentication methods for:
* Email/Password Auth
* OIDC Auth
This new version specifically supports more options for token expiration and improved logout functionality.
## Enabling the new authentication strategy
In the config folder, edit the `_features.conf` file to enable the new authentication strategy and disable the legacy authentication strategy as shown below:
```json
enabledFeatures = [
ktor-authnz
]
disabledFeatures = [
auth
]
```
We then update the new `ktor-authnz.conf` file in the config folder to configure the authentication strategy. Some interesting options to configure are:
### Auth flows
Username And Password Auth Flow:
```json
authFlow = {
method: email
expiration: "7d"
ok: true # Auth flow ends successfully with this step
}
```
OIDC Auth Flow:
```json
authFlow = {
method: oidc,
config: {
openIdConfigurationUrl: "http://localhost:8080/realms/master/.well-known/openid-configuration",
clientId: "waltid_ktor_authnz",
clientSecret: "fzYFC6oAgbjozv8NoaXuOIfPxmT4XoVM",
callbackUri: "http://wallet.localhost:7001/wallet-api/auth/account/oidc/callback",
pkceEnabled: true,
redirectAfterLogin: "http://wallet.localhost:7001/wallet-api/auth/oidc-callback-frontend"
},
success: true
}
```
Note: the expiry time for the OIDC token is based on the configuration of the OIDC provider itself.
### Token Types
```json
tokenType = STORE_IN_MEMORY
```
Supported:
- STORE_IN_MEMORY: In memory token store (single-instance, no configuration necessary)
- STORE_VALKEY: Store in Redis/Valkey/Redict/KeyDB (multi-instance, distributed logout supported) - configure Redis/Valkey/KeyDB instance below
- JWT: Tokens as stateless JWT (multi-instance, no logout support as it is stateless!) - configure keys below
## Setting up a keycloak instance for OIDC based authentication
While we support using any OIDC provider, you may decide to run your own. In this case, we recommend using the open source solution [Keycloak](https://www.keycloak.org/).
Instructions on how to setup and configure the keycloak instance for OIDC based authentication can be found [here](https://github.com/walt-id/waltid-identity/blob/main/waltid-libraries/auth/waltid-ktor-authnz/docs/oidc.md).
## Using the new authentication methods
### Email/Password Auth
```bash
curl -X POST http://localhost:7001/wallet-api/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "test@example.com", "password": "password"}'
```
### OIDC Auth
After completing the authentication configuration above, you should see a new set of endpoints available for the wallet API (auth/account/oidc)
```bash
curl -X GET http://localhost:7001/wallet-api/auth/account/oidc/auth
```
Will return a JSON response which contains a `next_step` object which your wallet client should use to redirect the user to authenticate with the OIDC provider. The user will be automatically redirected based on the configuration you have set up in the `ktor-authnz.conf` file.
If the user does not have an account yet, it will be automatically created.
#### Logout
This implementation supports logout through the OIDC provider (which will invalidate the session) and through the Wallet API directly using the following endpoint:
```bash
curl -X GET http://localhost:7001/wallet-api/auth/account/oidc/logout/frontchannel
```
--------------------------------------------------------------------------------
PAGE: OIDC Providers
URL: https://docs.walt.id/community-stack/wallet/authentication/jwt-oidc/oidc
RAW: https://docs.walt.id/community-stack/wallet/authentication/jwt-oidc/oidc.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# OIDC Providers
OpenID Connect (OIDC) Authentication
====================================
OpenID Connect (OIDC) is an identity authentication protocol that is an extension of OAuth
2.0. It standardizes the process for authenticating and authorizing users when they sign in to access digital
services.OIDC provides authentication, which means verifying that users are who they say they are.
Key components of OIDC include:
- **Authentication**: Verifying that the user is who they say they are.
- **Client**: The software, such as a website or application, that requests tokens used to authenticate a user or access
a resource.
- **Relying Parties**: Applications that use OpenID providers to authenticate users.
- **Identity Tokens**: Contain identity data including the outcome of the authentication process, an identifier for the
user, and information about how and when the user is authenticated.
- **OpenID Providers**: Applications for which a user already has an account. Their role in OIDC is to authenticate the
user and pass that information on to the relying party.
- **Users**: People or services that seek to access an application without creating a new account or providing a
username and password.
JSON Web Token (JWT) Authentication
===================================
JSON Web Token (JWT) is an open standard that defines a compact and self-contained way for securely transmitting
information between parties as a JSON object. This information can be verified and trusted because it is digitally
signed.
JWTs are useful in scenarios such as:
- **Authorization**: Once the user is logged in, each subsequent request will include the JWT, allowing the user to
access routes, services, and resources that are permitted with that token.
- **Information Exchange**: JWTs are a good way of securely transmitting information between parties. Because JWTs can
be signed, you can be sure the senders are who they say they are.
JWTs consist of three parts:
- **Header**: The type of the token, which is JWT, and the signing algorithm being used, such as HMAC SHA256 or RSA.
- **Payload**: Contains the claims, which are statements about an entity (typically, the user) and additional data.
- **Signature**: Created using a secret key.
For more detailed information, you can refer to the following resources:
- [What Is OpenID Connect (OIDC)? | Microsoft Security](https://www.microsoft.com/en-us/security/business/security-101/what-is-openid-connect-oidc)
- [How OpenID Connect Works - OpenID Foundation](https://openid.net/developers/how-connect-works/)
- [JSON Web Token Introduction - jwt.io](https://jwt.io/introduction/)
- [JWT authentication: Best practices and when to use it](https://blog.logrocket.com/jwt-authentication-best-practices/)
- [What Is JWT? How Does It Work? | Akana by Perforce](https://www.akana.com/blog/what-is-jwt)
Supported OIDC Providers :
==========================
The Wallet API supports the following OIDC providers:
- [**Keycloak**](https://docs.walt.id/community-stack/wallet/authentication/jwt-oidc/keycloak/overview.md): An open-source identity and access management solution.
- [**Auth0**](https://docs.walt.id/community-stack/wallet/authentication/jwt-oidc/auth0/overview.md): A flexible, drop-in solution to add authentication and authorization services to your applications.
--------------------------------------------------------------------------------
PAGE: OIDC Unique Sub Auth
URL: https://docs.walt.id/community-stack/wallet/authentication/jwt-oidc/oidc-unique-sub-auth
RAW: https://docs.walt.id/community-stack/wallet/authentication/jwt-oidc/oidc-unique-sub-auth.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# OIDC Unique Sub Auth
With this authentication strategy you can simply provide a valid JWT with a unique subject claim and the wallet-api
will create or login a user to the associated wallet account. This way you can use your own authentication
flows
and just provide the result (valid JWT) to the walt.id system. To verify the JWT provided is coming from your auth
server, you need to configure the relevant OIDC endpoints via a config.
## Config
In `waltid-wallet-api/config/oidc.conf` update the value `oidcRealm` and `oidcJwks` with your auth server values.
Below you find an example using Keycloak:
```json [oidc.conf]
enableOidcLogin = true
providerName = keycloak
# Enter the realm URL
oidcRealm = "https://keycloak.walt-test.cloud/realms/waltid-keycloak-ktor"
# JWKS (to verify access keys the user claims to received through OIDC server)
oidcJwks = "${oidcRealm}/protocol/openid-connect/certs"
authorizeUrl = "${oidcRealm}/protocol/openid-connect/auth"
accessTokenUrl = "${oidcRealm}/protocol/openid-connect/token"
logoutUrl = "${oidcRealm}/protocol/openid-connect/logout"
clientId = "waltid_backend_localhost"
clientSecret = "Xp8fJG5RTtj6lYUlqqIG57iovXMvfD5j"
# JWKS is cached: jwksCache = {
cacheSize = 10
cacheExpirationHours = 24
rateLimit: {
bucketSize: 10
refillRateMinutes: 1
}
}
```
## Create / Login
When using the OIDC unique subject authentication strategy, the login endpoint creates a new account if the `subject`
claim in the JWT is not already associated with an account. Otherwise, it simply logs in the user.
**Option: CURL**
[API Reference](https://wallet.demo.walt.id/swagger/index.html#/Authentication/post_wallet_api_auth_login)
```bash
curl -X 'POST' \
'http://0.0.0.0:7001/wallet-api/auth/login' \
-H 'accept: */*' \
-H 'Content-Type: application/json' \
-d '{
"type": "oidc-unique-subject",
"token": ""
}'
```
**Body Parameters**
```json
{
"type": "{type}"
"token": "string"
}
```
- `type`: _[string]_ - type of the authentication strategy, e.g. `oidc-unique-subject`
- `token`: _[string]_ - the JWT from your OIDC authentication system holding a unique `subject` claim.
**Example Response**
Now a session is automatically created for cookie-based authentication. For Bearer Token Authentication, the `token`
returned
must be provided in the header for each request that needs authentication. Refer to
the [overview section](https://docs.walt.id/community-stack/wallet/authentication/overview.md#cookie-based-authentication) for more details.
```json
{
"token": "KL-a_dk1qO8moCX4gxaGfb7_TS8RK-JWVKZk9BBP0-s",
"id": "018045e5-942c-4362-b535-658c4dd581ef",
"username": "c9234234234234-234234234-203942fasdfs"
}
```
## Logout
**Option: CURL**
[API Reference](https://wallet.demo.walt.id/swagger/index.html#/Authentication/post_wallet_api_auth_logout)
Deletes the session/invalidates the token.
```bash
curl -X 'POST' \
'http://0.0.0.0:7001/wallet-api/auth/logout' \
-H 'accept: */*' \
-d ''
```
--------------------------------------------------------------------------------
PAGE: Overview
URL: https://docs.walt.id/community-stack/wallet/authentication/jwt-oidc/auth0/overview
RAW: https://docs.walt.id/community-stack/wallet/authentication/jwt-oidc/auth0/overview.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Overview
Overview
=============================
Auth0 is a flexible, drop-in solution that provides authentication and authorization services for your applications. It
helps your team and organization avoid the cost, time, and risk associated with building your own solution to
authenticate and authorize users.
Key Features of Auth0
---------------------
- **User Authentication**: Auth0 provides a secure and reliable way for users to log in to your applications. It
supports various methods of authentication, including username/password and social account logins.
- **Authorization**: Auth0 allows you to define fine-grained access control for your applications and APIs.
- **User Management**: Auth0 provides a user management dashboard where you can manage user profiles, password resets,
blocking and deleting users.
Getting Started with Auth0
--------------------------
To get started with Auth0, you need to create an account and tenant. Once you have an account, you can start defining
how Auth0 works with your applications and APIs. You can also control who can access your Auth0 Dashboard.
For more detailed information, you can refer to the following resources:
- [Auth0 Overview](https://auth0.com/docs/get-started/auth0-overview)
- [Get Started - Auth0 Docs](https://dev.auth0.com/docs/get-started)
- [Introduction to Auth0 Fundamentals | Okta](https://www.okta.com/training/introduction-to-auth0-fundamentals)
- [Get Started - Auth0](https://auth0.com/docs/get-started)
Auth0 Integration with the Wallet API
-------------------------------------
The Wallet API supports Auth0 as an OIDC provider. You can use Auth0 to authenticate users and manage their sessions in
the wallet application.
For more information on how to integrate Auth0 with the Wallet API, refer to the following resources:
- [Auth0 Configuration](https://docs.walt.id/community-stack/wallet/authentication/jwt-oidc/auth0/configuration.md) : Auth0 documentation on integrating Auth0 with the
Wallet API.
--------------------------------------------------------------------------------
PAGE: Enter the realm URL
URL: https://docs.walt.id/community-stack/wallet/authentication/jwt-oidc/auth0/configuration
RAW: https://docs.walt.id/community-stack/wallet/authentication/jwt-oidc/auth0/configuration.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
## Auth0 Account
We assume that you have an Auth0 account. If you don't have one, you can create a free
account [here](https://auth0.com/signup).
## Auth0 Configuration
To integrate Auth0 with the Wallet API, you need to configure the Auth0 application and the Wallet API. The following
steps guide you through the configuration process.
### Step 1: Create an Auth0 Application
1. Log in to your Auth0 account.
2. Click on the **Applications** tab.
3. Click on the **Create Application** button.
4. Select the **Regular Web Application** option.
5. Enter a name for your application.
6. Click on the **Create** button.
7. In the **Settings** tab, configure the following settings:
- **Allowed Web Origins**: Enter the URL of your application. For example, `https://wallet.demo.walt.id`.
- **Allowed Callback URLs**: Enter the callback URL of your application. For
example, `https://wallet.demo.walt.id/wallet-api/auth/oidc-session`.
- **Allowed Logout URLs**: Enter the logout URL of your application. For example, `https://wallet.demo.walt.id/logout`.
- **Allowed Origins (CORS)**: Enter the URL of your application. For example, `https://wallet.demo.walt.id`.
### Step 2: Configure the Wallet API
In the Wallet API, you need to configure the Auth0 settings in the `oidc.conf` file.
```json [oidc.conf]
enableOidcLogin = true
providerName = auth0
# Enter the realm URL
oidcRealm = "https://dev-rv0r7nb07450ieht.us.auth0.com/"
# JWKS (to verify access keys the user claims to received through OIDC server)
oidcJwks = "{{oidcRealm}}/.well-known/jwks.json"
oidcScopes = ["openid",
"profile",
"offline_access",
"name",
"given_name",
"family_name",
"nickname",
"email",
"email_verified",
"picture",
"created_at",
"identities",
"phone",
"address","read:user_idp_tokens"]
authorizeUrl = "{{oidcRealm}}/authorize"
accessTokenUrl = "{{oidcRealm}}/oauth/token"
logoutUrl = "{{oidcRealm}}/oidc/logout"
clientId = "j5QFcCKd4iX9uTKudVnjqrtBaNaG2hHJ"
clientSecret = "GtuJ38HEcjjMjm8iC5CNGN8w1F_RDrn-OngT2JpXpub9M-SBgyXCkBbeTSqdRm6u"
audience = "{{oidcRealm}}/api/v2/"
# Keycloak authentication strategy
keycloakUserApi = ""
userEndpoint = "{{oidcRealm}}/userinfo"
# JWKS is cached:
jwksCache = {
cacheSize = 10
cacheExpirationHours = 24
rateLimit: {
bucketSize: 10
refillRateMinutes: 1
}
}
```
--------------------------------------------------------------------------------
PAGE: Overview
URL: https://docs.walt.id/community-stack/wallet/authentication/jwt-oidc/keycloak/overview
RAW: https://docs.walt.id/community-stack/wallet/authentication/jwt-oidc/keycloak/overview.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Keycloak
Keycloak provides a seamless way to integrate OpenID Connect (OIDC) into the web wallet, offering a standardized and secure authentication protocol. OIDC, built on top of OAuth 2.0, enables the secure authentication of users across different platforms and devices, making it an ideal choice for modern web applications.
### Getting Started
- [Keycloak server Setup](https://docs.walt.id/community-stack/wallet/authentication/jwt-oidc/keycloak/configuration.md) - Configure the Keycloak server to use it with the wallet.
- [Login/Create user endpoints](https://docs.walt.id/community-stack/wallet/authentication/jwt-oidc/keycloak/api-endpoints.md) - API endpoints to authenticate users with Keycloak.
--------------------------------------------------------------------------------
PAGE: Configuration
URL: https://docs.walt.id/community-stack/wallet/authentication/jwt-oidc/keycloak/configuration
RAW: https://docs.walt.id/community-stack/wallet/authentication/jwt-oidc/keycloak/configuration.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Configuration
## Installation
docker :
```bash
docker run -p 8080:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:24.0.1 start-dev --features=preview
```
or
Download the Keycloak server from the [official website](https://www.keycloak.org/downloads.html) and start it with the following command:
```bash
bin/kc.sh start-dev --features=preview
```
In either case it is important that the flag: __--features=preview__ is passed along in order to enable the "token-exchange" option (see below).
## Keycloak Realm Configuration
- Create a new realm named "waltid-keycloak-ktor" in the Keycloak admin console.

## Client Setup
- Setup a client with the following configurations:

- Client Capability Configuration
Configure client capabilities:

- Login Settings in Client Config :

1.Root URL : Public root URL of the wallet.
2\. Home URL : Default URL for redirects or links.
3\. Valid URI pattern for successful login redirects (wildcard: \*).
4\. Web origins : Allowed CORS origins.
## Service Account Configuration
To use the Keycloak user API, you need to add a service account role in Keycloak. This role is used to authenticate a user using the username and password.
1. Go to the Keycloak admin console and select the realm you want to use.
2. Go to the client **waltid_backend** and select the `Service Account Roles` tab.
3. Add the `manage-users` role to the service account.

## Token exchange for user impersonation Configuration
User impersonation in Keycloak allows one user to obtain a token that represents another user, enabling them to perform actions on behalf of that user. This process involves exchanging a token granted to the acting user with another token representing the target user.
To granting permission for the exchange of tokens , you need to define a token-exchange fine grain permission in the target client you want permission to exchange to.
1. Go to the Keycloak admin console and select the realm **waltid-keycloak-ktor**.
2. Go to the client **waltid_backend** and select the `Permissions` tab.
3. Toggle Permissions Enabled to On.

That page displays a **token-exchange** link.
4. Click on the **token-exchange** link and add the `token-exchange` permission to the client.

5. Click Client details in the breadcrumbs at the top of the screen.
Define a policy for this permission
7. Click Authorization in the breadcrumbs at the top of the screen.
8. Click Policies in the breadcrumbs at the top of the screen.
9. Click Create Policy.

10. After you create this policy, go back to the target client’s token-exchange permission and add the client policy you just defined.
Apply Client Policy

## Impersonation Configuration
1. Go to the Keycloak admin console and select the realm you want to use.
2. Go to the Users tab in the left-hand menu.
3. Go to permissions .

The page displays an impersonate link.
4. Click on the impersonate link and click the `impersonate` link.

5. Click Client details in the breadcrumbs at the top of the screen.
Define a policy for this permission

6. After you create this policy, go back to users' impersonation permission and add the client policy you just defined.
Apply Client Policy

## Keycloak integration in wallet-api
Read here how to configure wallet-api to support Keycloak login.
### Keycloak provider configuration
Update OIDC config file in `config/oidc.conf`:
```ini
enableOidcLogin = true
providerName = keycloak
oidcRealm = "https://keycloak.walt-test.cloud/realms/waltid-keycloak-ktor"
oidcJwks = "${oidcRealm}/protocol/openid-connect/certs"
authorizeUrl = "${oidcRealm}/protocol/openid-connect/auth"
accessTokenUrl = "${oidcRealm}/protocol/openid-connect/token"
logoutUrl = "${oidcRealm}/protocol/openid-connect/logout"
clientId = "waltid_backend"
keycloakUserApi = "https://keycloak.walt-test.cloud/admin/realms/waltid-keycloak-ktor/users"
clientSecret = "..."
jwksCache = {
cacheSize = 10
cacheExpirationHours = 24
rateLimit: {
bucketSize: 10
refillRateMinutes: 1
}
}
```
1. `oidcRealm`: The realm base URL: `/realms/`
2. `clientId`: Specify the client ID created in Keycloak.
3. `clientSecret`: The client secret is generated by Keycloak (found in `clients -> -> credentials`):

--------------------------------------------------------------------------------
PAGE: API Endpoints
URL: https://docs.walt.id/community-stack/wallet/authentication/jwt-oidc/keycloak/api-endpoints
RAW: https://docs.walt.id/community-stack/wallet/authentication/jwt-oidc/keycloak/api-endpoints.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# API Endpoints
## Client Access Token
The Keycloak client token is a security credential issued by the Keycloak server to client applications. It allows
clients to authenticate themselves when accessing protected resources. Obtained through authentication flows like client
credentials, it consists of a client ID and secret. Once authenticated, Keycloak issues the token, enabling secure
access to APIs and services.
**Option: CURL**
[API Reference](https://wallet.demo.walt.id/swagger/index.html#/Keycloak%20Authentication/get_wallet_api_auth_keycloak_token)
```bash
curl -X 'GET' \
'https://wallet.demo.walt.id/wallet-api/auth/keycloak/token' \
-H 'accept: */*'
```
**Example Response**
```json
{
eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJsOG9YbjJmOTg1RDE0UWN6NlNsNzVwUjJDQXU5T3FGS0pCOGV2QVBjSHJJIn0.
eyJleHAiOjE3MTAyODk0NDIsImlhdCI6MTcxMDI4OTE0MiwianRpIjoiZjczYTRhMzctOTU1MC00Zjg4LTk3ZWYtM2I0NWFiZWUwNmYxIiwiaXNzIjoiaHR0cHM6Ly9rZXljbG9hay53YWx0LXRlc3QuY2xvdWQvcmVhbG1zL3dhbHRpZC1rZXljbG9hay1rdG9yIiwiYXVkIjpbInJlYWxtLW1hbmFnZW1lbnQiLCJhY2NvdW50Il0sInN1YiI6IjdkZGFmODlmLTY5ZWItNGVjZi1hOWNhLWFjYmExMTIxMTk4NiIsInR5cCI6IkJlYXJlciIsImF6cCI6IndhbHRpZF9iYWNrZW5kIiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyIvKiJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJkZWZhdWx0LXJvbGVzLXdhbHRpZC1rZXljbG9hay1rdG9yIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJyZWFsbS1tYW5hZ2VtZW50Ijp7InJvbGVzIjpbIm1hbmFnZS11c2VycyJdfSwid2FsdGlkX2JhY2tlbmQiOnsicm9sZXMiOlsidW1hX3Byb3RlY3Rpb24iXX0sImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsImNsaWVudEhvc3QiOiIxOTIuMTY4LjEwNy4xOTIiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6InNlcnZpY2UtYWNjb3VudC13YWx0aWRfYmFja2VuZCIsImNsaWVudEFkZHJlc3MiOiIxOTIuMTY4LjEwNy4xOTIiLCJjbGllbnRfaWQiOiJ3YWx0aWRfYmFja2VuZCJ9.
PFMnPHgxwwdlMT7IpfXDxvzycwXOi2vYMxwdoRjoTjij8G81CIP3Ew5kzUPhEmZ05UpZHbpDUmouAXM_hiqtKq5TkC0Bakv4OKrP33g4eMKg595mGYjRPg6jExrq7OkWVULt__9c6IkExhiWl-QdzOcp7foETyITAmYiRp220S1ixRD1fcL1EI8aakU01v9TUx2o-H6i6qbYrv_1SPQTcM8nlfi9YHE8GTW58GkBAuewf7OuN1aPalZ9Wy41daR_l3vsbR6yX1NOpxSQRTL1wahvSntmTXetco0AN0xIuGWCqrpQxXIA5mKww6LL7o2hQHap_Dsz2vsvMhvc1bCKqQ
}
```
## Create
Creates a new user in the Keycloak server.
**Option: CURL**
[API Reference](https://wallet.demo.walt.id/swagger/index.html#/Keycloak%20Authentication/post_wallet_api_auth_keycloak_create)
```bash
curl -X 'POST' \
'https://wallet.demo.walt.id/wallet-api/auth/keycloak/create' \
-H 'accept: */*' \
-H 'Content-Type: application/json' \
-d '{
"username": "user",
"email": "user@email.com",
"password": "password",
"token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI0Z1poRkFDdjRYTXJDYmoyQlJpOHA3WHZmZWF4U2RsQ3V5aS1STDgyY0tvIn0.eyJleHAiOjE3MTA0MDU0MzAsImlhdCI6MTcxMDQwNTEzMCwianRpIjoiZjYyMGRlNjEtOWI3Mi00MmY0LWIxMWEtNDU3Yzc3YTA5ODNjIiwiaXNzIjoiaHR0cDovLzAuMC4wLjA6ODA4MC9yZWFsbXMvd2FsdGlkLWtleWNsb2FrLWt0b3IiLCJhdWQiOlsicmVhbG0tbWFuYWdlbWVudCIsImFjY291bnQiXSwic3ViIjoiN2RkYWY4OWYtNjllYi00ZWNmLWE5Y2EtYWNiYTExMjExOTg2IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoid2FsdGlkX2JhY2tlbmQiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbIi8qIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsImRlZmF1bHQtcm9sZXMtd2FsdGlkLWtleWNsb2FrLWt0b3IiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7InJlYWxtLW1hbmFnZW1lbnQiOnsicm9sZXMiOlsibWFuYWdlLXJlYWxtIiwibWFuYWdlLXVzZXJzIl19LCJ3YWx0aWRfYmFja2VuZCI6eyJyb2xlcyI6WyJ1bWFfcHJvdGVjdGlvbiJdfSwiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJwcm9maWxlIGVtYWlsIiwiY2xpZW50SG9zdCI6IjEyNy4wLjAuMSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicHJlZmVycmVkX3VzZXJuYW1lIjoic2VydmljZS1hY2NvdW50LXdhbHRpZF9iYWNrZW5kIiwiY2xpZW50QWRkcmVzcyI6IjEyNy4wLjAuMSIsImNsaWVudF9pZCI6IndhbHRpZF9iYWNrZW5kIn0.Tx7XqloWd68yW_bRkiiKBWJBL8hi6kYexZ_fRQP9qFr6EDZDdsvHr3SsoKjr0yBiZlrDtYt7dz45u0oRGbuimxL_AEws5i9nKSXLv4LbZvBvVfFV0iH0poKF-uuwKpeWOZLGQLREe7XY7a9FkyLN6tF15h-vQm6HDy8RU3xzjlWNSVcuNXK7Y01N0Lanq0IAhe5J7y58t8UcLHYC3Wv8Cu70p4agRhVQE5f9duZBDhEcM5HcdNBiDKYg3cGlw-PGwfSWOH1J-M9lWUYoinD8hkDmTh8t2Bju2L2G3wt4xvNP3JQljnnD7O-bSV6_VhvPNm7-Z86XtOJJQ1Xq1cry0g",
"type": "keycloak"
}'
```
**Body**
```json
{
"username": "user",
"password": "password",
"token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJsOG9YbjJmOTg1RDE0UWN6NlNsNzVwUjJDQXU5T3FGS0pCOGV2QVBjSHJJIn0.eyJleHAiOjE3MTAyODk0NDIsImlhdCI6MTcxMDI4OTE0MiwianRpIjoiZjczYTRh"
}
```
**Body Parameters**
- `type`: _[string]_ - the type of authentication system. In this case, it is `keycloak`.
- `username`: _[string]_ - the username of the user , should be unique.
- `password`: _[string]_ / _[optional]_- the password of the user.
- `token`: _[string]_ - the keycloak Client Access Token that can be generated by the Client Access Token endpoint .
**Example Response**
```bash
Registration succeeded
```
## Login
When using the Keycloak authentication strategy, the login endpoint creates a new account if the `subject`
claim in the JWT is not already associated with an account. Otherwise, it simply logs in the user.
We have 3 possible ways to authenticate a user:
1. Username + Password
2. Username + Access Token
3. User access token
**Option: CURL**
[API Reference](https://wallet.demo.walt.id/swagger/index.html#/Keycloak%20Authentication/post_wallet_api_auth_keycloak_login)
## Username + Password
The most common way to authenticate a user is by providing the username and password.
**Example**
```bash
curl -X 'POST' \
'https://wallet.demo.walt.id/wallet-api/auth/login' \
-H 'accept: */*' \
-H 'Content-Type: application/json' \
-d '{
"type": "keycloak",
"username": "user"
"password": "password"
}'
```
**Body**
```json
{
"type": "keycloak",
"username": "user",
"password": "password"
}
```
**Body Parameters**
- `type`: _[string]_ - the type of authentication system. In this case, it is `keycloak`.
- `username`: _[string]_ - the username of the user.
- `password`: _[string]_ - the password of the user.
## Username + Access Token
The username and access token are used to authenticate a user.
The access token is obtained from the Client Access Token endpoint.
**Example**
```bash
curl -X 'POST' \
'https://wallet.demo.walt.id/wallet-api/auth/keycloak/login' \
-H 'accept: */*' \
-H 'Content-Type: application/json' \
-d '{
"type": "keycloak",
"username": "Max_Mustermann",
"token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI0Z1poRkFDdjRYTXJDYmoyQlJpOHA3WHZmZWF4U2RsQ3V5aS1STDgyY0tvIn0.eyJleHAiOjE3MTAzNjkzNTksImlhdCI6MTcxMDM2OTA1OSwianRpIjoiYzAyMzk1OGQtYjg0My00NjgzLWExOTItYmIyMjJjZjYwMzAwIiwiaXNzIjoiaHR0cDovLzAuMC4wLjA6ODA4MC9yZWFsbXMvd2FsdGlkLWtleWNsb2FrLWt0b3IiLCJhdWQiOlsicmVhbG0tbWFuYWdlbWVudCIsImFjY291bnQiXSwic3ViIjoiMzQ0NzIyMmMtNzZhNS00MzczLWEyNWItM2E1OGQ1NGVhNTRhIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoid2FsdGlkX2JhY2tlbmRfbG9jYWxob3N0IiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyIqIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsImRlZmF1bHQtcm9sZXMtd2FsdGlkLWtleWNsb2FrLWt0b3IiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7InJlYWxtLW1hbmFnZW1lbnQiOnsicm9sZXMiOlsibWFuYWdlLXJlYWxtIiwibWFuYWdlLXVzZXJzIl19LCJ3YWx0aWRfYmFja2VuZF9sb2NhbGhvc3QiOnsicm9sZXMiOlsidW1hX3Byb3RlY3Rpb24iXX0sImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBlbWFpbCIsImNsaWVudEhvc3QiOiIxMjcuMC4wLjEiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6InNlcnZpY2UtYWNjb3VudC13YWx0aWRfYmFja2VuZF9sb2NhbGhvc3QiLCJjbGllbnRBZGRyZXNzIjoiMTI3LjAuMC4xIiwiY2xpZW50X2lkIjoid2FsdGlkX2JhY2tlbmRfbG9jYWxob3N0In0.M_06eq59abPSEYJolwaBIWAAH7yNMBVDr87HuhllXtg9eDZbLSrcQuPr5FzFAbBgjdZ2iFDzmWpdv20SZXTu0xrqgQnCV0spJRXJOPrmPYcNidMEY3Bc-6Ywb0mpap0eh7GvKsgq0II8uc3o3eEumCMgf_1fHqMI9yhqGoMOyou-J4faV6qkzUKZcyRMh0x03dEV9-E_JQlVOegrmduO2ka8E8k0hoGAWlz3x3rQ18xHwm1gsYqEIBx5kURO3Wy4A5vk45uVO13YI2Irm0XmOgCk1g14jboy4mqWAujb1eWVUsuYhJoVm-IVJE8pDt3YUqXZQIenLY-oYyEiC-uCxg"
}'
```
**Body**
```json
{
"type": "keycloak",
"username": "user",
"token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJsOG9YbjJmOTg1RDE0UWN6NlNsNzVwUjJDQXU5T3FGS0pCOGV2QVBjSHJJIn0"
}
```
**Body Parameters**
- `type`: _[string]_ - the type of authentication system. In this case, it is `keycloak`.
- `username`: _[string]_ - the username of the user.
- `token`: _[string]_ - the keycloak Client Access Token that can be generated by the Client Access Token endpoint .
## User access token
This method is used to authenticate a user using the user access token.
The user access token can be obtained using **postman** or any other tool that can make a request to the Keycloak
server.
In this case, the wallet-api will validate the user access token and create a session for the user.
**Postman Example**

**Example**
```bash
curl -X 'POST' \
'https://wallet.demo.walt.id/wallet-api/auth/keycloak/login' \
-H 'accept: */*' \
-H 'Content-Type: application/json' \
-d '{
"type": "keycloak",
"token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI0Z1poRkFDdjRYTXJDYmoyQlJpOHA3WHZmZWF4U2RsQ3V5aS1STDgyY0tvIn0.eyJleHAiOjE3MTAzNjk0NDUsImlhdCI6MTcxMDM2OTE0NSwianRpIjoiOTA3NGQ1ZDYtMjc4ZC00NWVjLTlkODQtMjg5ZWJmZGQwYzQ4IiwiaXNzIjoiaHR0cDovLzAuMC4wLjA6ODA4MC9yZWFsbXMvd2FsdGlkLWtleWNsb2FrLWt0b3IiLCJhdWQiOlsicmVhbG0tbWFuYWdlbWVudCIsImFjY291bnQiXSwic3ViIjoiMzk3NjE3OWUtM2UzMS00NDRhLWEyZDUtNmM1MmZmNGUzZTkyIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoid2FsdGlkX2JhY2tlbmRfbG9jYWxob3N0Iiwic2Vzc2lvbl9zdGF0ZSI6ImRlNzJjNzQxLTI3ZTktNDcwMC05MGY4LTMwNzdlMDBiYmMzZiIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiKiJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJkZWZhdWx0LXJvbGVzLXdhbHRpZC1rZXljbG9hay1rdG9yIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJyZWFsbS1tYW5hZ2VtZW50Ijp7InJvbGVzIjpbIm1hbmFnZS1yZWFsbSIsIm1hbmFnZS11c2VycyJdfSwiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJwcm9maWxlIGVtYWlsIiwic2lkIjoiZGU3MmM3NDEtMjdlOS00NzAwLTkwZjgtMzA3N2UwMGJiYzNmIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJtYXhfbXVzdGVybWFubiIsImVtYWlsIjoidXNlckBlbWFpbC5jb20ifQ.pzIhAFFkqN6X3JjnEqRpNiuXBsiY_6bPTNdWusBlfzayXeiMdTXYiCGbZRXmhVKml0VYXdRZKKPGiqJT8kmJu1hAp7rFOzxpJ0ScFbfJ4p1KK8ieY1axke_V2YiBgF1AQuMJQItgf-czqlT96eG-SWeyqGKGlEnd-RWeALpeDAdvaDpz4zq3fhZ_9cXMbpSautyFivE4zJEIjQyRGvOuKO9nXQUD8ITdQdCuIAgUtneP0ogHK_vdJthY_LlzOvpk7e_bpizw15s0foAZHV1ACW35QXWvLV_8CUWHTECXVG3FmDasoVOnXmkvd4GXJ6xgKWhme55AeF6wayo-AGoDgg"
}'
```
**Body**
```json
{
"type": "keycloak",
"token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJsOG9YbjJmOTg1RDE0UWN6NlNsNzVwUjJDQXU5T3FGS0pCOGV2QVBjSHJJIn0"
}
```
**Body Parameters**
- `type`: _[string]_ - the type of authentication system. In this case, it is `keycloak`.
- `token`: _[string]_ - the JWT access token of the user.
**Example Response**
Now a session is automatically created for the user and the response will contain the user's access token and the user's
unique identifier as well as the user's unique keycloak Id.
```json
{
"token": "KL-a_dk1qO8moCX4gxaGfb7_TS8RK-JWVKZk9BBP0-s",
"id": "018045e5-942c-4362-b535-658c4dd581ef",
"keycloakUserId": "c9234234234234-234234234-203942fasdfs"
}
```
## Logout
**Option: CURL**
[API Reference](https://wallet.demo.walt.id/swagger/index.html#/Authentication/post_wallet_api_auth_logout)
Deletes the session/invalidates the token.
```bash
curl -X 'POST' \
'https://wallet.demo.walt.id/wallet-api/auth/keycloak/logout' \
-H 'accept: */*' \
-H 'Content-Type: application/json' \
-d '{
"keycloakUserId": "c9234234234234-234234234-203942fasdfs",
"token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI0Z1poRkFDdjRYTXJDYmoyQlJpOHA3WHZmZWF4U2RsQ3V5aS1STDgyY0tvIn0.eyJleHAiOjE3MTAyOTA1OTEsImlhdCI6MTcxMDI5MDI5MSwianRpIjoiNWMzNWQwMWMtZmQ3MS00OTc3LTllMzUtMjI1Y2JlY2E0ZTA4IiwiaXNzIjoiaHR0cDovLzAuMC4wLjA6ODA4MC9yZWFsbXMvd2FsdGlkLWtleWNsb2FrLWt0b3IiLCJhdWQiOlsicmVhbG0tbWFuYWdlbWVudCIsImFjY291bnQiXSwic3ViIjoiMzQ0NzIyMmMtNzZhNS00MzczLWEyNWItM2E1OGQ1NGVhNTRhIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoid2FsdGlkX2JhY2tlbmRfbG9jYWxob3N0IiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyIqIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsImRlZmF1bHQtcm9sZXMtd2FsdGlkLWtleWNsb2FrLWt0b3IiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7InJlYWxtLW1hbmFnZW1lbnQiOnsicm9sZXMiOlsibWFuYWdlLXJlYWxtIiwibWFuYWdlLXVzZXJzIl19LCJ3YWx0aWRfYmFja2VuZF9sb2NhbGhvc3QiOnsicm9sZXMiOlsidW1hX3Byb3RlY3Rpb24iXX0sImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBlbWFpbCIsImNsaWVudEhvc3QiOiIxMjcuMC4wLjEiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6InNlcnZpY2UtYWNjb3VudC13YWx0aWRfYmFja2VuZF9sb2NhbGhvc3QiLCJjbGllbnRBZGRyZXNzIjoiMTI3LjAuMC4xIiwiY2xpZW50X2lkIjoid2FsdGlkX2JhY2tlbmRfbG9jYWxob3N0In0.ZvkWdSPL1TfSXqBjFJAZkn9VYrRmwp9WEUHOcDMMgsVA2sndnYT1BzV2ubkojIflQx6EWG7Y62cFBx5zCOnREbDbsDPn6y3zw8ppHv9KIHz6RLVv2ruvg3loqsk51ZXF24_h_JHPSpmftyNb7k77-JSzFSIy23ofPP2f24hPXIeM-gJAho5m-fsg6DmA8aGAemj40kukC0Y-Jg5IgNl5SSkBHAjzjqv0XYLX3o5k0HwPmDfgWwgHRbkoGndvTx8rswwUfFnWBIU0LoHnlXBVbCMqisUPXtL3yuhRN9m5L1wVCJlw3F6gHSLKxsuQrntKrp1mXV6N3bovm4LBU8NqfA"
}'
```
**Body**
```json
{
"keycloakUserId": "c9234234234234-234234234-203942fasdfs",
"token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI0Z1poRkFDdjRYTXJDYmoyQlJpOHA3WHZmZWF4U2RsQ3V5aS1STDgyY0tvIn0.eyJleHAiOjE3MTAyOTA1OTEsImlhdCI6MTcxMDI5MDI5MSwianRpIjoiNWMzNWQwMWMtZmQ3MS00OTc3LTllMzUtMjI1Y2JlY2E0ZTA4IiwiaXNzIjoiaHR0cDovLzAuMC4wLjA6ODA4MC9yZWFsbXMvd2FsdGlkLWtleWNsb2FrLWt0b3IiLCJhdWQiOlsicmVhbG0tbWFuYWdlbWVudCIsImFjY291bnQiXSwic3ViIjoiMzQ0NzIyMmMtNzZhNS00MzczLWEyNWItM2E1OGQ1NGVhNTRhIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoid2FsdGlkX2JhY2tlbmRfbG9jYWxob3N0IiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyIqIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsImRlZmF1bHQtcm9sZXMtd2FsdGlkLWtleWNsb2FrLWt0b3IiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7InJlYWxtLW1hbmFnZW1lbnQiOnsicm9sZXMiOlsibWFuYWdlLXJlYWxtIiwibWFuYWdlLXVzZXJzIl19LCJ3YWx0aWRfYmFja2VuZF9sb2NhbGhvc3QiOnsicm9sZXMiOlsidW1hX3Byb3RlY3Rpb24iXX0sImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBlbWFpbCIsImNsaWVudEhvc3QiOiIxMjcuMC4wLjEiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6InNlcnZpY2UtYWNjb3VudC13YWx0aWRfYmFja2VuZF9sb2NhbGhvc3QiLCJjbGllbnRBZGRyZXNzIjoiMTI3LjAuMC4xIiwiY2xpZW50X2lkIjoid2FsdGlkX2JhY2tlbmRfbG9jYWxob3N0In0.ZvkWdSPL1TfSXqBjFJAZkn9VYrRmwp9WEUHOcDMMgsVA2sndnYT1BzV2ubkojIflQx6EWG7Y62cFBx5zCOnREbDbsDPn6y3zw8ppHv9KIHz6RLVv2ruvg3loqsk51ZXF24_h_JHPSpmftyNb7k77-JSzFSIy23ofPP2f24hPXIeM-gJAho5m-fsg6DmA8aGAemj40kukC0Y-Jg5IgNl5SSkBHAjzjqv0XYLX3o5k0HwPmDfgWwgHRbkoGndvTx8rswwUfFnWBIU0LoHnlXBVbCMqisUPXtL3yuhRN9m5L1wVCJlw3F6gHSLKxsuQrntKrp1mXV6N3bovm4LBU8NqfA"
}
```
**Body Parameters**
- `keycloakUserId`: _[string]_ - the unique identifier of the user in the keycloak system.
- `token`: _[string]_ - the keycloak Client Access Token that can be generated by the Client Access Token endpoint .
--------------------------------------------------------------------------------
PAGE: Accounts & Wallets
URL: https://docs.walt.id/community-stack/wallet/accounts-wallets
RAW: https://docs.walt.id/community-stack/wallet/accounts-wallets.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Accounts & Wallets
## The Account vs. Wallet Distinction
When working with the Wallet API, it is important to understand the difference between an **account** and a **wallet**,
as they are separate concepts that serve different roles.
- An **account** is the authentication identity. When a user registers and logs in, a session token (or cookie) is
issued that is tied to the account. This token proves who you are.
- A **wallet** is the operational container — it holds the user's keys, DIDs, and credentials. Every action such as
receiving credentials, presenting credentials, or managing keys is scoped to a specific wallet, not just an account.
When a new account is registered, the Wallet API automatically provisions a default wallet for it, including a key and
DID (configurable via
the [registration defaults](https://docs.walt.id/community-stack/wallet/configurations/config-files/registration-defaults.md)). The API is
designed to support multiple wallets per account, so each wallet has its own unique ID.
Because a session token identifies your **account**, but all wallet operations require a **wallet ID**, you need to
retrieve the wallet ID before you can perform any wallet operations such as receiving or presenting credentials.
## Retrieve Wallet ID
**Option: CURL**
[API Reference](https://wallet.demo.walt.id/swagger/index.html#/Accounts/get_wallet_api_wallet_accounts_wallets)
```bash
curl -X 'GET' \
'http://0.0.0.0:7001/wallet-api/wallet/accounts/wallets' \
-H 'accept: application/json' \
-H 'authorization: Bearer {token}'
```
**Header Parameters**
- `authorization`: _String_ - Bearer token obtained from the [login endpoint](https://docs.walt.id/community-stack/wallet/authentication/email-password-auth.md#login). Format: `Bearer {token}`.
**Example Response**
```json
{
"account": "108753e3-5ab4-4096-8699-ad0653c79a09",
"wallets": [
{
"id": "4d7ef7c6-a76b-409b-b042-68d15a484082",
"name": "Wallet of Test",
"createdOn": "2025-04-16T09:19:11.585Z",
"addedOn": "2025-04-16T09:19:11.597Z",
"permission": "ADMINISTRATE"
}
]
}
```
**Response Fields**
- `account`: _String_ - The unique ID of the authenticated account.
- `wallets`: _Array_ - The list of wallets associated with this account. Each wallet object contains:
- `id`: _String_ - The unique wallet ID. **This is the value required for all subsequent wallet operations.**
- `name`: _String_ - A human-readable name for the wallet.
- `createdOn`: _String_ - ISO 8601 timestamp of when the wallet was created.
- `addedOn`: _String_ - ISO 8601 timestamp of when the wallet was added to this account.
- `permission`: _String_ - The permission level of the authenticated account over this wallet (e.g. `ADMINISTRATE`).
**Note:**
Save the `id` value from the wallet you want to work with. It is required as a path parameter for all wallet
operations including credential exchange, key management, and DID management.
--------------------------------------------------------------------------------
PAGE: Overview
URL: https://docs.walt.id/community-stack/wallet/key-management/overview
RAW: https://docs.walt.id/community-stack/wallet/key-management/overview.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Key Management
Via the wallet-api you can create and manage keys for your users based on different types - Ed25519, secp256k1, secp256r1,
and RSA. Those keys can be stored locally in the database of the wallet API or in an external KMS walt.id supports.
For a full list of endpoints for key management, please refer to the [Api Reference](https://wallet.demo.walt.id/swagger/index.html#/Keys).
### Supported KMS Solutions
- [HashiCorp Vault](https://docs.walt.id/community-stack/wallet/key-management/hashicorp-vault.md)
- [Oracle KMS](https://docs.walt.id/community-stack/wallet/key-management/oci-vault/overview.md)
- [AWS KMS](https://docs.walt.id/community-stack/wallet/key-management/aws-kms.md)
- [Azure Key Vault](https://docs.walt.id/community-stack/wallet/key-management/azure-key-vault.md)
### Getting Started
- [Create & Export A Local Key](https://docs.walt.id/community-stack/wallet/key-management/local.md) - How to create and export a key that is stored in the
wallet API database.
--------------------------------------------------------------------------------
PAGE: Sign and Verify Messages Feature
URL: https://docs.walt.id/community-stack/wallet/key-management/message-signing
RAW: https://docs.walt.id/community-stack/wallet/key-management/message-signing.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Sign and Verify Messages Feature
This document provides an overview of the "Sign and Verify Messages" feature in the wallet application, which allows users to sign JSON messages using keys managed in the wallet and verify their signatures. This feature is accessible through the user interface or via REST APIs.
---
## **Signing a Message**
### **REST API**
#### Endpoint
```bash
POST /wallet-api/wallet/{walletId}/keys/{keyId}/sign
```
#### Request
- **URL Parameters**:
- `walletId`: The ID of the wallet containing the key.
- `keyId`: The ID of the key to use for signing.
- **Headers**:
- `accept: application/json`
- `Content-Type: application/json`
- **Body** (Example):
```json
{
"exampleType": "object",
"data": {
"name": "John Doe",
"age": 30,
"isEmployed": true
}
}
```
#### cURL Example
```bash
curl -X 'POST' \
'http://127.0.0.1:7001/wallet-api/wallet/b59cdc3f-7022-4634-8668-de4fd578d2c9/keys/AIVbTGzmK6cnDHdrM-qNS_TghM-uvavIeOEHWfvmqQU/sign' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"exampleType": "object",
"data": {
"name": "John Doe",
"age": 30,
"isEmployed": true
}
}'
```
#### Response
```json
"eyJraWQiOiJBSVZiVEd6bUs2Y25ESGRyTS1xTlNfVGdoTS11dmF2SWVPRUhXZnZtcVFVIiwiYWxnIjoiRWREU0EifQ.eyJleGFtcGxlVHlwZSI6Im9iamVjdCIsImRhdGEiOnsibmFtZSI6IkpvaG4gRG9lIiwiYWdlIjozMCwiaXNFbXBsb3llZCI6dHJ1ZX19.dY7M2335Wn8NwL2D_tqGshqBQUqET0q8JxHsNhMg1CapKNHYUBdY5K_85slWgoQrU_I231qdbcoMYYsHUD80BA"
```
---
## **Verifying a Signature**
### **REST API**
#### Endpoint
```bash
POST /wallet-api/wallet/{walletId}/keys/verify
```
#### Request
- **URL Parameters**:
- `walletId`: The ID of the wallet.
- **Query Parameters**:
- `JWK`: The JSON Web Key (URL-encoded) used for verification.
- **Headers**:
- `accept: application/json`
- `Content-Type: application/json`
- **Body** (JWT):
```json
"eyJraWQiOiJBSVZiVEd6bUs2Y25ESGRyTS1xTlNfVGdoTS11dmF2SWVPRUhXZnZtcVFVIiwiYWxnIjoiRWREU0EifQ.eyJleGFtcGxlVHlwZSI6Im9iamVjdCIsImRhdGEiOnsibmFtZSI6IkpvaG4gRG9lIiwiYWdlIjozMCwiaXNFbXBsb3llZCI6dHJ1ZX19.dY7M2335Wn8NwL2D_tqGshqBQUqET0q8JxHsNhMg1CapKNHYUBdY5K_85slWgoQrU_I231qdbcoMYYsHUD80BA"
```
#### cURL Example
```bash
curl -X 'POST' \
'http://127.0.0.1:7001/wallet-api/wallet/b59cdc3f-7022-4634-8668-de4fd578d2c9/keys/verify?JWK=%7B%0A%20%20%22kty%22%3A%20%22OKP%22%2C%0A%20%20%22crv%22%3A%20%22Ed25519%22%2C%0A%20%20%22kid%22%3A%20%22viEJuASRBd06MPJW-XEEDkWahYnGmp6WIMjdkGKZezY%22%2C%0A%20%20%22x%22%3A%20%227lTgGVKIeZdP9aEofIFwSTdyBGmxYqo4AhumkCLn3vs%22%0A%7D' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '"eyJraWQiOiJBSVZiVEd6bUs2Y25ESGRyTS1xTlNfVGdoTS11dmF2SWVPRUhXZnZtcVFVIiwiYWxnIjoiRWREU0EifQ.eyJleGFtcGxlVHlwZSI6Im9iamVjdCIsImRhdGEiOnsibmFtZSI6IkpvaG4gRG9lIiwiYWdlIjozMCwiaXNFbXBsb3llZCI6dHJ1ZX19.dY7M2335Wn8NwL2D_tqGshqBQUqET0q8JxHsNhMg1CapKNHYUBdY5K_85slWgoQrU_I231qdbcoMYYsHUD80BA"'
```
#### Response
- `true` if the signature is valid.
- `false` if the signature is invalid.
---
## **User Interface**
The wallet application provides an intuitive interface for:
- Signing JSON messages by selecting a key and entering the message.
- Verifying JWT signatures by pasting the JWK and JWT.

---
## **Key Features**
- **Ease of Use**: Both API and UI options for signing and verifying messages.
- **Security**: Keys are securely managed in the wallet.
- **Standards-Compliant**: Utilizes JSON Web Keys (JWKs) and JSON Web Tokens (JWTs) for interoperability.
---
--------------------------------------------------------------------------------
PAGE: Local
URL: https://docs.walt.id/community-stack/wallet/key-management/local
RAW: https://docs.walt.id/community-stack/wallet/key-management/local.md
STACK: Community Stack (open source) — version 0.21.0
--------------------------------------------------------------------------------
# Local
Create a set of key pairs for the wallets manged via the wallet API, using one of the following algorithms: ed25519, secp256k1,
secp256r1, or RSA. This key will then be stored in the configured database of the wallet API.
If you are working in a production setup, it is highly recommended to use KMS providers like Hashicorp Vault or
Oracle KMS to secure your key material. This will ensure that your secrets are never exposed outside of a secure environment,
reducing the risk of key compromises. For a list of KMS integrations that are compatible with walt.id,
please visit the [overview page](https://docs.walt.id/community-stack/wallet/key-management/overview.md).
## Key Creation
**Option: CURL**
[API Reference](https://wallet.demo.walt.id/swagger/index.html#/Keys/post_wallet_api_wallet__wallet__keys_generate)
POST `wallet/$WALLET/keys/generate`
##### Example Request
```bash
curl -X 'POST' \
'http://localhost:7001/wallet-api/wallet/f01f8f55-d098-4c53-b47b-c97552829b39/keys/generate' \
-H 'accept: */*' \
-H 'Content-Type: application/json' \
-d '{
"backend": "jwk",
"keyType": "Ed25519"
}'
```
**Parameters**
- `WALLET`: _[string]_ - The ID of the wallet to generate the key into. You can find a list of all wallets associated
with an account using the following endpoint `/wallet-api/wallet/accounts/wallets`.
**Body**
```json
{
"backend": "jwk",
"keyType": "Ed25519"
}
```
**Body Parameters**
- `backend`: _String_ - The location where the key is stored. In our case `jwk` as we want to store it locally in the database.
- `algorithm`: _String_ - The algorithm to use to generate the key with; Ed25519, secp256k1, secp256r1, RSA.
- `name`: _String (optional)_ - An optional alias/name to assign to the key.
---
##### Example Response
The API will respond with the ID of the key. This ID is the internal reference and can be used in operations such as DID
create or key delete.
```
bhyprSoN1ciRt0poVV-gDz96tB49IZ8_fUc3azdpCJc
```
## Key Export
**Option: CURL**
[API Reference](https://wallet.demo.walt.id/swagger/index.html#/Keys/get_wallet_api_wallet__wallet__keys__keyId__export)
POST `/wallet-api/wallet/{wallet}/keys/export/{keyId}`
##### Example Request
```bash
curl -X 'GET' \
'https://wallet.demo.walt.id/wallet-api/wallet/3fb5b3d5-4f2a-43fe-8734-1903512ccabd/keys/export/bhyprSoN1ciRt0poVV-gDz96tB49IZ8_fUc3azdpCJc?format=JWK&loadPrivateKey=true' \
-H 'accept: */*'
```
**Parameters**
- `wallet`: _[string]_ - The ID of the wallet to generate the key into. You can find a list of all wallets associated
with an account using the following endpoint `/wallet-api/wallet/accounts/wallets`.
- `keyId`: _[string]_ - The ID of the key we received after the key creation in the last step. You can also list all
available keys using the `/wallet-api/wallet/{wallet}/keys` endpoint.
**Query Parameters**
- `format`: _[string]_ - The key export format; JWK / PEM. JWK by default.
- `loadPrivateKey`: _[boolean]_ - Enable to also receive the private key.
##### Example Response
The API will respond with the public key and/or private key in selected format based on the provided parameters. In our
case,
JWK.
```json
{
"kty": "OKP",
"d": "gKXb33AJ54WTPSrGNb0xwtu7hBGuIrkaOQB2ux8LVMo",
"crv": "Ed25519",
"kid": "bhyprSoN1ciRt0poVV-gDz96tB49IZ8_fUc3azdpCJc",
"x": "7fjYZdxTHzXHezJLsdlDdIF6wCwz2dmu6MSlXd3JWG4"
}
```
## Key Import
**Option: CURL**
[API Reference](https://wallet.demo.walt.id/swagger/index.html#/Keys/post_wallet_api_wallet__wallet__keys_import)