# walt.id Documentation — Community Stack (Open Source) > Full text of the walt.id Community Stack documentation (open-source Issuer API, Verifier API, Wallet API) plus stack-agnostic concepts. This file intentionally EXCLUDES the commercial Enterprise Stack — for that, fetch https://docs.walt.id/llms-enterprise-stack.txt. 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 2: 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: ![Trust Triangle Graphic](https://docs.walt.id/img/concepts/decentralised-identity/trust-triangle.png) ## 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. ![issuer-wallet-verifier-graphic](https://docs.walt.id/img/concepts/decentralised-identity/issuer-wallet-verifier.png) 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. ![waltid-infrastructure-overview](https://docs.walt.id/img/concepts/decentralised-identity/waltid-infrastructure-overview.png) ## **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: ![image of did](https://docs.walt.id/img/concepts/dids/did-schema.png) | 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 ![Did flow graphic](https://docs.walt.id/img/concepts/dids/did-flow-graphic.png) #### 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. ![How Selective Disclosure Works](https://docs.walt.id/img/concepts/selective-disclosure/how-it-works.png) 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**

![Pre-Authorized Code Flow Diagram - OID4VCI](https://docs.walt.id/img/concepts/openid4vci/pre-auth-code-flow.png) **Authorization Code Flow**

![Authorization Code Flow Diagram - OID4VCI](https://docs.walt.id/img/concepts/openid4vci/authorization-code-flow.png) ### 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. ![Cross Device Flow Diagram - OID4VCP](https://docs.walt.id/img/concepts/openid4vp/cross-device-flow-openid4vp.png) ### 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 2: 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. ![image of waltid community stack archiecture](https://docs.walt.id/img/home/the-community-stack/architecture-1.jpg) ### 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. ![Create Vault](https://docs.walt.id/img/concepts/oci-vault/oci-kms-create-vault.png) ## 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. ![Vault Details](https://docs.walt.id/img/concepts/oci-vault/oci-kms-endpoints.png) ## 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. ![Tenancy OCID](https://docs.walt.id/img/concepts/oci-vault/oci-kms-tenancy-ocid.png) ## 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. ![User OCID](https://docs.walt.id/img/concepts/oci-vault/oci-kms-user-ocid.png) ## 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**. ![AWS config IAM](https://docs.walt.id/img/concepts/aws/Aws-Instance-Auth.png) 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.
![image realm creation](https://docs.walt.id/img/concepts/OIDC/create-keycloack-realm.png) ## Client Setup - Setup a client with the following configurations:
![image of client configuration](https://docs.walt.id/img/concepts/OIDC/keyloack-client-configuration.png) - Client Capability Configuration Configure client capabilities:
![image of client capabilities](https://docs.walt.id/img/concepts/OIDC/keycloak-client-capabilities.png) - Login Settings in Client Config : ![image of client login settings](https://docs.walt.id/img/concepts/OIDC/keycloak-client-login-settings.png)
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. ![image of service account roles](https://docs.walt.id/img/concepts/OIDC/keycloak-service-account-roles.png) ## 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. ![image of token exchange permission](https://docs.walt.id/img/concepts/OIDC/keycloak-token-exchange-permission.png) That page displays a **token-exchange** link. 4. Click on the **token-exchange** link and add the `token-exchange` permission to the client. ![image of token exchange permission](https://docs.walt.id/img/concepts/OIDC/keycloak-token-exchange-permission-interface.png) 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. ![image of token exchange policy](https://docs.walt.id/img/concepts/OIDC/keycloak-token-exchange-policy-interface.png) 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 ![image of token exchange policy](https://docs.walt.id/img/concepts/OIDC/keycloak-token-exchange-permission-interface-with-policy.png) ## 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 . ![image of user permission](https://docs.walt.id/img/concepts/OIDC/keycloak-user-permission.png) The page displays an impersonate link. 4. Click on the impersonate link and click the `impersonate` link. ![image of user permission details](https://docs.walt.id/img/concepts/OIDC/keycloak-user-permission-interface.png) 5. Click Client details in the breadcrumbs at the top of the screen. Define a policy for this permission ![image of user permission policy](https://docs.walt.id/img/concepts/OIDC/keycloak-user-impersonator-policy.png) 6. After you create this policy, go back to users' impersonation permission and add the client policy you just defined. Apply Client Policy ![image of user permission policy apply](https://docs.walt.id/img/concepts/OIDC/keycloak-user-impersonator-policy-apply.png) ## 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`): ![image of client secret](https://docs.walt.id/img/concepts/OIDC/keyloack-client-secret.png) -------------------------------------------------------------------------------- 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** ![image of user access token](https://docs.walt.id/img/concepts/OIDC/keycloak-user-access-token.png) **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. ![image of web wallet message signing](https://docs.walt.id/img/concepts/app/message-signing.png) --- ## **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)

POST `/wallet-api/wallet/{wallet}/keys/import` ##### Example Request ```bash curl -X 'POST' \ 'https://wallet.demo.walt.id/wallet-api/wallet/07b97ac3-3edf-4738-8f17-481804f10856/keys/import?alias=myKeyAlias' \ -H 'accept: */*' \ -H 'Content-Type: application/json' \ -d '{ "kty": "EC", "d": "sMjI1SVu4vKHLr3JwgUMu10Ihn5OL0sCaqjfZP8xpUU", "crv": "P-256", "kid": "Si07jIXqLsMKHy0vgyvPbcIvIPxdqL7Qs6STqrx1UC8", "x": "q-LZDK-TZQSUczy_1K6TBFeVn60rMv4KjYvTePy2TGs", "y": "qTbiSREfWRZtAKZsW-k-0BHIIYpAN0fhnjaqeMIU5OY" }' ``` **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`. - `alias`: _[string]_ - The alias to assign to the imported key. **Body** ```json { "kty": "EC", "d": "sMjI1SVu4vKHLr3JwgUMu10Ihn5OL0sCaqjfZP8xpUU", "crv": "P-256", "kid": "Si07jIXqLsMKHy0vgyvPbcIvIPxdqL7Qs6STqrx1UC8", "x": "q-LZDK-TZQSUczy_1K6TBFeVn60rMv4KjYvTePy2TGs", "y": "qTbiSREfWRZtAKZsW-k-0BHIIYpAN0fhnjaqeMIU5OY" } ``` **Body Parameters** - The body should contain the key in JWK or PEM format that you want to import into the wallet. ##### Example Response The API will respond with the ID of the imported key. This ID is the internal reference and can be used in operations such as DID create or key delete. ```text Si07jIXqLsMKHy0vgyvPbcIvIPxdqL7Qs6STqrx1UC8 ``` -------------------------------------------------------------------------------- PAGE: HashiCorp Vault Transit Secrets Engine URL: https://docs.walt.id/community-stack/wallet/key-management/hashicorp-vault RAW: https://docs.walt.id/community-stack/wallet/key-management/hashicorp-vault.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # HashiCorp Vault Transit Secrets Engine Leverage HashiCorp Vault's [Transit Secrets Engine](https://developer.hashicorp.com/vault/docs/secrets/transit) to secure keys of wallets managed by the wallet API. 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 Below you find an example call to create a key in HashiCorp Vault and associate it with a wallet managed by the Wallet API. The Wallet 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. **Option: CURL** **Endpoint: ** `/keys/generate` | [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": "tse", "config": { "server": "https://2ab1-2001-871-25f-3c00-f8ac-11f4-dce2-9f1a.ngrok-free.app/v1/transit", "auth": { "roleId": "b50427bd-5070-0bec-3a00-dfe470ba349d", "secretId": "22ffc0f4-c308-e6d6-f5a4-246cd89bc17c" } }, "keyType": "Ed25519" }' ``` **Body** ```json { "backend": "tse", "config": { "server": "https://2ab1-2001-871-25f-3c00-f8ac-11f4-dce2-9f1a.ngrok-free.app/v1/transit", "auth": { "roleId": "b50427bd-5070-0bec-3a00-dfe470ba349d", "secretId": "22ffc0f4-c308-e6d6-f5a4-246cd89bc17c" } }, "keyType": "Ed25519" } ``` **Body Parameters** - `backend`: _String_ - The location where the key is stored. In our case `tse` as we want to store it HashiCorp Vault. - `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. - `keyType`: _String_ - the algorithm used to generate the key. For Vault TSE, use one of `Ed25519`, `secp256r1`, `secp384r1`, `secp521r1`, `RSA`, `RSA3072`, or `RSA4096`. --- **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 ``` -------------------------------------------------------------------------------- PAGE: AWS KMS URL: https://docs.walt.id/community-stack/wallet/key-management/aws-kms RAW: https://docs.walt.id/community-stack/wallet/key-management/aws-kms.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # AWS KMS The Wallet API supports AWS KMS as a key management solution. You can utilize AWS KMS to store your cryptographic keys in a secure and scalable way. We support both AWS KMS with a rest api implementation and AWS SDK for Kotlin to manage cryptographic keys. ## Key Creation Below you find an example call to create a key in AWS KMS 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 AWS KMS: - RSA , SECP256R1, SECP256K1 **Option: USING AWS REST API** **Endpoint:** `/keys/generate` | [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": "aws-rest-api", "config": { "auth":{ "region": "eu-central-1", "accessKeyId": "accessKeyId", "secretKey": "secretKeyId" }, "tags": { "key": "value" }, "keyType": "secp256r1" }' ``` **Body** ```json { "backend": "aws-rest-api", "config": { "auth": { "region": "eu-central-1", "accessKeyId": "accessKeyId", "secretKey": "secretKeyId" }, "tags": { "key": "value" } }, "keyType": "secp256r1" } ``` **Body Parameters** - `backend`: _String_ - The location where the key is stored. In our case `aws-rest-api` as we want to store it in aws's kms. - `config` - `region`: _String_ - The region where the key is stored. - `accessKeyId`: _String_ - The access key id for the AWS account. - `secretKey`: _String_ - The secret key for the AWS account. - `tags`: _Object_ - The metadata tags to add to the key for [AWS](https://docs.aws.amazon.com/kms/latest/developerguide/tagging-keys.html) - `keyType`: _String_ - the algorithm used to generate the key. For AWS only RSA and secp256r1 and secp256k1 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: USING AWS SDK** **Endpoint:** `/keys/generate` | [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": "aws", "config": { "region": "eu-central-1", "tags": { "key": "value" } }, "keyType": "secp256r1" }' ``` **Body** ```json { "backend": "aws", "config": { "region": "eu-central-1", "tags": { "key": "value" } }, "keyType": "secp256r1" } ``` **Body Parameters** - `backend`: _String_ - The location where the key is stored. In our case `aws` as we want to store it in aws's kms. - `config` - `region`: _String_ - The region where the key is stored. - `tags`: _Object_ - The metadata tags to add to the key for [AWS](https://docs.aws.amazon.com/kms/latest/developerguide/tagging-keys.html) - `keyType`: _String_ - the algorithm used to generate the key. For AWS only RSA and secp256r1 and secp256k1 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 ``` -------------------------------------------------------------------------------- PAGE: Azure Key Vault URL: https://docs.walt.id/community-stack/wallet/key-management/azure-key-vault RAW: https://docs.walt.id/community-stack/wallet/key-management/azure-key-vault.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Azure Key Vault The Wallet API supports Azure Key Vault as a key management solution. You can utilize Azure Key Vault to store your cryptographic keys in a secure and scalable way. ## Key Creation Below you find an example call to create a key in Azure Key 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 Azure Key Vault: - RSA , SECP256R1, SECP256K1 **Option: Azure** **Endpoint:** `/keys/generate` | [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": "azure", "config": { "auth": { "vaultUrl": "https://.vault.azure.net/", "clientId": "", "tenantId": "", "clientSecret": "" }, "tags": { "key": "value" } }, "keyType": "secp256r1" }' ``` **Body** ```json { "backend": "azure", "config": { "auth": { "vaultUrl": "https://.vault.azure.net/", "clientId": "", "tenantId": "", "clientSecret": "" }, "tags": { "key": "value" } }, "keyType": "secp256r1" } ``` **Body Parameters** - `backend`: _String_ - The location where the key is stored. In our case `azure` as we want to store it in azure's key vault. - `config` - `vaultUrl`: _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) - `keyType`: _String_ - the algorithm used to generate the key. For Azure only RSA and secp256r1 and secp256k1 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 ``` -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/community-stack/wallet/key-management/oci-vault/overview RAW: https://docs.walt.id/community-stack/wallet/key-management/oci-vault/overview.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Overview OCI Vault (Oracle Cloud Infrastructure Vault) provides a secure and reliable Key Management Service (KMS) that can be leveraged to safeguard keys of wallets managed by the Wallet API. This document outlines how to integrate OCI Vault (Oracle Cloud Infrastructure Vault) with the Wallet API and manage keys securely. If you are new to OCI Vault, make sure to familiarize yourself with the service through the guides available [here](https://docs.oracle.com/en-us/iaas/Content/KeyManagement/home.htm). The following sections assume that you have knowledge of how Vault works and have already set up and configured a key management service. # Using OCI Vault (Oracle Cloud Infrastructure Vault) with Wallet API There are two main methods for integrating OCI Vault with the Wallet API: 1. **OCI SDK**: Utilize the OCI SDK to manage keys in OCI Vault programmatically. 2. **OCI REST API**: Interact with the OCI REST API to manage keys in OCI Vault directly. # OCI SDK Integration with OCI Vault The OCI SDK provides a convenient way to manage keys in OCI Vault without the need to interact with the REST API directly. But you will have to configure the vault for instance principal authentication as you won't be providing any private keys. Another configuration should be done where you will have to provide the compartment id and the vault id to the Wallet API's `oci.conf` file. **Example Configuration :** ``` vaultId="ocid1.vault.oc1.iad.amaaaaaa4q6q6qia7q7" compartmentId="ocid1.compartment.oc1..aaaaaaaawirugoz35riiybcxsvf7bmelqsxo3sajaav5w3i2vqowcwqrllxa" ``` # OCI REST API - OCI Vault With the OCI REST API you can manage keys in OCI Vault without the need to interact with the SDK directly. You will have to provide the necessary configuration to the Wallet API to interact with the OCI Vault in the `oci-rest-api.conf`. **Example Configuration :** ``` 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 = "entcvrlraabc4-management.kms.eu-frankfurt-1.oraclecloud.com" cryptoEndpoint = "entcvrlraabc4-crypto.kms.eu-frankfurt-1.oraclecloud.com" signingKeyPem = """ PRIVATE_KEY_HERE """ ``` -------------------------------------------------------------------------------- PAGE: REST API URL: https://docs.walt.id/community-stack/wallet/key-management/oci-vault/rest-api RAW: https://docs.walt.id/community-stack/wallet/key-management/oci-vault/rest-api.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # REST API ## 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: CURL** **Endpoint:** `/keys/generate` | [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..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" }, "keyType": "secp256r1" }' ``` **Body** ```json { "backend": "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" }, "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 ``` -------------------------------------------------------------------------------- PAGE: OCI SDK URL: https://docs.walt.id/community-stack/wallet/key-management/oci-vault/sdk RAW: https://docs.walt.id/community-stack/wallet/key-management/oci-vault/sdk.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # OCI SDK ## 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: CURL** **Endpoint:** `/keys/generate` | [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 vault is located. - `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 ``` -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/community-stack/wallet/did-management/overview RAW: https://docs.walt.id/community-stack/wallet/did-management/overview.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # DID Management Via the wallet-api you can create and manage DIDs for your users based on different ecosystems (did:key, did:jwk, did:web did:cheqd). Those DIDs can then be used to receive, sign and present digital credentials. The API also has an in-build did:web registry, learn more about it [here](https://docs.walt.id/community-stack/wallet/did-management/did-web-registry.md). **Guides** - [List DIDs](https://docs.walt.id/community-stack/wallet/did-management/list-dids.md) – Retrieve all DIDs registered in a wallet. - [Import DIDs](https://docs.walt.id/community-stack/wallet/did-management/import-dids.md) – Import an existing DID and its key material into a wallet. For a full list of endpoints for did management, please refer to the [Api reference](https://wallet.demo.walt.id/swagger/index.html#/DIDs). -------------------------------------------------------------------------------- PAGE: DID Web Registry URL: https://docs.walt.id/community-stack/wallet/did-management/did-web-registry RAW: https://docs.walt.id/community-stack/wallet/did-management/did-web-registry.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # DID Web Registry The wallet-api has a build-in DID Web Registry to make did:web's created via API publicly resolvable without the need to host any external services. ## Create a did:web **Option: CURL** [API Reference](https://wallet.demo.walt.id/swagger/index.html#/DIDs/post_wallet_api_wallet__wallet__dids_create_web) ```bash curl -X 'POST' \ 'https://wallet.demo.walt.id/wallet-api/wallet/{wallet}/dids/create/web?domain={domain}&path={path}' \ -H 'accept: */*' \ -d '' ``` **Path Params** - `wallet`: _[string]_ - The ID of the wallet to add the DID to. You can find a list of all wallets associated with an account using the following endpoint `/wallet-api/wallet/accounts/wallets`. **Query Params** - `domain`: _[string]_ - The domain that will host the DID. When you want to use the in-build did:web registry, set this to the domain where the wallet API is hosted. - `path`: _[string]_ - The path at which the DID document should be hosted. If you are using the in-build did:web registry it must look like `/wallet-api/registry/$uniqueIdentifier` where the `uniqueIdentifier` can be any type of number or UUID. - `keyId`: _[string]_ - Associate did:web with a key in the wallet or create a new key if none is provided. - `alias`: _[string]_ - Alias for DID, shown on overview page and when selecting a DID for issuing a credential. **Example Response** ```json did:web:wallet.walt.id:wallet-api:registry:1 ``` ## Viewing the DID Document According to the [official specification](https://w3c-ccg.github.io/did-method-web/), if the required domain and path parameters are used for the in-build registry, the created DID will have its DID Document available under: `wallet-api-host-domain/wallet/registry/{uniqueIdentifier}/did.json` ## Resolving the DID You can also use the [Universal Resolver](https://dev.uniresolver.io/) to resolve the DID and view the DID document. -------------------------------------------------------------------------------- PAGE: Import dids into your wallet URL: https://docs.walt.id/community-stack/wallet/did-management/import-dids RAW: https://docs.walt.id/community-stack/wallet/did-management/import-dids.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Import dids into your wallet You can import existing DIDs into your wallet using the following API endpoint: **Option: CURL** **Endpoint:** `/wallet-api/wallet/{wallet}/dids/import` | [API Reference](https://wallet.test.waltid.cloud/swagger/index.html#/DIDs/post_wallet_api_wallet__wallet__dids_import) **Example Request:** ```bash curl -X 'POST' \ 'https://wallet.test.waltid.cloud/wallet-api/wallet/{wallet_id}/ dids/import' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -d '{ "did": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiSzdrSzdVVmdnd0JKNWRCeFpxZGhFWFZ1VktfWGdsQzRfQl9kckoxOWE5RSIsIngiOiJBbXhmcUZtZkpUTGtQelNSZUxQRGJJYzhtTmNmWjN1MDNZbUVGNTQ1QW5zIn0", "key": { "kty": "OKP", "d": "GypvW9liOrvh-woOUpYCg3g5ZiELpKQfK0bPO6M-VGU", "crv": "Ed25519", "kid": "K7kK7UVggwBJ5dBxZqdhEXVuVK_XglC4_B_drJ19a9E", "x": "AmxfqFmfJTLkPzSReLPDbIc8mNcfZ3u03YmEF545Ans" }, "alias": "test" } ' ``` **Body Parameters:** - `did`: The DID to be imported in the wallet. - `key`: The private key associated with the DID in JWK format or can be a String PEM. - `alias` (optional): An alias for the DID to help identify it within the wallet - **Response Codes:** - `201`: DID imported successfully. - `400`: Invalid DID or missing/invalid key material. - `401`: Invalid authentication. - `409`: DID already exists. -------------------------------------------------------------------------------- PAGE: List DIDs URL: https://docs.walt.id/community-stack/wallet/did-management/list-dids RAW: https://docs.walt.id/community-stack/wallet/did-management/list-dids.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # List DIDs Retrieve all DIDs registered in a specific wallet. The response includes the DID string, its method, the associated key ID, and optional alias. ## List DIDs **Option: CURL** **Endpoint:** `GET /wallet-api/wallet/{walletId}/dids` | [API Reference](https://wallet.demo.walt.id/swagger/index.html#/DIDs/get_wallet_api_wallet__wallet__dids) ##### Example Request ```bash curl -X 'GET' \ 'http://0.0.0.0:7001/wallet-api/wallet/{walletId}/dids' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {token}' ``` **Path Parameters** - `walletId`: _String_ - The ID of the wallet to list DIDs from. See [Accounts & Wallets](https://docs.walt.id/community-stack/wallet/accounts-wallets.md#retrieve-wallet-id) for how to retrieve it. **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 The API returns a list of DID objects registered in the wallet. ```json [ { "did": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoib05kaHFwWlV5TnZRSHdOLS1IOWFxQmJuTnRPYWpSN056bVJzNl8wdWtqZyIsIngiOiJtZTdKbWE3S05hejNMM3N6OVNlRTdqbDkxYW1UbTktd1Y4dXBJcllJYVJRIiwieSI6ImxFQWVDNHRkcnl4MnBCUmJleHlQcE52ejdxSk9wWFBVS3ZKSnZoQkh6QUEifQ", "alias": "Onboarding", "document": "{\"@context\":[\"https://www.w3.org/ns/did/v1\",\"https://w3id.org/security/suites/jws-2020/v1\"],\"id\":\"did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoib05kaHFwWlV5TnZRSHdOLS1IOWFxQmJuTnRPYWpSN056bVJzNl8wdWtqZyIsIngiOiJtZTdKbWE3S05hejNMM3N6OVNlRTdqbDkxYW1UbTktd1Y4dXBJcllJYVJRIiwieSI6ImxFQWVDNHRkcnl4MnBCUmJleHlQcE52ejdxSk9wWFBVS3ZKSnZoQkh6QUEifQ\",\"verificationMethod\":[{\"id\":\"did:jwk:...#0\",\"type\":\"JsonWebKey2020\",\"controller\":\"did:jwk:...\",\"publicKeyJwk\":{\"kty\":\"EC\",\"crv\":\"P-256\",\"kid\":\"oNdhqpZUyNvQHwN--H9aqBbnNtOajR7NzmRs6_0ukjg\",\"x\":\"me7Jma7KNaz3L3sz9SeE7jl91amTm9-wV8upIrYIaRQ\",\"y\":\"lEAeC4tdryx2pBRbexyPpNvz7qJOpXPUKvJJvhBHzAA\"}}],\"assertionMethod\":[\"did:jwk:...#0\"],\"authentication\":[\"did:jwk:...#0\"],\"capabilityInvocation\":[\"did:jwk:...#0\"],\"capabilityDelegation\":[\"did:jwk:...#0\"],\"keyAgreement\":[\"did:jwk:...#0\"]}", "keyId": "oNdhqpZUyNvQHwN--H9aqBbnNtOajR7NzmRs6_0ukjg", "default": true, "createdOn": "2026-04-29T05:53:48.719808Z" } ] ``` **Note:** The `document` field is returned as an escaped JSON string. Parse it to work with the DID document structure. Each object in the response includes: - `did`: _String_ - The full DID string. - `alias`: _String_ - An optional human-readable label assigned to the DID. - `document`: _String_ - The resolved DID document as an escaped JSON string, containing verification methods, assertion methods, authentication, capability invocation/delegation, and key agreement references. - `keyId`: _String_ - The ID of the cryptographic key associated with this DID, as stored in the wallet's key management. - `default`: _Boolean_ - Whether this is the wallet's default DID, used automatically when no specific DID is provided in credential operations. - `createdOn`: _String_ - ISO 8601 timestamp of when the DID was created in the wallet. -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/community-stack/wallet/credential-exchange/overview RAW: https://docs.walt.id/community-stack/wallet/credential-exchange/overview.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Credential Exchange Use the Wallet API to perform credential exchange using OpenID for Verifiable Credentials: receive credentials from issuers via OIDC4VCI, present credentials to verifiers via OIDC4VP, or directly import signed JWT/SD-JWT credentials. ## What is Credential Exchange? Credential exchange covers the flows to receive, import, and present credentials: - [OIDC4VCI for receiving (issuance)](https://docs.walt.id/concepts/data-exchange-protocols/openid4vci.md) - [OIDC4VP for presenting (verification)](https://docs.walt.id/concepts/data-exchange-protocols/openid4vp.md) - Direct JWT/SD-JWT import for credentials obtained outside OIDC4VCI Please see the [API reference](https://wallet.demo.walt.id/swagger/index.html#/Credential%20exchange) for the complete list of exchange endpoints. ## Guides For a more hands-on experience with our API, go through our detailed guides: 1. [Receiving a Credential via OIDC4VCI](https://docs.walt.id/community-stack/wallet/credential-exchange/guides/accept-w3c-vc-oid4vci.md): Learn how to receive and parse an OIDC4VCI offer request step by step. 2. [Presenting a Requested Credential via OIDC4VP](https://docs.walt.id/community-stack/wallet/credential-exchange/guides/present-digital-credentials-oid4vp.md): Understand how to respond to an OIDC4VP credential request interactively. 3. [Importing Verifiable Credentials (JWT/SD-JWT)](https://docs.walt.id/community-stack/wallet/credential-exchange/guides/import-w3c-vc): Learn how to import signed credentials directly into your wallet via the UI or API.
**Note:** NOTE: During credential issuance, users have the flexibility to decide which DID in their account will be associated with the received credential. The API supports the receipt and presentation of single or multiple credentials in one request. -------------------------------------------------------------------------------- PAGE: How to Accept W3C Verifiable Credentials via OID4VCI in a Wallet with walt.id URL: https://docs.walt.id/community-stack/wallet/credential-exchange/guides/accept-w3c-vc-oid4vci RAW: https://docs.walt.id/community-stack/wallet/credential-exchange/guides/accept-w3c-vc-oid4vci.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # How to Accept W3C Verifiable Credentials (JWT / SD-JWT) via OID4VCI in a Wallet with walt.id **TL;DR** **What you:** - Parse and process credential offer URLs in OID4VCI format - Accept credential offers via the Wallet API endpoint - Display credential details to users before acceptance - Handle credential offers from QR codes, links, or manual input **Relevant concepts:** - [OpenID4VCI](https://docs.walt.id/concepts/data-exchange-protocols/openid4vci.md) – Protocol for verifiable credential issuance - [Verifiable Credentials (W3C)](https://docs.walt.id/concepts/digital-credentials/verifiable-credentials-w3c.md) – W3C standard for digital credentials Video: https://youtu.be/tyNv9uPn9HM There are several methods by which a user can receive a credential into their wallet, and all are supported by the walt.id wallet API: 1. User may receive a direct link, which opens the wallet and prompts credential acceptance. 2. A website may display a QR code for the user to scan and accept the credential. 3. The user may manually input an offer URL string into a text field. Irrespective of the chosen method, these are just different ways of receiving, parsing and fulfilling a request URL. ## Credential Offer URL A credential offer URL is a standardized method, as per the OID4VCI specification, to communicate the issuance of credentials between issuer and wallet. This URL can take various forms, such as a QR code or a link, and generally begins with `openid-credential-offer://`. **Example Offer URL** ```json openid-credential-offer://issuer.portal.walt.id/?credential_offer= ``` Within the URL, a query parameter `credential_offer` provides the actual offer as a JSON object. **Example of a Credential Offer Object** ```json { "credential_issuer": "https://issuer.demo.walt.id", // This is the URL of the issuer of the credential(s) "credentials": [ // This array contains details about the types of credentials being offered { "format": "jwt_vc_json", // Specifies the format of the credential "types": [ // An array indicating the credential types "VerifiableCredential", "BankId" ], "credential_definition": { "@context": [ // Provides the contexts of the credential "https://www.w3.org/2018/credentials/v1" ], "types": [ // Reiterates the type of credential being offered. "VerifiableCredential", "BankId" ] } } ], "grants": { // Specifies how the credentials can be obtained "authorization_code": { "issuer_state": "" // Contains an issuer_state, a unique identifier for the grant }, "urn:ietf:params:oauth:grant-type:pre-authorized_code": { // Provides details for a pre-authorized code grant type, //including the actual pre-authorized_code and a flag user_pin_required indicating whether a PIN is required // for user authentication. "pre-authorized_code": "", "user_pin_required": false } } } ``` ## Accepting Credential Offers To accept a credential via the credential offer URL via the wallet API, please follow the steps below. **Option: CURL** **Endpoint:** `/wallet-api/wallet/{walletId}/exchange/useOfferRequest` | [API Reference](https://wallet.demo.walt.id/swagger/index.html#/Credential%20exchange/post_wallet_api_wallet__wallet__exchange_useOfferRequest) **Example Request** ```bash curl -X 'POST' \ 'http://0.0.0.0:7001/wallet-api/wallet/{walletId}/exchange/useOfferRequest?did={did}' \ -H 'accept: application/json' \ -H 'Content-Type: text/plain' \ -H 'authorization: Bearer {token}' \ -d 'openid-credential-offer://issuer.portal.walt.id/?credential_offer=' ``` **Path Parameters** - `walletId`: _String_ - The ID of the wallet to receive the credential into. See [Accounts & Wallets](https://docs.walt.id/community-stack/wallet/accounts-wallets.md#retrieve-wallet-id) for how to retrieve it. **Query Parameters** - `did` (optional): _String_ - The DID of the wallet holder to use when accepting the credential. If omitted, the wallet's default DID is used. **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}`. **Body** The credential offer URL received from the issuer, provided as plain text. ``` openid-credential-offer://issuer.portal.walt.id/?credential_offer= ``` **Note:** When using the terminal, the credential offer URL returned from the issuer may have a `%` at the end. Make sure to remove this before passing it to the wallet. --- **Example Response** On success, the API returns a list of the credentials that were received and stored in the wallet. ```json [ { "wallet": "6006b6f4-e651-46db-b6ae-e7bd6e9c40f2", "id": "urn:uuid:c2f7e988-a4cb-48c2-bbf6-ed97f6010abe", "document": "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaWFVcE5VelZpYTFwV1NXeHVZMlp4WDB4bVgxTjFlRW95U25SUk5VaDJZWG8zZEZkUWJrRnFWVlZrY3lJc0luZ2lPaUpHV21SMmQwTTRZVWRvVW5keGVsZHdkR1ZxTUU1YVozUjNXVUZKTVZONVJtY3hiVXRFUlZSUFpuRkZJbjAjaUpNUzVia1pWSWxuY2ZxX0xmX1N1eEoySnRRNUh2YXo3dFdQbkFqVVVkcyIsInR5cCI6IkpXVCIsImFsZyI6IkVkRFNBIn0.eyJpc3MiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaWFVcE5VelZpYTFwV1NXeHVZMlp4WDB4bVgxTjFlRW95U25SUk5VaDJZWG8zZEZkUWJrRnFWVlZrY3lJc0luZ2lPaUpHV21SMmQwTTRZVWRvVW5keGVsZHdkR1ZxTUU1YVozUjNXVUZKTVZONVJtY3hiVXRFUlZSUFpuRkZJbjAiLCJzdWIiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pYWxvMFdsRkhhbXBuTFhwWVJtWkdORnBtYmxkZlpEaFhZa2xqWjBWWVNXOXVOR2hKYlhoaFl6UmlaeUlzSW5naU9pSnVNVU5vUVROSlNVMVNka1ZOU201MVYwNTVkalUwYlRWclpVUmhheTFsZUhOME4zRjZlWGRQVG1aRklpd2llU0k2SWw5cVdWTjZVMTlEYjNSUVgyZE1NR3hpVlVOeVpXVk9UbVU0TVhJd2VIRXpNbGxsU2pKME5HMXhibGtpZlEiLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL2V4YW1wbGVzL3YxIl0sImlkIjoidXJuOnV1aWQ6YzJmN2U5ODgtYTRjYi00OGMyLWJiZjYtZWQ5N2Y2MDEwYWJlIiwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIlVuaXZlcnNpdHlEZWdyZWUiXSwiaXNzdWVyIjp7ImlkIjoiZGlkOmp3azpleUpyZEhraU9pSlBTMUFpTENKamNuWWlPaUpGWkRJMU5URTVJaXdpYTJsa0lqb2lhVXBOVXpWaWExcFdTV3h1WTJaeFgweG1YMU4xZUVveVNuUlJOVWgyWVhvM2RGZFFia0ZxVlZWa2N5SXNJbmdpT2lKR1dtUjJkME00WVVkb1VuZHhlbGR3ZEdWcU1FNWFaM1IzV1VGSk1WTjVSbWN4YlV0RVJWUlBabkZGSW4wIn0sImlzc3VhbmNlRGF0ZSI6IjIwMjYtMDQtMTZUMTU6MDg6MDEuNTczMzg0MTA1WiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmp3azpleUpyZEhraU9pSkZReUlzSW1OeWRpSTZJbEF0TWpVMklpd2lhMmxrSWpvaWFsbzBXbEZIYW1wbkxYcFlSbVpHTkZwbWJsZGZaRGhYWWtsalowVllTVzl1TkdoSmJYaGhZelJpWnlJc0luZ2lPaUp1TVVOb1FUTkpTVTFTZGtWTlNtNTFWMDU1ZGpVMGJUVnJaVVJoYXkxbGVITjBOM0Y2ZVhkUFRtWkZJaXdpZVNJNklsOXFXVk42VTE5RGIzUlFYMmRNTUd4aVZVTnlaV1ZPVG1VNE1YSXdlSEV6TWxsbFNqSjBORzF4YmxraWZRIiwiZGVncmVlIjp7InR5cGUiOiJCYWNoZWxvckRlZ3JlZSIsIm5hbWUiOiJCYWNoZWxvciBvZiBTY2llbmNlIGFuZCBBcnRzIn19LCJleHBpcmF0aW9uRGF0ZSI6IjIwMjctMDQtMTZUMTU6MDg6MDEuNTczNDA3ODA1WiJ9LCJqdGkiOiJ1cm46dXVpZDpjMmY3ZTk4OC1hNGNiLTQ4YzItYmJmNi1lZDk3ZjYwMTBhYmUiLCJleHAiOjE4MDc4ODgwODEsImlhdCI6MTc3NjM1MjA4MSwibmJmIjoxNzc2MzUyMDgxfQ.0pWxCrYF4LMMtzzFOnBx4kSDFTd7AZSYXfRnAA8IgJ1OSLnFXRjWsXnsL78VLOjxkEGmO8Paei3ewq2vDNn9CQ", "disclosures": "", "addedOn": "2026-04-16T15:08:01.604768543Z", "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:c2f7e988-a4cb-48c2-bbf6-ed97f6010abe", "type": [ "VerifiableCredential", "UniversityDegree" ], "issuer": { "id": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiaUpNUzVia1pWSWxuY2ZxX0xmX1N1eEoySnRRNUh2YXo3dFdQbkFqVVVkcyIsIngiOiJGWmR2d0M4YUdoUndxeldwdGVqME5aZ3R3WUFJMVN5RmcxbUtERVRPZnFFIn0" }, "issuanceDate": "2026-04-16T15:08:01.573384105Z", "credentialSubject": { "id": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoialo0WlFHampnLXpYRmZGNFpmbldfZDhXYkljZ0VYSW9uNGhJbXhhYzRiZyIsIngiOiJuMUNoQTNJSU1SdkVNSm51V055djU0bTVrZURhay1leHN0N3F6eXdPTmZFIiwieSI6Il9qWVN6U19Db3RQX2dMMGxiVUNyZWVOTmU4MXIweHEzMlllSjJ0NG1xblkifQ", "degree": { "type": "BachelorDegree", "name": "Bachelor of Science and Arts" } }, "expirationDate": "2027-04-16T15:08:01.573407805Z" } } ] ``` ## Example: Display Credentials to User Before Acceptance Before calling the API, you may want to show the user which credentials they are about to accept. In JavaScript, assuming you have the offer URL and a `decodeOfferURL(offerURL)` function that returns the parsed offer object: ```js var offerURL = "openid-credential-offer://..."; // Extract the offer object from the URL var offerObject = decodeOfferURL(offerURL); // Map credentials to a display-friendly format var credentialDetails = offerObject["credentials"].map((credential) => { var lastType = credential.types[credential.types.length - 1]; return { format: credential.format, type: lastType, }; }); // Display credential details to the user credentialDetails.forEach((credential) => { console.log(`Format: ${credential.format}`); console.log(`Type: ${credential.type}`); }); ``` The user would then see a list of credentials that will be received and can confirm whether they want to proceed with accepting the offer. **Note:** The above example assumes `decodeOfferURL(offerURL)` is implemented. In a real application, use a URL parsing library, handle JSON decoding, and add appropriate error handling. -------------------------------------------------------------------------------- PAGE: How to Accept SD-JWT VC Credentials via OID4VCI in a Wallet with walt.id URL: https://docs.walt.id/community-stack/wallet/credential-exchange/guides/accept-sd-jwt-vc-oid4vci RAW: https://docs.walt.id/community-stack/wallet/credential-exchange/guides/accept-sd-jwt-vc-oid4vci.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # How to Accept SD-JWT VC Credentials via OID4VCI in a Wallet with walt.id **TL;DR** **What you:** - Recognise the SD-JWT VC credential offer format - Accept SD-JWT VC credential offers via the Wallet API - Understand how selective disclosures appear in the API response **Relevant concepts:** - [OpenID4VCI](https://docs.walt.id/concepts/data-exchange-protocols/openid4vci.md) – Protocol for verifiable credential issuance - [SD-JWT VC (IETF)](https://docs.walt.id/concepts/digital-credentials/sd-jwt-vc.md) – IETF standard for selectively disclosable credentials - [Selective Disclosure](https://docs.walt.id/concepts/selective-disclosure.md) – Sharing only chosen attributes from a credential The API call to accept an SD-JWT VC credential is identical to accepting a W3C VC — the same `/wallet-api/wallet/{walletId}/exchange/useOfferRequest` endpoint is used. The differences lie in the **credential offer format** and the **structure of the response**. ## Credential Offer URL As with all OID4VCI issuance, the issuer communicates the credential offer via a URL beginning with `openid-credential-offer://`. **Example Offer URL** ``` openid-credential-offer://issuer.portal.walt.id/?credential_offer= ``` **Example Credential Offer Object** For SD-JWT VCs, the offer uses a `vct` field (Verifiable Credential Type) instead of the `types` array used by W3C credentials. This URI uniquely identifies the credential type defined by the issuer. ```json { "credential_issuer": "https://issuer.demo.walt.id", "credential_configuration_ids": [ "identity_credential" ], "grants": { "urn:ietf:params:oauth:grant-type:pre-authorized_code": { "pre-authorized_code": "", "user_pin_required": false } } } ``` ## Accepting Credential Offers **Option: CURL** **Endpoint:** `/wallet-api/wallet/{walletId}/exchange/useOfferRequest` | [API Reference](https://wallet.demo.walt.id/swagger/index.html#/Credential%20exchange/post_wallet_api_wallet__wallet__exchange_useOfferRequest) **Example Request** ```bash curl -X 'POST' \ 'http://0.0.0.0:7001/wallet-api/wallet/{walletId}/exchange/useOfferRequest?did={did}' \ -H 'accept: application/json' \ -H 'Content-Type: text/plain' \ -H 'authorization: Bearer {token}' \ -d 'openid-credential-offer://issuer.portal.walt.id/?credential_offer=' ``` **Path Parameters** - `walletId`: _String_ - The ID of the wallet to receive the credential into. See [Accounts & Wallets](https://docs.walt.id/community-stack/wallet/accounts-wallets.md#retrieve-wallet-id) for how to retrieve it. **Query Parameters** - `did` (optional): _String_ - The DID of the wallet holder to use when accepting the credential. If omitted, the wallet's default DID is used. **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}`. **Body** The credential offer URL received from the issuer, provided as plain text. ``` openid-credential-offer://issuer.portal.walt.id/?credential_offer= ``` **Note:** When using the terminal, the credential offer URL returned from the issuer may have a `%` at the end. Make sure to remove this before passing it to the wallet. --- **Example Response** On success, the API returns a list of the credentials received and stored in the wallet. ```json [ { "wallet": "6006b6f4-e651-46db-b6ae-e7bd6e9c40f2", "id": "urn:uuid:48841c75-36cb-416b-af2e-6d4e606c8ca6", "document": "eyJraWQiOiJkaWQ6a2V5Ono2TWtqb1JocTFqU05KZExpcnVTWHJGRnhhZ3FyenRaYVhIcUhHVVRLSmJjTnl3cCN6Nk1ram9SaHExalNOSmRMaXJ1U1hyRkZ4YWdxcnp0WmFYSHFIR1VUS0piY055d3AiLCJ0eXAiOiJ2YytzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJnaXZlbl9uYW1lIjoiSm9obiIsImZhbWlseV9uYW1lIjoiRG9lIiwiZW1haWwiOiJqb2huZG9lQGV4YW1wbGUuY29tIiwicGhvbmVfbnVtYmVyIjoiKzEtMjAyLTU1NS0wMTAxIiwiYWRkcmVzcyI6eyJzdHJlZXRfYWRkcmVzcyI6IjEyMyBNYWluIFN0IiwibG9jYWxpdHkiOiJBbnl0b3duIiwicmVnaW9uIjoiQW55c3RhdGUiLCJjb3VudHJ5IjoiVVMifSwiaXNfb3Zlcl8xOCI6dHJ1ZSwiaXNfb3Zlcl8yMSI6dHJ1ZSwiaXNfb3Zlcl82NSI6dHJ1ZSwiaWQiOiJ1cm46dXVpZDo0ODg0MWM3NS0zNmNiLTQxNmItYWYyZS02ZDRlNjA2YzhjYTYiLCJpYXQiOjE3NzYzNTIzNjEsIm5iZiI6MTc3NjM1MjM2MSwiZXhwIjoxODA3ODg4MzYxLCJfc2RfYWxnIjoic2hhLTI1NiIsImlzcyI6ImRpZDprZXk6ejZNa2pvUmhxMWpTTkpkTGlydVNYckZGeGFncXJ6dFphWEhxSEdVVEtKYmNOeXdwIiwiY25mIjp7Imp3ayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoialo0WlFHampnLXpYRmZGNFpmbldfZDhXYkljZ0VYSW9uNGhJbXhhYzRiZyIsIngiOiJuMUNoQTNJSU1SdkVNSm51V055djU0bTVrZURhay1leHN0N3F6eXdPTmZFIiwieSI6Il9qWVN6U19Db3RQX2dMMGxiVUNyZWVOTmU4MXIweHEzMlllSjJ0NG1xblkifX0sInZjdCI6Imh0dHBzOi8vaXNzdWVyLmRlbW8ud2FsdC5pZC9kcmFmdDEzL2lkZW50aXR5X2NyZWRlbnRpYWwiLCJfc2QiOlsid2NXMkJXU2g5MXhneHh1RjhFckJJMkkxOVpoUEFST2pBTEtoZGRrX0JXUSJdfQ.u198RgZC1pv3VbZy7DbMHh6g432qnT4AIHhploWyyITtW5PCbUp_6ewJBvNjwf1tfNu8cDhcdP8E4UyG0djZAQ", "disclosures": "WyJVd3hscTJaX2ZCdlpZNGF2Y2JDTWpRIiwiYmlydGhkYXRlIiwiMTk0MC0wMS0wMSJd", "addedOn": "2026-04-16T15:12:41.345828048Z", "pending": false, "format": "vc+sd-jwt", "parsedDocument": { "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:48841c75-36cb-416b-af2e-6d4e606c8ca6", "iat": 1776352361, "nbf": 1776352361, "exp": 1807888361, "_sd_alg": "sha-256", "iss": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp", "cnf": { "jwk": { "kty": "EC", "crv": "P-256", "kid": "jZ4ZQGjjg-zXFfF4ZfnW_d8WbIcgEXIon4hImxac4bg", "x": "n1ChA3IIMRvEMJnuWNyv54m5keDak-exst7qzywONfE", "y": "_jYSzS_CotP_gL0lbUCreeNNe81r0xq32YeJ2t4mqnY" } }, "vct": "https://issuer.demo.walt.id/draft13/identity_credential", "_sd": ["wcW2BWSh91xgxxuF8ErBI2I19ZhPAROjALKhddk_BWQ"] } } ] ``` **Response Fields** - `wallet`: _String_ - The ID of the wallet the credential was stored in. - `id`: _String_ - The unique identifier of the stored credential. - `document`: _String_ - The raw signed SD-JWT token. - `disclosures`: _String_ - Base64url-encoded disclosure(s) included with the credential. Each disclosure encodes a salt, claim name, and claim value — representing a selectively disclosable attribute the holder can choose to reveal when presenting. Decoding the value from the example above gives: ```json ["Uwxlq2Z_fBvZY4avcbCMjQ", "birthdate", "1940-01-01"] ``` - `addedOn`: _String_ - ISO 8601 timestamp of when the credential was stored. - `pending`: _Boolean_ - Whether the credential is pending acceptance. - `format`: _String_ - The credential format. `vc+sd-jwt` identifies this as an IETF SD-JWT VC. - `parsedDocument`: _Object_ - The decoded credential claims as a flat JSON object. Key fields include: - `vct`: _String_ - The Verifiable Credential Type URI identifying the credential type. - `iss`: _String_ - The DID of the issuer. - `cnf.jwk`: _Object_ - The holder's public key, binding the credential to the wallet. - `_sd_alg`: _String_ - The hash algorithm used for selective disclosure (typically `sha-256`). - `_sd`: _Array_ - Hashes of claims that are selectively disclosable but withheld in this issuance. - `iat`, `nbf`, `exp`: _Number_ - Issued at, not before, and expiration timestamps (Unix). -------------------------------------------------------------------------------- PAGE: How to Accept a Mobile Driver’s Licenses (ISO/IEC 18013-5 mDL) in a Wallet with walt.id URL: https://docs.walt.id/community-stack/wallet/credential-exchange/guides/accept-iso-18013-5-mdl-oid4vci RAW: https://docs.walt.id/community-stack/wallet/credential-exchange/guides/accept-iso-18013-5-mdl-oid4vci.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # How to Accept a Mobile Driver's Licenses (ISO/IEC 18013-5 mDL) in a Wallet with walt.id **TL;DR** **What you:** - Parse and process mDL credential offer URLs in OID4VCI format - Accept mDL credential offers via the Wallet API endpoint - Decode CBOR-encoded mDL credentials for user display - Handle offers from QR codes, links, or manual input **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 - [OpenID4VCI](https://docs.walt.id/concepts/data-exchange-protocols/openid4vci.md) – Protocol for verifiable credential issuance There are several methods by which a user can receive a mDL into their wallet, and all are supported by the wallet API: 1. User may receive a direct link, which opens the wallet and prompts credential acceptance. 2. A website may display a QR code for the user to scan and accept the credential. 3. The user may manually input an offer URL string into a text field. Irrespective of the chosen method, these are just different ways of receiving, parsing and fulfilling a request URL. ## Credential Offer URL A credential offer URL is a standardized method, as per the OID4VCI specification, to communicate the issuance of credentials between issuer and wallet. This URL can take various forms, such as a QR code or a link, and generally begins with `openid-credential-offer://` or `haip://`. **Example Offer URL** ```json openid-credential-offer://issuer.portal.walt.id/?credential_offer= ``` Within the URL, a query parameter `credential_offer` provides the actual offer. **Example of a Credential Offer Object** ```json { "credential_issuer": "http://localhost:7002", // This is the URL of the issuer of the credential(s) "credential_configuration_ids": [ // This array contains details about the types of credentials being offered "org.iso.18013.5.1.mDL" ], "grants": { // Specifies how the credentials can be obtained "authorization_code": { "issuer_state": "7a76d97f-aa99-45ee-a130-fb6674f699a4" // Contains an issuer_state, a unique identifier for the grant }, "urn:ietf:params:oauth:grant-type:pre-authorized_code": { // Provides details for a pre-authorized code grant type, //including the actual pre-authorized_code "pre-authorized_code": "eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiI3YTc2ZDk3Zi1hYTk5LTQ1ZWUtYTEzMC1mYjY2NzRmNjk5YTQiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjcwMDIiLCJhdWQiOiJUT0tFTiJ9.MTcBjskpqvzH_rcxrU3G1N-PiK7UvHgZ75cQfYiZsQJBBqgKROAlgxoVaD91oZF3TO00HHersxgZVDwTzXxuAg" } } } ``` ## Accepting Credential Offers To accept a credential via the credential offer URL: 1. Parse the credential offer URL so that the contained offer can be processed. 2. Provide the offer URL to the Wallet API so it can manage parsing and fulfilling the offer. 3. (Optional) Present the credentials to the user before finally accepting the credential offer. See an example below. **Accepting the offer** To accept the offer via the Wallet API: 1. Make a POST call to the `/wallet-api/wallet/{wallet}/exchange/useOfferRequest` endpoint. 2. Pass a user's `did` and the `walletId` as request parameters. 3. Include the credential offer URL in the request body. 4. Let the API parse the request, receive the credentials from the issuer, and store them in the user's wallet. **Note:** When receiving an mDL, the key for the selected DID must use the secp256r1 algorithm.
**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/{wallet}/exchange/useOfferRequest?did=did%3Akey%3Az6MknMppUdsg34t6oPevGDijgB9w9862Ai6Xu5iexjNU2Uoh' \ -H 'accept: */*' \ -H 'Content-Type: text/plain' \ -d 'openid-credential-offer://issuer.portal.walt.id/?credential_offer=' ``` You can receive the needed wallet id parameter via the `/wallet-api/wallet/accounts/wallets` endpoint. ## Display The Credential To The User To improve readability, use the `/wallet-api/util/parseMDoc` endpoint to decode the CBOR-encoded mDL into a simple JSON structure. -------------------------------------------------------------------------------- PAGE: How to Present Digital Credentials (W3C, SD-JWT VC, mDL) Via OID4VP with walt.id URL: https://docs.walt.id/community-stack/wallet/credential-exchange/guides/present-digital-credentials-oid4vp RAW: https://docs.walt.id/community-stack/wallet/credential-exchange/guides/present-digital-credentials-oid4vp.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # How to Present Digital Credentials (W3C, SD-JWT VC, mDL) Via OID4VP with walt.id **TL;DR** **What you:** - Extract and parse presentation definitions from OID4VP request URLs - Find credentials in a wallet that match presentation definition requirements - Resolve and fulfill presentation requests via the Wallet API - Handle selective disclosure for SD-JWT credentials **Relevant concepts:** - [OpenID4VP](https://docs.walt.id/concepts/data-exchange-protocols/openid4vp.md) – Protocol for requesting and receiving verifiable presentations. - [Verifiable Credentials (W3C)](https://docs.walt.id/concepts/digital-credentials/verifiable-credentials-w3c.md) – W3C standard for digital credentials - [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 - [SD-JWT VC](https://docs.walt.id/concepts/digital-credentials/sd-jwt-vc.md) – IETF SD-JWT VCs standard Video: https://youtu.be/lbrI6BERI8o There are several methods by which a user can present a credential to a verifier, and all are supported by the wallet API: 1. User may receive a direct link, which opens the wallet and prompts credential sharing. 2. A website may display a QR code for the user to scan and share the credential. 3. The user may manually input a credential presentation request URL string into a text field. Irrespective of the chosen method, these are just different ways of receiving, parsing and fulfilling a credential presenting request URL. ## Credential Presentation Request URL A credential presentation URL is a standardised method, as per the OID4VP specification, to communicate the needed credentials and claims from verifier to the wallet. The URL can take various forms, such as QR code or a link, and generally begins with `openid4vp://` or `haip://`. **Example Presentation Request** ``` openid4vp://authorize ?response_type=vp_token &client_id= &response_mode=direct_post &state= &presentation_definition= &client_id_scheme=redirect_uri &response_uri= ``` The most important parameter for us will be the `presentation_definition`. The other parameters mainly define: - **Internal wallet behaviour** - **Response types** - **Supported credential formats** - **Secure communication** between wallet and verifier - **Transaction data** to bind the presentation to a concrete action, such as a payment authorization For now, we don't need to worry about these details because the wallet API manages them. In a more advanced guide we will also dive into the other parameters. When the authorization request contains OpenID4VP `transaction_data`, the wallet binds it to the presentation by adding transaction data hashes. For `dc+sd-jwt` presentations, hashes are added to the KB-JWT. For `mso_mdoc` presentations, hashes are embedded in the DeviceSigned namespaces. #### The Presentation Definition The presentation definition specifies the criteria for the wallet to know what credentials and claims should be requested from the user and shard with the verifier. **Example** ```json { "input_descriptors": [ { "id": "UniversityDegree", "format": { "jwt_vc_json": { "alg": [ "ES256" ] } }, "constraints": { "fields": [ { "path": [ "$.type" ], "filter": { "type": "string", "pattern": "UniversityDegree" } } ] } } ] } ``` The definition says that one credential is requested and that it: - **Uses the JWT format** - **Requires the "ES256" algorithm** - **Must be of type "UniversityDegree"**, as enforced by the constraint filtering on the credential's `type` field You can find more info about definition documents [here](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html#name-presentation_definition-par). ## Fulfilling A Presentation Request In order to fulfill the presentation request received by the verifier, we need to go through the following steps: 1. Extract Presentation Definition from OID4VC presentation request 2. Find credentials matching the presentation definition in the user wallet 3. Request consent from the end-user of the wallet to share the credentials 4. Wrapping the credentials in a Verifiable Presentation and signing it using the holder (wallet user) DID 5. (optional) Redirect the user to the verification result page returned from the verifier ### 1. Extract Presentation Definition A simple example for the JavaScript browser environment would look like the following: ```js async function decodePresentationURL(offerURL) { // Create URL object const url = new URL(offerURL); // Get `presentation_definition_uri` query parameter const offerParam = url.searchParams.get("presentation_definition_uri"); // Resolve the URL and get the result const response = await fetch(offerParam); if (!response.ok) { throw new Error('Network response was not ok'); } // Convert result to text const resultText = await response.text(); // Decode and parse resultText to an Object const offerObject = JSON.parse(resultText); return offerObject; } ``` The output of the function would look as follows and will be used in the next step. It's identical to the one example we already saw of a presentation definition. ```json { "input_descriptors": [ { "id": "UniversityDegree", "format": { "jwt_vc_json": { "alg": [ "ES256" ] } }, "constraints": { "fields": [ { "path": [ "$.type" ], "filter": { "type": "string", "pattern": "UniversityDegree" } } ] } } ] } ``` **Note:** Optionally decode the presentation request URL with a site like [this](https://www.url-encode-decode.com/), then extract and resolve `presentation_definition` by pasting it into your browser search bar. ### 2. Find Credential(s) Matching Presentation Definition Use the `matchCredentialsForPresentationDefinition` endpoint in the wallet API to find all user credentials matching a Presentation Definition request. **Note:** `matchCredentialsForPresentationDefinition` is the legacy helper for Presentation Definition requests. For OpenID4VP 1.0 / DCQL requests, use `matchCredentialsForPresentationRequest` with the full authorization request, or resolve the request first via `resolvePresentationRequest`. **Info:** OpenID4VP `transaction_data` is currently supported for `dc+sd-jwt` and `mso_mdoc` requests only. W3C VC / `jwt_vc_json` requests can still be presented via OID4VP, but not with transaction-data binding. **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/{walletId}/exchange/matchCredentialsForPresentationDefinition' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {token}' \ -H 'Content-Type: application/json' \ -d '{ "input_descriptors": [ { "id": "UniversityDegree", "format": { "jwt_vc_json": { "alg": [ "ES256" ] } }, "constraints": { "fields": [ { "path": [ "$.type" ], "filter": { "type": "string", "pattern": "UniversityDegree" } } ] } } ] }' ``` **Path Parameters** - `walletId`: _String_ - The ID of the wallet. See [Accounts & Wallets](https://docs.walt.id/community-stack/wallet/accounts-wallets.md#retrieve-wallet-id) for how to retrieve it. **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}`. **Body** The extracted presentation definition as JSON, as explained [here](https://docs.walt.id/community-stack/wallet/credential-exchange/guides/present-vc-oid4vp#_1-extract-presentation-definition). ```json { "input_descriptors": [ { "id": "UniversityDegree", "format": { "jwt_vc_json": { "alg": [ "ES256" ] } }, "constraints": { "fields": [ { "path": [ "$.type" ], "filter": { "type": "string", "pattern": "UniversityDegree" } } ] } } ] } ``` **Body Parameters** The body takes the extracted presentation definition as JSON, as explained [here](https://docs.walt.id/community-stack/wallet/credential-exchange/guides/present-vc-oid4vp#_1-extract-presentation-definition). --- **Example Response** The response behaves as follows: - **If credentials match** the presentation definition, a list of these credentials is returned. - **If no credentials match**, an empty list is returned. - **Credential IDs** returned in the list should be noted for use in a later step. ```json [ { "wallet": "5f2eb7d7-7d04-461c-b93d-28d95efbf15b", "id": "urn:uuid:fb09ba3d-b73d-49ad-9d88-3d018878ea83", "document": "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pTTFsT1pEbEdibmc1U214NVVGWlpkMmRYUmtVek4wVXpSM2RKTUdWSGJFTkxPSGRHYkZkNFIyWndUU0lzSW5naU9pSkdiM1paTWpGTVFVRlBWR3huTFcwdFRtVkxWMmhhUlV3MVlVWnlibEl3ZFdOS2FrUTFWRXR3UjNWbklpd2llU0k2SWtOeVJrcG1SMVJrVURJNVNrcGpZM0JSV0hWNVRVOHpiMmgwZW5KVWNWQjZRbEJDU1ZSWmFqQnZaMEVpZlEiLCJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pTTFsT1pEbEdibmc1U214NVVGWlpkMmRYUmtVek4wVXpSM2RKTUdWSGJFTkxPSGRHYkZkNFIyWndUU0lzSW5naU9pSkdiM1paTWpGTVFVRlBWR3huTFcwdFRtVkxWMmhhUlV3MVlVWnlibEl3ZFdOS2FrUTFWRXR3UjNWbklpd2llU0k2SWtOeVJrcG1SMVJrVURJNVNrcGpZM0JSV0hWNVRVOHpiMmgwZW5KVWNWQjZRbEJDU1ZSWmFqQnZaMEVpZlEiLCJzdWIiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaVEyVTVNbGczUVhVMVRuQlRWV0ZoWlU5VFl6TkpSMjlDTFVacFNUTmtaMjFYT1Y5c2NGTldja3hrYXlJc0luZ2lPaUpIY1VabWRqbFNjemh1TlRrMk4wSXlVR3g0TW1wbFduRnlOWFZxVkRoM2RYWk5XbEpIYlZoeGVFSkZJbjAiLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVmVyaWZpYWJsZUF0dGVzdGF0aW9uIiwiVmVyaWZpYWJsZUlkIl0sImNyZWRlbnRpYWxTY2hlbWEiOnsiaWQiOiJodHRwczovL2FwaS5wcmVwcm9kLmVic2kuZXUvdHJ1c3RlZC1zY2hlbWFzLXJlZ2lzdHJ5L3YxL3NjaGVtYXMvMHhiNzdmODUxNmE5NjU2MzFiNGYxOTdhZDU0YzY1YTllMmY5OTM2ZWJmYjc2YmFlNDkwNmQzMzc0NGRiY2M2MGJhIiwidHlwZSI6IkZ1bGxKc29uU2NoZW1hVmFsaWRhdG9yMjAyMSJ9LCJjcmVkZW50aWFsU3ViamVjdCI6eyJjdXJyZW50QWRkcmVzcyI6WyIxIEJvdWxldmFyZCBkZSBsYSBMaWJlcnTDqSwgNTk4MDAgTGlsbGUiXSwiZGF0ZU9mQmlydGgiOiIxOTkzLTA0LTA4IiwiZmFtaWx5TmFtZSI6IkRPRSIsImZpcnN0TmFtZSI6IkphbmUiLCJnZW5kZXIiOiJGRU1BTEUiLCJpZCI6ImRpZDpqd2s6ZXlKcmRIa2lPaUpQUzFBaUxDSmpjbllpT2lKRlpESTFOVEU1SWl3aWEybGtJam9pUTJVNU1sZzNRWFUxVG5CVFZXRmhaVTlUWXpOSlIyOUNMVVpwU1ROa1oyMVhPVjlzY0ZOV2NreGtheUlzSW5naU9pSkhjVVptZGpsU2N6aHVOVGsyTjBJeVVHeDRNbXBsV25GeU5YVnFWRGgzZFhaTldsSkhiVmh4ZUVKRkluMCIsIm5hbWVBbmRGYW1pbHlOYW1lQXRCaXJ0aCI6IkphbmUgRE9FIiwicGVyc29uYWxJZGVudGlmaWVyIjoiMDkwNDAwODA4NEgiLCJwbGFjZU9mQmlydGgiOiJMSUxMRSwgRlJBTkNFIn0sImV2aWRlbmNlIjpbeyJkb2N1bWVudFByZXNlbmNlIjpbIlBoeXNpY2FsIl0sImV2aWRlbmNlRG9jdW1lbnQiOlsiUGFzc3BvcnQiXSwic3ViamVjdFByZXNlbmNlIjoiUGh5c2ljYWwiLCJ0eXBlIjpbIkRvY3VtZW50VmVyaWZpY2F0aW9uIl0sInZlcmlmaWVyIjoiZGlkOmVic2k6MkE5Qlo5U1VlNkJhdGFjU3B2czFWNUNkakh2THBRN2JFc2kySmI2TGRIS25ReGFOIn1dLCJpZCI6InVybjp1dWlkOmZiMDliYTNkLWI3M2QtNDlhZC05ZDg4LTNkMDE4ODc4ZWE4MyIsImlzc3VlZCI6IjIwMjEtMDgtMzFUMDA6MDA6MDBaIiwiaXNzdWVyIjoiZGlkOmp3azpleUpyZEhraU9pSkZReUlzSW1OeWRpSTZJbEF0TWpVMklpd2lhMmxrSWpvaU0xbE9aRGxHYm5nNVNteDVVRlpaZDJkWFJrVXpOMFV6UjNkSk1HVkhiRU5MT0hkR2JGZDRSMlp3VFNJc0luZ2lPaUpHYjNaWk1qRk1RVUZQVkd4bkxXMHRUbVZMVjJoYVJVdzFZVVp5YmxJd2RXTktha1ExVkV0d1IzVm5JaXdpZVNJNklrTnlSa3BtUjFSa1VESTVTa3BqWTNCUldIVjVUVTh6YjJoMGVuSlVjVkI2UWxCQ1NWUlphakJ2WjBFaWZRIiwidmFsaWRGcm9tIjoiMjAyMS0wOC0zMVQwMDowMDowMFoiLCJpc3N1YW5jZURhdGUiOiIyMDI0LTA4LTIxVDA5OjExOjMzLjI0NjM4OTA0MVoifSwianRpIjoidXJuOnV1aWQ6ZmIwOWJhM2QtYjczZC00OWFkLTlkODgtM2QwMTg4NzhlYTgzIiwiaWF0IjoxNzI0MjMxNDkzLCJuYmYiOjE3MjQyMzE0OTN9.OsPTKQC6tvD6TtxeoCd8FtaIThYrYeYRjCHzDXkmgTUjRw78faT9R2bljT03ncrb0YstK0EmjM9D5lwqMZ9ZIA", "addedOn": "2024-08-21T09:11:33.284Z", "format": "jwt_vc" } ] ``` **Error:** This Presentation Definition matching helper only supports JSON based formats, and therefore does not support mDocs. You must know the credential id of the mDoc you want to share and provide it directly in the exchange flow in the next step. ### 3. Get User Consent Using the result of the previous request, we can now display the credential(s) to the user (owner of the wallet) for confirming the exchange with the verifier. ### 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/{walletId}/exchange/resolvePresentationRequest' \ -H 'accept: text/plain' \ -H 'Authorization: Bearer {token}' \ -H 'Content-Type: text/plain' \ -d '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' ``` **Path Parameters** - `walletId`: _String_ - The ID of the wallet. See [Accounts & Wallets](https://docs.walt.id/community-stack/wallet/accounts-wallets.md#retrieve-wallet-id) for how to retrieve it. **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}`. **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%22UniversityDegree%22%2C%22format%22%3A%7B%22jwt_vc_json%22%3A%7B%22alg%22%3A%5B%22ES256%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%22UniversityDegree%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: W3C/mDL Credentials** **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/{walletId}/exchange/usePresentationRequest' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {token}' \ -H 'Content-Type: application/json' \ -d '{ "did": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiQ2U5Mlg3QXU1TnBTVWFhZU9TYzNJR29CLUZpSTNkZ21XOV9scFNWckxkayIsIngiOiJHcUZmdjlSczhuNTk2N0IyUGx4MmplWnFyNXVqVDh3dXZNWlJHbVhxeEJFIn0", "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%22UniversityDegree%22%2C%22format%22%3A%7B%22jwt_vc_json%22%3A%7B%22alg%22%3A%5B%22ES256%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%22UniversityDegree%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" ] }' ``` **Path Parameters** - `walletId`: _String_ - The ID of the wallet. See [Accounts & Wallets](https://docs.walt.id/community-stack/wallet/accounts-wallets.md#retrieve-wallet-id) for how to retrieve it. **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}`. **Body** ```json { "did": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiQ2U5Mlg3QXU1TnBTVWFhZU9TYzNJR29CLUZpSTNkZ21XOV9scFNWckxkayIsIngiOiJHcUZmdjlSczhuNTk2N0IyUGx4MmplWnFyNXVqVDh3dXZNWlJHbVhxeEJFIn0", "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%22UniversityDegree%22%2C%22format%22%3A%7B%22jwt_vc_json%22%3A%7B%22alg%22%3A%5B%22ES256%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%22UniversityDegree%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** - `did`: _String_ - DID owning the credential. Used for signature generation. See [List DIDs](https://docs.walt.id/community-stack/wallet/did-management/list-dids.md) for how to retrieve the DIDs in your wallet. - `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 } ``` **Option: SD-JWTs** **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/{walletId}/exchange/usePresentationRequest' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {token}' \ -H 'Content-Type: application/json' \ -d '{ "did": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiQ2U5Mlg3QXU1TnBTVWFhZU9TYzNJR29CLUZpSTNkZ21XOV9scFNWckxkayIsIngiOiJHcUZmdjlSczhuNTk2N0IyUGx4MmplWnFyNXVqVDh3dXZNWlJHbVhxeEJFIn0", "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%22UniversityDegree%22%2C%22format%22%3A%7B%22jwt_vc_json%22%3A%7B%22alg%22%3A%5B%22ES256%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%22UniversityDegree%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" ], "disclosures": { "urn:uuid:fb09ba3d-b73d-49ad-9d88-3d018878ea83": [ "WyJiZ1I5OTdnRUVRNEU5bFpXNEhwVjRRPT0iLCJuYW1lIiwiSkZGIHggdmMtZWR1IFBsdWdGZXN0IDMgSW50ZXJvcGVyYWJpbGl0eSJd" ] }, }' ``` **Path Parameters** - `walletId`: _String_ - The ID of the wallet. See [Accounts & Wallets](https://docs.walt.id/community-stack/wallet/accounts-wallets.md#retrieve-wallet-id) for how to retrieve it. **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}`. **Body** ```json { "did": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiQ2U5Mlg3QXU1TnBTVWFhZU9TYzNJR29CLUZpSTNkZ21XOV9scFNWckxkayIsIngiOiJHcUZmdjlSczhuNTk2N0IyUGx4MmplWnFyNXVqVDh3dXZNWlJHbVhxeEJFIn0", "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%22UniversityDegree%22%2C%22format%22%3A%7B%22jwt_vc_json%22%3A%7B%22alg%22%3A%5B%22ES256%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%22UniversityDegree%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" ], "disclosures": { "urn:uuid:fb09ba3d-b73d-49ad-9d88-3d018878ea83": [ "WyJiZ1I5OTdnRUVRNEU5bFpXNEhwVjRRPT0iLCJuYW1lIiwiSkZGIHggdmMtZWR1IFBsdWdGZXN0IDMgSW50ZXJvcGVyYWJpbGl0eSJd" ] } } ``` **Body Parameters** - `did`: _String_ - DID owning the credential. Used for signature generation. See [List DIDs](https://docs.walt.id/community-stack/wallet/did-management/list-dids.md) for how to retrieve the DIDs in your wallet. - `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 } ``` -------------------------------------------------------------------------------- PAGE: How to Accept Digital Credentials (W3C, SD-JWT VC, mDL) via OID4VC using External Signatures with walt.id URL: https://docs.walt.id/community-stack/wallet/credential-exchange/guides/accept-digital-credentials-oid4vci-external-signatures RAW: https://docs.walt.id/community-stack/wallet/credential-exchange/guides/accept-digital-credentials-oid4vci-external-signatures.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # How to Accept Digital Credentials (W3C, SD-JWT VC mDL) via OID4VC using External Signatures with walt.id **TL;DR** **What you:** - Use the prepare/submit flow for accepting credentials with external signature management - Create proof of possession tokens (JWT or CWT) for credential offers - Handle external signature flows for W3C, SD-JWT VC, and mDL credential formats **Relevant concepts:** - [OpenID4VCI](https://docs.walt.id/concepts/data-exchange-protocols/openid4vci.md) – Protocol for verifiable credential issuance - [Verifiable Credentials (W3C)](https://docs.walt.id/concepts/digital-credentials/verifiable-credentials-w3c.md) – W3C standard for digital credentials - [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 - [SD-JWT VC](https://docs.walt.id/concepts/digital-credentials/sd-jwt-vc.md) – IETF SD-JWT VCs standard There are several methods by which a user can receive a credential into their wallet, and all are supported by the wallet API: 1. User may receive a direct link, which opens the wallet and prompts credential acceptance. 2. A website may display a QR code for the user to scan and accept the credential. 3. The user may manually input an offer URL string into a text field. Irrespective of the chosen method, these are just different ways of receiving, parsing and fulfilling a request URL. ## Credential Offer URL A credential offer URL is a standardized method, as per the OID4VCI specification, to communicate the issuance of credentials between issuer and wallet. This URL can take various forms, such as a QR code or a link, and generally begins with `openid-credential-offer://`. **Example Offer URL** ```json openid-credential-offer://issuer.portal.walt.id/?credential_offer= ``` Within the URL, a query parameter `credential_offer` provides the actual offer as a JSON object. **Example of a Credential Offer Object** ```json { "credential_issuer": "https://issuer.demo.walt.id", // This is the URL of the issuer of the credential(s) "credential_configuration_ids": [ // identifies specific credentials that the Credential Issuer is offering. "OpenBadgeCredential_jwt_vc_json" ], "grants": { // Specifies how the credentials can be obtained "authorization_code": { "issuer_state": "5380b758-9778-4a4c-908a-0ad921516e80" // Contains an issuer_state, a unique identifier for the grant }, "urn:ietf:params:oauth:grant-type:pre-authorized_code": { // Provides details for a pre-authorized code grant type, //including the actual pre-authorized_code and a flag user_pin_required indicating whether a PIN is required // for user authentication. "pre-authorized_code": "eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiI1MzgwYjc1OC05Nzc4LTRhNGMtOTA4YS0wYWQ5MjE1MTZlODAiLCJpc3MiOiJodHRwczovL2lzc3Vlci5wb3J0YWwud2FsdC5pZCIsImF1ZCI6IlRPS0VOIn0.DeYsZgnx4oBmIa25_cnBFazdrVkSGiDqFxSGL88lKlZkpgcJUcgFBMBc-n8XFdGbJ-gHFsGxTJ4rdamqTmIPCA" } } } ``` ## Accepting the Offer To receive offered credentials, the wallet must provide the issuer with a proof of possession. When using external signatures: 1. In the regular Wallet API setup, the API manages the keys and can generate the proof of possession in the background. 2. With external signatures, the proof of possession must instead be created in the system that manages the keys. 3. Because of this, the regular `useOfferRequest` endpoint is split into two steps when working with external signatures. The `/prepare` step: - Lets the Wallet API gather all required information. - Returns structured data that should either be signed or passed to the next step `/submit`. - Prepares everything needed to finalize the exchange between wallet and issuer so credentials can be received and stored in the specified wallet account. - Ensures the proof of possession, along with other required information, is sent to the issuer. **Note:** Please note that the external signatures are an optional feature of the Wallet API. It needs to be enabled via the feature.conf by providing `external-signature-endpoints` in the `enabledFeatures` array. Learn more [here](https://docs.walt.id/community-stack/wallet/configurations/config-files/features.md).
**Option: W3C** ### 1. Prepare **Option: CURL** **Endpoint:`/{wallet}/exchange/external_signatures/offer/prepare`** ```bash curl -X 'POST' \ 'http://0.0.0.0:7001/wallet-api/{wallet}/exchange/external_signatures/offer/prepare' \ -H 'accept: */*' \ -H 'Content-Type: application/json' \ -d '{ "did": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiQ3ZlUzFZdWdUNWV2VnoxOGswWHBjVGQtUVVsS1AxRzdCdTJJMkpoSjF2YyIsIngiOiJCeGN2d0lBVDhudzRtdjVtUDhSTzlrYmc3N25sbVZoOG5RcWZVUEtuRDVBIiwieSI6IkthU01seTBRQUhSQzZKMXFDRDloRkZnV0dXMXp1cExwU3ZtVGtHMDJmNHcifQ", "offerURL": "openid-credential-offer://localhost:22222/?credential_offer_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2FcredentialOffer%3Fid%3D8030eb87-fa89-4820-ad36-8e89cc9ccdfe" }' ``` **Path Parameters** - `wallet`: - You can receive the needed wallet id parameter via the `/wallet-api/wallet/accounts/wallets` endpoint. **Body Parameters** - `did` - DID to which the credential should be bound to. - `offerURL` - the credential offer from the issuer. **Example Response** ```json { "did": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiQ3ZlUzFZdWdUNWV2VnoxOGswWHBjVGQtUVVsS1AxRzdCdTJJMkpoSjF2YyIsIngiOiJCeGN2d0lBVDhudzRtdjVtUDhSTzlrYmc3N25sbVZoOG5RcWZVUEtuRDVBIiwieSI6IkthU01seTBRQUhSQzZKMXFDRDloRkZnV0dXMXp1cExwU3ZtVGtHMDJmNHcifQ", "offerURL": "openid-credential-offer://localhost:22222/?credential_offer_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2FcredentialOffer%3Fid%3D8030eb87-fa89-4820-ad36-8e89cc9ccdfe", "accessToken": "eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiI4MDMwZWI4Ny1mYTg5LTQ4MjAtYWQzNi04ZTg5Y2M5Y2NkZmUiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjIyMjIyIiwiYXVkIjoiQUNDRVNTIn0.GpB9QZ-ZgCSNxBzlnkf_RBKmfMI15wH5rR2gW4EfLa640gOo9taaMLgYKax8f8kOgd5byvEf6hmGhWyGt7xTCQ", "offeredCredentialsProofRequests": [ { "offeredCredential": { "format": "jwt_vc_json", "credential_definition": { "type": [ "VerifiableCredential", "OpenBadgeCredential" ] }, "cryptographic_binding_methods_supported": [ "did" ], "customParameters": {} }, "proofOfPossessionParameters": { "proofType": "jwt", "header": { "typ": "openid4vci-proof+jwt", "kid": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiQ3ZlUzFZdWdUNWV2VnoxOGswWHBjVGQtUVVsS1AxRzdCdTJJMkpoSjF2YyIsIngiOiJCeGN2d0lBVDhudzRtdjVtUDhSTzlrYmc3N25sbVZoOG5RcWZVUEtuRDVBIiwieSI6IkthU01seTBRQUhSQzZKMXFDRDloRkZnV0dXMXp1cExwU3ZtVGtHMDJmNHcifQ#0" }, "payload": { "aud": "http://localhost:22222", "iat": 1727417971, "nonce": "4d513194-fa61-4099-9bbc-3fb0808ca4f1" } } } ], "credentialIssuer": "http://localhost:22222" } ``` The response provides all the information required for the next `/submit` step: - **`offeredCredentialsProofRequests`** – list of credentials offered by the issuer. - For each offered credential, the `offeredCredential` block describes the credential itself. - Each entry also includes `proofOfPossessionParameters`, which define the proof to generate and contain the following attributes: - `proofType` - the type of proof required. In our case `jwt`. - `header` - header for to be signed jwt. - `payload` - payload for to be signed jwt. ### 2. Creating Proof of Possession To create the proof of possession: 1. Create a `jwt` for each offered credential using the `header` and `payload` from the `proofOfPossessionParameters` in the response. 2. In the example above, only one credential (`OpenBadgeCredential`) is offered, so you only need a single signed `jwt`. ### 3. Submit With the `/submit` endpoint, you: - Send most of the information returned by the `/prepare` endpoint. - Include the proof-of-possession `jwt` created in step 2. #### Preparing the body of the request 1. We copy over the value `did`, `offerURL`, `accessToken` and `credentialIssuer` from the response to `/prepare`. ```json [request body] { "did": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiQ3ZlUzFZdWdUNWV2VnoxOGswWHBjVGQtUVVsS1AxRzdCdTJJMkpoSjF2YyIsIngiOiJCeGN2d0lBVDhudzRtdjVtUDhSTzlrYmc3N25sbVZoOG5RcWZVUEtuRDVBIiwieSI6IkthU01seTBRQUhSQzZKMXFDRDloRkZnV0dXMXp1cExwU3ZtVGtHMDJmNHcifQ", "offerURL": "openid-credential-offer://localhost:22222/?credential_offer_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2FcredentialOffer%3Fid%3Db953cc8a-4fb3-449b-9ab0-c9c281152e1b", "accessToken": "eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiJiOTUzY2M4YS00ZmIzLTQ0OWItOWFiMC1jOWMyODExNTJlMWIiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjIyMjIyIiwiYXVkIjoiQUNDRVNTIn0.ygzp7_r-0SVcSzHTdLSTa_kt3A3iNM98wXYlJagoiPy9wzDDvE654rp_K5-uSZWUBHFrAOOGZ-28b9cmv1AFBg", "credentialIssuer": "http://localhost:22222" } ``` 2. We add an array called `offeredCredentialProofsOfPossession` including all offered credentials with the related proof type and the signed `jwt` from [step 2 "Creating Proof of Possession".](https://docs.walt.id/community-stack/wallet/credential-exchange/guides/accept-digital-credentials-oid4vci-external-signatures.md#_2-creating-proof-of-possession) ```json [request body] { ... "offeredCredentialProofsOfPossession": [ { "offeredCredential": { "format": "jwt_vc_json", "credential_definition": { "type": [ "VerifiableCredential", "OpenBadgeCredential" ] }, "cryptographic_binding_methods_supported": [ "did" ] }, "proofType": "jwt", "signedProofOfPossession": "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pYTJrMFduaGlXRVJqTFVjM1QxRjVNa3gwY1d4Wk5IRkxVbXQ2UkdKTVF6TmhjWE5rVTFSNlRFcGlUU0lzSW5naU9pSlhiMmM1YWtaVllqbElkMWMzYWpsYVF6VjZZakZVZHkxWE5HMUpaVzVETUZkZlZXNWtjbXh5YVZoVklpd2llU0k2SWtoS2FWUkRjR0ZhVG5oM2JHUkpYMWxoVDFkVlVsTjZTVTVQT0daak0zQnVibEZXVWpoclREVjRNVGdpZlEjMCIsInR5cCI6Im9wZW5pZDR2Y2ktcHJvb2Yrand0IiwiYWxnIjoiRVMyNTYifQ.eyJpc3MiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pYTJrMFduaGlXRVJqTFVjM1QxRjVNa3gwY1d4Wk5IRkxVbXQ2UkdKTVF6TmhjWE5rVTFSNlRFcGlUU0lzSW5naU9pSlhiMmM1YWtaVllqbElkMWMzYWpsYVF6VjZZakZVZHkxWE5HMUpaVzVETUZkZlZXNWtjbXh5YVZoVklpd2llU0k2SWtoS2FWUkRjR0ZhVG5oM2JHUkpYMWxoVDFkVlVsTjZTVTVQT0daak0zQnVibEZXVWpoclREVjRNVGdpZlEiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjIyMjIyIiwiaWF0IjoxNzI3MDk0NjY2LCJub25jZSI6ImIzNTk3MmUwLTAzM2EtNDE5My1iOTE3LTBlZmZmMTljMWZkMCJ9.gveI7M2CNeCX8qdmFyToatLLigmyMNn-rgxjDHBFGz-BRr0PGi0j1KDOeDY--K8heXVD0E3LBL2cI1ap5Sl3lw" } ] ... } ``` **Object in offeredCredentialProofsOfPossession Array** ```json { "offeredCredential": { "format": "jwt_vc_json", "credential_definition": { "type": [ "VerifiableCredential", "OpenBadgeCredential" ] }, "cryptographic_binding_methods_supported": [ "did" ] }, "proofType": "jwt", "signedProofOfPossession": "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pYTJrMFduaGlXRVJqTFVjM1QxRjVNa3gwY1d4Wk5IRkxVbXQ2UkdKTVF6TmhjWE5rVTFSNlRFcGlUU0lzSW5naU9pSlhiMmM1YWtaVllqbElkMWMzYWpsYVF6VjZZakZVZHkxWE5HMUpaVzVETUZkZlZXNWtjbXh5YVZoVklpd2llU0k2SWtoS2FWUkRjR0ZhVG5oM2JHUkpYMWxoVDFkVlVsTjZTVTVQT0daak0zQnVibEZXVWpoclREVjRNVGdpZlEjMCIsInR5cCI6Im9wZW5pZDR2Y2ktcHJvb2Yrand0IiwiYWxnIjoiRVMyNTYifQ.eyJpc3MiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pYTJrMFduaGlXRVJqTFVjM1QxRjVNa3gwY1d4Wk5IRkxVbXQ2UkdKTVF6TmhjWE5rVTFSNlRFcGlUU0lzSW5naU9pSlhiMmM1YWtaVllqbElkMWMzYWpsYVF6VjZZakZVZHkxWE5HMUpaVzVETUZkZlZXNWtjbXh5YVZoVklpd2llU0k2SWtoS2FWUkRjR0ZhVG5oM2JHUkpYMWxoVDFkVlVsTjZTVTVQT0daak0zQnVibEZXVWpoclREVjRNVGdpZlEiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjIyMjIyIiwiaWF0IjoxNzI3MDk0NjY2LCJub25jZSI6ImIzNTk3MmUwLTAzM2EtNDE5My1iOTE3LTBlZmZmMTljMWZkMCJ9.gveI7M2CNeCX8qdmFyToatLLigmyMNn-rgxjDHBFGz-BRr0PGi0j1KDOeDY--K8heXVD0E3LBL2cI1ap5Sl3lw" } ``` - **offeredCredential** - Holds the same value as the object describing one offered credential in the `offeredCredentialsProofRequests` array of the `/prepare` response. - **cryptographic_binding_methods_supported** - Mirrors the corresponding value from the `offeredCredentialsProofRequests` entry in the `/prepare` response. - **proofType** - Will be `jwt`. - **signedProofOfPossession** - The signed proof of possession (jwt) from step 2 for the related credential. For the `offeredCredentialProofsOfPossession` array: - Include one object for each credential offered by the issuer. - Ensure the array has the same length as the `offeredCredentialsProofRequests` array returned by `/prepare`. **Final body** ```json [request body] { "did": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiQ3ZlUzFZdWdUNWV2VnoxOGswWHBjVGQtUVVsS1AxRzdCdTJJMkpoSjF2YyIsIngiOiJCeGN2d0lBVDhudzRtdjVtUDhSTzlrYmc3N25sbVZoOG5RcWZVUEtuRDVBIiwieSI6IkthU01seTBRQUhSQzZKMXFDRDloRkZnV0dXMXp1cExwU3ZtVGtHMDJmNHcifQ", "offerURL": "openid-credential-offer://localhost:22222/?credential_offer_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2FcredentialOffer%3Fid%3D8030eb87-fa89-4820-ad36-8e89cc9ccdfe", "accessToken": "eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiI4MDMwZWI4Ny1mYTg5LTQ4MjAtYWQzNi04ZTg5Y2M5Y2NkZmUiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjIyMjIyIiwiYXVkIjoiQUNDRVNTIn0.GpB9QZ-ZgCSNxBzlnkf_RBKmfMI15wH5rR2gW4EfLa640gOo9taaMLgYKax8f8kOgd5byvEf6hmGhWyGt7xTCQ", "offeredCredentialProofsOfPossession": [ { "offeredCredential": { "format": "jwt_vc_json", "credential_definition": { "type": [ "VerifiableCredential", "OpenBadgeCredential" ] }, "cryptographic_binding_methods_supported": [ "did" ] }, "proofType": "jwt", "signedProofOfPossession": "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pUTNabFV6RlpkV2RVTldWMlZub3hPR3N3V0hCalZHUXRVVlZzUzFBeFJ6ZENkVEpKTWtwb1NqRjJZeUlzSW5naU9pSkNlR04yZDBsQlZEaHVkelJ0ZGpWdFVEaFNUemxyWW1jM04yNXNiVlpvT0c1UmNXWlZVRXR1UkRWQklpd2llU0k2SWt0aFUwMXNlVEJSUVVoU1F6WktNWEZEUkRsb1JrWm5WMGRYTVhwMWNFeHdVM1p0Vkd0SE1ESm1OSGNpZlEjMCIsInR5cCI6Im9wZW5pZDR2Y2ktcHJvb2Yrand0IiwiYWxnIjoiRVMyNTYifQ.eyJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjIyMjIyIiwiaWF0IjoxNzI3NDE3OTcxLCJub25jZSI6IjRkNTEzMTk0LWZhNjEtNDA5OS05YmJjLTNmYjA4MDhjYTRmMSJ9.-NOvj6bfl0l8_BSxPR3UAyO-KkZ4zdfPZ401GNZl8DWg4MCtZN3lWIdnPBsqTY1aAiYfN3symtMzhdtCqpivAA" } ], "credentialIssuer": "http://localhost:22222" } ``` #### Making the Request **Option: CURL** **Endpoint:** `wallet-api/{wallet}/exchange/external_signatures/offer/submit` ```bash curl -X 'POST' \ 'http://0.0.0.0:7001/wallet-api/{wallet}/exchange/external_signatures/offer/submit' \ -H 'accept: */*' \ -H 'Content-Type: application/json' \ -d '{ "did": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiQ3ZlUzFZdWdUNWV2VnoxOGswWHBjVGQtUVVsS1AxRzdCdTJJMkpoSjF2YyIsIngiOiJCeGN2d0lBVDhudzRtdjVtUDhSTzlrYmc3N25sbVZoOG5RcWZVUEtuRDVBIiwieSI6IkthU01seTBRQUhSQzZKMXFDRDloRkZnV0dXMXp1cExwU3ZtVGtHMDJmNHcifQ", "offerURL": "openid-credential-offer://localhost:22222/?credential_offer_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2FcredentialOffer%3Fid%3D8030eb87-fa89-4820-ad36-8e89cc9ccdfe", "accessToken": "eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiI4MDMwZWI4Ny1mYTg5LTQ4MjAtYWQzNi04ZTg5Y2M5Y2NkZmUiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjIyMjIyIiwiYXVkIjoiQUNDRVNTIn0.GpB9QZ-ZgCSNxBzlnkf_RBKmfMI15wH5rR2gW4EfLa640gOo9taaMLgYKax8f8kOgd5byvEf6hmGhWyGt7xTCQ", "offeredCredentialProofsOfPossession": [ { "offeredCredential": { "format": "jwt_vc_json", "credential_definition": { "type": [ "VerifiableCredential", "OpenBadgeCredential" ] }, "cryptographic_binding_methods_supported": [ "did" ] }, "proofType": "jwt", "signedProofOfPossession": "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pUTNabFV6RlpkV2RVTldWMlZub3hPR3N3V0hCalZHUXRVVlZzUzFBeFJ6ZENkVEpKTWtwb1NqRjJZeUlzSW5naU9pSkNlR04yZDBsQlZEaHVkelJ0ZGpWdFVEaFNUemxyWW1jM04yNXNiVlpvT0c1UmNXWlZVRXR1UkRWQklpd2llU0k2SWt0aFUwMXNlVEJSUVVoU1F6WktNWEZEUkRsb1JrWm5WMGRYTVhwMWNFeHdVM1p0Vkd0SE1ESm1OSGNpZlEjMCIsInR5cCI6Im9wZW5pZDR2Y2ktcHJvb2Yrand0IiwiYWxnIjoiRVMyNTYifQ.eyJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjIyMjIyIiwiaWF0IjoxNzI3NDE3OTcxLCJub25jZSI6IjRkNTEzMTk0LWZhNjEtNDA5OS05YmJjLTNmYjA4MDhjYTRmMSJ9.-NOvj6bfl0l8_BSxPR3UAyO-KkZ4zdfPZ401GNZl8DWg4MCtZN3lWIdnPBsqTY1aAiYfN3symtMzhdtCqpivAA" } ], "credentialIssuer": "http://localhost:22222" }' ``` **Path Parameters** - `wallet`: - You can receive the needed wallet id parameter via the `/wallet-api/wallet/accounts/wallets` endpoint. **Body Parameters** - `did` - value copied from response to `/prepare` - `offerURL` - value copied from response to `/prepare` - `accessToken` - value copied from response to `/prepare` - `credentialIssuer` - value copied from response to `/prepare` - `offeredCredentialProofsOfPossession` - array of offered credentials with proofs.
Expand To Learn More **Object in offeredCredentialProofsOfPossession Array** ```json { "offeredCredential": { "format": "jwt_vc_json", "credential_definition": { "type": [ "VerifiableCredential", "OpenBadgeCredential" ] }, "cryptographic_binding_methods_supported": [ "did" ] }, "proofType": "jwt", "signedProofOfPossession": "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pYTJrMFduaGlXRVJqTFVjM1QxRjVNa3gwY1d4Wk5IRkxVbXQ2UkdKTVF6TmhjWE5rVTFSNlRFcGlUU0lzSW5naU9pSlhiMmM1YWtaVllqbElkMWMzYWpsYVF6VjZZakZVZHkxWE5HMUpaVzVETUZkZlZXNWtjbXh5YVZoVklpd2llU0k2SWtoS2FWUkRjR0ZhVG5oM2JHUkpYMWxoVDFkVlVsTjZTVTVQT0daak0zQnVibEZXVWpoclREVjRNVGdpZlEjMCIsInR5cCI6Im9wZW5pZDR2Y2ktcHJvb2Yrand0IiwiYWxnIjoiRVMyNTYifQ.eyJpc3MiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pYTJrMFduaGlXRVJqTFVjM1QxRjVNa3gwY1d4Wk5IRkxVbXQ2UkdKTVF6TmhjWE5rVTFSNlRFcGlUU0lzSW5naU9pSlhiMmM1YWtaVllqbElkMWMzYWpsYVF6VjZZakZVZHkxWE5HMUpaVzVETUZkZlZXNWtjbXh5YVZoVklpd2llU0k2SWtoS2FWUkRjR0ZhVG5oM2JHUkpYMWxoVDFkVlVsTjZTVTVQT0daak0zQnVibEZXVWpoclREVjRNVGdpZlEiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjIyMjIyIiwiaWF0IjoxNzI3MDk0NjY2LCJub25jZSI6ImIzNTk3MmUwLTAzM2EtNDE5My1iOTE3LTBlZmZmMTljMWZkMCJ9.gveI7M2CNeCX8qdmFyToatLLigmyMNn-rgxjDHBFGz-BRr0PGi0j1KDOeDY--K8heXVD0E3LBL2cI1ap5Sl3lw" } ``` - **offeredCredential** - Holds the same value as the object describing one offered credential in the `offeredCredentialsProofRequests` array of response provided by `/prepare`. - **cryptographic_binding_methods_supported** - Holds the same value as the object describing one offered credential in the `offeredCredentialsProofRequests` array of response provided by `/prepare`. - **proofType** - Will be `jwt` - **signedProofOfPossession** - the signed proof of possession (jwt) from step 2 for the related credential.

**Example Response** ```json [ { "wallet": "6e802709-7d60-4dd2-a599-6c775c8a66eb", "id": "urn:uuid:90271bc9-8879-4565-830a-6d01596dbb9c", "document": "eyJraWQiOiJkaWQ6a2V5Ono2TWtqb1JocTFqU05KZExpcnVTWHJGRnhhZ3FyenRaYVhIcUhHVVRLSmJjTnl3cCIsInR5cCI6IkpXVCIsImFsZyI6IkVkRFNBIn0.eyJpc3MiOiJkaWQ6a2V5Ono2TWtqb1JocTFqU05KZExpcnVTWHJGRnhhZ3FyenRaYVhIcUhHVVRLSmJjTnl3cCIsInN1YiI6ImRpZDpqd2s6ZXlKcmRIa2lPaUpQUzFBaUxDSmpjbllpT2lKRlpESTFOVEU1SWl3aWEybGtJam9pTVMxSk9FdHBPSGN4TkRGRGNpMVRaRUpHYUMxWVoyaHlUMjlHVVV0cFZGQTVXSEZtV1VVeGNXYzRSU0lzSW5naU9pSlJXVFZ3WlZKbmVucG9kRTUwVTJKNFQwZEtOMGRRVUhWa1NGVkxaRmwzYzFOTU4xb3lUVWxJYjJ4SkluMCIsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIiwiaHR0cHM6Ly9wdXJsLmltc2dsb2JhbC5vcmcvc3BlYy9vYi92M3AwL2NvbnRleHQuanNvbiJdLCJpZCI6InVybjp1dWlkOjkwMjcxYmM5LTg4NzktNDU2NS04MzBhLTZkMDE1OTZkYmI5YyIsInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJPcGVuQmFkZ2VDcmVkZW50aWFsIl0sIm5hbWUiOiJKRkYgeCB2Yy1lZHUgUGx1Z0Zlc3QgMyBJbnRlcm9wZXJhYmlsaXR5IiwiaXNzdWVyIjp7InR5cGUiOlsiUHJvZmlsZSJdLCJuYW1lIjoiSm9icyBmb3IgdGhlIEZ1dHVyZSAoSkZGKSIsInVybCI6Imh0dHBzOi8vd3d3LmpmZi5vcmcvIiwiaW1hZ2UiOiJodHRwczovL3czYy1jY2cuZ2l0aHViLmlvL3ZjLWVkL3BsdWdmZXN0LTEtMjAyMi9pbWFnZXMvSkZGX0xvZ29Mb2NrdXAucG5nIiwiaWQiOiJkaWQ6a2V5Ono2TWtqb1JocTFqU05KZExpcnVTWHJGRnhhZ3FyenRaYVhIcUhHVVRLSmJjTnl3cCJ9LCJjcmVkZW50aWFsU3ViamVjdCI6eyJ0eXBlIjpbIkFjaGlldmVtZW50U3ViamVjdCJdLCJhY2hpZXZlbWVudCI6eyJpZCI6InVybjp1dWlkOmFjMjU0YmQ1LThmYWQtNGJiMS05ZDI5LWVmZDkzODUzNjkyNiIsInR5cGUiOlsiQWNoaWV2ZW1lbnQiXSwibmFtZSI6IkpGRiB4IHZjLWVkdSBQbHVnRmVzdCAzIEludGVyb3BlcmFiaWxpdHkiLCJkZXNjcmlwdGlvbiI6IlRoaXMgd2FsbGV0IHN1cHBvcnRzIHRoZSB1c2Ugb2YgVzNDIFZlcmlmaWFibGUgQ3JlZGVudGlhbHMgYW5kIGhhcyBkZW1vbnN0cmF0ZWQgaW50ZXJvcGVyYWJpbGl0eSBkdXJpbmcgdGhlIHByZXNlbnRhdGlvbiByZXF1ZXN0IHdvcmtmbG93IGR1cmluZyBKRkYgeCBWQy1FRFUgUGx1Z0Zlc3QgMy4iLCJjcml0ZXJpYSI6eyJ0eXBlIjoiQ3JpdGVyaWEiLCJuYXJyYXRpdmUiOiJXYWxsZXQgc29sdXRpb25zIHByb3ZpZGVycyBlYXJuZWQgdGhpcyBiYWRnZSBieSBkZW1vbnN0cmF0aW5nIGludGVyb3BlcmFiaWxpdHkgZHVyaW5nIHRoZSBwcmVzZW50YXRpb24gcmVxdWVzdCB3b3JrZmxvdy4gVGhpcyBpbmNsdWRlcyBzdWNjZXNzZnVsbHkgcmVjZWl2aW5nIGEgcHJlc2VudGF0aW9uIHJlcXVlc3QsIGFsbG93aW5nIHRoZSBob2xkZXIgdG8gc2VsZWN0IGF0IGxlYXN0IHR3byB0eXBlcyBvZiB2ZXJpZmlhYmxlIGNyZWRlbnRpYWxzIHRvIGNyZWF0ZSBhIHZlcmlmaWFibGUgcHJlc2VudGF0aW9uLCByZXR1cm5pbmcgdGhlIHByZXNlbnRhdGlvbiB0byB0aGUgcmVxdWVzdG9yLCBhbmQgcGFzc2luZyB2ZXJpZmljYXRpb24gb2YgdGhlIHByZXNlbnRhdGlvbiBhbmQgdGhlIGluY2x1ZGVkIGNyZWRlbnRpYWxzLiJ9LCJpbWFnZSI6eyJpZCI6Imh0dHBzOi8vdzNjLWNjZy5naXRodWIuaW8vdmMtZWQvcGx1Z2Zlc3QtMy0yMDIzL2ltYWdlcy9KRkYtVkMtRURVLVBMVUdGRVNUMy1iYWRnZS1pbWFnZS5wbmciLCJ0eXBlIjoiSW1hZ2UifX0sImlkIjoiZGlkOmp3azpleUpyZEhraU9pSlBTMUFpTENKamNuWWlPaUpGWkRJMU5URTVJaXdpYTJsa0lqb2lNUzFKT0V0cE9IY3hOREZEY2kxVFpFSkdhQzFZWjJoeVQyOUdVVXRwVkZBNVdIRm1XVVV4Y1djNFJTSXNJbmdpT2lKUldUVndaVkpuZW5wb2RFNTBVMko0VDBkS04wZFFVSFZrU0ZWTFpGbDNjMU5NTjFveVRVbEliMnhKSW4wIn0sImlzc3VhbmNlRGF0ZSI6IjIwMjQtMDktMjRUMDY6NTg6NTUuOTAzOTQ2Nzc5WiIsImV4cGlyYXRpb25EYXRlIjoiMjAyNS0wOS0yNFQwNjo1ODo1NS45MDQwOTgxNzBaIn0sImp0aSI6InVybjp1dWlkOjkwMjcxYmM5LTg4NzktNDU2NS04MzBhLTZkMDE1OTZkYmI5YyIsImV4cCI6MTc1ODY5NzEzNSwiaWF0IjoxNzI3MTYxMTM1LCJuYmYiOjE3MjcxNjExMzV9.XmN-rsNKlo-umayMOcrclGWcXYCEZF0warrJ7eV3juuTxHJyoxdoQ9YrWGv8rs6Om6jegWbUulbpu5amjo9ECw", "disclosures": "", "addedOn": "2024-09-24T06:58:55.909707051Z", "format": "jwt_vc_json" } ] ``` The response indicates that the credential was stored successfully in the wallet. **Option: SD-JWT VC (IETF)** ### 1. Prepare **Option: CURL** **Endpoint:`/{wallet}/exchange/external_signatures/offer/prepare`** ```bash curl -X 'POST' \ 'http://0.0.0.0:7001/wallet-api/{wallet}/exchange/external_signatures/offer/prepare' \ -H 'accept: */*' \ -H 'Content-Type: application/json' \ -d '{ "did": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiQ3ZlUzFZdWdUNWV2VnoxOGswWHBjVGQtUVVsS1AxRzdCdTJJMkpoSjF2YyIsIngiOiJCeGN2d0lBVDhudzRtdjVtUDhSTzlrYmc3N25sbVZoOG5RcWZVUEtuRDVBIiwieSI6IkthU01seTBRQUhSQzZKMXFDRDloRkZnV0dXMXp1cExwU3ZtVGtHMDJmNHcifQ", "offerURL": "openid-credential-offer://localhost:22222/?credential_offer_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2FcredentialOffer%3Fid%3D8030eb87-fa89-4820-ad36-8e89cc9ccdfe" }' ``` **Path Parameters** - `wallet`: - You can receive the needed wallet id parameter via the `/wallet-api/wallet/accounts/wallets` endpoint. **Body Parameters** - `did` - DID to which the credential should be bound to. - `offerURL` - the credential offer from the issuer. **Example Response** ```json { "did": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiQ3ZlUzFZdWdUNWV2VnoxOGswWHBjVGQtUVVsS1AxRzdCdTJJMkpoSjF2YyIsIngiOiJCeGN2d0lBVDhudzRtdjVtUDhSTzlrYmc3N25sbVZoOG5RcWZVUEtuRDVBIiwieSI6IkthU01seTBRQUhSQzZKMXFDRDloRkZnV0dXMXp1cExwU3ZtVGtHMDJmNHcifQ", "offerURL": "openid-credential-offer://localhost:22222/?credential_offer_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2FcredentialOffer%3Fid%3D8030eb87-fa89-4820-ad36-8e89cc9ccdfe", "accessToken": "eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiI4MDMwZWI4Ny1mYTg5LTQ4MjAtYWQzNi04ZTg5Y2M5Y2NkZmUiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjIyMjIyIiwiYXVkIjoiQUNDRVNTIn0.GpB9QZ-ZgCSNxBzlnkf_RBKmfMI15wH5rR2gW4EfLa640gOo9taaMLgYKax8f8kOgd5byvEf6hmGhWyGt7xTCQ", "offeredCredentialsProofRequests": [ { "offeredCredential": { "format": "vc+sd-jwt", "vct": "http://localhost:22222/identity_credential", "cryptographic_binding_methods_supported": [ "jwk" ], "customParameters": {} }, "proofOfPossessionParameters": { "proofType": "jwt", "header": { "typ": "openid4vci-proof+jwt", "jwk": { "kty": "EC", "crv": "P-256", "kid": "YCm5zh_RuMxsJI_mzmrm7wft0fxKpRuA0Dg-EVXBNxk", "x": "uqUtdBaVFrRpwXY--D1TQYj0EXrGWJVih5bvHgb-UJ8", "y": "th6kYpPmlMIGDIGz1QoF8zKjyuMkqqH1Phx_DDTsDbg" } }, "payload": { "aud": "http://localhost:22222", "iat": 1727421749, "nonce": "84dba81a-c0a2-41ba-abe7-1f75514ae52d" } } } ], "credentialIssuer": "http://localhost:22222" } ``` The response provides all the information required for the next `/submit` step: - **`offeredCredentialsProofRequests`** – list of credentials offered by the issuer. - For each offered credential, the `offeredCredential` block describes the credential itself. - Each entry also includes `proofOfPossessionParameters`, which define the proof to generate and contain the following attributes: - `proofType` - the type of proof required. In our case `jwt`. - `header` - header for to be signed jwt. - `payload` - payload for to be signed jwt. ### 2. Creating Proof of Possession To create the proof of possession: 1. Create a `jwt` for each offered credential using the `header` and `payload` from the `proofOfPossessionParameters` in the response. 2. In the example above, only one credential (`identity_credential`) is offered, so you only need a single signed `jwt`. ### 3. Submit With the `/submit` endpoint, you: - Send most of the information returned by the `/prepare` endpoint. - Include the proof-of-possession `jwt` created in step 2. #### Preparing the body of the request 1. We copy over the value `did`, `offerURL`, `accessToken` and `credentialIssuer` from the response to `/prepare`. ```json [request body] { "did": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiQ3ZlUzFZdWdUNWV2VnoxOGswWHBjVGQtUVVsS1AxRzdCdTJJMkpoSjF2YyIsIngiOiJCeGN2d0lBVDhudzRtdjVtUDhSTzlrYmc3N25sbVZoOG5RcWZVUEtuRDVBIiwieSI6IkthU01seTBRQUhSQzZKMXFDRDloRkZnV0dXMXp1cExwU3ZtVGtHMDJmNHcifQ", "offerURL": "openid-credential-offer://localhost:22222/?credential_offer_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2FcredentialOffer%3Fid%3D8030eb87-fa89-4820-ad36-8e89cc9ccdfe", "accessToken": "eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiI4MDMwZWI4Ny1mYTg5LTQ4MjAtYWQzNi04ZTg5Y2M5Y2NkZmUiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjIyMjIyIiwiYXVkIjoiQUNDRVNTIn0.GpB9QZ-ZgCSNxBzlnkf_RBKmfMI15wH5rR2gW4EfLa640gOo9taaMLgYKax8f8kOgd5byvEf6hmGhWyGt7xTCQ", "credentialIssuer": "http://localhost:22222" } ``` 2. We add an array called `offeredCredentialProofsOfPossession` including all offered credentials with the related proof type and the signed `jwt` from [step 2 "Creating Proof of Possession".](https://docs.walt.id/community-stack/wallet/credential-exchange/guides/accept-digital-credentials-oid4vci-external-signatures.md#_2-creating-proof-of-possession) ```json [request body] { ... "offeredCredentialProofsOfPossession": [ { "offeredCredential": { "format": "vc+sd-jwt", "vct": "http://localhost:22222/identity_credential", "cryptographic_binding_methods_supported": [ "jwk" ], "customParameters": {} }, "proofType": "jwt", "signedProofOfPossession": "eyJ0eXAiOiJvcGVuaWQ0dmNpLXByb29mK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJraWQiOiJZQ201emhfUnVNeHNKSV9tem1ybTd3ZnQwZnhLcFJ1QTBEZy1FVlhCTnhrIiwieCI6InVxVXRkQmFWRnJScHdYWS0tRDFUUVlqMEVYckdXSlZpaDVidkhnYi1VSjgiLCJ5IjoidGg2a1lwUG1sTUlHRElHejFRb0Y4ektqeXVNa3FxSDFQaHhfRERUc0RiZyJ9fQ.eyJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjIyMjIyIiwiaWF0IjoxNzI3NDIxNzQ5LCJub25jZSI6Ijg0ZGJhODFhLWMwYTItNDFiYS1hYmU3LTFmNzU1MTRhZTUyZCJ9.y2X40DKyUXCfHb1gGzdGaz3__xfAk1Tss0cj3M4oxooI0edHUhuKUknS7yIe7bRQErqc8s0neIM53w0XQSpFbA" } ] ... } ``` **Object in offeredCredentialProofsOfPossession Array** ```json { "offeredCredential": { "format": "vc+sd-jwt", "vct": "http://localhost:22222/identity_credential", "cryptographic_binding_methods_supported": [ "jwk" ], "customParameters": {} }, "proofType": "jwt", "signedProofOfPossession": "eyJ0eXAiOiJvcGVuaWQ0dmNpLXByb29mK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJraWQiOiJZQ201emhfUnVNeHNKSV9tem1ybTd3ZnQwZnhLcFJ1QTBEZy1FVlhCTnhrIiwieCI6InVxVXRkQmFWRnJScHdYWS0tRDFUUVlqMEVYckdXSlZpaDVidkhnYi1VSjgiLCJ5IjoidGg2a1lwUG1sTUlHRElHejFRb0Y4ektqeXVNa3FxSDFQaHhfRERUc0RiZyJ9fQ.eyJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjIyMjIyIiwiaWF0IjoxNzI3NDIxNzQ5LCJub25jZSI6Ijg0ZGJhODFhLWMwYTItNDFiYS1hYmU3LTFmNzU1MTRhZTUyZCJ9.y2X40DKyUXCfHb1gGzdGaz3__xfAk1Tss0cj3M4oxooI0edHUhuKUknS7yIe7bRQErqc8s0neIM53w0XQSpFbA" } ``` - **offeredCredential** - Holds the same value as the object describing one offered credential in the `offeredCredentialsProofRequests` array of the `/prepare` response. - **cryptographic_binding_methods_supported** - Mirrors the corresponding value from the `offeredCredentialsProofRequests` entry in the `/prepare` response. - **proofType** - Will be `jwt`. - **signedProofOfPossession** - The signed proof of possession (jwt) from step 2 for the related credential. For the `offeredCredentialProofsOfPossession` array: - Include one object for each credential offered by the issuer. - Ensure the array has the same length as the `offeredCredentialsProofRequests` array returned by `/prepare`. **Final body** ```json [request body] { "did": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiQ3ZlUzFZdWdUNWV2VnoxOGswWHBjVGQtUVVsS1AxRzdCdTJJMkpoSjF2YyIsIngiOiJCeGN2d0lBVDhudzRtdjVtUDhSTzlrYmc3N25sbVZoOG5RcWZVUEtuRDVBIiwieSI6IkthU01seTBRQUhSQzZKMXFDRDloRkZnV0dXMXp1cExwU3ZtVGtHMDJmNHcifQ", "offerURL": "openid-credential-offer://localhost:22222/?credential_offer_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2FcredentialOffer%3Fid%3D8030eb87-fa89-4820-ad36-8e89cc9ccdfe", "accessToken": "eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiI4MDMwZWI4Ny1mYTg5LTQ4MjAtYWQzNi04ZTg5Y2M5Y2NkZmUiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjIyMjIyIiwiYXVkIjoiQUNDRVNTIn0.GpB9QZ-ZgCSNxBzlnkf_RBKmfMI15wH5rR2gW4EfLa640gOo9taaMLgYKax8f8kOgd5byvEf6hmGhWyGt7xTCQ", "offeredCredentialProofsOfPossession": [ { "offeredCredential": { "format": "vc+sd-jwt", "vct": "http://localhost:22222/identity_credential", "cryptographic_binding_methods_supported": [ "jwk" ], "customParameters": {} }, "proofType": "jwt", "signedProofOfPossession": "eyJ0eXAiOiJvcGVuaWQ0dmNpLXByb29mK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJraWQiOiJZQ201emhfUnVNeHNKSV9tem1ybTd3ZnQwZnhLcFJ1QTBEZy1FVlhCTnhrIiwieCI6InVxVXRkQmFWRnJScHdYWS0tRDFUUVlqMEVYckdXSlZpaDVidkhnYi1VSjgiLCJ5IjoidGg2a1lwUG1sTUlHRElHejFRb0Y4ektqeXVNa3FxSDFQaHhfRERUc0RiZyJ9fQ.eyJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjIyMjIyIiwiaWF0IjoxNzI3NDIxNzQ5LCJub25jZSI6Ijg0ZGJhODFhLWMwYTItNDFiYS1hYmU3LTFmNzU1MTRhZTUyZCJ9.y2X40DKyUXCfHb1gGzdGaz3__xfAk1Tss0cj3M4oxooI0edHUhuKUknS7yIe7bRQErqc8s0neIM53w0XQSpFbA" } ], "credentialIssuer": "http://localhost:22222" } ``` #### Making the Request **Option: CURL** **Endpoint:** `wallet-api/{wallet}/exchange/external_signatures/offer/submit` ```bash curl -X 'POST' \ 'http://0.0.0.0:7001/wallet-api/{wallet}/exchange/external_signatures/offer/submit' \ -H 'accept: */*' \ -H 'Content-Type: application/json' \ -d '{ "did": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiQ3ZlUzFZdWdUNWV2VnoxOGswWHBjVGQtUVVsS1AxRzdCdTJJMkpoSjF2YyIsIngiOiJCeGN2d0lBVDhudzRtdjVtUDhSTzlrYmc3N25sbVZoOG5RcWZVUEtuRDVBIiwieSI6IkthU01seTBRQUhSQzZKMXFDRDloRkZnV0dXMXp1cExwU3ZtVGtHMDJmNHcifQ", "offerURL": "openid-credential-offer://localhost:22222/?credential_offer_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2FcredentialOffer%3Fid%3D8030eb87-fa89-4820-ad36-8e89cc9ccdfe", "accessToken": "eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiI4MDMwZWI4Ny1mYTg5LTQ4MjAtYWQzNi04ZTg5Y2M5Y2NkZmUiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjIyMjIyIiwiYXVkIjoiQUNDRVNTIn0.GpB9QZ-ZgCSNxBzlnkf_RBKmfMI15wH5rR2gW4EfLa640gOo9taaMLgYKax8f8kOgd5byvEf6hmGhWyGt7xTCQ", "offeredCredentialProofsOfPossession": [ { "offeredCredential": { "format": "vc+sd-jwt", "vct": "http://localhost:22222/identity_credential", "cryptographic_binding_methods_supported": [ "jwk" ], "customParameters": {} }, "proofType": "jwt", "signedProofOfPossession": "eyJ0eXAiOiJvcGVuaWQ0dmNpLXByb29mK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJraWQiOiJZQ201emhfUnVNeHNKSV9tem1ybTd3ZnQwZnhLcFJ1QTBEZy1FVlhCTnhrIiwieCI6InVxVXRkQmFWRnJScHdYWS0tRDFUUVlqMEVYckdXSlZpaDVidkhnYi1VSjgiLCJ5IjoidGg2a1lwUG1sTUlHRElHejFRb0Y4ektqeXVNa3FxSDFQaHhfRERUc0RiZyJ9fQ.eyJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjIyMjIyIiwiaWF0IjoxNzI3NDIxNzQ5LCJub25jZSI6Ijg0ZGJhODFhLWMwYTItNDFiYS1hYmU3LTFmNzU1MTRhZTUyZCJ9.y2X40DKyUXCfHb1gGzdGaz3__xfAk1Tss0cj3M4oxooI0edHUhuKUknS7yIe7bRQErqc8s0neIM53w0XQSpFbA" } ], "credentialIssuer": "http://localhost:22222" }' ``` **Path Parameters** - `wallet`: - You can receive the needed wallet id parameter via the `/wallet-api/wallet/accounts/wallets` endpoint. **Body Parameters** - `did` - value copied from response to `/prepare` - `offerURL` - value copied from response to `/prepare` - `accessToken` - value copied from response to `/prepare` - `credentialIssuer` - value copied from response to `/prepare` - `offeredCredentialProofsOfPossession` - array of offered credentials with proofs.
Expand To Learn More **Object in offeredCredentialProofsOfPossession Array** ```json { "offeredCredential": { "format": "jwt_vc_json", "credential_definition": { "type": [ "VerifiableCredential", "OpenBadgeCredential" ] }, "cryptographic_binding_methods_supported": [ "did" ] }, "proofType": "jwt", "signedProofOfPossession": "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pYTJrMFduaGlXRVJqTFVjM1QxRjVNa3gwY1d4Wk5IRkxVbXQ2UkdKTVF6TmhjWE5rVTFSNlRFcGlUU0lzSW5naU9pSlhiMmM1YWtaVllqbElkMWMzYWpsYVF6VjZZakZVZHkxWE5HMUpaVzVETUZkZlZXNWtjbXh5YVZoVklpd2llU0k2SWtoS2FWUkRjR0ZhVG5oM2JHUkpYMWxoVDFkVlVsTjZTVTVQT0daak0zQnVibEZXVWpoclREVjRNVGdpZlEjMCIsInR5cCI6Im9wZW5pZDR2Y2ktcHJvb2Yrand0IiwiYWxnIjoiRVMyNTYifQ.eyJpc3MiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pYTJrMFduaGlXRVJqTFVjM1QxRjVNa3gwY1d4Wk5IRkxVbXQ2UkdKTVF6TmhjWE5rVTFSNlRFcGlUU0lzSW5naU9pSlhiMmM1YWtaVllqbElkMWMzYWpsYVF6VjZZakZVZHkxWE5HMUpaVzVETUZkZlZXNWtjbXh5YVZoVklpd2llU0k2SWtoS2FWUkRjR0ZhVG5oM2JHUkpYMWxoVDFkVlVsTjZTVTVQT0daak0zQnVibEZXVWpoclREVjRNVGdpZlEiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjIyMjIyIiwiaWF0IjoxNzI3MDk0NjY2LCJub25jZSI6ImIzNTk3MmUwLTAzM2EtNDE5My1iOTE3LTBlZmZmMTljMWZkMCJ9.gveI7M2CNeCX8qdmFyToatLLigmyMNn-rgxjDHBFGz-BRr0PGi0j1KDOeDY--K8heXVD0E3LBL2cI1ap5Sl3lw" } ``` - **offeredCredential** - Holds the same value as the object describing one offered credential in the `offeredCredentialsProofRequests` array of response provided by `/prepare`. - **cryptographic_binding_methods_supported** - Holds the same value as the object describing one offered credential in the `offeredCredentialsProofRequests` array of response provided by `/prepare`. - **proofType** - Will be `jwt` - **signedProofOfPossession** - the signed proof of possession (jwt) from step 2 for the related credential.
**Option: mDL** ### 1. Prepare **Option: CURL** **Endpoint:`/{wallet}/exchange/external_signatures/offer/prepare`** ```bash curl -X 'POST' \ 'http://0.0.0.0:7001/wallet-api/{wallet}/exchange/external_signatures/offer/prepare' \ -H 'accept: */*' \ -H 'Content-Type: application/json' \ -d '{ "did": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiQ3ZlUzFZdWdUNWV2VnoxOGswWHBjVGQtUVVsS1AxRzdCdTJJMkpoSjF2YyIsIngiOiJCeGN2d0lBVDhudzRtdjVtUDhSTzlrYmc3N25sbVZoOG5RcWZVUEtuRDVBIiwieSI6IkthU01seTBRQUhSQzZKMXFDRDloRkZnV0dXMXp1cExwU3ZtVGtHMDJmNHcifQ", "offerURL": "openid-credential-offer://localhost:22222/?credential_offer_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2FcredentialOffer%3Fid%3D8030eb87-fa89-4820-ad36-8e89cc9ccdfe" }' ``` **Path Parameters** - `wallet`: - You can receive the needed wallet id parameter via the `/wallet-api/wallet/accounts/wallets` endpoint. **Body Parameters** - `did` - DID to which the credential should be bound to. - `offerURL` - the credential offer from the issuer. **Example Response** ```json { "did": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiQ3ZlUzFZdWdUNWV2VnoxOGswWHBjVGQtUVVsS1AxRzdCdTJJMkpoSjF2YyIsIngiOiJCeGN2d0lBVDhudzRtdjVtUDhSTzlrYmc3N25sbVZoOG5RcWZVUEtuRDVBIiwieSI6IkthU01seTBRQUhSQzZKMXFDRDloRkZnV0dXMXp1cExwU3ZtVGtHMDJmNHcifQ", "offerURL": "openid-credential-offer://localhost:22222/?credential_offer_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2FcredentialOffer%3Fid%3D8030eb87-fa89-4820-ad36-8e89cc9ccdfe", "accessToken": "eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiIwYWUyYThjZS00MThmLTQxYmQtOGYyMi0zZWIyNDUxMjdiZmYiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjIyMjIyIiwiYXVkIjoiQUNDRVNTIn0.EBsSIl8wlkLTenGrUb5kD6DCTgC1yzBc2MB1cQ-tUWXuTP_WjXRtSZq6MasAzFdavdeEu4_4v5RtZ79-covACA", "offeredCredentialsProofRequests": [ { "offeredCredential": { "format": "mso_mdoc", "doctype": "org.iso.18013.5.1.mDL", "proof_types_supported": { "cwt": { "proof_signing_alg_values_supported": [ "ES256" ] } }, "cryptographic_binding_methods_supported": [ "cose_key" ], "customParameters": {} }, "proofOfPossessionParameters": { "proofType": "cwt", "header": "ogN0b3BlbmlkNHZjaS1wcm9vZitjd3RoQ09TRV9LZXlYS6QBAiABIVggiSe+d9mpkjhymrOaV/FnjpZeKXzk8WfVVswkXzVvkqciWCDAaRYPpW3ieOpxiRpdZedMTjzRfh0oUWQSbGVQhW8m2A==", "payload": "owN2aHR0cDovL2xvY2FsaG9zdDoyMjIyMgYaZvZW7wpYJGY0MjJiNTI1LTMxYTAtNDdmMS1hNjJkLTU4N2UwNTUyMDQxNA==" } } ], "credentialIssuer": "http://localhost:22222" } ``` The response provides all the information required for the next `/submit` step: - **`offeredCredentialsProofRequests`** – list of credentials offered by the issuer. - For each offered credential, the `offeredCredential` block describes the credential itself. - Each entry also includes `proofOfPossessionParameters`, which define the proof to generate and contain the following attributes: - `proofType` - the type of proof required. In our case `cwt`. - `header` - header for to be signed jwt. - `payload` - payload for to be signed jwt. ### 2. Creating Proof of Possession To create the proof of possession: 1. Create a `cwt` for each offered credential using the `header` and `payload` from the `proofOfPossessionParameters` in the response. 2. In the example above, only one credential (`mDL`) is offered, so you only need a single signed `cwt`. ### 3. Submit With the `/submit` endpoint, you: - Send most of the information returned by the `/prepare` endpoint. - Include the proof-of-possession `cwt` created in step 2. #### Preparing the body of the request 1. We copy over the value `did`, `offerURL`, `accessToken` and `credentialIssuer` from the response to `/prepare`. ```json [request body] { "did": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiQ3ZlUzFZdWdUNWV2VnoxOGswWHBjVGQtUVVsS1AxRzdCdTJJMkpoSjF2YyIsIngiOiJCeGN2d0lBVDhudzRtdjVtUDhSTzlrYmc3N25sbVZoOG5RcWZVUEtuRDVBIiwieSI6IkthU01seTBRQUhSQzZKMXFDRDloRkZnV0dXMXp1cExwU3ZtVGtHMDJmNHcifQ", "offerURL": "openid-credential-offer://localhost:22222/?credential_offer_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2FcredentialOffer%3Fid%3D8030eb87-fa89-4820-ad36-8e89cc9ccdfe", "accessToken": "eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiIwYWUyYThjZS00MThmLTQxYmQtOGYyMi0zZWIyNDUxMjdiZmYiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjIyMjIyIiwiYXVkIjoiQUNDRVNTIn0.EBsSIl8wlkLTenGrUb5kD6DCTgC1yzBc2MB1cQ-tUWXuTP_WjXRtSZq6MasAzFdavdeEu4_4v5RtZ79-covACA", "credentialIssuer": "http://localhost:22222" } ``` 2. We add an array called `offeredCredentialProofsOfPossession` including all offered credentials with the related proof type and the signed `cwt` from [step 2 "Creating Proof of Possession".](https://docs.walt.id/community-stack/wallet/credential-exchange/guides/accept-digital-credentials-oid4vci-external-signatures.md#_2-creating-proof-of-possession) ```json [request body] { ... "offeredCredentialProofsOfPossession": [ { "offeredCredential": { "format": "mso_mdoc", "doctype": "org.iso.18013.5.1.mDL", "proof_types_supported": { "cwt": { "proof_signing_alg_values_supported": [ "ES256" ] } }, "cryptographic_binding_methods_supported": [ "cose_key" ], "customParameters": {} }, "proofType": "cwt", "signedProofOfPossession": "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pUTNabFV6RlpkV2RVTldWMlZub3hPR3N3V0hCalZHUXRVVlZzUzFBeFJ6ZENkVEpKTWtwb1NqRjJZeUlzSW5naU9pSkNlR04yZDBsQlZEaHVkelJ0ZGpWdFVEaFNUemxyWW1jM04yNXNiVlpvT0c1UmNXWlZVRXR1UkRWQklpd2llU0k2SWt0aFUwMXNlVEJSUVVoU1F6WktNWEZEUkRsb1JrWm5WMGRYTVhwMWNFeHdVM1p0Vkd0SE1ESm1OSGNpZlEjMCIsInR5cCI6Im9wZW5pZDR2Y2ktcHJvb2Yrand0IiwiYWxnIjoiRVMyNTYifQ.eyJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjIyMjIyIiwiaWF0IjoxNzI3NDE3OTcxLCJub25jZSI6IjRkNTEzMTk0LWZhNjEtNDA5OS05YmJjLTNmYjA4MDhjYTRmMSJ9.-NOvj6bfl0l8_BSxPR3UAyO-KkZ4zdfPZ401GNZl8DWg4MCtZN3lWIdnPBsqTY1aAiYfN3symtMzhdtCqpivAA" } ] ... } ``` **Object in offeredCredentialProofsOfPossession Array** ```json { "offeredCredential": { "format": "mso_mdoc", "doctype": "org.iso.18013.5.1.mDL", "proof_types_supported": { "cwt": { "proof_signing_alg_values_supported": [ "ES256" ] } }, "cryptographic_binding_methods_supported": [ "cose_key" ], "customParameters": {} }, "proofType": "cwt", "signedProofOfPossession": "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pUTNabFV6RlpkV2RVTldWMlZub3hPR3N3V0hCalZHUXRVVlZzUzFBeFJ6ZENkVEpKTWtwb1NqRjJZeUlzSW5naU9pSkNlR04yZDBsQlZEaHVkelJ0ZGpWdFVEaFNUemxyWW1jM04yNXNiVlpvT0c1UmNXWlZVRXR1UkRWQklpd2llU0k2SWt0aFUwMXNlVEJSUVVoU1F6WktNWEZEUkRsb1JrWm5WMGRYTVhwMWNFeHdVM1p0Vkd0SE1ESm1OSGNpZlEjMCIsInR5cCI6Im9wZW5pZDR2Y2ktcHJvb2Yrand0IiwiYWxnIjoiRVMyNTYifQ.eyJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjIyMjIyIiwiaWF0IjoxNzI3NDE3OTcxLCJub25jZSI6IjRkNTEzMTk0LWZhNjEtNDA5OS05YmJjLTNmYjA4MDhjYTRmMSJ9.-NOvj6bfl0l8_BSxPR3UAyO-KkZ4zdfPZ401GNZl8DWg4MCtZN3lWIdnPBsqTY1aAiYfN3symtMzhdtCqpivAA" } ``` - **offeredCredential** - Holds the same value as the object describing one offered credential in the `offeredCredentialsProofRequests` array of the `/prepare` response. - **cryptographic_binding_methods_supported** - Mirrors the corresponding value from the `offeredCredentialsProofRequests` entry in the `/prepare` response. - **proofType** - Will be `cwt`. - **signedProofOfPossession** - The signed proof of possession (cwt) from step 2 for the related credential. For the `offeredCredentialProofsOfPossession` array: - Include one object for each credential offered by the issuer. - Ensure the array has the same length as the `offeredCredentialsProofRequests` array returned by `/prepare`. **Final body** ```json [request body] { "did": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiQ3ZlUzFZdWdUNWV2VnoxOGswWHBjVGQtUVVsS1AxRzdCdTJJMkpoSjF2YyIsIngiOiJCeGN2d0lBVDhudzRtdjVtUDhSTzlrYmc3N25sbVZoOG5RcWZVUEtuRDVBIiwieSI6IkthU01seTBRQUhSQzZKMXFDRDloRkZnV0dXMXp1cExwU3ZtVGtHMDJmNHcifQ", "offerURL": "openid-credential-offer://localhost:22222/?credential_offer_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2FcredentialOffer%3Fid%3D8030eb87-fa89-4820-ad36-8e89cc9ccdfe", "accessToken": "eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiIwYWUyYThjZS00MThmLTQxYmQtOGYyMi0zZWIyNDUxMjdiZmYiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjIyMjIyIiwiYXVkIjoiQUNDRVNTIn0.EBsSIl8wlkLTenGrUb5kD6DCTgC1yzBc2MB1cQ-tUWXuTP_WjXRtSZq6MasAzFdavdeEu4_4v5RtZ79-covACA", "offeredCredentialProofsOfPossession": [ { "offeredCredential": { "format": "mso_mdoc", "doctype": "org.iso.18013.5.1.mDL", "proof_types_supported": { "cwt": { "proof_signing_alg_values_supported": [ "ES256" ] } }, "cryptographic_binding_methods_supported": [ "cose_key" ], "customParameters": {} }, "proofType": "cwt", "signedProofOfPossession": "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pUTNabFV6RlpkV2RVTldWMlZub3hPR3N3V0hCalZHUXRVVlZzUzFBeFJ6ZENkVEpKTWtwb1NqRjJZeUlzSW5naU9pSkNlR04yZDBsQlZEaHVkelJ0ZGpWdFVEaFNUemxyWW1jM04yNXNiVlpvT0c1UmNXWlZVRXR1UkRWQklpd2llU0k2SWt0aFUwMXNlVEJSUVVoU1F6WktNWEZEUkRsb1JrWm5WMGRYTVhwMWNFeHdVM1p0Vkd0SE1ESm1OSGNpZlEjMCIsInR5cCI6Im9wZW5pZDR2Y2ktcHJvb2Yrand0IiwiYWxnIjoiRVMyNTYifQ.eyJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjIyMjIyIiwiaWF0IjoxNzI3NDE3OTcxLCJub25jZSI6IjRkNTEzMTk0LWZhNjEtNDA5OS05YmJjLTNmYjA4MDhjYTRmMSJ9.-NOvj6bfl0l8_BSxPR3UAyO-KkZ4zdfPZ401GNZl8DWg4MCtZN3lWIdnPBsqTY1aAiYfN3symtMzhdtCqpivAA" } ], "credentialIssuer": "http://localhost:22222" } ``` #### Making the Request **Option: CURL** **Endpoint:** `wallet-api/{wallet}/exchange/external_signatures/offer/submit` ```bash curl -X 'POST' \ 'http://0.0.0.0:7001/wallet-api/{wallet}/exchange/external_signatures/offer/submit' \ -H 'accept: */*' \ -H 'Content-Type: application/json' \ -d '{ "did": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiQ3ZlUzFZdWdUNWV2VnoxOGswWHBjVGQtUVVsS1AxRzdCdTJJMkpoSjF2YyIsIngiOiJCeGN2d0lBVDhudzRtdjVtUDhSTzlrYmc3N25sbVZoOG5RcWZVUEtuRDVBIiwieSI6IkthU01seTBRQUhSQzZKMXFDRDloRkZnV0dXMXp1cExwU3ZtVGtHMDJmNHcifQ", "offerURL": "openid-credential-offer://localhost:22222/?credential_offer_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2FcredentialOffer%3Fid%3D8030eb87-fa89-4820-ad36-8e89cc9ccdfe", "accessToken": "eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiIwYWUyYThjZS00MThmLTQxYmQtOGYyMi0zZWIyNDUxMjdiZmYiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjIyMjIyIiwiYXVkIjoiQUNDRVNTIn0.EBsSIl8wlkLTenGrUb5kD6DCTgC1yzBc2MB1cQ-tUWXuTP_WjXRtSZq6MasAzFdavdeEu4_4v5RtZ79-covACA", "offeredCredentialProofsOfPossession": [ { "offeredCredential": { "format": "mso_mdoc", "doctype": "org.iso.18013.5.1.mDL", "proof_types_supported": { "cwt": { "proof_signing_alg_values_supported": [ "ES256" ] } }, "cryptographic_binding_methods_supported": [ "cose_key" ], "customParameters": {} }, "proofType": "cwt", "signedProofOfPossession": "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pUTNabFV6RlpkV2RVTldWMlZub3hPR3N3V0hCalZHUXRVVlZzUzFBeFJ6ZENkVEpKTWtwb1NqRjJZeUlzSW5naU9pSkNlR04yZDBsQlZEaHVkelJ0ZGpWdFVEaFNUemxyWW1jM04yNXNiVlpvT0c1UmNXWlZVRXR1UkRWQklpd2llU0k2SWt0aFUwMXNlVEJSUVVoU1F6WktNWEZEUkRsb1JrWm5WMGRYTVhwMWNFeHdVM1p0Vkd0SE1ESm1OSGNpZlEjMCIsInR5cCI6Im9wZW5pZDR2Y2ktcHJvb2Yrand0IiwiYWxnIjoiRVMyNTYifQ.eyJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjIyMjIyIiwiaWF0IjoxNzI3NDE3OTcxLCJub25jZSI6IjRkNTEzMTk0LWZhNjEtNDA5OS05YmJjLTNmYjA4MDhjYTRmMSJ9.-NOvj6bfl0l8_BSxPR3UAyO-KkZ4zdfPZ401GNZl8DWg4MCtZN3lWIdnPBsqTY1aAiYfN3symtMzhdtCqpivAA" } ], "credentialIssuer": "http://localhost:22222" }' ``` **Path Parameters** - `wallet`: - You can receive the needed wallet id parameter via the `/wallet-api/wallet/accounts/wallets` endpoint. **Body Parameters** - `did` - value copied from response to `/prepare` - `offerURL` - value copied from response to `/prepare` - `accessToken` - value copied from response to `/prepare` - `credentialIssuer` - value copied from response to `/prepare` - `offeredCredentialProofsOfPossession` - array of offered credentials with proofs.
Expand To Learn More **Object in offeredCredentialProofsOfPossession Array** ```json { "offeredCredential": { "format": "mso_mdoc", "doctype": "org.iso.18013.5.1.mDL", "proof_types_supported": { "cwt": { "proof_signing_alg_values_supported": [ "ES256" ] } }, "cryptographic_binding_methods_supported": [ "cose_key" ], "customParameters": {} }, "proofType": "cwt", "signedProofOfPossession": "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pUTNabFV6RlpkV2RVTldWMlZub3hPR3N3V0hCalZHUXRVVlZzUzFBeFJ6ZENkVEpKTWtwb1NqRjJZeUlzSW5naU9pSkNlR04yZDBsQlZEaHVkelJ0ZGpWdFVEaFNUemxyWW1jM04yNXNiVlpvT0c1UmNXWlZVRXR1UkRWQklpd2llU0k2SWt0aFUwMXNlVEJSUVVoU1F6WktNWEZEUkRsb1JrWm5WMGRYTVhwMWNFeHdVM1p0Vkd0SE1ESm1OSGNpZlEjMCIsInR5cCI6Im9wZW5pZDR2Y2ktcHJvb2Yrand0IiwiYWxnIjoiRVMyNTYifQ.eyJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjIyMjIyIiwiaWF0IjoxNzI3NDE3OTcxLCJub25jZSI6IjRkNTEzMTk0LWZhNjEtNDA5OS05YmJjLTNmYjA4MDhjYTRmMSJ9.-NOvj6bfl0l8_BSxPR3UAyO-KkZ4zdfPZ401GNZl8DWg4MCtZN3lWIdnPBsqTY1aAiYfN3symtMzhdtCqpivAA" } ``` - **offeredCredential** - Holds the same value as the object describing one offered credential in the `offeredCredentialsProofRequests` array of response provided by `/prepare`. - **cryptographic_binding_methods_supported** - Holds the same value as the object describing one offered credential in the `offeredCredentialsProofRequests` array of response provided by `/prepare`. - **proofType** - Will be `cwt` - **signedProofOfPossession** - the signed proof of possession (cwt) from step 2 for the related credential.

-------------------------------------------------------------------------------- PAGE: How to Present Digital Credentials (W3C, SD-JWT VC) via OID4VP using External Signatures with walt.id URL: https://docs.walt.id/community-stack/wallet/credential-exchange/guides/present-digital-credentials-external-signatures RAW: https://docs.walt.id/community-stack/wallet/credential-exchange/guides/present-digital-credentials-external-signatures.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # How to Present Digital Credentials (W3C, SD-JWT VC) via OID4VP using External Signatures with walt.id **TL;DR** **What you:** - Extract and parse presentation definitions from OID4VP request URLs - Use the prepare/submit flow for presenting credentials with external signature management - Create verifiable presentation proofs (JWT or CWT) depending on the credential format - Sign VP tokens externally and submit them to fulfill presentation requests - Handle selective disclosure for SD-JWT credentials in presentation flows **Relevant concepts:** - [OpenID4VP](https://docs.walt.id/concepts/data-exchange-protocols/openid4vp.md) – Protocol for requesting and receiving verifiable presentations - [Verifiable Credentials (W3C)](https://docs.walt.id/concepts/digital-credentials/verifiable-credentials-w3c.md) – W3C standard for digital credentials - [SD-JWT VC](https://docs.walt.id/concepts/digital-credentials/sd-jwt-vc.md) – IETF SD-JWT VCs standard There are several methods by which a user can present a credential to a verifier, and all are supported by the wallet API: 1. User may receive a direct link, which opens the wallet and prompts credential sharing. 2. A website may display a QR code for the user to scan and share the credential. 3. The user may manually input a credential presentation request URL string into a text field. Irrespective of the chosen method, these are just different ways of receiving, parsing and fulfilling a credential presenting request URL. ## Credential Presentation Request URL A credential presentation URL is a standardised method, as per the OID4VP specification, to communicate the needed credentials and claims from verifier to the wallet. The URL can take various forms, such as QR code or a link, and generally begins with `openid4vp://` or `haip://`. **Example Presentation Request** ``` openid4vp://authorize ?response_type=vp_token &client_id= &response_mode=direct_post &state= &presentation_definition= &client_id_scheme=redirect_uri &response_uri= ``` The most important parameter for us will be the `presentation_definition`. The other parameters mainly define: - **Internal wallet behaviour** - **Response types** - **Supported credential formats** - **Secure communication** between wallet and verifier For now, we don't need to worry about these details because the wallet API manages them. In a more advanced guide we will also dive into the other parameters. #### The Presentation Definition The presentation definition specifies the criteria for the wallet to know what credentials and claims should be requested from the user and shard with the verifier. **Example** ```json { "input_descriptors": [ { "id": "VerifiableId", "format": { "jwt_vc_json": { "alg": [ "EdDSA" ] } }, "constraints": { "fields": [ { "path": [ "$.type" ], "filter": { "type": "string", "pattern": "VerifiableId" } } ] } } ] } ``` The definition says that one credential is requested and that it: - **Uses the JWT format** - **Requires the "EdDSA" algorithm** - **Must be of type "VerifiableId"**, as enforced by the constraint filtering on the credential's `type` field You can find more info about definition documents [here](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html#name-presentation_definition-par). ## Fulfilling A Presentation Request In order to fulfill the presentation request received by the verifier, we need to go through the following steps: 1. Extract Presentation Definition from OID4VC presentation request 2. Find credentials matching the presentation definition in the user wallet 3. Request consent from the end-user of the wallet to share the credentials 4. Wrapping the credentials in a Verifiable Presentation and signing it using the holder key 5. (optional) Redirect the user to the verification result page returned from the verifier ### 1. Extract Presentation Definition A simple example for the JavaScript browser environment would look like the following: ```js async function decodePresentationURL(offerURL) { // Create URL object const url = new URL(offerURL); // Get `presentation_definition_uri` query parameter const offerParam = url.searchParams.get("presentation_definition_uri"); // Resolve the URL and get the result const response = await fetch(offerParam); if (!response.ok) { throw new Error('Network response was not ok'); } // Convert result to text const resultText = await response.text(); // Decode and parse resultText to an Object const offerObject = JSON.parse(resultText); return offerObject; } ``` The output of the function would look as follows and will be used in the next step. It's identical to the one example we already saw of a presentation definition. ```json { "input_descriptors": [ { "id": "VerifiableId", "format": { "jwt_vc_json": { "alg": [ "EdDSA" ] } }, "constraints": { "fields": [ { "path": [ "$.type" ], "filter": { "type": "string", "pattern": "VerifiableId" } } ] } } ] } ``` **Note:** Optionally decode the presentation request URL with a site like [this](https://www.url-encode-decode.com/), then extract and resolve `presentation_definition` by pasting it into your browser search bar. ### 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. **Error:** The open source wallet currently only supports Presentation Definition. In the latest versions of the the OID4VP standard, this has been replaced by DCQL.
**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' \ 'https://wallet.demo.walt.id/wallet-api/wallet/{wallet_id}/exchange/matchCredentialsForPresentationDefinition' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -d '{ "input_descriptors": [ { "id": "VerifiableId", "format": { "jwt_vc_json": { "alg": [ "EdDSA" ] } }, "constraints": { "fields": [ { "path": [ "$.type" ], "filter": { "type": "string", "pattern": "VerifiableId" } } ] } } ] }' ``` **Path Parameters** - **wallet_id** - Extract needed wallet id parameter via the `/wallet-api/wallet/accounts/wallets` endpoint. **Body** The extracted presentation definition as JSON, as explained [here](https://docs.walt.id/community-stack/wallet/credential-exchange/guides/present-digital-credentials-oid4vp.md#_1-extract-presentation-definition). ```json { "input_descriptors": [ { "id": "VerifiableId", "format": { "jwt_vc_json": { "alg": [ "EdDSA" ] } }, "constraints": { "fields": [ { "path": [ "$.type" ], "filter": { "type": "string", "pattern": "VerifiableId" } } ] } } ] } ``` **Body Parameters** The body takes the extracted presentation definition as JSON, as explained [here](https://docs.walt.id/community-stack/wallet/credential-exchange/guides/present-digital-credentials-oid4vp.md#_1-extract-presentation-definition). --- **Example Response** The response behaves as follows: - **If credentials match** the presentation definition, a list of these credentials is returned. - **If no credentials match**, an empty list is returned. - **Credential IDs** returned in the list should be noted for use in a later step. ```json [ { "wallet": "5f2eb7d7-7d04-461c-b93d-28d95efbf15b", "id": "urn:uuid:fb09ba3d-b73d-49ad-9d88-3d018878ea83", "document": "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pTTFsT1pEbEdibmc1U214NVVGWlpkMmRYUmtVek4wVXpSM2RKTUdWSGJFTkxPSGRHYkZkNFIyWndUU0lzSW5naU9pSkdiM1paTWpGTVFVRlBWR3huTFcwdFRtVkxWMmhhUlV3MVlVWnlibEl3ZFdOS2FrUTFWRXR3UjNWbklpd2llU0k2SWtOeVJrcG1SMVJrVURJNVNrcGpZM0JSV0hWNVRVOHpiMmgwZW5KVWNWQjZRbEJDU1ZSWmFqQnZaMEVpZlEiLCJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pTTFsT1pEbEdibmc1U214NVVGWlpkMmRYUmtVek4wVXpSM2RKTUdWSGJFTkxPSGRHYkZkNFIyWndUU0lzSW5naU9pSkdiM1paTWpGTVFVRlBWR3huTFcwdFRtVkxWMmhhUlV3MVlVWnlibEl3ZFdOS2FrUTFWRXR3UjNWbklpd2llU0k2SWtOeVJrcG1SMVJrVURJNVNrcGpZM0JSV0hWNVRVOHpiMmgwZW5KVWNWQjZRbEJDU1ZSWmFqQnZaMEVpZlEiLCJzdWIiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaVEyVTVNbGczUVhVMVRuQlRWV0ZoWlU5VFl6TkpSMjlDTFVacFNUTmtaMjFYT1Y5c2NGTldja3hrYXlJc0luZ2lPaUpIY1VabWRqbFNjemh1TlRrMk4wSXlVR3g0TW1wbFduRnlOWFZxVkRoM2RYWk5XbEpIYlZoeGVFSkZJbjAiLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVmVyaWZpYWJsZUF0dGVzdGF0aW9uIiwiVmVyaWZpYWJsZUlkIl0sImNyZWRlbnRpYWxTY2hlbWEiOnsiaWQiOiJodHRwczovL2FwaS5wcmVwcm9kLmVic2kuZXUvdHJ1c3RlZC1zY2hlbWFzLXJlZ2lzdHJ5L3YxL3NjaGVtYXMvMHhiNzdmODUxNmE5NjU2MzFiNGYxOTdhZDU0YzY1YTllMmY5OTM2ZWJmYjc2YmFlNDkwNmQzMzc0NGRiY2M2MGJhIiwidHlwZSI6IkZ1bGxKc29uU2NoZW1hVmFsaWRhdG9yMjAyMSJ9LCJjcmVkZW50aWFsU3ViamVjdCI6eyJjdXJyZW50QWRkcmVzcyI6WyIxIEJvdWxldmFyZCBkZSBsYSBMaWJlcnTDqSwgNTk4MDAgTGlsbGUiXSwiZGF0ZU9mQmlydGgiOiIxOTkzLTA0LTA4IiwiZmFtaWx5TmFtZSI6IkRPRSIsImZpcnN0TmFtZSI6IkphbmUiLCJnZW5kZXIiOiJGRU1BTEUiLCJpZCI6ImRpZDpqd2s6ZXlKcmRIa2lPaUpQUzFBaUxDSmpjbllpT2lKRlpESTFOVEU1SWl3aWEybGtJam9pUTJVNU1sZzNRWFUxVG5CVFZXRmhaVTlUWXpOSlIyOUNMVVpwU1ROa1oyMVhPVjlzY0ZOV2NreGtheUlzSW5naU9pSkhjVVptZGpsU2N6aHVOVGsyTjBJeVVHeDRNbXBsV25GeU5YVnFWRGgzZFhaTldsSkhiVmh4ZUVKRkluMCIsIm5hbWVBbmRGYW1pbHlOYW1lQXRCaXJ0aCI6IkphbmUgRE9FIiwicGVyc29uYWxJZGVudGlmaWVyIjoiMDkwNDAwODA4NEgiLCJwbGFjZU9mQmlydGgiOiJMSUxMRSwgRlJBTkNFIn0sImV2aWRlbmNlIjpbeyJkb2N1bWVudFByZXNlbmNlIjpbIlBoeXNpY2FsIl0sImV2aWRlbmNlRG9jdW1lbnQiOlsiUGFzc3BvcnQiXSwic3ViamVjdFByZXNlbmNlIjoiUGh5c2ljYWwiLCJ0eXBlIjpbIkRvY3VtZW50VmVyaWZpY2F0aW9uIl0sInZlcmlmaWVyIjoiZGlkOmVic2k6MkE5Qlo5U1VlNkJhdGFjU3B2czFWNUNkakh2THBRN2JFc2kySmI2TGRIS25ReGFOIn1dLCJpZCI6InVybjp1dWlkOmZiMDliYTNkLWI3M2QtNDlhZC05ZDg4LTNkMDE4ODc4ZWE4MyIsImlzc3VlZCI6IjIwMjEtMDgtMzFUMDA6MDA6MDBaIiwiaXNzdWVyIjoiZGlkOmp3azpleUpyZEhraU9pSkZReUlzSW1OeWRpSTZJbEF0TWpVMklpd2lhMmxrSWpvaU0xbE9aRGxHYm5nNVNteDVVRlpaZDJkWFJrVXpOMFV6UjNkSk1HVkhiRU5MT0hkR2JGZDRSMlp3VFNJc0luZ2lPaUpHYjNaWk1qRk1RVUZQVkd4bkxXMHRUbVZMVjJoYVJVdzFZVVp5YmxJd2RXTktha1ExVkV0d1IzVm5JaXdpZVNJNklrTnlSa3BtUjFSa1VESTVTa3BqWTNCUldIVjVUVTh6YjJoMGVuSlVjVkI2UWxCQ1NWUlphakJ2WjBFaWZRIiwidmFsaWRGcm9tIjoiMjAyMS0wOC0zMVQwMDowMDowMFoiLCJpc3N1YW5jZURhdGUiOiIyMDI0LTA4LTIxVDA5OjExOjMzLjI0NjM4OTA0MVoifSwianRpIjoidXJuOnV1aWQ6ZmIwOWJhM2QtYjczZC00OWFkLTlkODgtM2QwMTg4NzhlYTgzIiwiaWF0IjoxNzI0MjMxNDkzLCJuYmYiOjE3MjQyMzE0OTN9.OsPTKQC6tvD6TtxeoCd8FtaIThYrYeYRjCHzDXkmgTUjRw78faT9R2bljT03ncrb0YstK0EmjM9D5lwqMZ9ZIA", "addedOn": "2024-08-21T09:11:33.284Z", "format": "jwt_vc" } ] ``` **Error:** Our implementation of Presentation Definition only supports JSON based formats, and therefore does not support mDocs. You must know the credential id of the mDoc you want to share and provide it directly in the exchange flow in the next step. ### 3. Get User Consent Using the result of the previous request, we can now display the credential(s) to the user (owner of the wallet) for confirming the exchange with the verifier. ### 4. Share Credential(s) with Verifier To share requested credentials with the verifier, the wallet must sign a VP token. When using external signatures: 1. In the regular Wallet API setup, the API manages the keys and can generate the presentation signatures in the background. 2. With external signatures, the VP signature must instead be created in the system that manages the keys. 3. Because of this, the regular `usePresentationRequest` endpoint is split into two steps. The `/prepare` step: - Lets the Wallet API gather all required information. - Returns structured data that should either be signed or passed to the next step `/submit`. - Prepares everything needed to finalize the exchange between wallet and verifier. **Note:** Please note that the external signatures are an optional feature of the Wallet API. It needs to be enabled via the feature.conf by providing `external-signature-endpoints` in the `enabledFeatures` array. Learn more [here](https://docs.walt.id/community-stack/wallet/configurations/config-files/features.md).
**Option: W3C JWT** #### 1. Preparation Using the `/prepare` endpoint we will send to the wallet API: The DID owning the credentials, the request from the verifier and the credential IDs matching the request from the verifier which we got in the last step. **Option: CURL** **Endpoint:** `wallet-api/{wallet}/exchange/external_signatures/presentation/prepare` ```bash curl -X 'POST' \ 'http://0.0.0.0:7001/wallet-api/{wallet}/exchange/external_signatures/presentation/prepare' \ -H 'accept: */*' \ -H 'Content-Type: application/json' \ -d '{ "did": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiWWlkYnltc2loVThaUFEwZ1B3cE1YWmNYM2RIaGxkaldwaEl6clRQaHh4RSIsIngiOiJ6cnVMdHprT2NjYWhNTk5MZjFiTkJVN3AxM254a19CQjU5d1VXelFWVG1jIiwieSI6Inp4UUV1NFF4bmVlQnNGYXNTYkptVjNoUUhJYXVGb3FsNWR6Z1VyTzJ0UjgifQ", "presentationRequest": "openid4vp://authorize?response_type=vp_token&client_id=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fverify&response_mode=direct_post&state=RVgQIBRCUy0J&presentation_definition_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fpd%2FRVgQIBRCUy0J&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=15c72cd0-8c84-45dc-aae2-a310f6075a8f&response_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fverify%2FRVgQIBRCUy0J", "selectedCredentialIdList": [ "urn:uuid:a072cdef-4912-4cdf-99cb-63c0ad7eeed1" ] }' ``` **Path Parameters** - `wallet`: - You can receive the needed wallet id parameter via the `/wallet-api/wallet/accounts/wallets` endpoint. **Body Parameters** - `did` - DID to owning credential(s) to be presented. - `presentationRequest` - the presentation request from the verifier. - `selectedCredentialIdList` - IDs of the credential(s) which should be presented. Response from `/matchCredentialForPresentationDefinition` endpoint. **Example Response** ```json { "did": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiWWlkYnltc2loVThaUFEwZ1B3cE1YWmNYM2RIaGxkaldwaEl6clRQaHh4RSIsIngiOiJ6cnVMdHprT2NjYWhNTk5MZjFiTkJVN3AxM254a19CQjU5d1VXelFWVG1jIiwieSI6Inp4UUV1NFF4bmVlQnNGYXNTYkptVjNoUUhJYXVGb3FsNWR6Z1VyTzJ0UjgifQ", "presentationRequest": "openid4vp://authorize?response_type=vp_token&client_id=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fverify&response_mode=direct_post&state=RVgQIBRCUy0J&presentation_definition_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fpd%2FRVgQIBRCUy0J&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=15c72cd0-8c84-45dc-aae2-a310f6075a8f&response_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fverify%2FRVgQIBRCUy0J", "selectedCredentialIdList": [ "urn:uuid:a072cdef-4912-4cdf-99cb-63c0ad7eeed1" ], "presentationSubmission": { "id": "urn:uuid:c85e079e-d194-44cd-8268-85f4ee819fef", "definition_id": "urn:uuid:c85e079e-d194-44cd-8268-85f4ee819fef", "descriptor_map": [ { "id": "OpenBadgeCredential", "format": "jwt_vp", "path": "$", "path_nested": { "id": "OpenBadgeCredential", "format": "jwt_vc_json", "path": "$.verifiableCredential[0]", "customParameters": {} } } ], "customParameters": {} }, "w3CJwtVpProofParameters": { "header": { "kid": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiWWlkYnltc2loVThaUFEwZ1B3cE1YWmNYM2RIaGxkaldwaEl6clRQaHh4RSIsIngiOiJ6cnVMdHprT2NjYWhNTk5MZjFiTkJVN3AxM254a19CQjU5d1VXelFWVG1jIiwieSI6Inp4UUV1NFF4bmVlQnNGYXNTYkptVjNoUUhJYXVGb3FsNWR6Z1VyTzJ0UjgifQ#0", "typ": "JWT" }, "payload": { "sub": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiWWlkYnltc2loVThaUFEwZ1B3cE1YWmNYM2RIaGxkaldwaEl6clRQaHh4RSIsIngiOiJ6cnVMdHprT2NjYWhNTk5MZjFiTkJVN3AxM254a19CQjU5d1VXelFWVG1jIiwieSI6Inp4UUV1NFF4bmVlQnNGYXNTYkptVjNoUUhJYXVGb3FsNWR6Z1VyTzJ0UjgifQ", "nbf": 1727428853, "iat": 1727428913, "jti": "urn:uuid:c85e079e-d194-44cd-8268-85f4ee819fef", "iss": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiWWlkYnltc2loVThaUFEwZ1B3cE1YWmNYM2RIaGxkaldwaEl6clRQaHh4RSIsIngiOiJ6cnVMdHprT2NjYWhNTk5MZjFiTkJVN3AxM254a19CQjU5d1VXelFWVG1jIiwieSI6Inp4UUV1NFF4bmVlQnNGYXNTYkptVjNoUUhJYXVGb3FsNWR6Z1VyTzJ0UjgifQ", "aud": "http://localhost:22222/openid4vc/verify", "nonce": "15c72cd0-8c84-45dc-aae2-a310f6075a8f", "vp": { "@context": [ "https://www.w3.org/2018/credentials/v1" ], "type": [ "VerifiablePresentation" ], "id": "urn:uuid:c85e079e-d194-44cd-8268-85f4ee819fef", "holder": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiWWlkYnltc2loVThaUFEwZ1B3cE1YWmNYM2RIaGxkaldwaEl6clRQaHh4RSIsIngiOiJ6cnVMdHprT2NjYWhNTk5MZjFiTkJVN3AxM254a19CQjU5d1VXelFWVG1jIiwieSI6Inp4UUV1NFF4bmVlQnNGYXNTYkptVjNoUUhJYXVGb3FsNWR6Z1VyTzJ0UjgifQ", "verifiableCredential": [ "eyJraWQiOiJkaWQ6a2V5Ono2TWtqb1JocTFqU05KZExpcnVTWHJGRnhhZ3FyenRaYVhIcUhHVVRLSmJjTnl3cCIsInR5cCI6IkpXVCIsImFsZyI6IkVkRFNBIn0.eyJpc3MiOiJkaWQ6a2V5Ono2TWtqb1JocTFqU05KZExpcnVTWHJGRnhhZ3FyenRaYVhIcUhHVVRLSmJjTnl3cCIsInN1YiI6ImRpZDpqd2s6ZXlKcmRIa2lPaUpGUXlJc0ltTnlkaUk2SWxBdE1qVTJJaXdpYTJsa0lqb2lXV2xrWW5sdGMybG9WVGhhVUZFd1oxQjNjRTFZV21OWU0yUklhR3hrYWxkd2FFbDZjbFJRYUhoNFJTSXNJbmdpT2lKNmNuVk1kSHByVDJOallXaE5UazVNWmpGaVRrSlZOM0F4TTI1NGExOUNRalU1ZDFWWGVsRldWRzFqSWl3aWVTSTZJbnA0VVVWMU5GRjRibVZsUW5OR1lYTlRZa3B0VmpOb1VVaEpZWFZHYjNGc05XUjZaMVZ5VHpKMFVqZ2lmUSIsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIiwiaHR0cHM6Ly9wdXJsLmltc2dsb2JhbC5vcmcvc3BlYy9vYi92M3AwL2NvbnRleHQuanNvbiJdLCJpZCI6InVybjp1dWlkOmEwNzJjZGVmLTQ5MTItNGNkZi05OWNiLTYzYzBhZDdlZWVkMSIsInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJPcGVuQmFkZ2VDcmVkZW50aWFsIl0sIm5hbWUiOiJKRkYgeCB2Yy1lZHUgUGx1Z0Zlc3QgMyBJbnRlcm9wZXJhYmlsaXR5IiwiaXNzdWVyIjp7InR5cGUiOlsiUHJvZmlsZSJdLCJpZCI6ImRpZDprZXk6ejZNa2pvUmhxMWpTTkpkTGlydVNYckZGeGFncXJ6dFphWEhxSEdVVEtKYmNOeXdwIiwibmFtZSI6IkpvYnMgZm9yIHRoZSBGdXR1cmUgKEpGRikiLCJ1cmwiOiJodHRwczovL3d3dy5qZmYub3JnLyIsImltYWdlIjoiaHR0cHM6Ly93M2MtY2NnLmdpdGh1Yi5pby92Yy1lZC9wbHVnZmVzdC0xLTIwMjIvaW1hZ2VzL0pGRl9Mb2dvTG9ja3VwLnBuZyJ9LCJpc3N1YW5jZURhdGUiOiIyMDI0LTA5LTI3VDA5OjIxOjUzLjE0NTQxNTQzM1oiLCJleHBpcmF0aW9uRGF0ZSI6IjIwMjUtMDktMjdUMDk6MjE6NTMuMTQ1NTMwMDcyWiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmp3azpleUpyZEhraU9pSkZReUlzSW1OeWRpSTZJbEF0TWpVMklpd2lhMmxrSWpvaVdXbGtZbmx0YzJsb1ZUaGFVRkV3WjFCM2NFMVlXbU5ZTTJSSWFHeGthbGR3YUVsNmNsUlFhSGg0UlNJc0luZ2lPaUo2Y25WTWRIcHJUMk5qWVdoTlRrNU1aakZpVGtKVk4zQXhNMjU0YTE5Q1FqVTVkMVZYZWxGV1ZHMWpJaXdpZVNJNklucDRVVVYxTkZGNGJtVmxRbk5HWVhOVFlrcHRWak5vVVVoSllYVkdiM0ZzTldSNloxVnlUekowVWpnaWZRIiwidHlwZSI6WyJBY2hpZXZlbWVudFN1YmplY3QiXSwiYWNoaWV2ZW1lbnQiOnsiaWQiOiJ1cm46dXVpZDphYzI1NGJkNS04ZmFkLTRiYjEtOWQyOS1lZmQ5Mzg1MzY5MjYiLCJ0eXBlIjpbIkFjaGlldmVtZW50Il0sIm5hbWUiOiJKRkYgeCB2Yy1lZHUgUGx1Z0Zlc3QgMyBJbnRlcm9wZXJhYmlsaXR5IiwiZGVzY3JpcHRpb24iOiJUaGlzIHdhbGxldCBzdXBwb3J0cyB0aGUgdXNlIG9mIFczQyBWZXJpZmlhYmxlIENyZWRlbnRpYWxzIGFuZCBoYXMgZGVtb25zdHJhdGVkIGludGVyb3BlcmFiaWxpdHkgZHVyaW5nIHRoZSBwcmVzZW50YXRpb24gcmVxdWVzdCB3b3JrZmxvdyBkdXJpbmcgSkZGIHggVkMtRURVIFBsdWdGZXN0IDMuIiwiY3JpdGVyaWEiOnsidHlwZSI6IkNyaXRlcmlhIiwibmFycmF0aXZlIjoiV2FsbGV0IHNvbHV0aW9ucyBwcm92aWRlcnMgZWFybmVkIHRoaXMgYmFkZ2UgYnkgZGVtb25zdHJhdGluZyBpbnRlcm9wZXJhYmlsaXR5IGR1cmluZyB0aGUgcHJlc2VudGF0aW9uIHJlcXVlc3Qgd29ya2Zsb3cuIFRoaXMgaW5jbHVkZXMgc3VjY2Vzc2Z1bGx5IHJlY2VpdmluZyBhIHByZXNlbnRhdGlvbiByZXF1ZXN0LCBhbGxvd2luZyB0aGUgaG9sZGVyIHRvIHNlbGVjdCBhdCBsZWFzdCB0d28gdHlwZXMgb2YgdmVyaWZpYWJsZSBjcmVkZW50aWFscyB0byBjcmVhdGUgYSB2ZXJpZmlhYmxlIHByZXNlbnRhdGlvbiwgcmV0dXJuaW5nIHRoZSBwcmVzZW50YXRpb24gdG8gdGhlIHJlcXVlc3RvciwgYW5kIHBhc3NpbmcgdmVyaWZpY2F0aW9uIG9mIHRoZSBwcmVzZW50YXRpb24gYW5kIHRoZSBpbmNsdWRlZCBjcmVkZW50aWFscy4ifSwiaW1hZ2UiOnsiaWQiOiJodHRwczovL3czYy1jY2cuZ2l0aHViLmlvL3ZjLWVkL3BsdWdmZXN0LTMtMjAyMy9pbWFnZXMvSkZGLVZDLUVEVS1QTFVHRkVTVDMtYmFkZ2UtaW1hZ2UucG5nIiwidHlwZSI6IkltYWdlIn19fX0sImp0aSI6InVybjp1dWlkOmEwNzJjZGVmLTQ5MTItNGNkZi05OWNiLTYzYzBhZDdlZWVkMSIsImV4cCI6MTc1ODk2NDkxMywiaWF0IjoxNzI3NDI4OTEzLCJuYmYiOjE3Mjc0Mjg5MTN9.KHRO2M2EewjvmEN8Y3HyhJtLQUEay2HOg4Axfu-ON-dYGFlpX635dvQj1pW0iuv9uy-XCbcVWH9MbDxfoKu3Bg" ] } } } } ``` In the response, the `w3CJwtVpProofParameters` key provides the data needed to create the VP proof: - **`header`** – JWT header to use when creating the VP proof. - **`payload`** – JWT payload that must be signed with the holder's key. All other fields from the response are forwarded unchanged to the `/submit` endpoint together with the signed JWT. #### 2. Signing the VP To sign the VP: 1. Take the `header` and `payload` from `w3CJwtVpProofParameters` returned by `/prepare`. 2. Create a `jwt` using this header and payload. 3. Sign the JWT with the holder's key in the external system. #### 3. Submit With the `/submit` endpoint, you: - Send most of the information returned by the `/prepare` endpoint. - Include the signed VP token created in step 2. ##### Preparing the body of the request **1.** We add the signed VP token as `w3cJwtVpProof` ```json [request body] { "w3cJwtVpProof": "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pV1dsa1lubHRjMmxvVlRoYVVGRXdaMUIzY0UxWVdtTllNMlJJYUd4a2FsZHdhRWw2Y2xSUWFIaDRSU0lzSW5naU9pSjZjblZNZEhwclQyTmpZV2hOVGs1TVpqRmlUa0pWTjNBeE0yNTRhMTlDUWpVNWQxVlhlbEZXVkcxaklpd2llU0k2SW5wNFVVVjFORkY0Ym1WbFFuTkdZWE5UWWtwdFZqTm9VVWhKWVhWR2IzRnNOV1I2WjFWeVR6SjBVamdpZlEjMCIsInR5cCI6IkpXVCIsImFsZyI6IkVTMjU2In0.eyJzdWIiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pV1dsa1lubHRjMmxvVlRoYVVGRXdaMUIzY0UxWVdtTllNMlJJYUd4a2FsZHdhRWw2Y2xSUWFIaDRSU0lzSW5naU9pSjZjblZNZEhwclQyTmpZV2hOVGs1TVpqRmlUa0pWTjNBeE0yNTRhMTlDUWpVNWQxVlhlbEZXVkcxaklpd2llU0k2SW5wNFVVVjFORkY0Ym1WbFFuTkdZWE5UWWtwdFZqTm9VVWhKWVhWR2IzRnNOV1I2WjFWeVR6SjBVamdpZlEiLCJuYmYiOjE3Mjc0Mjg4NTMsImlhdCI6MTcyNzQyODkxMywianRpIjoidXJuOnV1aWQ6Yzg1ZTA3OWUtZDE5NC00NGNkLTgyNjgtODVmNGVlODE5ZmVmIiwiaXNzIjoiZGlkOmp3azpleUpyZEhraU9pSkZReUlzSW1OeWRpSTZJbEF0TWpVMklpd2lhMmxrSWpvaVdXbGtZbmx0YzJsb1ZUaGFVRkV3WjFCM2NFMVlXbU5ZTTJSSWFHeGthbGR3YUVsNmNsUlFhSGg0UlNJc0luZ2lPaUo2Y25WTWRIcHJUMk5qWVdoTlRrNU1aakZpVGtKVk4zQXhNMjU0YTE5Q1FqVTVkMVZYZWxGV1ZHMWpJaXdpZVNJNklucDRVVVYxTkZGNGJtVmxRbk5HWVhOVFlrcHRWak5vVVVoSllYVkdiM0ZzTldSNloxVnlUekowVWpnaWZRIiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDoyMjIyMi9vcGVuaWQ0dmMvdmVyaWZ5Iiwibm9uY2UiOiIxNWM3MmNkMC04Yzg0LTQ1ZGMtYWFlMi1hMzEwZjYwNzVhOGYiLCJ2cCI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVQcmVzZW50YXRpb24iXSwiaWQiOiJ1cm46dXVpZDpjODVlMDc5ZS1kMTk0LTQ0Y2QtODI2OC04NWY0ZWU4MTlmZWYiLCJob2xkZXIiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pV1dsa1lubHRjMmxvVlRoYVVGRXdaMUIzY0UxWVdtTllNMlJJYUd4a2FsZHdhRWw2Y2xSUWFIaDRSU0lzSW5naU9pSjZjblZNZEhwclQyTmpZV2hOVGs1TVpqRmlUa0pWTjNBeE0yNTRhMTlDUWpVNWQxVlhlbEZXVkcxaklpd2llU0k2SW5wNFVVVjFORkY0Ym1WbFFuTkdZWE5UWWtwdFZqTm9VVWhKWVhWR2IzRnNOV1I2WjFWeVR6SjBVamdpZlEiLCJ2ZXJpZmlhYmxlQ3JlZGVudGlhbCI6WyJleUpyYVdRaU9pSmthV1E2YTJWNU9ubzJUV3RxYjFKb2NURnFVMDVLWkV4cGNuVlRXSEpHUm5oaFozRnllblJhWVZoSWNVaEhWVlJMU21KalRubDNjQ0lzSW5SNWNDSTZJa3BYVkNJc0ltRnNaeUk2SWtWa1JGTkJJbjAuZXlKcGMzTWlPaUprYVdRNmEyVjVPbm8yVFd0cWIxSm9jVEZxVTA1S1pFeHBjblZUV0hKR1JuaGhaM0Z5ZW5SYVlWaEljVWhIVlZSTFNtSmpUbmwzY0NJc0luTjFZaUk2SW1ScFpEcHFkMnM2WlhsS2NtUklhMmxQYVVwR1VYbEpjMGx0VG5sa2FVazJTV3hCZEUxcVZUSkphWGRwWVRKc2EwbHFiMmxYVjJ4cldXNXNkR015Ykc5V1ZHaGhWVVpGZDFveFFqTmpSVEZaVjIxT1dVMHlVa2xoUjNocllXeGtkMkZGYkRaamJGSlJZVWhvTkZKVFNYTkpibWRwVDJsS05tTnVWazFrU0hCeVZESk9hbGxYYUU1VWF6Vk5XbXBHYVZSclNsWk9NMEY0VFRJMU5HRXhPVU5SYWxVMVpERldXR1ZzUmxkV1J6RnFTV2wzYVdWVFNUWkpibkEwVlZWV01VNUdSalJpYlZac1VXNU9SMWxZVGxSWmEzQjBWbXBPYjFWVmFFcFpXRlpIWWpOR2MwNVhValphTVZaNVZIcEtNRlZxWjJsbVVTSXNJblpqSWpwN0lrQmpiMjUwWlhoMElqcGJJbWgwZEhCek9pOHZkM2QzTG5jekxtOXlaeTh5TURFNEwyTnlaV1JsYm5ScFlXeHpMM1l4SWl3aWFIUjBjSE02THk5d2RYSnNMbWx0YzJkc2IySmhiQzV2Y21jdmMzQmxZeTl2WWk5Mk0zQXdMMk52Ym5SbGVIUXVhbk52YmlKZExDSnBaQ0k2SW5WeWJqcDFkV2xrT21Fd056SmpaR1ZtTFRRNU1USXROR05rWmkwNU9XTmlMVFl6WXpCaFpEZGxaV1ZrTVNJc0luUjVjR1VpT2xzaVZtVnlhV1pwWVdKc1pVTnlaV1JsYm5ScFlXd2lMQ0pQY0dWdVFtRmtaMlZEY21Wa1pXNTBhV0ZzSWwwc0ltNWhiV1VpT2lKS1JrWWdlQ0IyWXkxbFpIVWdVR3gxWjBabGMzUWdNeUJKYm5SbGNtOXdaWEpoWW1sc2FYUjVJaXdpYVhOemRXVnlJanA3SW5SNWNHVWlPbHNpVUhKdlptbHNaU0pkTENKcFpDSTZJbVJwWkRwclpYazZlalpOYTJwdlVtaHhNV3BUVGtwa1RHbHlkVk5ZY2taR2VHRm5jWEo2ZEZwaFdFaHhTRWRWVkV0S1ltTk9lWGR3SWl3aWJtRnRaU0k2SWtwdlluTWdabTl5SUhSb1pTQkdkWFIxY21VZ0tFcEdSaWtpTENKMWNtd2lPaUpvZEhSd2N6b3ZMM2QzZHk1cVptWXViM0puTHlJc0ltbHRZV2RsSWpvaWFIUjBjSE02THk5M00yTXRZMk5uTG1kcGRHaDFZaTVwYnk5Mll5MWxaQzl3YkhWblptVnpkQzB4TFRJd01qSXZhVzFoWjJWekwwcEdSbDlNYjJkdlRHOWphM1Z3TG5CdVp5SjlMQ0pwYzNOMVlXNWpaVVJoZEdVaU9pSXlNREkwTFRBNUxUSTNWREE1T2pJeE9qVXpMakUwTlRReE5UUXpNMW9pTENKbGVIQnBjbUYwYVc5dVJHRjBaU0k2SWpJd01qVXRNRGt0TWpkVU1EazZNakU2TlRNdU1UUTFOVE13TURjeVdpSXNJbU55WldSbGJuUnBZV3hUZFdKcVpXTjBJanA3SW1sa0lqb2laR2xrT21wM2F6cGxlVXB5WkVocmFVOXBTa1pSZVVselNXMU9lV1JwU1RaSmJFRjBUV3BWTWtscGQybGhNbXhyU1dwdmFWZFhiR3RaYm14MFl6SnNiMVpVYUdGVlJrVjNXakZDTTJORk1WbFhiVTVaVFRKU1NXRkhlR3RoYkdSM1lVVnNObU5zVWxGaFNHZzBVbE5KYzBsdVoybFBhVW8yWTI1V1RXUkljSEpVTWs1cVdWZG9UbFJyTlUxYWFrWnBWR3RLVms0elFYaE5NalUwWVRFNVExRnFWVFZrTVZaWVpXeEdWMVpITVdwSmFYZHBaVk5KTmtsdWNEUlZWVll4VGtaR05HSnRWbXhSYms1SFdWaE9WRmxyY0hSV2FrNXZWVlZvU2xsWVZrZGlNMFp6VGxkU05sb3hWbmxVZWtvd1ZXcG5hV1pSSWl3aWRIbHdaU0k2V3lKQlkyaHBaWFpsYldWdWRGTjFZbXBsWTNRaVhTd2lZV05vYVdWMlpXMWxiblFpT25zaWFXUWlPaUoxY200NmRYVnBaRHBoWXpJMU5HSmtOUzA0Wm1Ga0xUUmlZakV0T1dReU9TMWxabVE1TXpnMU16WTVNallpTENKMGVYQmxJanBiSWtGamFHbGxkbVZ0Wlc1MElsMHNJbTVoYldVaU9pSktSa1lnZUNCMll5MWxaSFVnVUd4MVowWmxjM1FnTXlCSmJuUmxjbTl3WlhKaFltbHNhWFI1SWl3aVpHVnpZM0pwY0hScGIyNGlPaUpVYUdseklIZGhiR3hsZENCemRYQndiM0owY3lCMGFHVWdkWE5sSUc5bUlGY3pReUJXWlhKcFptbGhZbXhsSUVOeVpXUmxiblJwWVd4eklHRnVaQ0JvWVhNZ1pHVnRiMjV6ZEhKaGRHVmtJR2x1ZEdWeWIzQmxjbUZpYVd4cGRIa2daSFZ5YVc1bklIUm9aU0J3Y21WelpXNTBZWFJwYjI0Z2NtVnhkV1Z6ZENCM2IzSnJabXh2ZHlCa2RYSnBibWNnU2taR0lIZ2dWa010UlVSVklGQnNkV2RHWlhOMElETXVJaXdpWTNKcGRHVnlhV0VpT25zaWRIbHdaU0k2SWtOeWFYUmxjbWxoSWl3aWJtRnljbUYwYVhabElqb2lWMkZzYkdWMElITnZiSFYwYVc5dWN5QndjbTkyYVdSbGNuTWdaV0Z5Ym1Wa0lIUm9hWE1nWW1Ga1oyVWdZbmtnWkdWdGIyNXpkSEpoZEdsdVp5QnBiblJsY205d1pYSmhZbWxzYVhSNUlHUjFjbWx1WnlCMGFHVWdjSEpsYzJWdWRHRjBhVzl1SUhKbGNYVmxjM1FnZDI5eWEyWnNiM2N1SUZSb2FYTWdhVzVqYkhWa1pYTWdjM1ZqWTJWemMyWjFiR3g1SUhKbFkyVnBkbWx1WnlCaElIQnlaWE5sYm5SaGRHbHZiaUJ5WlhGMVpYTjBMQ0JoYkd4dmQybHVaeUIwYUdVZ2FHOXNaR1Z5SUhSdklITmxiR1ZqZENCaGRDQnNaV0Z6ZENCMGQyOGdkSGx3WlhNZ2IyWWdkbVZ5YVdacFlXSnNaU0JqY21Wa1pXNTBhV0ZzY3lCMGJ5QmpjbVZoZEdVZ1lTQjJaWEpwWm1saFlteGxJSEJ5WlhObGJuUmhkR2x2Yml3Z2NtVjBkWEp1YVc1bklIUm9aU0J3Y21WelpXNTBZWFJwYjI0Z2RHOGdkR2hsSUhKbGNYVmxjM1J2Y2l3Z1lXNWtJSEJoYzNOcGJtY2dkbVZ5YVdacFkyRjBhVzl1SUc5bUlIUm9aU0J3Y21WelpXNTBZWFJwYjI0Z1lXNWtJSFJvWlNCcGJtTnNkV1JsWkNCamNtVmtaVzUwYVdGc2N5NGlmU3dpYVcxaFoyVWlPbnNpYVdRaU9pSm9kSFJ3Y3pvdkwzY3pZeTFqWTJjdVoybDBhSFZpTG1sdkwzWmpMV1ZrTDNCc2RXZG1aWE4wTFRNdE1qQXlNeTlwYldGblpYTXZTa1pHTFZaRExVVkVWUzFRVEZWSFJrVlRWRE10WW1Ga1oyVXRhVzFoWjJVdWNHNW5JaXdpZEhsd1pTSTZJa2x0WVdkbEluMTlmWDBzSW1wMGFTSTZJblZ5YmpwMWRXbGtPbUV3TnpKalpHVm1MVFE1TVRJdE5HTmtaaTA1T1dOaUxUWXpZekJoWkRkbFpXVmtNU0lzSW1WNGNDSTZNVGMxT0RrMk5Ea3hNeXdpYVdGMElqb3hOekkzTkRJNE9URXpMQ0p1WW1ZaU9qRTNNamMwTWpnNU1UTjkuS0hSTzJNMkVld2p2bUVOOFkzSHloSnRMUVVFYXkySE9nNEF4ZnUtT04tZFlHRmxwWDYzNWR2UWoxcFcwaXV2OXV5LVhDYmNWV0g5TWJEeGZvS3UzQmciXX19.pAQDUnGESJRhACTyok-ICREUbQ9fCuJvwIAVwyuBInhM2EogJK29Jp4eWwDZ1pRJMyB0NubyjcU3CzFrAzUvhw" } ``` **2.** We copy over the value `did`, `presentationRequest`, `presentationSubmission`, and `selectedCredentialIdList` from the response to `/prepare`. ```json [request body] { "did": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiWWlkYnltc2loVThaUFEwZ1B3cE1YWmNYM2RIaGxkaldwaEl6clRQaHh4RSIsIngiOiJ6cnVMdHprT2NjYWhNTk5MZjFiTkJVN3AxM254a19CQjU5d1VXelFWVG1jIiwieSI6Inp4UUV1NFF4bmVlQnNGYXNTYkptVjNoUUhJYXVGb3FsNWR6Z1VyTzJ0UjgifQ", "presentationRequest": "openid4vp://authorize?response_type=vp_token&client_id=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fverify&response_mode=direct_post&state=RVgQIBRCUy0J&presentation_definition_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fpd%2FRVgQIBRCUy0J&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=15c72cd0-8c84-45dc-aae2-a310f6075a8f&response_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fverify%2FRVgQIBRCUy0J", "presentationSubmission": { "id": "urn:uuid:c85e079e-d194-44cd-8268-85f4ee819fef", "definition_id": "urn:uuid:c85e079e-d194-44cd-8268-85f4ee819fef", "descriptor_map": [ { "id": "OpenBadgeCredential", "format": "jwt_vp", "path": "$", "path_nested": { "id": "OpenBadgeCredential", "format": "jwt_vc_json", "path": "$.verifiableCredential[0]", "customParameters": {} } } ], "customParameters": {} }, "selectedCredentialIdList": [ "urn:uuid:a072cdef-4912-4cdf-99cb-63c0ad7eeed1" ], } ``` **3. Final Body** ```json [request body] { "did": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiWWlkYnltc2loVThaUFEwZ1B3cE1YWmNYM2RIaGxkaldwaEl6clRQaHh4RSIsIngiOiJ6cnVMdHprT2NjYWhNTk5MZjFiTkJVN3AxM254a19CQjU5d1VXelFWVG1jIiwieSI6Inp4UUV1NFF4bmVlQnNGYXNTYkptVjNoUUhJYXVGb3FsNWR6Z1VyTzJ0UjgifQ", "presentationRequest": "openid4vp://authorize?response_type=vp_token&client_id=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fverify&response_mode=direct_post&state=RVgQIBRCUy0J&presentation_definition_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fpd%2FRVgQIBRCUy0J&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=15c72cd0-8c84-45dc-aae2-a310f6075a8f&response_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fverify%2FRVgQIBRCUy0J", "presentationSubmission": { "id": "urn:uuid:c85e079e-d194-44cd-8268-85f4ee819fef", "definition_id": "urn:uuid:c85e079e-d194-44cd-8268-85f4ee819fef", "descriptor_map": [ { "id": "OpenBadgeCredential", "format": "jwt_vp", "path": "$", "path_nested": { "id": "OpenBadgeCredential", "format": "jwt_vc_json", "path": "$.verifiableCredential[0]", "customParameters": {} } } ], "customParameters": {} }, "selectedCredentialIdList": [ "urn:uuid:a072cdef-4912-4cdf-99cb-63c0ad7eeed1" ], "w3cJwtVpProof": "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pV1dsa1lubHRjMmxvVlRoYVVGRXdaMUIzY0UxWVdtTllNMlJJYUd4a2FsZHdhRWw2Y2xSUWFIaDRSU0lzSW5naU9pSjZjblZNZEhwclQyTmpZV2hOVGs1TVpqRmlUa0pWTjNBeE0yNTRhMTlDUWpVNWQxVlhlbEZXVkcxaklpd2llU0k2SW5wNFVVVjFORkY0Ym1WbFFuTkdZWE5UWWtwdFZqTm9VVWhKWVhWR2IzRnNOV1I2WjFWeVR6SjBVamdpZlEjMCIsInR5cCI6IkpXVCIsImFsZyI6IkVTMjU2In0.eyJzdWIiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pV1dsa1lubHRjMmxvVlRoYVVGRXdaMUIzY0UxWVdtTllNMlJJYUd4a2FsZHdhRWw2Y2xSUWFIaDRSU0lzSW5naU9pSjZjblZNZEhwclQyTmpZV2hOVGs1TVpqRmlUa0pWTjNBeE0yNTRhMTlDUWpVNWQxVlhlbEZXVkcxaklpd2llU0k2SW5wNFVVVjFORkY0Ym1WbFFuTkdZWE5UWWtwdFZqTm9VVWhKWVhWR2IzRnNOV1I2WjFWeVR6SjBVamdpZlEiLCJuYmYiOjE3Mjc0Mjg4NTMsImlhdCI6MTcyNzQyODkxMywianRpIjoidXJuOnV1aWQ6Yzg1ZTA3OWUtZDE5NC00NGNkLTgyNjgtODVmNGVlODE5ZmVmIiwiaXNzIjoiZGlkOmp3azpleUpyZEhraU9pSkZReUlzSW1OeWRpSTZJbEF0TWpVMklpd2lhMmxrSWpvaVdXbGtZbmx0YzJsb1ZUaGFVRkV3WjFCM2NFMVlXbU5ZTTJSSWFHeGthbGR3YUVsNmNsUlFhSGg0UlNJc0luZ2lPaUo2Y25WTWRIcHJUMk5qWVdoTlRrNU1aakZpVGtKVk4zQXhNMjU0YTE5Q1FqVTVkMVZYZWxGV1ZHMWpJaXdpZVNJNklucDRVVVYxTkZGNGJtVmxRbk5HWVhOVFlrcHRWak5vVVVoSllYVkdiM0ZzTldSNloxVnlUekowVWpnaWZRIiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDoyMjIyMi9vcGVuaWQ0dmMvdmVyaWZ5Iiwibm9uY2UiOiIxNWM3MmNkMC04Yzg0LTQ1ZGMtYWFlMi1hMzEwZjYwNzVhOGYiLCJ2cCI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVQcmVzZW50YXRpb24iXSwiaWQiOiJ1cm46dXVpZDpjODVlMDc5ZS1kMTk0LTQ0Y2QtODI2OC04NWY0ZWU4MTlmZWYiLCJob2xkZXIiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pV1dsa1lubHRjMmxvVlRoYVVGRXdaMUIzY0UxWVdtTllNMlJJYUd4a2FsZHdhRWw2Y2xSUWFIaDRSU0lzSW5naU9pSjZjblZNZEhwclQyTmpZV2hOVGs1TVpqRmlUa0pWTjNBeE0yNTRhMTlDUWpVNWQxVlhlbEZXVkcxaklpd2llU0k2SW5wNFVVVjFORkY0Ym1WbFFuTkdZWE5UWWtwdFZqTm9VVWhKWVhWR2IzRnNOV1I2WjFWeVR6SjBVamdpZlEiLCJ2ZXJpZmlhYmxlQ3JlZGVudGlhbCI6WyJleUpyYVdRaU9pSmthV1E2YTJWNU9ubzJUV3RxYjFKb2NURnFVMDVLWkV4cGNuVlRXSEpHUm5oaFozRnllblJhWVZoSWNVaEhWVlJMU21KalRubDNjQ0lzSW5SNWNDSTZJa3BYVkNJc0ltRnNaeUk2SWtWa1JGTkJJbjAuZXlKcGMzTWlPaUprYVdRNmEyVjVPbm8yVFd0cWIxSm9jVEZxVTA1S1pFeHBjblZUV0hKR1JuaGhaM0Z5ZW5SYVlWaEljVWhIVlZSTFNtSmpUbmwzY0NJc0luTjFZaUk2SW1ScFpEcHFkMnM2WlhsS2NtUklhMmxQYVVwR1VYbEpjMGx0VG5sa2FVazJTV3hCZEUxcVZUSkphWGRwWVRKc2EwbHFiMmxYVjJ4cldXNXNkR015Ykc5V1ZHaGhWVVpGZDFveFFqTmpSVEZaVjIxT1dVMHlVa2xoUjNocllXeGtkMkZGYkRaamJGSlJZVWhvTkZKVFNYTkpibWRwVDJsS05tTnVWazFrU0hCeVZESk9hbGxYYUU1VWF6Vk5XbXBHYVZSclNsWk9NMEY0VFRJMU5HRXhPVU5SYWxVMVpERldXR1ZzUmxkV1J6RnFTV2wzYVdWVFNUWkpibkEwVlZWV01VNUdSalJpYlZac1VXNU9SMWxZVGxSWmEzQjBWbXBPYjFWVmFFcFpXRlpIWWpOR2MwNVhValphTVZaNVZIcEtNRlZxWjJsbVVTSXNJblpqSWpwN0lrQmpiMjUwWlhoMElqcGJJbWgwZEhCek9pOHZkM2QzTG5jekxtOXlaeTh5TURFNEwyTnlaV1JsYm5ScFlXeHpMM1l4SWl3aWFIUjBjSE02THk5d2RYSnNMbWx0YzJkc2IySmhiQzV2Y21jdmMzQmxZeTl2WWk5Mk0zQXdMMk52Ym5SbGVIUXVhbk52YmlKZExDSnBaQ0k2SW5WeWJqcDFkV2xrT21Fd056SmpaR1ZtTFRRNU1USXROR05rWmkwNU9XTmlMVFl6WXpCaFpEZGxaV1ZrTVNJc0luUjVjR1VpT2xzaVZtVnlhV1pwWVdKc1pVTnlaV1JsYm5ScFlXd2lMQ0pQY0dWdVFtRmtaMlZEY21Wa1pXNTBhV0ZzSWwwc0ltNWhiV1VpT2lKS1JrWWdlQ0IyWXkxbFpIVWdVR3gxWjBabGMzUWdNeUJKYm5SbGNtOXdaWEpoWW1sc2FYUjVJaXdpYVhOemRXVnlJanA3SW5SNWNHVWlPbHNpVUhKdlptbHNaU0pkTENKcFpDSTZJbVJwWkRwclpYazZlalpOYTJwdlVtaHhNV3BUVGtwa1RHbHlkVk5ZY2taR2VHRm5jWEo2ZEZwaFdFaHhTRWRWVkV0S1ltTk9lWGR3SWl3aWJtRnRaU0k2SWtwdlluTWdabTl5SUhSb1pTQkdkWFIxY21VZ0tFcEdSaWtpTENKMWNtd2lPaUpvZEhSd2N6b3ZMM2QzZHk1cVptWXViM0puTHlJc0ltbHRZV2RsSWpvaWFIUjBjSE02THk5M00yTXRZMk5uTG1kcGRHaDFZaTVwYnk5Mll5MWxaQzl3YkhWblptVnpkQzB4TFRJd01qSXZhVzFoWjJWekwwcEdSbDlNYjJkdlRHOWphM1Z3TG5CdVp5SjlMQ0pwYzNOMVlXNWpaVVJoZEdVaU9pSXlNREkwTFRBNUxUSTNWREE1T2pJeE9qVXpMakUwTlRReE5UUXpNMW9pTENKbGVIQnBjbUYwYVc5dVJHRjBaU0k2SWpJd01qVXRNRGt0TWpkVU1EazZNakU2TlRNdU1UUTFOVE13TURjeVdpSXNJbU55WldSbGJuUnBZV3hUZFdKcVpXTjBJanA3SW1sa0lqb2laR2xrT21wM2F6cGxlVXB5WkVocmFVOXBTa1pSZVVselNXMU9lV1JwU1RaSmJFRjBUV3BWTWtscGQybGhNbXhyU1dwdmFWZFhiR3RaYm14MFl6SnNiMVpVYUdGVlJrVjNXakZDTTJORk1WbFhiVTVaVFRKU1NXRkhlR3RoYkdSM1lVVnNObU5zVWxGaFNHZzBVbE5KYzBsdVoybFBhVW8yWTI1V1RXUkljSEpVTWs1cVdWZG9UbFJyTlUxYWFrWnBWR3RLVms0elFYaE5NalUwWVRFNVExRnFWVFZrTVZaWVpXeEdWMVpITVdwSmFYZHBaVk5KTmtsdWNEUlZWVll4VGtaR05HSnRWbXhSYms1SFdWaE9WRmxyY0hSV2FrNXZWVlZvU2xsWVZrZGlNMFp6VGxkU05sb3hWbmxVZWtvd1ZXcG5hV1pSSWl3aWRIbHdaU0k2V3lKQlkyaHBaWFpsYldWdWRGTjFZbXBsWTNRaVhTd2lZV05vYVdWMlpXMWxiblFpT25zaWFXUWlPaUoxY200NmRYVnBaRHBoWXpJMU5HSmtOUzA0Wm1Ga0xUUmlZakV0T1dReU9TMWxabVE1TXpnMU16WTVNallpTENKMGVYQmxJanBiSWtGamFHbGxkbVZ0Wlc1MElsMHNJbTVoYldVaU9pSktSa1lnZUNCMll5MWxaSFVnVUd4MVowWmxjM1FnTXlCSmJuUmxjbTl3WlhKaFltbHNhWFI1SWl3aVpHVnpZM0pwY0hScGIyNGlPaUpVYUdseklIZGhiR3hsZENCemRYQndiM0owY3lCMGFHVWdkWE5sSUc5bUlGY3pReUJXWlhKcFptbGhZbXhsSUVOeVpXUmxiblJwWVd4eklHRnVaQ0JvWVhNZ1pHVnRiMjV6ZEhKaGRHVmtJR2x1ZEdWeWIzQmxjbUZpYVd4cGRIa2daSFZ5YVc1bklIUm9aU0J3Y21WelpXNTBZWFJwYjI0Z2NtVnhkV1Z6ZENCM2IzSnJabXh2ZHlCa2RYSnBibWNnU2taR0lIZ2dWa010UlVSVklGQnNkV2RHWlhOMElETXVJaXdpWTNKcGRHVnlhV0VpT25zaWRIbHdaU0k2SWtOeWFYUmxjbWxoSWl3aWJtRnljbUYwYVhabElqb2lWMkZzYkdWMElITnZiSFYwYVc5dWN5QndjbTkyYVdSbGNuTWdaV0Z5Ym1Wa0lIUm9hWE1nWW1Ga1oyVWdZbmtnWkdWdGIyNXpkSEpoZEdsdVp5QnBiblJsY205d1pYSmhZbWxzYVhSNUlHUjFjbWx1WnlCMGFHVWdjSEpsYzJWdWRHRjBhVzl1SUhKbGNYVmxjM1FnZDI5eWEyWnNiM2N1SUZSb2FYTWdhVzVqYkhWa1pYTWdjM1ZqWTJWemMyWjFiR3g1SUhKbFkyVnBkbWx1WnlCaElIQnlaWE5sYm5SaGRHbHZiaUJ5WlhGMVpYTjBMQ0JoYkd4dmQybHVaeUIwYUdVZ2FHOXNaR1Z5SUhSdklITmxiR1ZqZENCaGRDQnNaV0Z6ZENCMGQyOGdkSGx3WlhNZ2IyWWdkbVZ5YVdacFlXSnNaU0JqY21Wa1pXNTBhV0ZzY3lCMGJ5QmpjbVZoZEdVZ1lTQjJaWEpwWm1saFlteGxJSEJ5WlhObGJuUmhkR2x2Yml3Z2NtVjBkWEp1YVc1bklIUm9aU0J3Y21WelpXNTBZWFJwYjI0Z2RHOGdkR2hsSUhKbGNYVmxjM1J2Y2l3Z1lXNWtJSEJoYzNOcGJtY2dkbVZ5YVdacFkyRjBhVzl1SUc5bUlIUm9aU0J3Y21WelpXNTBZWFJwYjI0Z1lXNWtJSFJvWlNCcGJtTnNkV1JsWkNCamNtVmtaVzUwYVdGc2N5NGlmU3dpYVcxaFoyVWlPbnNpYVdRaU9pSm9kSFJ3Y3pvdkwzY3pZeTFqWTJjdVoybDBhSFZpTG1sdkwzWmpMV1ZrTDNCc2RXZG1aWE4wTFRNdE1qQXlNeTlwYldGblpYTXZTa1pHTFZaRExVVkVWUzFRVEZWSFJrVlRWRE10WW1Ga1oyVXRhVzFoWjJVdWNHNW5JaXdpZEhsd1pTSTZJa2x0WVdkbEluMTlmWDBzSW1wMGFTSTZJblZ5YmpwMWRXbGtPbUV3TnpKalpHVm1MVFE1TVRJdE5HTmtaaTA1T1dOaUxUWXpZekJoWkRkbFpXVmtNU0lzSW1WNGNDSTZNVGMxT0RrMk5Ea3hNeXdpYVdGMElqb3hOekkzTkRJNE9URXpMQ0p1WW1ZaU9qRTNNamMwTWpnNU1UTjkuS0hSTzJNMkVld2p2bUVOOFkzSHloSnRMUVVFYXkySE9nNEF4ZnUtT04tZFlHRmxwWDYzNWR2UWoxcFcwaXV2OXV5LVhDYmNWV0g5TWJEeGZvS3UzQmciXX19.pAQDUnGESJRhACTyok-ICREUbQ9fCuJvwIAVwyuBInhM2EogJK29Jp4eWwDZ1pRJMyB0NubyjcU3CzFrAzUvhw" } ``` ##### Making the Request We will now pass the body to the `/submit` endpoint. This endpoint will then share all credentials with the verifier and respond with a redirect URI if provided by the verifier. **Option: CURL** **Endpoint:** `/wallet-api/{wallet}/exchange/external_signatures/presentation/submit` ```bash curl -X 'POST' \ 'http://0.0.0.0:7001/wallet-api/{wallet}/exchange/external_signatures/presentation/submit' \ -H 'accept: */*' \ -H 'Content-Type: application/json' \ -d '{ "did": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiWWlkYnltc2loVThaUFEwZ1B3cE1YWmNYM2RIaGxkaldwaEl6clRQaHh4RSIsIngiOiJ6cnVMdHprT2NjYWhNTk5MZjFiTkJVN3AxM254a19CQjU5d1VXelFWVG1jIiwieSI6Inp4UUV1NFF4bmVlQnNGYXNTYkptVjNoUUhJYXVGb3FsNWR6Z1VyTzJ0UjgifQ", "presentationRequest": "openid4vp://authorize?response_type=vp_token&client_id=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fverify&response_mode=direct_post&state=RVgQIBRCUy0J&presentation_definition_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fpd%2FRVgQIBRCUy0J&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=15c72cd0-8c84-45dc-aae2-a310f6075a8f&response_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fverify%2FRVgQIBRCUy0J", "presentationSubmission": { "id": "urn:uuid:c85e079e-d194-44cd-8268-85f4ee819fef", "definition_id": "urn:uuid:c85e079e-d194-44cd-8268-85f4ee819fef", "descriptor_map": [ { "id": "OpenBadgeCredential", "format": "jwt_vp", "path": "$", "path_nested": { "id": "OpenBadgeCredential", "format": "jwt_vc_json", "path": "$.verifiableCredential[0]", "customParameters": {} } } ], "customParameters": {} }, "selectedCredentialIdList": [ "urn:uuid:a072cdef-4912-4cdf-99cb-63c0ad7eeed1" ], "w3cJwtVpProof": "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pV1dsa1lubHRjMmxvVlRoYVVGRXdaMUIzY0UxWVdtTllNMlJJYUd4a2FsZHdhRWw2Y2xSUWFIaDRSU0lzSW5naU9pSjZjblZNZEhwclQyTmpZV2hOVGs1TVpqRmlUa0pWTjNBeE0yNTRhMTlDUWpVNWQxVlhlbEZXVkcxaklpd2llU0k2SW5wNFVVVjFORkY0Ym1WbFFuTkdZWE5UWWtwdFZqTm9VVWhKWVhWR2IzRnNOV1I2WjFWeVR6SjBVamdpZlEjMCIsInR5cCI6IkpXVCIsImFsZyI6IkVTMjU2In0.eyJzdWIiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pV1dsa1lubHRjMmxvVlRoYVVGRXdaMUIzY0UxWVdtTllNMlJJYUd4a2FsZHdhRWw2Y2xSUWFIaDRSU0lzSW5naU9pSjZjblZNZEhwclQyTmpZV2hOVGs1TVpqRmlUa0pWTjNBeE0yNTRhMTlDUWpVNWQxVlhlbEZXVkcxaklpd2llU0k2SW5wNFVVVjFORkY0Ym1WbFFuTkdZWE5UWWtwdFZqTm9VVWhKWVhWR2IzRnNOV1I2WjFWeVR6SjBVamdpZlEiLCJuYmYiOjE3Mjc0Mjg4NTMsImlhdCI6MTcyNzQyODkxMywianRpIjoidXJuOnV1aWQ6Yzg1ZTA3OWUtZDE5NC00NGNkLTgyNjgtODVmNGVlODE5ZmVmIiwiaXNzIjoiZGlkOmp3azpleUpyZEhraU9pSkZReUlzSW1OeWRpSTZJbEF0TWpVMklpd2lhMmxrSWpvaVdXbGtZbmx0YzJsb1ZUaGFVRkV3WjFCM2NFMVlXbU5ZTTJSSWFHeGthbGR3YUVsNmNsUlFhSGg0UlNJc0luZ2lPaUo2Y25WTWRIcHJUMk5qWVdoTlRrNU1aakZpVGtKVk4zQXhNMjU0YTE5Q1FqVTVkMVZYZWxGV1ZHMWpJaXdpZVNJNklucDRVVVYxTkZGNGJtVmxRbk5HWVhOVFlrcHRWak5vVVVoSllYVkdiM0ZzTldSNloxVnlUekowVWpnaWZRIiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDoyMjIyMi9vcGVuaWQ0dmMvdmVyaWZ5Iiwibm9uY2UiOiIxNWM3MmNkMC04Yzg0LTQ1ZGMtYWFlMi1hMzEwZjYwNzVhOGYiLCJ2cCI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVQcmVzZW50YXRpb24iXSwiaWQiOiJ1cm46dXVpZDpjODVlMDc5ZS1kMTk0LTQ0Y2QtODI2OC04NWY0ZWU4MTlmZWYiLCJob2xkZXIiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pV1dsa1lubHRjMmxvVlRoYVVGRXdaMUIzY0UxWVdtTllNMlJJYUd4a2FsZHdhRWw2Y2xSUWFIaDRSU0lzSW5naU9pSjZjblZNZEhwclQyTmpZV2hOVGs1TVpqRmlUa0pWTjNBeE0yNTRhMTlDUWpVNWQxVlhlbEZXVkcxaklpd2llU0k2SW5wNFVVVjFORkY0Ym1WbFFuTkdZWE5UWWtwdFZqTm9VVWhKWVhWR2IzRnNOV1I2WjFWeVR6SjBVamdpZlEiLCJ2ZXJpZmlhYmxlQ3JlZGVudGlhbCI6WyJleUpyYVdRaU9pSmthV1E2YTJWNU9ubzJUV3RxYjFKb2NURnFVMDVLWkV4cGNuVlRXSEpHUm5oaFozRnllblJhWVZoSWNVaEhWVlJMU21KalRubDNjQ0lzSW5SNWNDSTZJa3BYVkNJc0ltRnNaeUk2SWtWa1JGTkJJbjAuZXlKcGMzTWlPaUprYVdRNmEyVjVPbm8yVFd0cWIxSm9jVEZxVTA1S1pFeHBjblZUV0hKR1JuaGhaM0Z5ZW5SYVlWaEljVWhIVlZSTFNtSmpUbmwzY0NJc0luTjFZaUk2SW1ScFpEcHFkMnM2WlhsS2NtUklhMmxQYVVwR1VYbEpjMGx0VG5sa2FVazJTV3hCZEUxcVZUSkphWGRwWVRKc2EwbHFiMmxYVjJ4cldXNXNkR015Ykc5V1ZHaGhWVVpGZDFveFFqTmpSVEZaVjIxT1dVMHlVa2xoUjNocllXeGtkMkZGYkRaamJGSlJZVWhvTkZKVFNYTkpibWRwVDJsS05tTnVWazFrU0hCeVZESk9hbGxYYUU1VWF6Vk5XbXBHYVZSclNsWk9NMEY0VFRJMU5HRXhPVU5SYWxVMVpERldXR1ZzUmxkV1J6RnFTV2wzYVdWVFNUWkpibkEwVlZWV01VNUdSalJpYlZac1VXNU9SMWxZVGxSWmEzQjBWbXBPYjFWVmFFcFpXRlpIWWpOR2MwNVhValphTVZaNVZIcEtNRlZxWjJsbVVTSXNJblpqSWpwN0lrQmpiMjUwWlhoMElqcGJJbWgwZEhCek9pOHZkM2QzTG5jekxtOXlaeTh5TURFNEwyTnlaV1JsYm5ScFlXeHpMM1l4SWl3aWFIUjBjSE02THk5d2RYSnNMbWx0YzJkc2IySmhiQzV2Y21jdmMzQmxZeTl2WWk5Mk0zQXdMMk52Ym5SbGVIUXVhbk52YmlKZExDSnBaQ0k2SW5WeWJqcDFkV2xrT21Fd056SmpaR1ZtTFRRNU1USXROR05rWmkwNU9XTmlMVFl6WXpCaFpEZGxaV1ZrTVNJc0luUjVjR1VpT2xzaVZtVnlhV1pwWVdKc1pVTnlaV1JsYm5ScFlXd2lMQ0pQY0dWdVFtRmtaMlZEY21Wa1pXNTBhV0ZzSWwwc0ltNWhiV1VpT2lKS1JrWWdlQ0IyWXkxbFpIVWdVR3gxWjBabGMzUWdNeUJKYm5SbGNtOXdaWEpoWW1sc2FYUjVJaXdpYVhOemRXVnlJanA3SW5SNWNHVWlPbHNpVUhKdlptbHNaU0pkTENKcFpDSTZJbVJwWkRwclpYazZlalpOYTJwdlVtaHhNV3BUVGtwa1RHbHlkVk5ZY2taR2VHRm5jWEo2ZEZwaFdFaHhTRWRWVkV0S1ltTk9lWGR3SWl3aWJtRnRaU0k2SWtwdlluTWdabTl5SUhSb1pTQkdkWFIxY21VZ0tFcEdSaWtpTENKMWNtd2lPaUpvZEhSd2N6b3ZMM2QzZHk1cVptWXViM0puTHlJc0ltbHRZV2RsSWpvaWFIUjBjSE02THk5M00yTXRZMk5uTG1kcGRHaDFZaTVwYnk5Mll5MWxaQzl3YkhWblptVnpkQzB4TFRJd01qSXZhVzFoWjJWekwwcEdSbDlNYjJkdlRHOWphM1Z3TG5CdVp5SjlMQ0pwYzNOMVlXNWpaVVJoZEdVaU9pSXlNREkwTFRBNUxUSTNWREE1T2pJeE9qVXpMakUwTlRReE5UUXpNMW9pTENKbGVIQnBjbUYwYVc5dVJHRjBaU0k2SWpJd01qVXRNRGt0TWpkVU1EazZNakU2TlRNdU1UUTFOVE13TURjeVdpSXNJbU55WldSbGJuUnBZV3hUZFdKcVpXTjBJanA3SW1sa0lqb2laR2xrT21wM2F6cGxlVXB5WkVocmFVOXBTa1pSZVVselNXMU9lV1JwU1RaSmJFRjBUV3BWTWtscGQybGhNbXhyU1dwdmFWZFhiR3RaYm14MFl6SnNiMVpVYUdGVlJrVjNXakZDTTJORk1WbFhiVTVaVFRKU1NXRkhlR3RoYkdSM1lVVnNObU5zVWxGaFNHZzBVbE5KYzBsdVoybFBhVW8yWTI1V1RXUkljSEpVTWs1cVdWZG9UbFJyTlUxYWFrWnBWR3RLVms0elFYaE5NalUwWVRFNVExRnFWVFZrTVZaWVpXeEdWMVpITVdwSmFYZHBaVk5KTmtsdWNEUlZWVll4VGtaR05HSnRWbXhSYms1SFdWaE9WRmxyY0hSV2FrNXZWVlZvU2xsWVZrZGlNMFp6VGxkU05sb3hWbmxVZWtvd1ZXcG5hV1pSSWl3aWRIbHdaU0k2V3lKQlkyaHBaWFpsYldWdWRGTjFZbXBsWTNRaVhTd2lZV05vYVdWMlpXMWxiblFpT25zaWFXUWlPaUoxY200NmRYVnBaRHBoWXpJMU5HSmtOUzA0Wm1Ga0xUUmlZakV0T1dReU9TMWxabVE1TXpnMU16WTVNallpTENKMGVYQmxJanBiSWtGamFHbGxkbVZ0Wlc1MElsMHNJbTVoYldVaU9pSktSa1lnZUNCMll5MWxaSFVnVUd4MVowWmxjM1FnTXlCSmJuUmxjbTl3WlhKaFltbHNhWFI1SWl3aVpHVnpZM0pwY0hScGIyNGlPaUpVYUdseklIZGhiR3hsZENCemRYQndiM0owY3lCMGFHVWdkWE5sSUc5bUlGY3pReUJXWlhKcFptbGhZbXhsSUVOeVpXUmxiblJwWVd4eklHRnVaQ0JvWVhNZ1pHVnRiMjV6ZEhKaGRHVmtJR2x1ZEdWeWIzQmxjbUZpYVd4cGRIa2daSFZ5YVc1bklIUm9aU0J3Y21WelpXNTBZWFJwYjI0Z2NtVnhkV1Z6ZENCM2IzSnJabXh2ZHlCa2RYSnBibWNnU2taR0lIZ2dWa010UlVSVklGQnNkV2RHWlhOMElETXVJaXdpWTNKcGRHVnlhV0VpT25zaWRIbHdaU0k2SWtOeWFYUmxjbWxoSWl3aWJtRnljbUYwYVhabElqb2lWMkZzYkdWMElITnZiSFYwYVc5dWN5QndjbTkyYVdSbGNuTWdaV0Z5Ym1Wa0lIUm9hWE1nWW1Ga1oyVWdZbmtnWkdWdGIyNXpkSEpoZEdsdVp5QnBiblJsY205d1pYSmhZbWxzYVhSNUlHUjFjbWx1WnlCMGFHVWdjSEpsYzJWdWRHRjBhVzl1SUhKbGNYVmxjM1FnZDI5eWEyWnNiM2N1SUZSb2FYTWdhVzVqYkhWa1pYTWdjM1ZqWTJWemMyWjFiR3g1SUhKbFkyVnBkbWx1WnlCaElIQnlaWE5sYm5SaGRHbHZiaUJ5WlhGMVpYTjBMQ0JoYkd4dmQybHVaeUIwYUdVZ2FHOXNaR1Z5SUhSdklITmxiR1ZqZENCaGRDQnNaV0Z6ZENCMGQyOGdkSGx3WlhNZ2IyWWdkbVZ5YVdacFlXSnNaU0JqY21Wa1pXNTBhV0ZzY3lCMGJ5QmpjbVZoZEdVZ1lTQjJaWEpwWm1saFlteGxJSEJ5WlhObGJuUmhkR2x2Yml3Z2NtVjBkWEp1YVc1bklIUm9aU0J3Y21WelpXNTBZWFJwYjI0Z2RHOGdkR2hsSUhKbGNYVmxjM1J2Y2l3Z1lXNWtJSEJoYzNOcGJtY2dkbVZ5YVdacFkyRjBhVzl1SUc5bUlIUm9aU0J3Y21WelpXNTBZWFJwYjI0Z1lXNWtJSFJvWlNCcGJtTnNkV1JsWkNCamNtVmtaVzUwYVdGc2N5NGlmU3dpYVcxaFoyVWlPbnNpYVdRaU9pSm9kSFJ3Y3pvdkwzY3pZeTFqWTJjdVoybDBhSFZpTG1sdkwzWmpMV1ZrTDNCc2RXZG1aWE4wTFRNdE1qQXlNeTlwYldGblpYTXZTa1pHTFZaRExVVkVWUzFRVEZWSFJrVlRWRE10WW1Ga1oyVXRhVzFoWjJVdWNHNW5JaXdpZEhsd1pTSTZJa2x0WVdkbEluMTlmWDBzSW1wMGFTSTZJblZ5YmpwMWRXbGtPbUV3TnpKalpHVm1MVFE1TVRJdE5HTmtaaTA1T1dOaUxUWXpZekJoWkRkbFpXVmtNU0lzSW1WNGNDSTZNVGMxT0RrMk5Ea3hNeXdpYVdGMElqb3hOekkzTkRJNE9URXpMQ0p1WW1ZaU9qRTNNamMwTWpnNU1UTjkuS0hSTzJNMkVld2p2bUVOOFkzSHloSnRMUVVFYXkySE9nNEF4ZnUtT04tZFlHRmxwWDYzNWR2UWoxcFcwaXV2OXV5LVhDYmNWV0g5TWJEeGZvS3UzQmciXX19.pAQDUnGESJRhACTyok-ICREUbQ9fCuJvwIAVwyuBInhM2EogJK29Jp4eWwDZ1pRJMyB0NubyjcU3CzFrAzUvhw" }' ``` **Path Parameters** - `wallet`: - You can receive the needed wallet id parameter via the `/wallet-api/wallet/accounts/wallets` endpoint. **Body Parameters** - `did` - value copied from response to `/prepare` - `w3cJwtVpProof` - signed JWT from[ step 2 "Signing the VP"](https://docs.walt.id/community-stack/wallet/credential-exchange/guides/present-digital-credentials-external-signatures.md#_2-signing-the-vp). - `presentationRequest` - value copied from response to `/prepare` - `presentationSubmission` - value copied from response to `/prepare` - `selectedCredentialIdList` - value copied from response to `/prepare` **Example Response** If the verifier provided a redirect URI we will receive it as a response. ```json { "redirectUri": null } ``` **Option: W3C SD-JWT** #### 1. Preparation Using the `/prepare` endpoint we will send to the wallet API: The DID owning the credentials, the request from the verifier and the credential IDs matching the request from the verifier which we got in the last step. **Option: CURL** **Endpoint:** `wallet-api/{wallet}/exchange/external_signatures/presentation/prepare` ```bash curl -X 'POST' \ 'http://0.0.0.0:7001/wallet-api/{wallet}/exchange/external_signatures/presentation/prepare' \ -H 'accept: */*' \ -H 'Content-Type: application/json' \ -d '{ "did": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiWWlkYnltc2loVThaUFEwZ1B3cE1YWmNYM2RIaGxkaldwaEl6clRQaHh4RSIsIngiOiJ6cnVMdHprT2NjYWhNTk5MZjFiTkJVN3AxM254a19CQjU5d1VXelFWVG1jIiwieSI6Inp4UUV1NFF4bmVlQnNGYXNTYkptVjNoUUhJYXVGb3FsNWR6Z1VyTzJ0UjgifQ", "presentationRequest": "openid4vp://authorize?response_type=vp_token&client_id=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fverify&response_mode=direct_post&state=RVgQIBRCUy0J&presentation_definition_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fpd%2FRVgQIBRCUy0J&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=15c72cd0-8c84-45dc-aae2-a310f6075a8f&response_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fverify%2FRVgQIBRCUy0J", "selectedCredentialIdList": [ "urn:uuid:a072cdef-4912-4cdf-99cb-63c0ad7eeed1" ] }' ``` **Path Parameters** - `wallet`: - You can receive the needed wallet id parameter via the `/wallet-api/wallet/accounts/wallets` endpoint. **Body Parameters** - `did` - DID to owning credential(s) to be presented. - `presentationRequest` - the presentation request from the verifier. - `selectedCredentialIdList` - IDs of the credential(s) which should be presented. Response from `/matchCredentialForPresentationDefinition` endpoint. **Example Response** ```json { "did": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoib05ZMkFBVFQ4S1BQeG5Jb3V5MXY5SkE3M0NFQnNwbVJEWjNNdDM4Y0dDVSIsIngiOiJmN2lSZllxdFJVSm5ZWFJhY0VLUFFvMExyUVpmaTcxZ0JZRGZhMWVSelYwIiwieSI6InQ0emZwZXhpaHJBZHlYTU1YSUtkRjNJb0FZUlNqWUJWOEo0QVF4Ym5OQlEifQ", "presentationRequest": "openid4vp://authorize?response_type=vp_token&client_id=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fverify&response_mode=direct_post&state=kbXoWmRv3kqp&presentation_definition_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fpd%2FkbXoWmRv3kqp&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=09670caf-e69c-4130-909e-ecf4d15c5337&response_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fverify%2FkbXoWmRv3kqp", "selectedCredentialIdList": [ "urn:uuid:b0bd6fc9-e3db-4d23-ba0e-0705f6524f71" ], "presentationSubmission": { "id": "urn:uuid:a54fc636-a132-4416-ac4b-74a8e316b4f8", "definition_id": "urn:uuid:a54fc636-a132-4416-ac4b-74a8e316b4f8", "descriptor_map": [ { "format": "jwt_vp", "path": "$", "path_nested": { "format": "jwt_vc_json", "path": "$.verifiableCredential[0]", "customParameters": {} } } ], "customParameters": {} }, "w3CJwtVpProofParameters": { "header": { "kid": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoib05ZMkFBVFQ4S1BQeG5Jb3V5MXY5SkE3M0NFQnNwbVJEWjNNdDM4Y0dDVSIsIngiOiJmN2lSZllxdFJVSm5ZWFJhY0VLUFFvMExyUVpmaTcxZ0JZRGZhMWVSelYwIiwieSI6InQ0emZwZXhpaHJBZHlYTU1YSUtkRjNJb0FZUlNqWUJWOEo0QVF4Ym5OQlEifQ#0", "typ": "JWT" }, "payload": { "sub": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoib05ZMkFBVFQ4S1BQeG5Jb3V5MXY5SkE3M0NFQnNwbVJEWjNNdDM4Y0dDVSIsIngiOiJmN2lSZllxdFJVSm5ZWFJhY0VLUFFvMExyUVpmaTcxZ0JZRGZhMWVSelYwIiwieSI6InQ0emZwZXhpaHJBZHlYTU1YSUtkRjNJb0FZUlNqWUJWOEo0QVF4Ym5OQlEifQ", "nbf": 1727429586, "iat": 1727429646, "jti": "urn:uuid:a54fc636-a132-4416-ac4b-74a8e316b4f8", "iss": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoib05ZMkFBVFQ4S1BQeG5Jb3V5MXY5SkE3M0NFQnNwbVJEWjNNdDM4Y0dDVSIsIngiOiJmN2lSZllxdFJVSm5ZWFJhY0VLUFFvMExyUVpmaTcxZ0JZRGZhMWVSelYwIiwieSI6InQ0emZwZXhpaHJBZHlYTU1YSUtkRjNJb0FZUlNqWUJWOEo0QVF4Ym5OQlEifQ", "aud": "http://localhost:22222/openid4vc/verify", "nonce": "09670caf-e69c-4130-909e-ecf4d15c5337", "vp": { "@context": [ "https://www.w3.org/2018/credentials/v1" ], "type": [ "VerifiablePresentation" ], "id": "urn:uuid:a54fc636-a132-4416-ac4b-74a8e316b4f8", "holder": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoib05ZMkFBVFQ4S1BQeG5Jb3V5MXY5SkE3M0NFQnNwbVJEWjNNdDM4Y0dDVSIsIngiOiJmN2lSZllxdFJVSm5ZWFJhY0VLUFFvMExyUVpmaTcxZ0JZRGZhMWVSelYwIiwieSI6InQ0emZwZXhpaHJBZHlYTU1YSUtkRjNJb0FZUlNqWUJWOEo0QVF4Ym5OQlEifQ", "verifiableCredential": [ "eyJraWQiOiJkaWQ6a2V5Ono2TWtqb1JocTFqU05KZExpcnVTWHJGRnhhZ3FyenRaYVhIcUhHVVRLSmJjTnl3cCIsInR5cCI6Imp3dF92Y19qc29uIiwiYWxnIjoiRWREU0EifQ.eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vcHVybC5pbXNnbG9iYWwub3JnL3NwZWMvb2IvdjNwMC9jb250ZXh0Lmpzb24iXSwiaWQiOiJ1cm46dXVpZDpiMGJkNmZjOS1lM2RiLTRkMjMtYmEwZS0wNzA1ZjY1MjRmNzEiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiT3BlbkJhZGdlQ3JlZGVudGlhbCJdLCJpc3N1ZXIiOnsidHlwZSI6WyJQcm9maWxlIl0sImlkIjoiZGlkOmtleTp6Nk1ram9SaHExalNOSmRMaXJ1U1hyRkZ4YWdxcnp0WmFYSHFIR1VUS0piY055d3AiLCJuYW1lIjoiSm9icyBmb3IgdGhlIEZ1dHVyZSAoSkZGKSIsInVybCI6Imh0dHBzOi8vd3d3LmpmZi5vcmcvIiwiaW1hZ2UiOiJodHRwczovL3czYy1jY2cuZ2l0aHViLmlvL3ZjLWVkL3BsdWdmZXN0LTEtMjAyMi9pbWFnZXMvSkZGX0xvZ29Mb2NrdXAucG5nIn0sImlzc3VhbmNlRGF0ZSI6IjIwMjQtMDktMjdUMDk6MzQ6MDUuOTk0MDE5MjI4WiIsImV4cGlyYXRpb25EYXRlIjoiMjAyNS0wOS0yN1QwOTozNDowNS45OTQxMzY0NjBaIiwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pYjA1Wk1rRkJWRlE0UzFCUWVHNUpiM1Y1TVhZNVNrRTNNME5GUW5Od2JWSkVXak5OZERNNFkwZERWU0lzSW5naU9pSm1OMmxTWmxseGRGSlZTbTVaV0ZKaFkwVkxVRkZ2TUV4eVVWcG1hVGN4WjBKWlJHWmhNV1ZTZWxZd0lpd2llU0k2SW5RMGVtWndaWGhwYUhKQlpIbFlUVTFZU1V0a1JqTkpiMEZaVWxOcVdVSldPRW8wUVZGNFltNU9RbEVpZlEiLCJ0eXBlIjpbIkFjaGlldmVtZW50U3ViamVjdCJdLCJhY2hpZXZlbWVudCI6eyJpZCI6InVybjp1dWlkOmFjMjU0YmQ1LThmYWQtNGJiMS05ZDI5LWVmZDkzODUzNjkyNiIsInR5cGUiOlsiQWNoaWV2ZW1lbnQiXSwibmFtZSI6IkpGRiB4IHZjLWVkdSBQbHVnRmVzdCAzIEludGVyb3BlcmFiaWxpdHkiLCJkZXNjcmlwdGlvbiI6IlRoaXMgd2FsbGV0IHN1cHBvcnRzIHRoZSB1c2Ugb2YgVzNDIFZlcmlmaWFibGUgQ3JlZGVudGlhbHMgYW5kIGhhcyBkZW1vbnN0cmF0ZWQgaW50ZXJvcGVyYWJpbGl0eSBkdXJpbmcgdGhlIHByZXNlbnRhdGlvbiByZXF1ZXN0IHdvcmtmbG93IGR1cmluZyBKRkYgeCBWQy1FRFUgUGx1Z0Zlc3QgMy4iLCJjcml0ZXJpYSI6eyJ0eXBlIjoiQ3JpdGVyaWEiLCJuYXJyYXRpdmUiOiJXYWxsZXQgc29sdXRpb25zIHByb3ZpZGVycyBlYXJuZWQgdGhpcyBiYWRnZSBieSBkZW1vbnN0cmF0aW5nIGludGVyb3BlcmFiaWxpdHkgZHVyaW5nIHRoZSBwcmVzZW50YXRpb24gcmVxdWVzdCB3b3JrZmxvdy4gVGhpcyBpbmNsdWRlcyBzdWNjZXNzZnVsbHkgcmVjZWl2aW5nIGEgcHJlc2VudGF0aW9uIHJlcXVlc3QsIGFsbG93aW5nIHRoZSBob2xkZXIgdG8gc2VsZWN0IGF0IGxlYXN0IHR3byB0eXBlcyBvZiB2ZXJpZmlhYmxlIGNyZWRlbnRpYWxzIHRvIGNyZWF0ZSBhIHZlcmlmaWFibGUgcHJlc2VudGF0aW9uLCByZXR1cm5pbmcgdGhlIHByZXNlbnRhdGlvbiB0byB0aGUgcmVxdWVzdG9yLCBhbmQgcGFzc2luZyB2ZXJpZmljYXRpb24gb2YgdGhlIHByZXNlbnRhdGlvbiBhbmQgdGhlIGluY2x1ZGVkIGNyZWRlbnRpYWxzLiJ9LCJpbWFnZSI6eyJpZCI6Imh0dHBzOi8vdzNjLWNjZy5naXRodWIuaW8vdmMtZWQvcGx1Z2Zlc3QtMy0yMDIzL2ltYWdlcy9KRkYtVkMtRURVLVBMVUdGRVNUMy1iYWRnZS1pbWFnZS5wbmciLCJ0eXBlIjoiSW1hZ2UifX19LCJqdGkiOiJ1cm46dXVpZDpiMGJkNmZjOS1lM2RiLTRkMjMtYmEwZS0wNzA1ZjY1MjRmNzEiLCJleHAiOjE3NTg5NjU2NDUsImlhdCI6MTcyNzQyOTY0NSwibmJmIjoxNzI3NDI5NjQ1LCJfc2QiOlsiZlkzOXFvR3dETWt1STlLWV9OUWdkbXVLSkhLNGhkU2hCczVzTUNtUk8yVSJdfQ.TumoxEnpugt9uRfi98KJCjEUi2XKvkyryPPZ5zBNyIBbuEZkuGOYy8R6s8YjJnv7dWsEzWbO4MFeLFLD3f9oBQ~WyJvV2tvcnV3NEcxam5BMHFsUjVVSElnPT0iLCJuYW1lIiwiSkZGIHggdmMtZWR1IFBsdWdGZXN0IDMgSW50ZXJvcGVyYWJpbGl0eSJd" ] } } } } ``` In the response we will find a `w3CJwtVpProofParameters` key, which represents the header and payload info that we need to put into a JWT. This JWT then needs to be signed with the key of the holder which is stored in an external system. All the other information we simply pass on to the `/submit` endpoint next to the signed JWT in the step after next. #### 2. Signing the VP Now you need to create a `jwt` with the header and payload described in `w3CJwtVpProofParameters` received by the response to `/prepare` and sign it with the key of the holder. #### 3. Submit With the `/submit` endpoint we will send most of the information returned by the `/prepare` endpoint next to the created and signed VP token from step 2. ##### Preparing the body of the request **1.** We add the signed VP token as `w3cJwtVpProof` ```json [request body] { "w3cJwtVpProof": "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pYjA1Wk1rRkJWRlE0UzFCUWVHNUpiM1Y1TVhZNVNrRTNNME5GUW5Od2JWSkVXak5OZERNNFkwZERWU0lzSW5naU9pSm1OMmxTWmxseGRGSlZTbTVaV0ZKaFkwVkxVRkZ2TUV4eVVWcG1hVGN4WjBKWlJHWmhNV1ZTZWxZd0lpd2llU0k2SW5RMGVtWndaWGhwYUhKQlpIbFlUVTFZU1V0a1JqTkpiMEZaVWxOcVdVSldPRW8wUVZGNFltNU9RbEVpZlEjMCIsInR5cCI6IkpXVCIsImFsZyI6IkVTMjU2In0.eyJzdWIiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pYjA1Wk1rRkJWRlE0UzFCUWVHNUpiM1Y1TVhZNVNrRTNNME5GUW5Od2JWSkVXak5OZERNNFkwZERWU0lzSW5naU9pSm1OMmxTWmxseGRGSlZTbTVaV0ZKaFkwVkxVRkZ2TUV4eVVWcG1hVGN4WjBKWlJHWmhNV1ZTZWxZd0lpd2llU0k2SW5RMGVtWndaWGhwYUhKQlpIbFlUVTFZU1V0a1JqTkpiMEZaVWxOcVdVSldPRW8wUVZGNFltNU9RbEVpZlEiLCJuYmYiOjE3Mjc0Mjk1ODYsImlhdCI6MTcyNzQyOTY0NiwianRpIjoidXJuOnV1aWQ6YTU0ZmM2MzYtYTEzMi00NDE2LWFjNGItNzRhOGUzMTZiNGY4IiwiaXNzIjoiZGlkOmp3azpleUpyZEhraU9pSkZReUlzSW1OeWRpSTZJbEF0TWpVMklpd2lhMmxrSWpvaWIwNVpNa0ZCVkZRNFMxQlFlRzVKYjNWNU1YWTVTa0UzTTBORlFuTndiVkpFV2pOTmRETTRZMGREVlNJc0luZ2lPaUptTjJsU1psbHhkRkpWU201WldGSmhZMFZMVUZGdk1FeHlVVnBtYVRjeFowSlpSR1poTVdWU2VsWXdJaXdpZVNJNkluUTBlbVp3WlhocGFISkJaSGxZVFUxWVNVdGtSak5KYjBGWlVsTnFXVUpXT0VvMFFWRjRZbTVPUWxFaWZRIiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDoyMjIyMi9vcGVuaWQ0dmMvdmVyaWZ5Iiwibm9uY2UiOiIwOTY3MGNhZi1lNjljLTQxMzAtOTA5ZS1lY2Y0ZDE1YzUzMzciLCJ2cCI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVQcmVzZW50YXRpb24iXSwiaWQiOiJ1cm46dXVpZDphNTRmYzYzNi1hMTMyLTQ0MTYtYWM0Yi03NGE4ZTMxNmI0ZjgiLCJob2xkZXIiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pYjA1Wk1rRkJWRlE0UzFCUWVHNUpiM1Y1TVhZNVNrRTNNME5GUW5Od2JWSkVXak5OZERNNFkwZERWU0lzSW5naU9pSm1OMmxTWmxseGRGSlZTbTVaV0ZKaFkwVkxVRkZ2TUV4eVVWcG1hVGN4WjBKWlJHWmhNV1ZTZWxZd0lpd2llU0k2SW5RMGVtWndaWGhwYUhKQlpIbFlUVTFZU1V0a1JqTkpiMEZaVWxOcVdVSldPRW8wUVZGNFltNU9RbEVpZlEiLCJ2ZXJpZmlhYmxlQ3JlZGVudGlhbCI6WyJleUpyYVdRaU9pSmthV1E2YTJWNU9ubzJUV3RxYjFKb2NURnFVMDVLWkV4cGNuVlRXSEpHUm5oaFozRnllblJhWVZoSWNVaEhWVlJMU21KalRubDNjQ0lzSW5SNWNDSTZJbXAzZEY5MlkxOXFjMjl1SWl3aVlXeG5Jam9pUldSRVUwRWlmUS5leUpBWTI5dWRHVjRkQ0k2V3lKb2RIUndjem92TDNkM2R5NTNNeTV2Y21jdk1qQXhPQzlqY21Wa1pXNTBhV0ZzY3k5Mk1TSXNJbWgwZEhCek9pOHZjSFZ5YkM1cGJYTm5iRzlpWVd3dWIzSm5MM053WldNdmIySXZkak53TUM5amIyNTBaWGgwTG1wemIyNGlYU3dpYVdRaU9pSjFjbTQ2ZFhWcFpEcGlNR0prTm1aak9TMWxNMlJpTFRSa01qTXRZbUV3WlMwd056QTFaalkxTWpSbU56RWlMQ0owZVhCbElqcGJJbFpsY21sbWFXRmliR1ZEY21Wa1pXNTBhV0ZzSWl3aVQzQmxia0poWkdkbFEzSmxaR1Z1ZEdsaGJDSmRMQ0pwYzNOMVpYSWlPbnNpZEhsd1pTSTZXeUpRY205bWFXeGxJbDBzSW1sa0lqb2laR2xrT210bGVUcDZOazFyYW05U2FIRXhhbE5PU21STWFYSjFVMWh5UmtaNFlXZHhjbnAwV21GWVNIRklSMVZVUzBwaVkwNTVkM0FpTENKdVlXMWxJam9pU205aWN5Qm1iM0lnZEdobElFWjFkSFZ5WlNBb1NrWkdLU0lzSW5WeWJDSTZJbWgwZEhCek9pOHZkM2QzTG1wbVppNXZjbWN2SWl3aWFXMWhaMlVpT2lKb2RIUndjem92TDNjell5MWpZMmN1WjJsMGFIVmlMbWx2TDNaakxXVmtMM0JzZFdkbVpYTjBMVEV0TWpBeU1pOXBiV0ZuWlhNdlNrWkdYMHh2WjI5TWIyTnJkWEF1Y0c1bkluMHNJbWx6YzNWaGJtTmxSR0YwWlNJNklqSXdNalF0TURrdE1qZFVNRGs2TXpRNk1EVXVPVGswTURFNU1qSTRXaUlzSW1WNGNHbHlZWFJwYjI1RVlYUmxJam9pTWpBeU5TMHdPUzB5TjFRd09Ub3pORG93TlM0NU9UUXhNelkwTmpCYUlpd2lZM0psWkdWdWRHbGhiRk4xWW1wbFkzUWlPbnNpYVdRaU9pSmthV1E2YW5kck9tVjVTbkprU0d0cFQybEtSbEY1U1hOSmJVNTVaR2xKTmtsc1FYUk5hbFV5U1dsM2FXRXliR3RKYW05cFlqQTFXazFyUmtKV1JsRTBVekZDVVdWSE5VcGlNMVkxVFZoWk5WTnJSVE5OTUU1R1VXNU9kMkpXU2tWWGFrNU9aRVJOTkZrd1pFUldVMGx6U1c1bmFVOXBTbTFPTW14VFdteHNlR1JHU2xaVGJUVmFWMFpLYUZrd1ZreFZSa1oyVFVWNGVWVldjRzFoVkdONFdqQktXbEpIV21oTlYxWlRaV3haZDBscGQybGxVMGsyU1c1Uk1HVnRXbmRhV0dod1lVaEtRbHBJYkZsVVZURlpVMVYwYTFKcVRrcGlNRVphVld4T2NWZFZTbGRQUlc4d1VWWkdORmx0TlU5UmJFVnBabEVpTENKMGVYQmxJanBiSWtGamFHbGxkbVZ0Wlc1MFUzVmlhbVZqZENKZExDSmhZMmhwWlhabGJXVnVkQ0k2ZXlKcFpDSTZJblZ5YmpwMWRXbGtPbUZqTWpVMFltUTFMVGhtWVdRdE5HSmlNUzA1WkRJNUxXVm1aRGt6T0RVek5qa3lOaUlzSW5SNWNHVWlPbHNpUVdOb2FXVjJaVzFsYm5RaVhTd2libUZ0WlNJNklrcEdSaUI0SUhaakxXVmtkU0JRYkhWblJtVnpkQ0F6SUVsdWRHVnliM0JsY21GaWFXeHBkSGtpTENKa1pYTmpjbWx3ZEdsdmJpSTZJbFJvYVhNZ2QyRnNiR1YwSUhOMWNIQnZjblJ6SUhSb1pTQjFjMlVnYjJZZ1Z6TkRJRlpsY21sbWFXRmliR1VnUTNKbFpHVnVkR2xoYkhNZ1lXNWtJR2hoY3lCa1pXMXZibk4wY21GMFpXUWdhVzUwWlhKdmNHVnlZV0pwYkdsMGVTQmtkWEpwYm1jZ2RHaGxJSEJ5WlhObGJuUmhkR2x2YmlCeVpYRjFaWE4wSUhkdmNtdG1iRzkzSUdSMWNtbHVaeUJLUmtZZ2VDQldReTFGUkZVZ1VHeDFaMFpsYzNRZ015NGlMQ0pqY21sMFpYSnBZU0k2ZXlKMGVYQmxJam9pUTNKcGRHVnlhV0VpTENKdVlYSnlZWFJwZG1VaU9pSlhZV3hzWlhRZ2MyOXNkWFJwYjI1eklIQnliM1pwWkdWeWN5QmxZWEp1WldRZ2RHaHBjeUJpWVdSblpTQmllU0JrWlcxdmJuTjBjbUYwYVc1bklHbHVkR1Z5YjNCbGNtRmlhV3hwZEhrZ1pIVnlhVzVuSUhSb1pTQndjbVZ6Wlc1MFlYUnBiMjRnY21WeGRXVnpkQ0IzYjNKclpteHZkeTRnVkdocGN5QnBibU5zZFdSbGN5QnpkV05qWlhOelpuVnNiSGtnY21WalpXbDJhVzVuSUdFZ2NISmxjMlZ1ZEdGMGFXOXVJSEpsY1hWbGMzUXNJR0ZzYkc5M2FXNW5JSFJvWlNCb2IyeGtaWElnZEc4Z2MyVnNaV04wSUdGMElHeGxZWE4wSUhSM2J5QjBlWEJsY3lCdlppQjJaWEpwWm1saFlteGxJR055WldSbGJuUnBZV3h6SUhSdklHTnlaV0YwWlNCaElIWmxjbWxtYVdGaWJHVWdjSEpsYzJWdWRHRjBhVzl1TENCeVpYUjFjbTVwYm1jZ2RHaGxJSEJ5WlhObGJuUmhkR2x2YmlCMGJ5QjBhR1VnY21WeGRXVnpkRzl5TENCaGJtUWdjR0Z6YzJsdVp5QjJaWEpwWm1sallYUnBiMjRnYjJZZ2RHaGxJSEJ5WlhObGJuUmhkR2x2YmlCaGJtUWdkR2hsSUdsdVkyeDFaR1ZrSUdOeVpXUmxiblJwWVd4ekxpSjlMQ0pwYldGblpTSTZleUpwWkNJNkltaDBkSEJ6T2k4dmR6TmpMV05qWnk1bmFYUm9kV0l1YVc4dmRtTXRaV1F2Y0d4MVoyWmxjM1F0TXkweU1ESXpMMmx0WVdkbGN5OUtSa1l0VmtNdFJVUlZMVkJNVlVkR1JWTlVNeTFpWVdSblpTMXBiV0ZuWlM1d2JtY2lMQ0owZVhCbElqb2lTVzFoWjJVaWZYMTlMQ0pxZEdraU9pSjFjbTQ2ZFhWcFpEcGlNR0prTm1aak9TMWxNMlJpTFRSa01qTXRZbUV3WlMwd056QTFaalkxTWpSbU56RWlMQ0psZUhBaU9qRTNOVGc1TmpVMk5EVXNJbWxoZENJNk1UY3lOelF5T1RZME5Td2libUptSWpveE56STNOREk1TmpRMUxDSmZjMlFpT2xzaVpsa3pPWEZ2UjNkRVRXdDFTVGxMV1Y5T1VXZGtiWFZMU2toTE5HaGtVMmhDY3pWelRVTnRVazh5VlNKZGZRLlR1bW94RW5wdWd0OXVSZmk5OEtKQ2pFVWkyWEt2a3lyeVBQWjV6Qk55SUJidUVaa3VHT1l5OFI2czhZakpudjdkV3NFeldiTzRNRmVMRkxEM2Y5b0JRfld5SnZWMnR2Y25WM05FY3hhbTVCTUhGc1VqVlZTRWxuUFQwaUxDSnVZVzFsSWl3aVNrWkdJSGdnZG1NdFpXUjFJRkJzZFdkR1pYTjBJRE1nU1c1MFpYSnZjR1Z5WVdKcGJHbDBlU0pkIl19fQ.WxYlmga0lMAFEbHWXqJ0IKLU-hyhFgBtOTk1KKUsT9wfdC5im9BQfemrTE1sTvKOsiuZ9RBBLReeGaUalpTcgg" } ``` **2.** We copy over the value `did`, `presentationRequest`, `presentationSubmission`, and `selectedCredentialIdList` from the response to `/prepare`. ```json [request body] { "did": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoib05ZMkFBVFQ4S1BQeG5Jb3V5MXY5SkE3M0NFQnNwbVJEWjNNdDM4Y0dDVSIsIngiOiJmN2lSZllxdFJVSm5ZWFJhY0VLUFFvMExyUVpmaTcxZ0JZRGZhMWVSelYwIiwieSI6InQ0emZwZXhpaHJBZHlYTU1YSUtkRjNJb0FZUlNqWUJWOEo0QVF4Ym5OQlEifQ", "presentationRequest": "openid4vp://authorize?response_type=vp_token&client_id=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fverify&response_mode=direct_post&state=kbXoWmRv3kqp&presentation_definition_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fpd%2FkbXoWmRv3kqp&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=09670caf-e69c-4130-909e-ecf4d15c5337&response_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fverify%2FkbXoWmRv3kqp", "presentationSubmission": { "id": "urn:uuid:a54fc636-a132-4416-ac4b-74a8e316b4f8", "definition_id": "urn:uuid:a54fc636-a132-4416-ac4b-74a8e316b4f8", "descriptor_map": [ { "format": "jwt_vp", "path": "$", "path_nested": { "format": "jwt_vc_json", "path": "$.verifiableCredential[0]", "customParameters": {} } } ], "customParameters": {} }, "selectedCredentialIdList": [ "urn:uuid:b0bd6fc9-e3db-4d23-ba0e-0705f6524f71" ] } ``` **3. Final Body** ```json [request body] { "did": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoib05ZMkFBVFQ4S1BQeG5Jb3V5MXY5SkE3M0NFQnNwbVJEWjNNdDM4Y0dDVSIsIngiOiJmN2lSZllxdFJVSm5ZWFJhY0VLUFFvMExyUVpmaTcxZ0JZRGZhMWVSelYwIiwieSI6InQ0emZwZXhpaHJBZHlYTU1YSUtkRjNJb0FZUlNqWUJWOEo0QVF4Ym5OQlEifQ", "presentationRequest": "openid4vp://authorize?response_type=vp_token&client_id=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fverify&response_mode=direct_post&state=kbXoWmRv3kqp&presentation_definition_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fpd%2FkbXoWmRv3kqp&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=09670caf-e69c-4130-909e-ecf4d15c5337&response_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fverify%2FkbXoWmRv3kqp", "presentationSubmission": { "id": "urn:uuid:a54fc636-a132-4416-ac4b-74a8e316b4f8", "definition_id": "urn:uuid:a54fc636-a132-4416-ac4b-74a8e316b4f8", "descriptor_map": [ { "format": "jwt_vp", "path": "$", "path_nested": { "format": "jwt_vc_json", "path": "$.verifiableCredential[0]", "customParameters": {} } } ], "customParameters": {} }, "selectedCredentialIdList": [ "urn:uuid:b0bd6fc9-e3db-4d23-ba0e-0705f6524f71" ], "w3cJwtVpProof": "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pYjA1Wk1rRkJWRlE0UzFCUWVHNUpiM1Y1TVhZNVNrRTNNME5GUW5Od2JWSkVXak5OZERNNFkwZERWU0lzSW5naU9pSm1OMmxTWmxseGRGSlZTbTVaV0ZKaFkwVkxVRkZ2TUV4eVVWcG1hVGN4WjBKWlJHWmhNV1ZTZWxZd0lpd2llU0k2SW5RMGVtWndaWGhwYUhKQlpIbFlUVTFZU1V0a1JqTkpiMEZaVWxOcVdVSldPRW8wUVZGNFltNU9RbEVpZlEjMCIsInR5cCI6IkpXVCIsImFsZyI6IkVTMjU2In0.eyJzdWIiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pYjA1Wk1rRkJWRlE0UzFCUWVHNUpiM1Y1TVhZNVNrRTNNME5GUW5Od2JWSkVXak5OZERNNFkwZERWU0lzSW5naU9pSm1OMmxTWmxseGRGSlZTbTVaV0ZKaFkwVkxVRkZ2TUV4eVVWcG1hVGN4WjBKWlJHWmhNV1ZTZWxZd0lpd2llU0k2SW5RMGVtWndaWGhwYUhKQlpIbFlUVTFZU1V0a1JqTkpiMEZaVWxOcVdVSldPRW8wUVZGNFltNU9RbEVpZlEiLCJuYmYiOjE3Mjc0Mjk1ODYsImlhdCI6MTcyNzQyOTY0NiwianRpIjoidXJuOnV1aWQ6YTU0ZmM2MzYtYTEzMi00NDE2LWFjNGItNzRhOGUzMTZiNGY4IiwiaXNzIjoiZGlkOmp3azpleUpyZEhraU9pSkZReUlzSW1OeWRpSTZJbEF0TWpVMklpd2lhMmxrSWpvaWIwNVpNa0ZCVkZRNFMxQlFlRzVKYjNWNU1YWTVTa0UzTTBORlFuTndiVkpFV2pOTmRETTRZMGREVlNJc0luZ2lPaUptTjJsU1psbHhkRkpWU201WldGSmhZMFZMVUZGdk1FeHlVVnBtYVRjeFowSlpSR1poTVdWU2VsWXdJaXdpZVNJNkluUTBlbVp3WlhocGFISkJaSGxZVFUxWVNVdGtSak5KYjBGWlVsTnFXVUpXT0VvMFFWRjRZbTVPUWxFaWZRIiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDoyMjIyMi9vcGVuaWQ0dmMvdmVyaWZ5Iiwibm9uY2UiOiIwOTY3MGNhZi1lNjljLTQxMzAtOTA5ZS1lY2Y0ZDE1YzUzMzciLCJ2cCI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVQcmVzZW50YXRpb24iXSwiaWQiOiJ1cm46dXVpZDphNTRmYzYzNi1hMTMyLTQ0MTYtYWM0Yi03NGE4ZTMxNmI0ZjgiLCJob2xkZXIiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pYjA1Wk1rRkJWRlE0UzFCUWVHNUpiM1Y1TVhZNVNrRTNNME5GUW5Od2JWSkVXak5OZERNNFkwZERWU0lzSW5naU9pSm1OMmxTWmxseGRGSlZTbTVaV0ZKaFkwVkxVRkZ2TUV4eVVWcG1hVGN4WjBKWlJHWmhNV1ZTZWxZd0lpd2llU0k2SW5RMGVtWndaWGhwYUhKQlpIbFlUVTFZU1V0a1JqTkpiMEZaVWxOcVdVSldPRW8wUVZGNFltNU9RbEVpZlEiLCJ2ZXJpZmlhYmxlQ3JlZGVudGlhbCI6WyJleUpyYVdRaU9pSmthV1E2YTJWNU9ubzJUV3RxYjFKb2NURnFVMDVLWkV4cGNuVlRXSEpHUm5oaFozRnllblJhWVZoSWNVaEhWVlJMU21KalRubDNjQ0lzSW5SNWNDSTZJbXAzZEY5MlkxOXFjMjl1SWl3aVlXeG5Jam9pUldSRVUwRWlmUS5leUpBWTI5dWRHVjRkQ0k2V3lKb2RIUndjem92TDNkM2R5NTNNeTV2Y21jdk1qQXhPQzlqY21Wa1pXNTBhV0ZzY3k5Mk1TSXNJbWgwZEhCek9pOHZjSFZ5YkM1cGJYTm5iRzlpWVd3dWIzSm5MM053WldNdmIySXZkak53TUM5amIyNTBaWGgwTG1wemIyNGlYU3dpYVdRaU9pSjFjbTQ2ZFhWcFpEcGlNR0prTm1aak9TMWxNMlJpTFRSa01qTXRZbUV3WlMwd056QTFaalkxTWpSbU56RWlMQ0owZVhCbElqcGJJbFpsY21sbWFXRmliR1ZEY21Wa1pXNTBhV0ZzSWl3aVQzQmxia0poWkdkbFEzSmxaR1Z1ZEdsaGJDSmRMQ0pwYzNOMVpYSWlPbnNpZEhsd1pTSTZXeUpRY205bWFXeGxJbDBzSW1sa0lqb2laR2xrT210bGVUcDZOazFyYW05U2FIRXhhbE5PU21STWFYSjFVMWh5UmtaNFlXZHhjbnAwV21GWVNIRklSMVZVUzBwaVkwNTVkM0FpTENKdVlXMWxJam9pU205aWN5Qm1iM0lnZEdobElFWjFkSFZ5WlNBb1NrWkdLU0lzSW5WeWJDSTZJbWgwZEhCek9pOHZkM2QzTG1wbVppNXZjbWN2SWl3aWFXMWhaMlVpT2lKb2RIUndjem92TDNjell5MWpZMmN1WjJsMGFIVmlMbWx2TDNaakxXVmtMM0JzZFdkbVpYTjBMVEV0TWpBeU1pOXBiV0ZuWlhNdlNrWkdYMHh2WjI5TWIyTnJkWEF1Y0c1bkluMHNJbWx6YzNWaGJtTmxSR0YwWlNJNklqSXdNalF0TURrdE1qZFVNRGs2TXpRNk1EVXVPVGswTURFNU1qSTRXaUlzSW1WNGNHbHlZWFJwYjI1RVlYUmxJam9pTWpBeU5TMHdPUzB5TjFRd09Ub3pORG93TlM0NU9UUXhNelkwTmpCYUlpd2lZM0psWkdWdWRHbGhiRk4xWW1wbFkzUWlPbnNpYVdRaU9pSmthV1E2YW5kck9tVjVTbkprU0d0cFQybEtSbEY1U1hOSmJVNTVaR2xKTmtsc1FYUk5hbFV5U1dsM2FXRXliR3RKYW05cFlqQTFXazFyUmtKV1JsRTBVekZDVVdWSE5VcGlNMVkxVFZoWk5WTnJSVE5OTUU1R1VXNU9kMkpXU2tWWGFrNU9aRVJOTkZrd1pFUldVMGx6U1c1bmFVOXBTbTFPTW14VFdteHNlR1JHU2xaVGJUVmFWMFpLYUZrd1ZreFZSa1oyVFVWNGVWVldjRzFoVkdONFdqQktXbEpIV21oTlYxWlRaV3haZDBscGQybGxVMGsyU1c1Uk1HVnRXbmRhV0dod1lVaEtRbHBJYkZsVVZURlpVMVYwYTFKcVRrcGlNRVphVld4T2NWZFZTbGRQUlc4d1VWWkdORmx0TlU5UmJFVnBabEVpTENKMGVYQmxJanBiSWtGamFHbGxkbVZ0Wlc1MFUzVmlhbVZqZENKZExDSmhZMmhwWlhabGJXVnVkQ0k2ZXlKcFpDSTZJblZ5YmpwMWRXbGtPbUZqTWpVMFltUTFMVGhtWVdRdE5HSmlNUzA1WkRJNUxXVm1aRGt6T0RVek5qa3lOaUlzSW5SNWNHVWlPbHNpUVdOb2FXVjJaVzFsYm5RaVhTd2libUZ0WlNJNklrcEdSaUI0SUhaakxXVmtkU0JRYkhWblJtVnpkQ0F6SUVsdWRHVnliM0JsY21GaWFXeHBkSGtpTENKa1pYTmpjbWx3ZEdsdmJpSTZJbFJvYVhNZ2QyRnNiR1YwSUhOMWNIQnZjblJ6SUhSb1pTQjFjMlVnYjJZZ1Z6TkRJRlpsY21sbWFXRmliR1VnUTNKbFpHVnVkR2xoYkhNZ1lXNWtJR2hoY3lCa1pXMXZibk4wY21GMFpXUWdhVzUwWlhKdmNHVnlZV0pwYkdsMGVTQmtkWEpwYm1jZ2RHaGxJSEJ5WlhObGJuUmhkR2x2YmlCeVpYRjFaWE4wSUhkdmNtdG1iRzkzSUdSMWNtbHVaeUJLUmtZZ2VDQldReTFGUkZVZ1VHeDFaMFpsYzNRZ015NGlMQ0pqY21sMFpYSnBZU0k2ZXlKMGVYQmxJam9pUTNKcGRHVnlhV0VpTENKdVlYSnlZWFJwZG1VaU9pSlhZV3hzWlhRZ2MyOXNkWFJwYjI1eklIQnliM1pwWkdWeWN5QmxZWEp1WldRZ2RHaHBjeUJpWVdSblpTQmllU0JrWlcxdmJuTjBjbUYwYVc1bklHbHVkR1Z5YjNCbGNtRmlhV3hwZEhrZ1pIVnlhVzVuSUhSb1pTQndjbVZ6Wlc1MFlYUnBiMjRnY21WeGRXVnpkQ0IzYjNKclpteHZkeTRnVkdocGN5QnBibU5zZFdSbGN5QnpkV05qWlhOelpuVnNiSGtnY21WalpXbDJhVzVuSUdFZ2NISmxjMlZ1ZEdGMGFXOXVJSEpsY1hWbGMzUXNJR0ZzYkc5M2FXNW5JSFJvWlNCb2IyeGtaWElnZEc4Z2MyVnNaV04wSUdGMElHeGxZWE4wSUhSM2J5QjBlWEJsY3lCdlppQjJaWEpwWm1saFlteGxJR055WldSbGJuUnBZV3h6SUhSdklHTnlaV0YwWlNCaElIWmxjbWxtYVdGaWJHVWdjSEpsYzJWdWRHRjBhVzl1TENCeVpYUjFjbTVwYm1jZ2RHaGxJSEJ5WlhObGJuUmhkR2x2YmlCMGJ5QjBhR1VnY21WeGRXVnpkRzl5TENCaGJtUWdjR0Z6YzJsdVp5QjJaWEpwWm1sallYUnBiMjRnYjJZZ2RHaGxJSEJ5WlhObGJuUmhkR2x2YmlCaGJtUWdkR2hsSUdsdVkyeDFaR1ZrSUdOeVpXUmxiblJwWVd4ekxpSjlMQ0pwYldGblpTSTZleUpwWkNJNkltaDBkSEJ6T2k4dmR6TmpMV05qWnk1bmFYUm9kV0l1YVc4dmRtTXRaV1F2Y0d4MVoyWmxjM1F0TXkweU1ESXpMMmx0WVdkbGN5OUtSa1l0VmtNdFJVUlZMVkJNVlVkR1JWTlVNeTFpWVdSblpTMXBiV0ZuWlM1d2JtY2lMQ0owZVhCbElqb2lTVzFoWjJVaWZYMTlMQ0pxZEdraU9pSjFjbTQ2ZFhWcFpEcGlNR0prTm1aak9TMWxNMlJpTFRSa01qTXRZbUV3WlMwd056QTFaalkxTWpSbU56RWlMQ0psZUhBaU9qRTNOVGc1TmpVMk5EVXNJbWxoZENJNk1UY3lOelF5T1RZME5Td2libUptSWpveE56STNOREk1TmpRMUxDSmZjMlFpT2xzaVpsa3pPWEZ2UjNkRVRXdDFTVGxMV1Y5T1VXZGtiWFZMU2toTE5HaGtVMmhDY3pWelRVTnRVazh5VlNKZGZRLlR1bW94RW5wdWd0OXVSZmk5OEtKQ2pFVWkyWEt2a3lyeVBQWjV6Qk55SUJidUVaa3VHT1l5OFI2czhZakpudjdkV3NFeldiTzRNRmVMRkxEM2Y5b0JRfld5SnZWMnR2Y25WM05FY3hhbTVCTUhGc1VqVlZTRWxuUFQwaUxDSnVZVzFsSWl3aVNrWkdJSGdnZG1NdFpXUjFJRkJzZFdkR1pYTjBJRE1nU1c1MFpYSnZjR1Z5WVdKcGJHbDBlU0pkIl19fQ.WxYlmga0lMAFEbHWXqJ0IKLU-hyhFgBtOTk1KKUsT9wfdC5im9BQfemrTE1sTvKOsiuZ9RBBLReeGaUalpTcgg" } ``` ##### Making the Request We will now pass the body to the `/submit` endpoint. This endpoint will then share all credentials with the verifier and respond with a redirect URI if provided by the verifier. **Option: CURL** **Endpoint:** `/wallet-api/{wallet}/exchange/external_signatures/presentation/submit` ```bash curl -X 'POST' \ 'http://0.0.0.0:7001/wallet-api/{wallet}/exchange/external_signatures/presentation/submit' \ -H 'accept: */*' \ -H 'Content-Type: application/json' \ -d '{ "did": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoib05ZMkFBVFQ4S1BQeG5Jb3V5MXY5SkE3M0NFQnNwbVJEWjNNdDM4Y0dDVSIsIngiOiJmN2lSZllxdFJVSm5ZWFJhY0VLUFFvMExyUVpmaTcxZ0JZRGZhMWVSelYwIiwieSI6InQ0emZwZXhpaHJBZHlYTU1YSUtkRjNJb0FZUlNqWUJWOEo0QVF4Ym5OQlEifQ", "presentationRequest": "openid4vp://authorize?response_type=vp_token&client_id=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fverify&response_mode=direct_post&state=kbXoWmRv3kqp&presentation_definition_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fpd%2FkbXoWmRv3kqp&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=09670caf-e69c-4130-909e-ecf4d15c5337&response_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fverify%2FkbXoWmRv3kqp", "presentationSubmission": { "id": "urn:uuid:a54fc636-a132-4416-ac4b-74a8e316b4f8", "definition_id": "urn:uuid:a54fc636-a132-4416-ac4b-74a8e316b4f8", "descriptor_map": [ { "format": "jwt_vp", "path": "$", "path_nested": { "format": "jwt_vc_json", "path": "$.verifiableCredential[0]", "customParameters": {} } } ], "customParameters": {} }, "selectedCredentialIdList": [ "urn:uuid:b0bd6fc9-e3db-4d23-ba0e-0705f6524f71" ], "w3cJwtVpProof": "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pYjA1Wk1rRkJWRlE0UzFCUWVHNUpiM1Y1TVhZNVNrRTNNME5GUW5Od2JWSkVXak5OZERNNFkwZERWU0lzSW5naU9pSm1OMmxTWmxseGRGSlZTbTVaV0ZKaFkwVkxVRkZ2TUV4eVVWcG1hVGN4WjBKWlJHWmhNV1ZTZWxZd0lpd2llU0k2SW5RMGVtWndaWGhwYUhKQlpIbFlUVTFZU1V0a1JqTkpiMEZaVWxOcVdVSldPRW8wUVZGNFltNU9RbEVpZlEjMCIsInR5cCI6IkpXVCIsImFsZyI6IkVTMjU2In0.eyJzdWIiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pYjA1Wk1rRkJWRlE0UzFCUWVHNUpiM1Y1TVhZNVNrRTNNME5GUW5Od2JWSkVXak5OZERNNFkwZERWU0lzSW5naU9pSm1OMmxTWmxseGRGSlZTbTVaV0ZKaFkwVkxVRkZ2TUV4eVVWcG1hVGN4WjBKWlJHWmhNV1ZTZWxZd0lpd2llU0k2SW5RMGVtWndaWGhwYUhKQlpIbFlUVTFZU1V0a1JqTkpiMEZaVWxOcVdVSldPRW8wUVZGNFltNU9RbEVpZlEiLCJuYmYiOjE3Mjc0Mjk1ODYsImlhdCI6MTcyNzQyOTY0NiwianRpIjoidXJuOnV1aWQ6YTU0ZmM2MzYtYTEzMi00NDE2LWFjNGItNzRhOGUzMTZiNGY4IiwiaXNzIjoiZGlkOmp3azpleUpyZEhraU9pSkZReUlzSW1OeWRpSTZJbEF0TWpVMklpd2lhMmxrSWpvaWIwNVpNa0ZCVkZRNFMxQlFlRzVKYjNWNU1YWTVTa0UzTTBORlFuTndiVkpFV2pOTmRETTRZMGREVlNJc0luZ2lPaUptTjJsU1psbHhkRkpWU201WldGSmhZMFZMVUZGdk1FeHlVVnBtYVRjeFowSlpSR1poTVdWU2VsWXdJaXdpZVNJNkluUTBlbVp3WlhocGFISkJaSGxZVFUxWVNVdGtSak5KYjBGWlVsTnFXVUpXT0VvMFFWRjRZbTVPUWxFaWZRIiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDoyMjIyMi9vcGVuaWQ0dmMvdmVyaWZ5Iiwibm9uY2UiOiIwOTY3MGNhZi1lNjljLTQxMzAtOTA5ZS1lY2Y0ZDE1YzUzMzciLCJ2cCI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVQcmVzZW50YXRpb24iXSwiaWQiOiJ1cm46dXVpZDphNTRmYzYzNi1hMTMyLTQ0MTYtYWM0Yi03NGE4ZTMxNmI0ZjgiLCJob2xkZXIiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pYjA1Wk1rRkJWRlE0UzFCUWVHNUpiM1Y1TVhZNVNrRTNNME5GUW5Od2JWSkVXak5OZERNNFkwZERWU0lzSW5naU9pSm1OMmxTWmxseGRGSlZTbTVaV0ZKaFkwVkxVRkZ2TUV4eVVWcG1hVGN4WjBKWlJHWmhNV1ZTZWxZd0lpd2llU0k2SW5RMGVtWndaWGhwYUhKQlpIbFlUVTFZU1V0a1JqTkpiMEZaVWxOcVdVSldPRW8wUVZGNFltNU9RbEVpZlEiLCJ2ZXJpZmlhYmxlQ3JlZGVudGlhbCI6WyJleUpyYVdRaU9pSmthV1E2YTJWNU9ubzJUV3RxYjFKb2NURnFVMDVLWkV4cGNuVlRXSEpHUm5oaFozRnllblJhWVZoSWNVaEhWVlJMU21KalRubDNjQ0lzSW5SNWNDSTZJbXAzZEY5MlkxOXFjMjl1SWl3aVlXeG5Jam9pUldSRVUwRWlmUS5leUpBWTI5dWRHVjRkQ0k2V3lKb2RIUndjem92TDNkM2R5NTNNeTV2Y21jdk1qQXhPQzlqY21Wa1pXNTBhV0ZzY3k5Mk1TSXNJbWgwZEhCek9pOHZjSFZ5YkM1cGJYTm5iRzlpWVd3dWIzSm5MM053WldNdmIySXZkak53TUM5amIyNTBaWGgwTG1wemIyNGlYU3dpYVdRaU9pSjFjbTQ2ZFhWcFpEcGlNR0prTm1aak9TMWxNMlJpTFRSa01qTXRZbUV3WlMwd056QTFaalkxTWpSbU56RWlMQ0owZVhCbElqcGJJbFpsY21sbWFXRmliR1ZEY21Wa1pXNTBhV0ZzSWl3aVQzQmxia0poWkdkbFEzSmxaR1Z1ZEdsaGJDSmRMQ0pwYzNOMVpYSWlPbnNpZEhsd1pTSTZXeUpRY205bWFXeGxJbDBzSW1sa0lqb2laR2xrT210bGVUcDZOazFyYW05U2FIRXhhbE5PU21STWFYSjFVMWh5UmtaNFlXZHhjbnAwV21GWVNIRklSMVZVUzBwaVkwNTVkM0FpTENKdVlXMWxJam9pU205aWN5Qm1iM0lnZEdobElFWjFkSFZ5WlNBb1NrWkdLU0lzSW5WeWJDSTZJbWgwZEhCek9pOHZkM2QzTG1wbVppNXZjbWN2SWl3aWFXMWhaMlVpT2lKb2RIUndjem92TDNjell5MWpZMmN1WjJsMGFIVmlMbWx2TDNaakxXVmtMM0JzZFdkbVpYTjBMVEV0TWpBeU1pOXBiV0ZuWlhNdlNrWkdYMHh2WjI5TWIyTnJkWEF1Y0c1bkluMHNJbWx6YzNWaGJtTmxSR0YwWlNJNklqSXdNalF0TURrdE1qZFVNRGs2TXpRNk1EVXVPVGswTURFNU1qSTRXaUlzSW1WNGNHbHlZWFJwYjI1RVlYUmxJam9pTWpBeU5TMHdPUzB5TjFRd09Ub3pORG93TlM0NU9UUXhNelkwTmpCYUlpd2lZM0psWkdWdWRHbGhiRk4xWW1wbFkzUWlPbnNpYVdRaU9pSmthV1E2YW5kck9tVjVTbkprU0d0cFQybEtSbEY1U1hOSmJVNTVaR2xKTmtsc1FYUk5hbFV5U1dsM2FXRXliR3RKYW05cFlqQTFXazFyUmtKV1JsRTBVekZDVVdWSE5VcGlNMVkxVFZoWk5WTnJSVE5OTUU1R1VXNU9kMkpXU2tWWGFrNU9aRVJOTkZrd1pFUldVMGx6U1c1bmFVOXBTbTFPTW14VFdteHNlR1JHU2xaVGJUVmFWMFpLYUZrd1ZreFZSa1oyVFVWNGVWVldjRzFoVkdONFdqQktXbEpIV21oTlYxWlRaV3haZDBscGQybGxVMGsyU1c1Uk1HVnRXbmRhV0dod1lVaEtRbHBJYkZsVVZURlpVMVYwYTFKcVRrcGlNRVphVld4T2NWZFZTbGRQUlc4d1VWWkdORmx0TlU5UmJFVnBabEVpTENKMGVYQmxJanBiSWtGamFHbGxkbVZ0Wlc1MFUzVmlhbVZqZENKZExDSmhZMmhwWlhabGJXVnVkQ0k2ZXlKcFpDSTZJblZ5YmpwMWRXbGtPbUZqTWpVMFltUTFMVGhtWVdRdE5HSmlNUzA1WkRJNUxXVm1aRGt6T0RVek5qa3lOaUlzSW5SNWNHVWlPbHNpUVdOb2FXVjJaVzFsYm5RaVhTd2libUZ0WlNJNklrcEdSaUI0SUhaakxXVmtkU0JRYkhWblJtVnpkQ0F6SUVsdWRHVnliM0JsY21GaWFXeHBkSGtpTENKa1pYTmpjbWx3ZEdsdmJpSTZJbFJvYVhNZ2QyRnNiR1YwSUhOMWNIQnZjblJ6SUhSb1pTQjFjMlVnYjJZZ1Z6TkRJRlpsY21sbWFXRmliR1VnUTNKbFpHVnVkR2xoYkhNZ1lXNWtJR2hoY3lCa1pXMXZibk4wY21GMFpXUWdhVzUwWlhKdmNHVnlZV0pwYkdsMGVTQmtkWEpwYm1jZ2RHaGxJSEJ5WlhObGJuUmhkR2x2YmlCeVpYRjFaWE4wSUhkdmNtdG1iRzkzSUdSMWNtbHVaeUJLUmtZZ2VDQldReTFGUkZVZ1VHeDFaMFpsYzNRZ015NGlMQ0pqY21sMFpYSnBZU0k2ZXlKMGVYQmxJam9pUTNKcGRHVnlhV0VpTENKdVlYSnlZWFJwZG1VaU9pSlhZV3hzWlhRZ2MyOXNkWFJwYjI1eklIQnliM1pwWkdWeWN5QmxZWEp1WldRZ2RHaHBjeUJpWVdSblpTQmllU0JrWlcxdmJuTjBjbUYwYVc1bklHbHVkR1Z5YjNCbGNtRmlhV3hwZEhrZ1pIVnlhVzVuSUhSb1pTQndjbVZ6Wlc1MFlYUnBiMjRnY21WeGRXVnpkQ0IzYjNKclpteHZkeTRnVkdocGN5QnBibU5zZFdSbGN5QnpkV05qWlhOelpuVnNiSGtnY21WalpXbDJhVzVuSUdFZ2NISmxjMlZ1ZEdGMGFXOXVJSEpsY1hWbGMzUXNJR0ZzYkc5M2FXNW5JSFJvWlNCb2IyeGtaWElnZEc4Z2MyVnNaV04wSUdGMElHeGxZWE4wSUhSM2J5QjBlWEJsY3lCdlppQjJaWEpwWm1saFlteGxJR055WldSbGJuUnBZV3h6SUhSdklHTnlaV0YwWlNCaElIWmxjbWxtYVdGaWJHVWdjSEpsYzJWdWRHRjBhVzl1TENCeVpYUjFjbTVwYm1jZ2RHaGxJSEJ5WlhObGJuUmhkR2x2YmlCMGJ5QjBhR1VnY21WeGRXVnpkRzl5TENCaGJtUWdjR0Z6YzJsdVp5QjJaWEpwWm1sallYUnBiMjRnYjJZZ2RHaGxJSEJ5WlhObGJuUmhkR2x2YmlCaGJtUWdkR2hsSUdsdVkyeDFaR1ZrSUdOeVpXUmxiblJwWVd4ekxpSjlMQ0pwYldGblpTSTZleUpwWkNJNkltaDBkSEJ6T2k4dmR6TmpMV05qWnk1bmFYUm9kV0l1YVc4dmRtTXRaV1F2Y0d4MVoyWmxjM1F0TXkweU1ESXpMMmx0WVdkbGN5OUtSa1l0VmtNdFJVUlZMVkJNVlVkR1JWTlVNeTFpWVdSblpTMXBiV0ZuWlM1d2JtY2lMQ0owZVhCbElqb2lTVzFoWjJVaWZYMTlMQ0pxZEdraU9pSjFjbTQ2ZFhWcFpEcGlNR0prTm1aak9TMWxNMlJpTFRSa01qTXRZbUV3WlMwd056QTFaalkxTWpSbU56RWlMQ0psZUhBaU9qRTNOVGc1TmpVMk5EVXNJbWxoZENJNk1UY3lOelF5T1RZME5Td2libUptSWpveE56STNOREk1TmpRMUxDSmZjMlFpT2xzaVpsa3pPWEZ2UjNkRVRXdDFTVGxMV1Y5T1VXZGtiWFZMU2toTE5HaGtVMmhDY3pWelRVTnRVazh5VlNKZGZRLlR1bW94RW5wdWd0OXVSZmk5OEtKQ2pFVWkyWEt2a3lyeVBQWjV6Qk55SUJidUVaa3VHT1l5OFI2czhZakpudjdkV3NFeldiTzRNRmVMRkxEM2Y5b0JRfld5SnZWMnR2Y25WM05FY3hhbTVCTUhGc1VqVlZTRWxuUFQwaUxDSnVZVzFsSWl3aVNrWkdJSGdnZG1NdFpXUjFJRkJzZFdkR1pYTjBJRE1nU1c1MFpYSnZjR1Z5WVdKcGJHbDBlU0pkIl19fQ.WxYlmga0lMAFEbHWXqJ0IKLU-hyhFgBtOTk1KKUsT9wfdC5im9BQfemrTE1sTvKOsiuZ9RBBLReeGaUalpTcgg" }' ``` **Path Parameters** - `wallet`: - You can receive the needed wallet id parameter via the `/wallet-api/wallet/accounts/wallets` endpoint. **Body Parameters** - `did` - value copied from response to `/prepare` - `w3cJwtVpProof` - signed JWT from[ step 2 "Signing the VP"](https://docs.walt.id/community-stack/wallet/credential-exchange/guides/present-digital-credentials-external-signatures.md#_2-signing-the-vp). - `presentationRequest` - value copied from response to `/prepare` - `presentationSubmission` - value copied from response to `/prepare` - `selectedCredentialIdList` - value copied from response to `/prepare` **Example Response** If the verifier provided a redirect URI we will receive it as a response. ```json { "redirectUri": null } ``` **Option: SD-JWT VC** #### 1. Preparation Using the `/prepare` endpoint we will send to the wallet API: The DID owning the credentials, the request from the verifier and the credential IDs matching the request from the verifier which we got in the last step. **Option: CURL** **Endpoint:** `wallet-api/{wallet}/exchange/external_signatures/presentation/prepare` ```bash curl -X 'POST' \ 'http://0.0.0.0:7001/wallet-api/{wallet}/exchange/external_signatures/presentation/prepare' \ -H 'accept: */*' \ -H 'Content-Type: application/json' \ -d '{ "did": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiS2V1bzRITDUwTV8wdFU5U2xYdnhQbUgwQmNHYkFBRVBKVEZCaGItYVhObyIsIngiOiJpZHhJWVFmT1ltRDFQTzB3WFBwQWtTQ0lJOExqbFhkWXpWbG83d0tZZFU0IiwieSI6ImNGZVJacHdQRHc1aTY4czNCajAwb01VVWRLcjhqMlg3UjZrWUxueFlaOFUifQ", "presentationRequest": "openid4vp://?response_type=vp_token&client_id=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fverify&response_mode=direct_post&state=3yydz2ROSuwl&presentation_definition_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fpd%2F3yydz2ROSuwl&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=67871ae6-3c15-47d8-bad9-5661453a5b60&response_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fverify%2F3yydz2ROSuwl", "selectedCredentialIdList": [ "4a55eb7a-6fdb-4ed6-8f3d-161994ada9d0" ], "disclosures": { "4a55eb7a-6fdb-4ed6-8f3d-161994ada9d0": [ "WyIxYi1aNzVhOVgwRTVZRU9VOUY3dXFBPT0iLCJiaXJ0aGRhdGUiLCIxOTQwLTAxLTAxIl0" ] } }' ``` **Path Parameters** - `wallet`: - You can receive the needed wallet id parameter via the `/wallet-api/wallet/accounts/wallets` endpoint. **Body Parameters** - `did` - DID to owning credential(s) to be presented. - `presentationRequest` - the presentation request from the verifier. - `selectedCredentialIdList` - IDs of the credential(s) which should be presented. Response from `/matchCredentialForPresentationDefinition` endpoint. - `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** ```json { "did": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiS2V1bzRITDUwTV8wdFU5U2xYdnhQbUgwQmNHYkFBRVBKVEZCaGItYVhObyIsIngiOiJpZHhJWVFmT1ltRDFQTzB3WFBwQWtTQ0lJOExqbFhkWXpWbG83d0tZZFU0IiwieSI6ImNGZVJacHdQRHc1aTY4czNCajAwb01VVWRLcjhqMlg3UjZrWUxueFlaOFUifQ", "presentationRequest": "openid4vp://?response_type=vp_token&client_id=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fverify&response_mode=direct_post&state=3yydz2ROSuwl&presentation_definition_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fpd%2F3yydz2ROSuwl&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=67871ae6-3c15-47d8-bad9-5661453a5b60&response_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fverify%2F3yydz2ROSuwl", "selectedCredentialIdList": [ "4a55eb7a-6fdb-4ed6-8f3d-161994ada9d0" ], "presentationSubmission": { "id": "urn:uuid:43825a14-909a-4100-b1bf-20a5369f5993", "definition_id": "urn:uuid:43825a14-909a-4100-b1bf-20a5369f5993", "descriptor_map": [ { "id": "av4SBACIXkuA", "format": "vc+sd-jwt", "path": "$" } ], "customParameters": {} }, "ietfSdJwtVpProofParameters": [ { "credentialId": "4a55eb7a-6fdb-4ed6-8f3d-161994ada9d0", "sdJwtVc": "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWVDSTZJa2N3VWtsT1FtbEdMVzlSVlVRelpEVkVSMjVsWjFGMVdHVnVTVEk1U2tSaFRVZHZUWFpwYjB0U1FrMGlMQ0o1SWpvaVpXUXpaVVpIY3pKd1JYUnljRGQyUVZvM1FreGpZbkpWZEhCTGExbFhRVlF5U2xCVlVVczBiRTQwUlNKOSIsInR5cCI6InZjK3NkLWp3dCIsImFsZyI6IkVTMjU2In0.eyJmYW1pbHlfbmFtZSI6IkRvZSIsImdpdmVuX25hbWUiOiJKb2huIiwiaXNzIjoiZGlkOmp3azpleUpyZEhraU9pSkZReUlzSW1OeWRpSTZJbEF0TWpVMklpd2llQ0k2SWtjd1VrbE9RbWxHTFc5UlZVUXpaRFZFUjI1bFoxRjFXR1Z1U1RJNVNrUmhUVWR2VFhacGIwdFNRazBpTENKNUlqb2laV1F6WlVaSGN6SndSWFJ5Y0RkMlFWbzNRa3hqWW5KVmRIQkxhMWxYUVZReVNsQlZVVXMwYkU0MFJTSjkiLCJjbmYiOnsiandrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJraWQiOiJLZXVvNEhMNTBNXzB0VTlTbFh2eFBtSDBCY0diQUFFUEpURkJoYi1hWE5vIiwieCI6ImlkeElZUWZPWW1EMVBPMHdYUHBBa1NDSUk4TGpsWGRZelZsbzd3S1lkVTQiLCJ5IjoiY0ZlUlpwd1BEdzVpNjhzM0JqMDBvTVVVZEtyOGoyWDdSNmtZTG54WVo4VSJ9fSwidmN0IjoiaHR0cDovL2xvY2FsaG9zdDoyMjIyMi9pZGVudGl0eV9jcmVkZW50aWFsIiwic3ViIjoiS2V1bzRITDUwTV8wdFU5U2xYdnhQbUgwQmNHYkFBRVBKVEZCaGItYVhObyIsIl9zZCI6WyJtWU1nTUtEWnZ4ZDBDeVZHbTZpa2Z5VkxzTTVyZC0wLWhTdW5sT1Nfc29JIl19.Nr9dAmu62Mg5G8B-C-aubE7GvbVgGT6Mq91f0bdRF4XW1H72JqN0hxt38v1tPozd7tKqWlm1EJlP_u-gavHhUQ~WyIxYi1aNzVhOVgwRTVZRU9VOUY3dXFBPT0iLCJiaXJ0aGRhdGUiLCIxOTQwLTAxLTAxIl0~", "header": { "kid": "Keuo4HL50M_0tU9SlXvxPmH0BcGbAAEPJTFBhb-aXNo", "typ": "kb+jwt" }, "payload": { "iat": 1727431472, "aud": "http://localhost:22222/openid4vc/verify", "nonce": "67871ae6-3c15-47d8-bad9-5661453a5b60", "sd_hash": "Qflclr7AF2MEpYQfJOU6oFGzYI9u8SxmOJ5bXMaSR6Y" } } ] } ``` In the response we will find a `ietfSdJwtVpProofParameters` key, which represents the header and payload info that we need to put into a JWT. This JWT then needs to be signed with the key of the holder which is stored in an external system. All the other information we simply pass on to the `/submit` endpoint next to the signed JWT in the step after next. #### 2. Signing the VP Now you need to create a `jwt` with the header and payload described in `ietfSdJwtVpProofParameters` received by the response to `/prepare` and sign it with the key of the holder. #### 3. Submit With the `/submit` endpoint we will send most of the information returned by the `/prepare` endpoint next to the created and signed VP token from step 2. ##### Preparing the body of the request **1.** We add the signed VP token within an object into the `ietfSdJwtVpProofs` array ```json [request body] { "ietfSdJwtVpProofs": [ { "credentialId": "4a55eb7a-6fdb-4ed6-8f3d-161994ada9d0", "sdJwtVc": "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWVDSTZJa2N3VWtsT1FtbEdMVzlSVlVRelpEVkVSMjVsWjFGMVdHVnVTVEk1U2tSaFRVZHZUWFpwYjB0U1FrMGlMQ0o1SWpvaVpXUXpaVVpIY3pKd1JYUnljRGQyUVZvM1FreGpZbkpWZEhCTGExbFhRVlF5U2xCVlVVczBiRTQwUlNKOSIsInR5cCI6InZjK3NkLWp3dCIsImFsZyI6IkVTMjU2In0.eyJmYW1pbHlfbmFtZSI6IkRvZSIsImdpdmVuX25hbWUiOiJKb2huIiwiaXNzIjoiZGlkOmp3azpleUpyZEhraU9pSkZReUlzSW1OeWRpSTZJbEF0TWpVMklpd2llQ0k2SWtjd1VrbE9RbWxHTFc5UlZVUXpaRFZFUjI1bFoxRjFXR1Z1U1RJNVNrUmhUVWR2VFhacGIwdFNRazBpTENKNUlqb2laV1F6WlVaSGN6SndSWFJ5Y0RkMlFWbzNRa3hqWW5KVmRIQkxhMWxYUVZReVNsQlZVVXMwYkU0MFJTSjkiLCJjbmYiOnsiandrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJraWQiOiJLZXVvNEhMNTBNXzB0VTlTbFh2eFBtSDBCY0diQUFFUEpURkJoYi1hWE5vIiwieCI6ImlkeElZUWZPWW1EMVBPMHdYUHBBa1NDSUk4TGpsWGRZelZsbzd3S1lkVTQiLCJ5IjoiY0ZlUlpwd1BEdzVpNjhzM0JqMDBvTVVVZEtyOGoyWDdSNmtZTG54WVo4VSJ9fSwidmN0IjoiaHR0cDovL2xvY2FsaG9zdDoyMjIyMi9pZGVudGl0eV9jcmVkZW50aWFsIiwic3ViIjoiS2V1bzRITDUwTV8wdFU5U2xYdnhQbUgwQmNHYkFBRVBKVEZCaGItYVhObyIsIl9zZCI6WyJtWU1nTUtEWnZ4ZDBDeVZHbTZpa2Z5VkxzTTVyZC0wLWhTdW5sT1Nfc29JIl19.Nr9dAmu62Mg5G8B-C-aubE7GvbVgGT6Mq91f0bdRF4XW1H72JqN0hxt38v1tPozd7tKqWlm1EJlP_u-gavHhUQ~WyIxYi1aNzVhOVgwRTVZRU9VOUY3dXFBPT0iLCJiaXJ0aGRhdGUiLCIxOTQwLTAxLTAxIl0~", "vpTokenProof": "eyJraWQiOiJLZXVvNEhMNTBNXzB0VTlTbFh2eFBtSDBCY0diQUFFUEpURkJoYi1hWE5vIiwidHlwIjoia2Irand0IiwiYWxnIjoiRVMyNTYifQ.eyJpYXQiOjE3Mjc0MzE0NzIsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3Q6MjIyMjIvb3BlbmlkNHZjL3ZlcmlmeSIsIm5vbmNlIjoiNjc4NzFhZTYtM2MxNS00N2Q4LWJhZDktNTY2MTQ1M2E1YjYwIiwic2RfaGFzaCI6IlFmbGNscjdBRjJNRXBZUWZKT1U2b0ZHellJOXU4U3htT0o1YlhNYVNSNlkifQ.Tiy7HiBZFOB2_txrqxve5gLajYqGdznY17qBJiOxVB5kb5RCLM313BsuAGReUnIiIdSy5yY1F2q102c9K6YN4Q" } ] } ``` **Object details for `ietfSdJwtVpProofs` array** ```json { "credentialId": "4a55eb7a-6fdb-4ed6-8f3d-161994ada9d0", "sdJwtVc": "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWVDSTZJa2N3VWtsT1FtbEdMVzlSVlVRelpEVkVSMjVsWjFGMVdHVnVTVEk1U2tSaFRVZHZUWFpwYjB0U1FrMGlMQ0o1SWpvaVpXUXpaVVpIY3pKd1JYUnljRGQyUVZvM1FreGpZbkpWZEhCTGExbFhRVlF5U2xCVlVVczBiRTQwUlNKOSIsInR5cCI6InZjK3NkLWp3dCIsImFsZyI6IkVTMjU2In0.eyJmYW1pbHlfbmFtZSI6IkRvZSIsImdpdmVuX25hbWUiOiJKb2huIiwiaXNzIjoiZGlkOmp3azpleUpyZEhraU9pSkZReUlzSW1OeWRpSTZJbEF0TWpVMklpd2llQ0k2SWtjd1VrbE9RbWxHTFc5UlZVUXpaRFZFUjI1bFoxRjFXR1Z1U1RJNVNrUmhUVWR2VFhacGIwdFNRazBpTENKNUlqb2laV1F6WlVaSGN6SndSWFJ5Y0RkMlFWbzNRa3hqWW5KVmRIQkxhMWxYUVZReVNsQlZVVXMwYkU0MFJTSjkiLCJjbmYiOnsiandrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJraWQiOiJLZXVvNEhMNTBNXzB0VTlTbFh2eFBtSDBCY0diQUFFUEpURkJoYi1hWE5vIiwieCI6ImlkeElZUWZPWW1EMVBPMHdYUHBBa1NDSUk4TGpsWGRZelZsbzd3S1lkVTQiLCJ5IjoiY0ZlUlpwd1BEdzVpNjhzM0JqMDBvTVVVZEtyOGoyWDdSNmtZTG54WVo4VSJ9fSwidmN0IjoiaHR0cDovL2xvY2FsaG9zdDoyMjIyMi9pZGVudGl0eV9jcmVkZW50aWFsIiwic3ViIjoiS2V1bzRITDUwTV8wdFU5U2xYdnhQbUgwQmNHYkFBRVBKVEZCaGItYVhObyIsIl9zZCI6WyJtWU1nTUtEWnZ4ZDBDeVZHbTZpa2Z5VkxzTTVyZC0wLWhTdW5sT1Nfc29JIl19.Nr9dAmu62Mg5G8B-C-aubE7GvbVgGT6Mq91f0bdRF4XW1H72JqN0hxt38v1tPozd7tKqWlm1EJlP_u-gavHhUQ~WyIxYi1aNzVhOVgwRTVZRU9VOUY3dXFBPT0iLCJiaXJ0aGRhdGUiLCIxOTQwLTAxLTAxIl0~", "vpTokenProof": "eyJraWQiOiJLZXVvNEhMNTBNXzB0VTlTbFh2eFBtSDBCY0diQUFFUEpURkJoYi1hWE5vIiwidHlwIjoia2Irand0IiwiYWxnIjoiRVMyNTYifQ.eyJpYXQiOjE3Mjc0MzE0NzIsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3Q6MjIyMjIvb3BlbmlkNHZjL3ZlcmlmeSIsIm5vbmNlIjoiNjc4NzFhZTYtM2MxNS00N2Q4LWJhZDktNTY2MTQ1M2E1YjYwIiwic2RfaGFzaCI6IlFmbGNscjdBRjJNRXBZUWZKT1U2b0ZHellJOXU4U3htT0o1YlhNYVNSNlkifQ.Tiy7HiBZFOB2_txrqxve5gLajYqGdznY17qBJiOxVB5kb5RCLM313BsuAGReUnIiIdSy5yY1F2q102c9K6YN4Q" } ``` - `credentialId` - value copied over from response to `/prepare`. - `sdJwtVc` - value copied over from response to `/prepare`. - `vpTokenProof` - signed JWT from step 2. **2.** We copy over the value `did`, `presentationRequest`, `presentationSubmission`, and `selectedCredentialIdList` from the response to `/prepare`. Next to that we add the `disclosures` which we send to `/preare`. ```json [request body] { "did": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiS2V1bzRITDUwTV8wdFU5U2xYdnhQbUgwQmNHYkFBRVBKVEZCaGItYVhObyIsIngiOiJpZHhJWVFmT1ltRDFQTzB3WFBwQWtTQ0lJOExqbFhkWXpWbG83d0tZZFU0IiwieSI6ImNGZVJacHdQRHc1aTY4czNCajAwb01VVWRLcjhqMlg3UjZrWUxueFlaOFUifQ", "presentationRequest": "openid4vp://?response_type=vp_token&client_id=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fverify&response_mode=direct_post&state=3yydz2ROSuwl&presentation_definition_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fpd%2F3yydz2ROSuwl&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=67871ae6-3c15-47d8-bad9-5661453a5b60&response_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fverify%2F3yydz2ROSuwl", "presentationSubmission": { "id": "urn:uuid:43825a14-909a-4100-b1bf-20a5369f5993", "definition_id": "urn:uuid:43825a14-909a-4100-b1bf-20a5369f5993", "descriptor_map": [ { "id": "av4SBACIXkuA", "format": "vc+sd-jwt", "path": "$" } ], "customParameters": {} }, "selectedCredentialIdList": [ "4a55eb7a-6fdb-4ed6-8f3d-161994ada9d0" ], "disclosures": { "4a55eb7a-6fdb-4ed6-8f3d-161994ada9d0": [ "WyIxYi1aNzVhOVgwRTVZRU9VOUY3dXFBPT0iLCJiaXJ0aGRhdGUiLCIxOTQwLTAxLTAxIl0" ] } } ``` **3. Final Body** ```json [request body] { "did": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiS2V1bzRITDUwTV8wdFU5U2xYdnhQbUgwQmNHYkFBRVBKVEZCaGItYVhObyIsIngiOiJpZHhJWVFmT1ltRDFQTzB3WFBwQWtTQ0lJOExqbFhkWXpWbG83d0tZZFU0IiwieSI6ImNGZVJacHdQRHc1aTY4czNCajAwb01VVWRLcjhqMlg3UjZrWUxueFlaOFUifQ", "presentationRequest": "openid4vp://?response_type=vp_token&client_id=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fverify&response_mode=direct_post&state=3yydz2ROSuwl&presentation_definition_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fpd%2F3yydz2ROSuwl&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=67871ae6-3c15-47d8-bad9-5661453a5b60&response_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fverify%2F3yydz2ROSuwl", "presentationSubmission": { "id": "urn:uuid:43825a14-909a-4100-b1bf-20a5369f5993", "definition_id": "urn:uuid:43825a14-909a-4100-b1bf-20a5369f5993", "descriptor_map": [ { "id": "av4SBACIXkuA", "format": "vc+sd-jwt", "path": "$" } ], "customParameters": {} }, "selectedCredentialIdList": [ "4a55eb7a-6fdb-4ed6-8f3d-161994ada9d0" ], "disclosures": { "4a55eb7a-6fdb-4ed6-8f3d-161994ada9d0": [ "WyIxYi1aNzVhOVgwRTVZRU9VOUY3dXFBPT0iLCJiaXJ0aGRhdGUiLCIxOTQwLTAxLTAxIl0" ] }, "ietfSdJwtVpProofs": [ { "credentialId": "4a55eb7a-6fdb-4ed6-8f3d-161994ada9d0", "sdJwtVc": "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWVDSTZJa2N3VWtsT1FtbEdMVzlSVlVRelpEVkVSMjVsWjFGMVdHVnVTVEk1U2tSaFRVZHZUWFpwYjB0U1FrMGlMQ0o1SWpvaVpXUXpaVVpIY3pKd1JYUnljRGQyUVZvM1FreGpZbkpWZEhCTGExbFhRVlF5U2xCVlVVczBiRTQwUlNKOSIsInR5cCI6InZjK3NkLWp3dCIsImFsZyI6IkVTMjU2In0.eyJmYW1pbHlfbmFtZSI6IkRvZSIsImdpdmVuX25hbWUiOiJKb2huIiwiaXNzIjoiZGlkOmp3azpleUpyZEhraU9pSkZReUlzSW1OeWRpSTZJbEF0TWpVMklpd2llQ0k2SWtjd1VrbE9RbWxHTFc5UlZVUXpaRFZFUjI1bFoxRjFXR1Z1U1RJNVNrUmhUVWR2VFhacGIwdFNRazBpTENKNUlqb2laV1F6WlVaSGN6SndSWFJ5Y0RkMlFWbzNRa3hqWW5KVmRIQkxhMWxYUVZReVNsQlZVVXMwYkU0MFJTSjkiLCJjbmYiOnsiandrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJraWQiOiJLZXVvNEhMNTBNXzB0VTlTbFh2eFBtSDBCY0diQUFFUEpURkJoYi1hWE5vIiwieCI6ImlkeElZUWZPWW1EMVBPMHdYUHBBa1NDSUk4TGpsWGRZelZsbzd3S1lkVTQiLCJ5IjoiY0ZlUlpwd1BEdzVpNjhzM0JqMDBvTVVVZEtyOGoyWDdSNmtZTG54WVo4VSJ9fSwidmN0IjoiaHR0cDovL2xvY2FsaG9zdDoyMjIyMi9pZGVudGl0eV9jcmVkZW50aWFsIiwic3ViIjoiS2V1bzRITDUwTV8wdFU5U2xYdnhQbUgwQmNHYkFBRVBKVEZCaGItYVhObyIsIl9zZCI6WyJtWU1nTUtEWnZ4ZDBDeVZHbTZpa2Z5VkxzTTVyZC0wLWhTdW5sT1Nfc29JIl19.Nr9dAmu62Mg5G8B-C-aubE7GvbVgGT6Mq91f0bdRF4XW1H72JqN0hxt38v1tPozd7tKqWlm1EJlP_u-gavHhUQ~WyIxYi1aNzVhOVgwRTVZRU9VOUY3dXFBPT0iLCJiaXJ0aGRhdGUiLCIxOTQwLTAxLTAxIl0~", "vpTokenProof": "eyJraWQiOiJLZXVvNEhMNTBNXzB0VTlTbFh2eFBtSDBCY0diQUFFUEpURkJoYi1hWE5vIiwidHlwIjoia2Irand0IiwiYWxnIjoiRVMyNTYifQ.eyJpYXQiOjE3Mjc0MzE0NzIsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3Q6MjIyMjIvb3BlbmlkNHZjL3ZlcmlmeSIsIm5vbmNlIjoiNjc4NzFhZTYtM2MxNS00N2Q4LWJhZDktNTY2MTQ1M2E1YjYwIiwic2RfaGFzaCI6IlFmbGNscjdBRjJNRXBZUWZKT1U2b0ZHellJOXU4U3htT0o1YlhNYVNSNlkifQ.Tiy7HiBZFOB2_txrqxve5gLajYqGdznY17qBJiOxVB5kb5RCLM313BsuAGReUnIiIdSy5yY1F2q102c9K6YN4Q" } ] } ``` ##### Making the Request We will now pass the body to the `/submit` endpoint. This endpoint will then share all credentials with the verifier and respond with a redirect URI if provided by the verifier. **Option: CURL** **Endpoint:** `/wallet-api/{wallet}/exchange/external_signatures/presentation/submit` ```bash curl -X 'POST' \ 'http://0.0.0.0:7001/wallet-api/{wallet}/exchange/external_signatures/presentation/submit' \ -H 'accept: */*' \ -H 'Content-Type: application/json' \ -d '{ "did": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiS2V1bzRITDUwTV8wdFU5U2xYdnhQbUgwQmNHYkFBRVBKVEZCaGItYVhObyIsIngiOiJpZHhJWVFmT1ltRDFQTzB3WFBwQWtTQ0lJOExqbFhkWXpWbG83d0tZZFU0IiwieSI6ImNGZVJacHdQRHc1aTY4czNCajAwb01VVWRLcjhqMlg3UjZrWUxueFlaOFUifQ", "presentationRequest": "openid4vp://?response_type=vp_token&client_id=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fverify&response_mode=direct_post&state=3yydz2ROSuwl&presentation_definition_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fpd%2F3yydz2ROSuwl&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=67871ae6-3c15-47d8-bad9-5661453a5b60&response_uri=http%3A%2F%2Flocalhost%3A22222%2Fopenid4vc%2Fverify%2F3yydz2ROSuwl", "presentationSubmission": { "id": "urn:uuid:43825a14-909a-4100-b1bf-20a5369f5993", "definition_id": "urn:uuid:43825a14-909a-4100-b1bf-20a5369f5993", "descriptor_map": [ { "id": "av4SBACIXkuA", "format": "vc+sd-jwt", "path": "$" } ], "customParameters": {} }, "selectedCredentialIdList": [ "4a55eb7a-6fdb-4ed6-8f3d-161994ada9d0" ], "disclosures": { "4a55eb7a-6fdb-4ed6-8f3d-161994ada9d0": [ "WyIxYi1aNzVhOVgwRTVZRU9VOUY3dXFBPT0iLCJiaXJ0aGRhdGUiLCIxOTQwLTAxLTAxIl0" ] }, "ietfSdJwtVpProofs": [ { "credentialId": "4a55eb7a-6fdb-4ed6-8f3d-161994ada9d0", "sdJwtVc": "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWVDSTZJa2N3VWtsT1FtbEdMVzlSVlVRelpEVkVSMjVsWjFGMVdHVnVTVEk1U2tSaFRVZHZUWFpwYjB0U1FrMGlMQ0o1SWpvaVpXUXpaVVpIY3pKd1JYUnljRGQyUVZvM1FreGpZbkpWZEhCTGExbFhRVlF5U2xCVlVVczBiRTQwUlNKOSIsInR5cCI6InZjK3NkLWp3dCIsImFsZyI6IkVTMjU2In0.eyJmYW1pbHlfbmFtZSI6IkRvZSIsImdpdmVuX25hbWUiOiJKb2huIiwiaXNzIjoiZGlkOmp3azpleUpyZEhraU9pSkZReUlzSW1OeWRpSTZJbEF0TWpVMklpd2llQ0k2SWtjd1VrbE9RbWxHTFc5UlZVUXpaRFZFUjI1bFoxRjFXR1Z1U1RJNVNrUmhUVWR2VFhacGIwdFNRazBpTENKNUlqb2laV1F6WlVaSGN6SndSWFJ5Y0RkMlFWbzNRa3hqWW5KVmRIQkxhMWxYUVZReVNsQlZVVXMwYkU0MFJTSjkiLCJjbmYiOnsiandrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJraWQiOiJLZXVvNEhMNTBNXzB0VTlTbFh2eFBtSDBCY0diQUFFUEpURkJoYi1hWE5vIiwieCI6ImlkeElZUWZPWW1EMVBPMHdYUHBBa1NDSUk4TGpsWGRZelZsbzd3S1lkVTQiLCJ5IjoiY0ZlUlpwd1BEdzVpNjhzM0JqMDBvTVVVZEtyOGoyWDdSNmtZTG54WVo4VSJ9fSwidmN0IjoiaHR0cDovL2xvY2FsaG9zdDoyMjIyMi9pZGVudGl0eV9jcmVkZW50aWFsIiwic3ViIjoiS2V1bzRITDUwTV8wdFU5U2xYdnhQbUgwQmNHYkFBRVBKVEZCaGItYVhObyIsIl9zZCI6WyJtWU1nTUtEWnZ4ZDBDeVZHbTZpa2Z5VkxzTTVyZC0wLWhTdW5sT1Nfc29JIl19.Nr9dAmu62Mg5G8B-C-aubE7GvbVgGT6Mq91f0bdRF4XW1H72JqN0hxt38v1tPozd7tKqWlm1EJlP_u-gavHhUQ~WyIxYi1aNzVhOVgwRTVZRU9VOUY3dXFBPT0iLCJiaXJ0aGRhdGUiLCIxOTQwLTAxLTAxIl0~", "vpTokenProof": "eyJraWQiOiJLZXVvNEhMNTBNXzB0VTlTbFh2eFBtSDBCY0diQUFFUEpURkJoYi1hWE5vIiwidHlwIjoia2Irand0IiwiYWxnIjoiRVMyNTYifQ.eyJpYXQiOjE3Mjc0MzE0NzIsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3Q6MjIyMjIvb3BlbmlkNHZjL3ZlcmlmeSIsIm5vbmNlIjoiNjc4NzFhZTYtM2MxNS00N2Q4LWJhZDktNTY2MTQ1M2E1YjYwIiwic2RfaGFzaCI6IlFmbGNscjdBRjJNRXBZUWZKT1U2b0ZHellJOXU4U3htT0o1YlhNYVNSNlkifQ.Tiy7HiBZFOB2_txrqxve5gLajYqGdznY17qBJiOxVB5kb5RCLM313BsuAGReUnIiIdSy5yY1F2q102c9K6YN4Q" } ] }' ``` **Path Parameters** - `wallet`: - You can receive the needed wallet id parameter via the `/wallet-api/wallet/accounts/wallets` endpoint. **Body Parameters** - `did` - value copied from response to `/prepare` - `ietfSdJwtVpProofs` - sign jwt from[ step 2 "Signing the VP"](https://docs.walt.id/community-stack/wallet/credential-exchange/guides/present-digital-credentials-external-signatures.md#_2-signing-the-vp). - `presentationRequest` - value copied from response to `/prepare` - `presentationSubmission` - value copied from response to `/prepare` - `selectedCredentialIdList` - value copied from response to `/prepare` - `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 } ``` -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/community-stack/wallet/credential-management/overview RAW: https://docs.walt.id/community-stack/wallet/credential-management/overview.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Credential Management Use the Wallet API to list, view, import, and delete credentials stored in a user wallet. ## What is Credential Management? Credential management covers operations on credentials already stored in a wallet, including: - **Listing credentials** – View all credentials stored in a wallet - **Retrieving credential details** – Get full details of a specific credential - **Deleting credentials** – Remove credentials from the wallet (soft-delete or permanent) - **Importing credentials** – Import signed JWT/SD-JWT credentials directly into the wallet For a full list of endpoints, please refer to the [API Reference](https://wallet.demo.walt.id/swagger/index.html#/WalletCredentials). ## Getting Started - [List Credentials](https://docs.walt.id/community-stack/wallet/credential-management/list-credentials.md) – Retrieve all credentials stored in a wallet. - [View a Credential](https://docs.walt.id/community-stack/wallet/credential-management/view-credential.md) – Retrieve the full details of a specific credential by ID. - [Delete a Credential](https://docs.walt.id/community-stack/wallet/credential-management/delete-credential.md) – Soft-delete or permanently remove a credential from a wallet. - [Import a Credential](https://docs.walt.id/community-stack/wallet/credential-management/import-credential.md) – Import a signed JWT or SD-JWT credential directly into a wallet.
**Note:** For receiving credentials from issuers via OID4VCI or presenting them to verifiers via OID4VP, refer to the [Credential Exchange](https://docs.walt.id/community-stack/wallet/credential-exchange/overview.md) section. -------------------------------------------------------------------------------- PAGE: List Credentials URL: https://docs.walt.id/community-stack/wallet/credential-management/list-credentials RAW: https://docs.walt.id/community-stack/wallet/credential-management/list-credentials.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # List Credentials Retrieve all credentials stored in a specific wallet. The response includes the full credential document, parsed content, and metadata such as format and date added. ## List Credentials **Option: CURL** **Endpoint:** `GET /wallet-api/wallet/{walletId}/credentials` | [API Reference](https://wallet.demo.walt.id/swagger/index.html#/WalletCredentials/get_wallet_api_wallet__wallet__credentials) ##### Example Request ```bash curl -X 'GET' \ 'http://0.0.0.0:7001/wallet-api/wallet/{walletId}/credentials' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {token}' ``` **Path Parameters** - `walletId`: _String_ - The ID of the wallet to list credentials from. See [Accounts & Wallets](https://docs.walt.id/community-stack/wallet/accounts-wallets.md#retrieve-wallet-id) for how to retrieve it. **Query Parameters** - `showDeleted`: _Boolean (optional)_ - Controls which credentials are returned based on deletion status. `true` returns only soft-deleted credentials. `false` returns only active (non-deleted) credentials. If omitted, all credentials are returned regardless of deletion status. - `showPending`: _Boolean (optional)_ - Controls which credentials are returned based on pending status. `true` returns only pending credentials. `false` returns only non-pending credentials. If omitted, all credentials are returned regardless of pending status. - `category`: _String (optional, repeatable)_ - Filter by category name. Repeat the parameter to match multiple categories (e.g. `?category=work&category=education`). Passing an empty value (`?category=`) returns credentials that have **no category assigned**. If omitted entirely, credentials from all categories are returned. - `descending`: _Boolean (optional)_ - Set to `true` to sort in descending order. Defaults to `false`. **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 The API returns a list of credential objects stored in the wallet. Each object includes the raw credential document, the parsed credential, and metadata. ```json [ { "wallet": "6006b6f4-e651-46db-b6ae-e7bd6e9c40f2", "id": "urn:uuid:c2f7e988-a4cb-48c2-bbf6-ed97f6010abe", "document": "eyJraWQiOiJkaWQ6...", "disclosures": "", "addedOn": "2026-04-16T15:08:01.604768543Z", "pending": false, "format": "jwt_vc_json", "parsedDocument": { "@context": [ "https://www.w3.org/2018/credentials/v1" ], "id": "urn:uuid:c2f7e988-a4cb-48c2-bbf6-ed97f6010abe", "type": ["VerifiableCredential", "UniversityDegree"], "issuer": { "id": "did:jwk:eyJrdH..." }, "issuanceDate": "2026-04-16T15:08:01.573384105Z", "credentialSubject": { "degree": { "type": "BachelorDegree", "name": "Bachelor of Science and Arts" } } } } ] ``` The `id` field of each object (e.g. `urn:uuid:c2f7e988-a4cb-48c2-bbf6-ed97f6010abe`) is the credential ID used in other operations such as [deleting a credential](https://docs.walt.id/community-stack/wallet/credential-management/delete-credential.md). -------------------------------------------------------------------------------- PAGE: View a Credential URL: https://docs.walt.id/community-stack/wallet/credential-management/view-credential RAW: https://docs.walt.id/community-stack/wallet/credential-management/view-credential.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # View a Credential Retrieve the full details of a single credential stored in a wallet by its ID. To view a credential, you first need its `id`. See [List Credentials](https://docs.walt.id/community-stack/wallet/credential-management/list-credentials.md) for how to retrieve it. ## View a Credential **Option: CURL** **Endpoint:** `GET /wallet-api/wallet/{walletId}/credentials/{credentialId}` | [API Reference](https://wallet.demo.walt.id/swagger/index.html#/WalletCredentials/get_wallet_api_wallet__wallet__credentials__credentialId_) ##### Example Request ```bash curl -X 'GET' \ 'http://0.0.0.0:7001/wallet-api/wallet/{walletId}/credentials/{credentialId}' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {token}' ``` **Path Parameters** - `walletId`: _String_ - The ID of the wallet containing the credential. See [Accounts & Wallets](https://docs.walt.id/community-stack/wallet/accounts-wallets.md#retrieve-wallet-id) for how to retrieve it. - `credentialId`: _String_ - The `id` of the credential to retrieve (e.g. `urn:uuid:c2f7e988-a4cb-48c2-bbf6-ed97f6010abe`). See [List Credentials](https://docs.walt.id/community-stack/wallet/credential-management/list-credentials.md) for how to retrieve it. **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 The API returns a single credential object with the raw credential document, the parsed credential, and metadata. ```json { "wallet": "6006b6f4-e651-46db-b6ae-e7bd6e9c40f2", "id": "urn:uuid:c2f7e988-a4cb-48c2-bbf6-ed97f6010abe", "document": "eyJraWQiOiJkaWQ6...", "disclosures": "WyJ3N204WTdYc1d5NWtTYTFpaG1WVXJBIiwiYmlydGhkYXRlIiwiMTk0MC0wMS0wMSJd", "addedOn": "2026-04-16T15:08:01.604768543Z", "deletedOn": null, "pending": false, "format": "vc+sd-jwt", "parsedDocument": { "given_name": "John", "family_name": "Doe", "id": "urn:uuid:c2f7e988-a4cb-48c2-bbf6-ed97f6010abe", "iss": "did:key:z6Mkjo...", "vct": "http://localhost:7002/identity_credential" } } ``` **Response Fields** - `wallet`: The ID of the wallet the credential belongs to. - `id`: The unique credential ID, used in other operations such as [deleting a credential](https://docs.walt.id/community-stack/wallet/credential-management/delete-credential.md). - `document`: The raw credential string. For JWT-based credentials this is the encoded JWT (starts with `ey`); for JSON-LD credentials this is the full JSON document with proof. - `disclosures`: Base64url-encoded SD-JWT disclosures string. Empty for non-SD-JWT credentials. - `addedOn`: ISO 8601 timestamp of when the credential was stored in the wallet. - `deletedOn`: ISO 8601 timestamp of when the credential was soft-deleted, or `null` if the credential is active. - `pending`: Whether the credential is awaiting acceptance or rejection. - `format`: The credential format (e.g. `jwt_vc_json`, `ldp_vc`, `vc+sd-jwt`). - `parsedDocument`: The decoded credential payload as a JSON object. The structure varies by credential type and format. | Status | Description | |--------|-------------| | `200` | Credential returned successfully | | `404` | Credential not found for the given `credentialId` | -------------------------------------------------------------------------------- PAGE: Delete a Credential URL: https://docs.walt.id/community-stack/wallet/credential-management/delete-credential RAW: https://docs.walt.id/community-stack/wallet/credential-management/delete-credential.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Delete a Credential Remove a credential from a wallet. The operation supports two modes: - **Soft-delete** (default) — The credential is marked as deleted and hidden from the wallet view but remains in the database and can be restored. - **Permanent delete** — The credential is irreversibly removed from the database. To delete a credential, you first need its `id`. See [List Credentials](https://docs.walt.id/community-stack/wallet/credential-management/list-credentials.md) for how to retrieve it. ## Delete a Credential **Option: CURL** **Endpoint:** `DELETE /wallet-api/wallet/{walletId}/credentials/{credentialId}` | [API Reference](https://wallet.demo.walt.id/swagger/index.html#/WalletCredentials/delete_wallet_api_wallet__wallet__credentials__credentialId_) ##### Example Request ```bash curl -X 'DELETE' \ 'http://0.0.0.0:7001/wallet-api/wallet/{walletId}/credentials/{credentialId}' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {token}' ``` **Path Parameters** - `walletId`: _String_ - The ID of the wallet containing the credential. See [Accounts & Wallets](https://docs.walt.id/community-stack/wallet/accounts-wallets.md#retrieve-wallet-id) for how to retrieve it. - `credentialId`: _String_ - The `id` of the credential to delete (e.g. `urn:uuid:c2f7e988-a4cb-48c2-bbf6-ed97f6010abe`). See [List Credentials](https://docs.walt.id/community-stack/wallet/credential-management/list-credentials.md) for how to retrieve it. **Query Parameters** - `permanent`: _Boolean (optional)_ - Set to `true` to permanently remove the credential. If omitted or `false`, a soft-delete is performed. **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 On success, the API returns `202 Accepted` with no response body. | Status | Description | |--------|-------------| | `202` | Credential deleted successfully | | `400` | Credential could not be deleted (e.g. not found) | **Permanent deletion example:** ```bash curl -X 'DELETE' \ 'http://0.0.0.0:7001/wallet-api/wallet/{walletId}/credentials/{credentialId}?permanent=true' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {token}' ``` **Note:** Permanent deletion cannot be undone. Use soft-delete when you want to keep the option to restore the credential. -------------------------------------------------------------------------------- PAGE: Import a Credential URL: https://docs.walt.id/community-stack/wallet/credential-management/import-credential RAW: https://docs.walt.id/community-stack/wallet/credential-management/import-credential.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Import a Credential Import a signed Verifiable Credential in JWT or SD-JWT format directly into a wallet. This is useful when a credential has been issued outside of the standard OID4VCI flow and needs to be stored manually. The import endpoint validates the credential's format, verifies its signature, checks the DID binding, and prevents duplicate imports. ## Import a Credential **Option: CURL** **Endpoint:** `POST /wallet-api/wallet/{walletId}/credentials/import` | [API Reference](https://wallet.demo.walt.id/swagger/index.html#/WalletCredentials/post_wallet_api_wallet__wallet__credentials_import) ##### Example Request ```bash curl -X 'POST' \ 'http://0.0.0.0:7001/wallet-api/wallet/{walletId}/credentials/import' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer {token}' \ -d '{ "jwt": "", "associated_did": "" }' ``` **Path Parameters** - `walletId`: _String_ - The ID of the wallet to import the credential into. See [Accounts & Wallets](https://docs.walt.id/community-stack/wallet/accounts-wallets.md#retrieve-wallet-id) for how to retrieve it. **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}`. **Body Parameters** - `jwt`: _String_ - The signed Verifiable Credential in JWT or SD-JWT format. - `associated_did`: _String_ - A DID owned by the wallet to associate the credential with. The credential's `sub` claim must match this DID. --- ##### Example Response On success, the API returns `201 Created` with no response body. | Status | Description | |--------|-------------| | `201` | Credential imported successfully | | `400` | Invalid JWT format, VC structure, or validation failed | | `401` | Invalid or missing authentication | | `409` | Credential already exists in the wallet | ## Validation The following checks are performed on every import: - **Format** — The value must be a valid JWT or SD-JWT (three Base64URL-encoded parts separated by dots, or an SD-JWT with `~`-separated disclosures). - **VC structure** — The JWT payload must contain valid W3C Verifiable Credential claims. - **Signature** — The issuer's cryptographic signature is verified. - **DID binding** — The `sub` claim must match the `associated_did` provided. - **Time window** — The `iat` must not be in the future; `exp` (if present) must not be in the past. - **Duplicate check** — If a credential with the same `id` already exists, the import is rejected with `409 Conflict`. ## SD-JWT Support The endpoint accepts both regular JWTs and SD-JWTs. SD-JWTs follow the format: ``` ~~~...~ ``` The full credential including all disclosures is stored in the wallet, and all validation checks apply equally. **Note:** For receiving credentials from issuers through the standard protocol, see [Credential Exchange](https://docs.walt.id/community-stack/wallet/credential-exchange/overview.md). -------------------------------------------------------------------------------- PAGE: Feature Manger URL: https://docs.walt.id/community-stack/wallet/configurations/feature-manager RAW: https://docs.walt.id/community-stack/wallet/configurations/feature-manager.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Feature Manger Use the feature manager to control which wallet 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/wallet/configurations/config-files/features.md). A list of available features can be found here. -------------------------------------------------------------------------------- PAGE: Logging URL: https://docs.walt.id/community-stack/wallet/configurations/logging RAW: https://docs.walt.id/community-stack/wallet/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: Features URL: https://docs.walt.id/community-stack/wallet/configurations/config-files/features RAW: https://docs.walt.id/community-stack/wallet/configurations/config-files/features.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Features With the `_features.conf` you can control which wallet API features are enabled. They are managed via the [feature manager](https://docs.walt.id/community-stack/wallet/configurations/feature-manager.md). ## Available Features **Base Features**
Please note that the base features should always be enabled to make full use of the wallet API. - `web` - Web service - `wallet-service` - Wallet service implementation - `db` - Database manager - `logins` - Logins method management - `auth` - Base authentication system **Optional Features** - `key-generation-defaults` - Key generation defaults (key backend & generation config) configuration - `registration-defaults` - Registration defaults (key, DID) configuration - `oidc` - OIDC login feature - `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. - `runtime` - Runtime mock provider configuration - `push` - Push notifications - `external-signature-endpoints` - Receive and present credentials using externally generated signatures. This is only required if keys are stored outside the Wallet API system. By default, keys are managed within the Wallet API, including those handled through our supported KMS integrations. - `trusted-ca` - allows JWTs signed with a trusted X509 certificate (X5C-JWS-Header) to authenticate wallet accounts. Please note that the features listed below are accessible via the manager. However, as they are still under development, we advise against using them at this time. - `trust` - Trust records - `rejectionreason` - Rejection reasons use case - `notification` - Notification dispatch use case - `tenant` - Cloud-based tenant management ### Example Setup ```editorconfig[_features.conf] enabledFeatures = [] disabledFeatures = [ debug-endpoints, healthchecks ] ``` -------------------------------------------------------------------------------- PAGE: DB URL: https://docs.walt.id/community-stack/wallet/configurations/config-files/db RAW: https://docs.walt.id/community-stack/wallet/configurations/config-files/db.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # DB DB configuration describes which database should be used for data persistence and the connection string required to connect to the database. ### Example ```editorconfig[db.conf] dataSource { # -- jdbcUrl -- # postgres: "jdbc:postgresql://127.0.0.1:5432/postgres" # sqlite: "jdbc:sqlite:data/wallet.db" # mssql: "jdbc:sqlserver://localhost:1433;databaseName=master" jdbcUrl = "jdbc:postgresql://127.0.0.1:5432/postgres" # -- driverClassName -- # postgres: "org.postgresql.Driver" # sqlite: "org.sqlite.JDBC" # mssql: "com.microsoft.sqlserver.jdbc.SQLServerDriver" driverClassName = "org.postgresql.Driver" username = "postgres" # leave empty for sqlite password = "postgres" # leave empty for sqlite transactionIsolation = "TRANSACTION_SERIALIZABLE" maximumPoolSize = 16 minimumIdle = 4 maxLifetime = 60000 autoCommit = false dataSource { journalMode = WAL fullColumnNames = false } } recreateDatabaseOnStart = false ``` -------------------------------------------------------------------------------- PAGE: Auth URL: https://docs.walt.id/community-stack/wallet/configurations/config-files/auth RAW: https://docs.walt.id/community-stack/wallet/configurations/config-files/auth.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Auth ### Example ```editorconfig[auth.conf] encryptionKey = "<128 bit -- key>" // a 128 bit (16 chars) key signKey = "<128 bit -- key>" // a 128 bit (16 chars) key # Available Signing Algorithms are: RS256 or HS256. # If HS256, then provide the 256+ bit key in the tokenKey field. If RS256, then provide the private key in JWK format in the tokenKey field. tokenKey = "" // at least 256 bit (32 chars) # Specify your iss and sub claims audTokenClaim = "audience value" issTokenClaim = "issuer value" tokenLifetime = "30"//days # Examples: ; tokenKey = "hjklwcptiniwjkdwwkigreumcayoyiso" ; tokenKey = "{"p":"8DhAtwXUO8as8-jY_e0OIX2tB8TmoLkLTwJtJXmWrE7pFUVFMviXCHiNd3x_KngJel002xGW3zTHweSqVIkKO_0HqSEed9XKrKQnJTypU5_M-lDHNUtYTp6ATB-rjuGL8F-BxjoMZUTq5arUQ8IxHJda0iQSqwIH-8F-ivHpO2U","kty":"RSA","q":"xoPLLuoXVh9Wk4m8cTXhLf-RA9lapIKhwoPadRJSA8ceA1dqhsH1Vjgk0UVxV1nc-LJUBvpPsWuJDoDc7SUPNZR79Z2UyXZXBBMnXx82UOuK_wJ6yi111rLUDgeHZhh67MAL0TSX6Pje1Xl2s--i7UQuOba6P06SFrl81S--mGM","d":"TSmJ37dU1yZ7NSfL3hm6yfFPCtjww0D4t6uiy8KTpi3Oy7C8_vx1HFZLPr6AlDJkIGfOvHAJ87gVexguJGQgmhu0VJzEsH4IXiVx9L8aNqloc6umhS44dLZU6mwr5EVhx3jz0qI3t6CORzzs6BNnzLtUCIErwpFLDzyI9T0-vxivIBlG4pjmJL0I0zpirMGg-E2oYYIYWgRAc6teibQ5auS3skxIJINBOiAKhvQGU1uOxRnPHiGzEk-sStwrgBbCmlWFy4xXBymkUZTpPwW-dqCpxIUXcfDI-AQ85XmdWoOZRvX5YpX5BDdkeHy1Og3GAsf7w8BCQeROv6fnLEgVPQ","e":"AQAB","kid":"44benLH7kwpQ5uynsoM8JkhftDR6QYpAxVeVm5i-Cqw","qi":"Nw-UbUD9eBIEUJr_y2VkIi1zzrFY19DU3842PRY7sal4pUO1OSO65v5KJwxRsww-k4oD2IrJM1vwCOYXMOkGadldpYDbHZcZAXd4MMfLrrmNKS7eP60m36n5WUookGOowqhLScCYlArYF7JcptN2E77-gRFGf-Yo7KRJA8Q0TYI","dp":"uB31hBJErBhIAZ6rilyCYysHTxgcyH1r7H_oljuOP70vqyzvr_SXFlgH3dgfQ7bDVPVRiML_h07V-IfCAY8TuAzlLT2EM8c6U35xvnmzMrQdkJIPTYrVQVT88tunS5kWAAPD3OIrFAdxntyDWvfhWo-rfbtGT6n74ntY-TgXUn0","dq":"s2Iit6kXH17AKCAdpZuPeLZo_zZovyHANHpbbND6RamniFN00oLwRFIFiwmaStnMYPscDRYh72wB9JYjjTOg08TyYtdbvNTv-qGoQT9-II9IEfnWpV2BD1qXXnoVMv_KDKfZxdklsIM7MWJKDnzsGEU1FDf3gv9n6ZsBn88-Bos","n":"ukctf01ANV4BiJ3eBdoqepmTh1CspUkzKK3zFa2kfUM2O1HMGrNUoJYyUZOFTcrPATOils1I27zGkYeLOYVHQn-QM4kjXDB_OxTpczYOkSu2s50QBk5jTAIb2pLC8ynnDAQWAXFzVOyT_e_PY3crhnosI900tzvyjtAhb8BxIAe7co7OCScagv8A2MOcm-3I9LsM_ecjM5ACfNpOwPzrxPgQzKp3vZapBVLOp1LqiKuyf1_1gTGMmr17MnliPoMo-jiQQaaU-DE-rnuARTabE05b7TqdK2WXFWnXnIRxwoklbZajXJY5EqeMlbt4FNVfmtFC7FXv_3GGUCNoqGvwDw"}" ; audTokenClaim = "http://localhost:7001" ; issTokenClaim = "http://localhost:7001" ; tokenLifetime = "30"//days ``` -------------------------------------------------------------------------------- PAGE: Logins URL: https://docs.walt.id/community-stack/wallet/configurations/config-files/logins RAW: https://docs.walt.id/community-stack/wallet/configurations/config-files/logins.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Logins The `logins.conf` lets you configure which login methods are enabled for wallet authentication ### Example ```editorconfig[logins.conf] enabledLoginMethods: [ "email", "web3", "oidc" ] ``` -------------------------------------------------------------------------------- PAGE: Web URL: https://docs.walt.id/community-stack/wallet/configurations/config-files/web RAW: https://docs.walt.id/community-stack/wallet/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 = 7001 ``` -------------------------------------------------------------------------------- PAGE: Registration Defaults URL: https://docs.walt.id/community-stack/wallet/configurations/config-files/registration-defaults RAW: https://docs.walt.id/community-stack/wallet/configurations/config-files/registration-defaults.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Registration Defaults When creating a new account through the Wallet API, a key and associated DID are generated into the wallet of the account. The configuration below allows you to specify the algorithm for the default key and the method of the DID. If you don't want to have a default key and associated DID, please provide `registration-defaults` in the `disabledFeatures` array in the `feature.conf` file. ### Example ```editorconfig[registration-defaults.conf] // Setup what key should be generated on registration defaultKeyConfig: { backend: jwk keyType: Ed25519 } // Setup what DID should be generated based on above above defined key on registration defaultDidConfig: { method: jwk } // -- Hashicorp Vault TSE key example -- // defaultKeyConfig: { // backend: tse // config: { // server: "http://127.0.0.1:8200/v1/transit" // accessKey: "" // } // keyType: Ed25519 // } // -- Oracle Cloud Infrastructure Vault KMS key example -- // defaultKeyConfig: { // backend: oci // config: { // tenancyOcid: "ocid1.tenancy.oc1..", // userOcid: "ocid1.user.oc1..", // fingerprint: "aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99", // cryptoEndpoint: "-crypto.kms..oraclecloud.com", // managementEndpoint: "-management.kms..oraclecloud.com", // signingKeyPem: "" // } // keyType: secp256r1 // } // -- did:web example -- // defaultDidConfig: { // method: web // config: { // domain: "https://wallet.demo.walt.id" // path: "/wallet-api/registry/[random-uuid]" // automatically generates random UUID for path // } // } ``` -------------------------------------------------------------------------------- PAGE: Key generation defaults URL: https://docs.walt.id/community-stack/wallet/configurations/config-files/key-generation-defaults RAW: https://docs.walt.id/community-stack/wallet/configurations/config-files/key-generation-defaults.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Key generation defaults ### Example ```editorconfig[key-generation-defaults.conf] keyGenerationDefaults { oci { vaultId: "ocid1.vault.oc1.eu-frankfurt-1...", compartmentId: "ocid1.compartment.oc1...", } oci-rest-api { tenancyOcid: "ocid1.tenancy.oc1...", compartmentOcid: "ocid1.tenancy.oc1...", userOcid: "ocid1.user.oc1...", fingerprint: "aa:bb:cc:dd:ee:ff...", managementEndpoint: "123456789-management.kms.eu-frankfurt-1.oraclecloud.com", cryptoEndpoint: "123456789-crypto.kms.eu-frankfurt-1.oraclecloud.com", signingKeyPem: """ SIGNING KEY HERE """ } } ``` -------------------------------------------------------------------------------- PAGE: OIDC URL: https://docs.walt.id/community-stack/wallet/configurations/config-files/oidc RAW: https://docs.walt.id/community-stack/wallet/configurations/config-files/oidc.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # OIDC With the `oidc.conf` file you can setup OIDC login to enable wallet account authentication using an external OIDC server, like e.g. Keycloak. ### Example ```editorconfig[oidc.conf] # This configuration file is used for setting up OIDC login # with an OIDC server for the Web Wallet. # # OIDC in this file is exclusively related to login/authentication with the Web Wallet. # It does not refer to any OIDC credential exchange protocol. # This example configuration can be used for Keycloak. # enable/disable OIDC login (backend functionality - separately enable/disable in frontend) publicBaseUrl = "http://localhost:7101" providerName = keycloak # Enter the realm URL oidcRealm = "http://0.0.0.0:8080/realms/waltid-keycloak-ktor" # JWKS (to verify access keys the user claims to received through OIDC server) oidcJwks = "${oidcRealm}/protocol/openid-connect/certs" oidcScopes = ["roles"] authorizeUrl = "${oidcRealm}/protocol/openid-connect/auth" accessTokenUrl = "${oidcRealm}/protocol/openid-connect/token" logoutUrl = "${oidcRealm}/protocol/openid-connect/logout" clientId = "waltid_backend" clientSecret = "**********" # Keycloak authentication strategy keycloakUserApi = "http://0.0.0.0:8080/admin/realms/waltid-keycloak-ktor/users" # JWKS is cached: jwksCache = { cacheSize = 10 cacheExpirationHours = 24 rateLimit: { bucketSize: 10 refillRateMinutes: 1 } } ``` -------------------------------------------------------------------------------- PAGE: Trusted CA URL: https://docs.walt.id/community-stack/wallet/configurations/config-files/trusted-ca RAW: https://docs.walt.id/community-stack/wallet/configurations/config-files/trusted-ca.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Trusted CA This authentication method allows clients possessing a X.509 certificate (signed by a CA that is trusted by the Wallet API) to access wallet accounts by signing JWTs themselves. In this process, clients should populate the x5c header appropriately. The Wallet API is set up with a list of trusted CA certificates, which are specified in the `trusted-ca.conf` file, see example below. Those certificates should be JSON stringified PEM X.509 certificates. Client certificates are then verified for validity and trustworthiness against this list. List of trusted PEM-encoded x509 certificates: ``` certificates = [ "-----BEGIN CERTIFICATE----- MIIFAzCCAuugAwIBAgIUZAcmlp6i2cSlvcb2nJ3jAdOMxb0wDQYJKoZIhvcNAQEL BQAwETEPMA0GA1UEAwwGUm9vdENBMB4XDTI0MDgyODEzMDAzM1oXDTM0MDgyNjEz MDAzM1owETEPMA0GA1UEAwwGUm9vdENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A MIICCgKCAgEAqb8OySEhtQaE8ocqiZ3jhFd4iClK0PNvbw1SRZvoOplvrRzuHWJI Bq6IunC/VUWXRhwovW6EJ2D8SUxzzz2u32nMHdDpP9uhAth/y134n6FmU8W8Z7aD vY6ySi6W/wXGmUcPYpde3nGCLkxwCkyRvZBIXJqClHoMvSMSd/m3FE2qOfIbp5M4 8kUzwGPie1TBwQ+p9yMRrsZBqZbRyZe/5d1CilhpcuMCVU1rsNrO50YlJuihh0qn Nmi47KLHUlK5PiWbJiUA5rd8Z66Ml5wVQee++HCLF90FwQNr4/gCjz6DBM2AydkH j8YAaET7So/0bfd1WyJhp0YRt+U/4TNk+sop8cy617QqRrcFWpxJvxlgbeOn8kvX /Kih1g/mUilpXDY9Snw7NIPDz4vdCgCpcYxfne90QDDmdVX9yggHoS+NgrY/EBV4 rprxQKA1mS5U2fZRsQAJ5DDLZv8DXebsK1fIuIT9WYRGZBaBvw9tnzh6GrRtry8m 9fLzZYspJfOkYkH7V3mhFwZTbRa3ANyHDmPOtR5BO8CHBVDEzHQKPFyCpVEUTueZ Dq7poT5dCjnPJ6xvaf1c0In7tBNux/0QB8WZVLmZVnpJ8toxBfbnYvF5yqcZk30r YPX1ZDgpqDC66cACOGefV3J4BIpd98QlvkZzbDg971zB6VN08Q8TIHsCAwEAAaNT MFEwHQYDVR0OBBYEFFIr4DuYd4k8QO/07x2usuvveLYxMB8GA1UdIwQYMBaAFFIr 4DuYd4k8QO/07x2usuvveLYxMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL BQADggIBABwWbzbdtPDym52T7/bnNgvxGWylcKP9tpsEWWRGIcQi4Y6Xvrn/+NFj /vwjhm9q7k/9ks76TRXYf3DOppCFd80tj1AZPjB3zlWMzVvh74AhDgtn/Q7tuo0Z QqSwu05JQt8HW6IeW0cl8UwGtZoKYiJnVEuOK9aFhUdFzu9DSRlpY/h5jI7vkJCr IoHy4XD9OPYpRIQ+EpomZqUxBZZ/BfvAPJqHVaPtI7j9lDuTE5PoHAKYN6KZrVGU 992K/5Q+wwHiddskQpoRJPK24HdXVcIsoIyHhk1PZqtuTIHRSOY3AoLJCHRhu3gj ZiHBS1Ui7kDcbOpDi4lrfwONvkY3cOeaIpEhZBC7y1bT2Ln8Bpnjz1cbsIGLuHMu CRfdFu2cPFajwAZUh+72OoITpn+PkSPO0iTTi8dBAcfKlKdzr+0CsDK7U38So35X d6OBu3sDiZhE7LkYd4A329f+z1pc9CSd9COcmmBPE1EGDxIt0qKfBw4/xWRbd4GZ Eq7IcjznzKs0KALjdbns+pmr1j+TwGtHSymF8t8Y1rHv3dTBdAN3BejPlsc2IL60 wDo2ZnKpE1IFs6ae/JhY4Y5+5iv9Rc3d23/SVN4HygwVZx8c1/PqJ0XZMH5O2t4K FVH0vHAZqlS+fs2BBahpPupnhbvmNfzR9N43VG69nlcUDBgkMoFz -----END CERTIFICATE----- " ] ``` **Example to convert a X.509 Certificate** ```bash jq -sR . root-ca1-cert.pem ``` `root-ca1-cert.pem` refers to the filename. -------------------------------------------------------------------------------- PAGE: Notifications URL: https://docs.walt.id/community-stack/wallet/configurations/config-files/notifications RAW: https://docs.walt.id/community-stack/wallet/configurations/config-files/notifications.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Notifications **Note:** This feature is not yet generally available! -------------------------------------------------------------------------------- PAGE: Rejection Reason URL: https://docs.walt.id/community-stack/wallet/configurations/config-files/rejectionreason RAW: https://docs.walt.id/community-stack/wallet/configurations/config-files/rejectionreason.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Rejection Reason **Note:** This feature is not yet generally available! -------------------------------------------------------------------------------- PAGE: Runtime URL: https://docs.walt.id/community-stack/wallet/configurations/config-files/runtime RAW: https://docs.walt.id/community-stack/wallet/configurations/config-files/runtime.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Runtime The `runtime.conf` lets you enable mocked responses for certain features. At the moment, it only mocks the responses of the credential categories API endpoints. ### Example ```editorconfig[runtime.conf] mock = true ``` -------------------------------------------------------------------------------- PAGE: Trust URL: https://docs.walt.id/community-stack/wallet/configurations/config-files/trust RAW: https://docs.walt.id/community-stack/wallet/configurations/config-files/trust.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Trust **Note:** This feature is not yet generally available! -------------------------------------------------------------------------------- PAGE: Full API Reference URL: https://docs.walt.id/community-stack/wallet/full-api-reference RAW: https://docs.walt.id/community-stack/wallet/full-api-reference.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Full API Reference Find it [here](https://wallet.demo.walt.id/swagger/index.html) -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/community-stack/wallet/apps/overview RAW: https://docs.walt.id/community-stack/wallet/apps/overview.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Overview The walt.id wallet service offers next to the wallet API a set of white-label applications listed below that help you build your use-cases more quickly. - [Web Wallet](https://docs.walt.id/community-stack/wallet/apps/web-wallet/overview.md) - A custodial web-wallet (PWA) solution for credentials and tokens. -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/community-stack/wallet/apps/web-wallet/overview RAW: https://docs.walt.id/community-stack/wallet/apps/web-wallet/overview.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Overview White-label digital ID wallet solution to manage keys, DIDs, credentials and tokens. Functionality is powered by the walt.id wallet API making it a custodial solution. Video: https://www.youtube.com/watch?v=HW9CNFmRFlI ## Getting Started Make sure you have [docker](https://docs.docker.com/engine/install/) installed on your machine. **Option: CURL** 1. Clone [walt.id identity](https://github.com/walt-id/waltid-identity) ```bash git clone https://github.com/walt-id/waltid-identity.git ``` ## Chose a Wallet Version Once you cloned the repo, you will find under `waltid-applications/waltid-web-wallet/apps` two different versions. The first version is a good fit for end-user/business use-cases and demos where the audience knows less about the ins and outs of digital identity. The second version is more targeted towards an audience that wants more customization and advanced features, such as the creation of specific keys based on various algorithms. 1. [The demo wallet](https://wallet.demo.walt.id/) - offers core digital identity features in a simplified UI. 2. [The dev wallet](https://wallet-dev.walt.id) - offers all digital identity features provided by the walt.id wallet API with a lot of customisation options for the end user. Users can for example create specific keys based on different algorithms and link them to DIDs. Under the hood, they are using the same functionality provided by the [walt.id wallet API](https://docs.walt.id/community-stack/wallet/getting-started.md). ## Launch The Wallet A docker-compose configuration is provided as a convenience for quick-start. It sets up and starts all modules required to fully benefit from the wallet's functions. The runtime configuration files should be available in the same folder as the docker-compose file. There are sample configuration files provided, except walt.yaml which should be updated with the corresponding API keys. **Option: CURL** 1. Start docker compose ```bash cd docker-compose && docker-compose up ``` 2. Visit the wallet - [Demo Wallet](http://localhost:7101) - [Dev Wallet](http://localhost:7104) ## Services By executing the docker compose, the following services where started: - [Web Wallet Frontend](http://localhost:7101) - [Wallet API](http://localhost:7001) - the API used by the web wallet to enable the management of VCs, DIDs and their related keys. Next to that, the following services were also started but are not required to operate the wallet. Though they can be useful to run through a full issuance and verification use-case. - [Issuer API](http://localhost:7002) - enable apps to issue credentials (W3C JWTs and SD-JWTs) via OID4VC. - [Verifier API](http://localhost:7003) - enable apps to verify credentials (W3C JWTs and SD-JWTs) via OID4VP/SIOPv2. - [Web Portal](http://localhost:7102) - An issuer and verifier portal for credentials which are provided by the [VC Repository](http://localhost:7103). - [Verifiable Credentials Repository](http://localhost:7103) - A set of W3C compliant credential data structures. -------------------------------------------------------------------------------- PAGE: Try It Out! URL: https://docs.walt.id/community-stack/wallet/apps/web-wallet/try-it-out! RAW: https://docs.walt.id/community-stack/wallet/apps/web-wallet/try-it-out!.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Try It Out! Try the web wallet [here](https://wallet.demo.walt.id/) -------------------------------------------------------------------------------- PAGE: IOS URL: https://docs.walt.id/community-stack/wallet/apps/ios RAW: https://docs.walt.id/community-stack/wallet/apps/ios.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # IOS Use digital identity features from the walt.id stack to build wallets in iOS environments. ## Capability Overview - **Keys** - Create and manage keys based on different algorithms, like RSA and Secp256r1 via the IOS keychain. - **DID** - Create and manage DIDs secured via keys in the IOS keychain. - **Receive Credentials** - Receive credentials via OID4VC. - **Present Credentials** - Present credentials via OID4VP. ## Get Started - [IOS crypto example app](https://github.com/walt-id/waltid-identity/tree/main/waltid-applications/waltid-crypto-ios-testApp) - [IOS openid4vc example app](https://github.com/walt-id/waltid-identity/tree/main/waltid-applications/waltid-openid4vc-ios-testApp) -------------------------------------------------------------------------------- PAGE: Android URL: https://docs.walt.id/community-stack/wallet/apps/android RAW: https://docs.walt.id/community-stack/wallet/apps/android.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Android Use digital identity features from the walt.id stack to build wallets in android environments. ## Capability Overview - **Key Generation** - Create keys based on different algorithms, like RSA and Secp256r1. - **DID Creation** - Sign texts using raw and JWS signing options. - **Signing** - Create a did:key or did:jwk. - **Verification** - Verify signed text. - **Credential Presentation** - Create and sign verifiable presentations for W3C credentials. ## Get Started - [Android demo project](https://github.com/walt-id/waltid-identity/tree/main/waltid-applications/waltid-android) - Explore capabilities in our Demo project. -------------------------------------------------------------------------------- PAGE: FAQs URL: https://docs.walt.id/community-stack/wallet/faq RAW: https://docs.walt.id/community-stack/wallet/faq.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Wallet API FAQs This page addresses common questions about the walt.id Wallet API for the Community Stack. ## General Questions **Q: What credential formats does the Wallet support?** A: The Wallet 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 - **Combined credentials**: The wallet can handle multiple credentials from different issuers **Q: Which OID4VCI/VP draft versions are supported?** A: The Wallet supports OID4VCI/VP Draft 11 and Draft 13, with support for DCQL (Draft 28) coming soon. The wallet can evaluate incoming credential share requests based on different languages (DIF or DCQL) and match stored credentials for successful credential presentation. **Q: What types of wallets does walt.id support?** A: walt.id offers two main wallet variations: - **Consumer Wallet (B2C)**: Enables organizations to launch new identity wallet apps or embed identity wallet capabilities into existing apps for consumers. Available in both Community & Enterprise Stack. - **Organizational Wallet (B2B)**: Enables organizations to request, store, manage and present digital credentials of their organization and employees. Available only in the Enterprise Stack. ## Architecture & Deployment **Q: What are the deployment options for the Wallet API?** A: In the Community Stack, the Wallet API is deployed as a stateful service. This enables complete custodial wallet use cases, where wallet data and keys are managed securely within the service's database. **Q: How does the Community Stack Wallet differ from the Enterprise Stack?** A: The Community Stack Wallet follows a one-user-one-wallet model with built-in authentication (email/password, OIDC or others). The Enterprise Stack supports a flexible wallet model where multiple parties can access a single wallet, includes multi-tenant support, and has no built-in end-user authentication layer - authentication is handled at the application level. **Q: What is the progressive web app (PWA) option?** A: walt.id offers a white-label progressive web app that enables wallet functionality via browser or mobile applications. It's the easiest and most effective way to demonstrate and test capabilities of credential issuance and verification services, supporting different experiences including single and cross-device flows. Its powered by the Community Stack wallet API. ## Key Management & Security **Q: How does key management work with the Wallet API?** A: The Wallet API supports multiple key management approaches: - **Database storage**: Keys can be stored in the configured DB of the wallet API - **External KMS**: Integration with AWS KMS, Azure Key Vault, Google Cloud KMS, Hashicorp Vault, OCI KMS, and others **Q: What cryptographic operations does the wallet perform?** A: The wallet automatically performs Proof of Key/DID ownership when interacting with issuers and verifiers. It handles credential presentation signatures. **Q: How are DIDs managed in the wallet?** A: In the Community Stack, DIDs can be created, stored, and hosted directly through the Wallet API with a database specific to the API. ## Credential Management **Q: How does credential storage work?** A: Credentials received from issuers can be stored in the configured database of the Wallet API. **Q: How does the wallet handle credential matching for presentation requests?** A: The wallet parses credential requests from verifiers (whether in Presentation Definition format or DCQL) and attempts to find and match credentials stored in the user's wallet to fulfill the verifier's requirements. It also handles the signing of Verifiable Presentations and holder binding JWTs. **Q: Can the wallet handle multiple credentials from different issuers?** A: Yes, the wallet is designed to handle multiple credentials from different authorities and trust chains. ## Authentication & User Management **Q: How does authentication work in the Community Stack Wallet?** A: The Community Stack Wallet follows a one-user-one-wallet model with two authentication options: 1. **Traditional login**: Email and password authentication 2. **External OIDC**: Connection to external OIDC-compliant authentication providers 3. **Web3 Auth**: Users login using their web3 wallet 4. **X.509 Certificate**: Auth via x.509 certificates ## Integration & Exchange **Q: How do I get started with the Wallet API?** A: Start with the [getting started guide](https://docs.walt.id/community-stack/wallet/getting-started.md) which covers setup and basic wallet operations. You can also explore the [progressive web app](https://docs.walt.id/community-stack/wallet/apps/overview.md) for a complete white-label solution. **Q: How does credential exchange work with issuers and verifiers?** A: The wallet supports full credential exchange flows: - **With issuers**: Uses the receive endpoint to handle protocol interactions and automatically performs proof of key possession - **With verifiers**: Uses the present endpoint to manage interactions, parse credential requests, match stored credentials, and sign Verifiable Presentations ## Standards & Protocols **Q: What protocols does the wallet support for credential exchange?** A: The wallet integrates and supports core protocol flows based on: - **OID4VCI/VP**: For receiving and presenting credentials - **ISO/IEC 18013-7**: For mDL/mDoc handling - **DIF Presentation Definition**: For parsing credential requests - **DCQL**: Another format for credential request (coming soon) **Q: How does the wallet handle different credential formats?** A: The wallet uses a unified architecture powered by core libraries (crypto, did, openid4vc, digital-credentials, dif-definitions-parser, dcql-parser) that abstract away different credential formats. This ensures credential information can be displayed clearly to end users regardless of format. **Q: What is the role of the dif-definitions-parser library?** A: This library helps interpret presentation definitions provided by verifier parties and assists in finding and matching stored credentials in the wallet to meet specified sharing requests. ## Development **Q: How does the Community Stack handle session management?** A: The Community Stack Wallet API is typically deployed as a single instance using Docker. It can be paired with a web-wallet frontend implemented as a Progressive Web App (PWA) for enhanced user experience. **Q: Can I build custom wallet applications?** A: Yes, you can build custom wallet applications using: - **REST APIs**: For backend wallet functionality - **SDKs**: For iOS and Android mobile applications - **Progressive Web App**: For browser-based wallet solutions - **Libraries**: For direct integration into existing applications -------------------------------------------------------------------------------- PAGE: Getting Started URL: https://docs.walt.id/community-stack/verifier2/getting-started RAW: https://docs.walt.id/community-stack/verifier2/getting-started.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Getting Started **Verifier2** is an open-source HTTP API service that acts as the relying party in a digital credential exchange. It sends a presentation request to a holder's wallet, receives the signed credential presentation in response, and validates it against configurable policies. Credential exchange runs over [OID4VP v1.0](https://docs.walt.id/concepts/data-exchange-protocols.md), with additional support for the [Digital Credentials API](https://docs.walt.id/concepts/data-exchange-protocols/dc-api.md) and [ISO/IEC 18013-7](https://docs.walt.id/concepts/data-exchange-protocols.md). Supported credential formats include [W3C VC](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), [ISO 18013-5 mDL](https://docs.walt.id/concepts/digital-credentials/mdoc-mdl-iso.md), and ISO 23220 PhotoID. **Note:** The original verifier service, which supports Draft 14 and Draft 20 of the OID4VP specification, is deprecated and will be removed in Q2 2026. If you require documentation for it, see the existing [Verifier documentation](https://docs.walt.id/community-stack/verifier/getting-started.md). For new projects, use **Verifier2** instead. If you're migrating from Verifier (v1), see the [Migration Guide](https://docs.walt.id/community-stack/verifier2/migration-from-verifier1.md). ## Supported Standards Verifier2 supports the following 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), ISO 23220 PhotoID | | **Credential Exchange:** | [OID4VP](https://docs.walt.id/concepts/data-exchange-protocols.md) (Version 1.0), [ISO/IEC 18013-7](https://docs.walt.id/concepts/data-exchange-protocols.md), [Digital Credentials API](https://docs.walt.id/concepts/data-exchange-protocols/dc-api.md) | | **Signature Algorithms:** | ed25519, secp256k1, secp256r1, RSA | ## Setup If you are new to the stack, the **Docker Compose quick start** is the fastest way to get running — it spins up the Verifier, Issuer, and Wallet together in one command, which mirrors a real verification flow. Once running, the API is available at `http://localhost:7004`. **Note:** When running via Docker Compose, the original Verifier (v1) runs on port `7003` and Verifier2 runs on port `7004` to avoid conflicts. When running Verifier2 standalone with Docker, it uses the default port `7003`. - [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. _(Recommended for first-time users)_ - [Run standalone with Docker](https://docs.walt.id/community-stack/verifier2/setup.md) - Pull and run the verifier container in isolation using the [walt.id identity repo](https://github.com/walt-id/waltid-identity.git). See the [configuration reference](https://docs.walt.id/community-stack/verifier2/configurations) for available config options. - [Full API reference](https://docs.walt.id/community-stack/verifier2/full-api-reference.md) - Explore all available endpoints via Swagger. ## Features - [Credential Verification](https://docs.walt.id/community-stack/verifier2/credential-verification/vc-oid4vp.md) - Request and verify various digital credential types and formats ([W3C](https://docs.walt.id/community-stack/verifier2/credential-verification/vc-oid4vp.md), [SD-JWT](https://docs.walt.id/community-stack/verifier2/credential-verification/sd-jwt-vc-oid4vp.md), [mDL](https://docs.walt.id/community-stack/verifier2/credential-verification/mdl-oid4vp.md)) via OID4VP. - [Policies](https://docs.walt.id/community-stack/verifier2/credential-verification/policies/overview) - Apply built-in or custom policies to control validation checks such as expiry, schema conformance, and trust anchor verification. - [Callbacks & SSE](https://docs.walt.id/community-stack/verifier2/callback-and-sse.md) - Get notified when a verification session completes via a webhook callback or a Server-Sent Events (SSE) stream, including the session status, policy results, and presented credentials. ## Start Verifying Choose the credential type and exchange protocol that matches your use case: - [W3C Verifiable Credentials via OID4VP](https://docs.walt.id/community-stack/verifier2/credential-verification/vc-oid4vp.md) - [SD-JWT VC via OID4VP](https://docs.walt.id/community-stack/verifier2/credential-verification/sd-jwt-vc-oid4vp.md) - [Mobile Driver's Licenses (mDL) via OID4VP](https://docs.walt.id/community-stack/verifier2/credential-verification/mdl-oid4vp.md) - [Verifiable Credentials via Digital Credentials API](https://docs.walt.id/community-stack/verifier2/credential-verification/dc-api.md) -------------------------------------------------------------------------------- PAGE: Setup URL: https://docs.walt.id/community-stack/verifier2/setup RAW: https://docs.walt.id/community-stack/verifier2/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. Clone the Identity Repo** Clone the repository to your local machine. The repo contains the default configuration files that will be mounted into the container in the next step. ```bash git clone https://github.com/walt-id/waltid-identity.git && cd waltid-identity ``` **2. Pull the Docker Image** Pull the pre-built Docker image from Docker Hub: ```bash docker pull waltid/verifier-api2 ``` **3. Run the Docker Image** Run the container, mounting the config directory from the cloned repository. This gives the service its default configuration — see the note below for details. ```bash docker run -p 7003:7003 \ -itv $(pwd)/waltid-services/waltid-verifier-api2/config:/waltid-verifier-api2/config \ -t waltid/verifier-api2 ``` The `-v` flag mounts `waltid-services/waltid-verifier-api2/config` from the cloned repository into the container. This directory contains the service configuration files (e.g. server port, trust store settings). You can edit these files before starting the container to customise the service behaviour. **Note:** See the [configuration reference](https://docs.walt.id/community-stack/verifier2/configurations/feature-manager.md) for a full description of all available config files and their fields. Once the container starts, the API is available at `http://localhost:7003`. You can confirm it is running by opening `http://localhost:7003/swagger` in your browser. ### Option 2: Building the Image Yourself **Note:** This option requires **Java (JDK 17+)**, **Gradle**, and **Docker** to be installed on your machine. **1. Clone the Identity Repo** Clone the repository to your local machine: ```bash git clone https://github.com/walt-id/waltid-identity.git && cd waltid-identity ``` **2. Build 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-api2:publishImageToLocalRegistry # image: waltid/verifier-api: ``` ```bash # Production (multi-arch push to your registry) export DOCKER_USERNAME= export DOCKER_PASSWORD= ./gradlew :waltid-services:waltid-verifier-api2:publishImage # image: docker.io//verifier-api: ``` Note: multi-arch images require a registry push. Local tar output is single-arch only. **3. Run 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-api2/config:/waltid-verifier-api2/config \ -t waltid/verifier-api2 ``` The `-v` flag mounts `waltid-services/waltid-verifier-api2/config` from the cloned repository into the container. This directory contains the service configuration files (e.g. server port, trust store settings). You can edit these files before starting the container to customise the service behaviour. **Note:** See the [configuration reference](https://docs.walt.id/community-stack/verifier2/configurations) for a full description of all available config files and their fields. Once the container starts, the API is available at `http://localhost:7003`. You can confirm it is running by opening `http://localhost:7003/swagger` in your browser. ## Start Verifying Credentials Now that the Verifier2 API is running, choose the credential type that matches your use case: - [W3C Verifiable Credentials via OID4VP](https://docs.walt.id/community-stack/verifier2/credential-verification/vc-oid4vp.md) - [SD-JWT VC via OID4VP](https://docs.walt.id/community-stack/verifier2/credential-verification/sd-jwt-vc-oid4vp.md) - [Mobile Driver's Licenses (mDL) via OID4VP](https://docs.walt.id/community-stack/verifier2/credential-verification/mdl-oid4vp.md) - [Verifiable Credentials via Digital Credentials API](https://docs.walt.id/community-stack/verifier2/credential-verification/dc-api.md) - [Full API reference](https://docs.walt.id/community-stack/verifier2/full-api-reference.md) -------------------------------------------------------------------------------- PAGE: Verifying Verifiable Credentials (W3C) via OID4VP URL: https://docs.walt.id/community-stack/verifier2/credential-verification/vc-oid4vp RAW: https://docs.walt.id/community-stack/verifier2/credential-verification/vc-oid4vp.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Verifying Verifiable Credentials (W3C) via OID4VP This guide shows you how to verify a W3C Verifiable Credential using the walt.id Verifier2 API and the OID4VP protocol. The verification process sends a credential request to a holder's wallet, receives the signed credential presentation, and validates it against your policies. [**Verifiable Credential (VC)**](https://docs.walt.id/concepts/digital-credentials/verifiable-credentials-w3c.md): A digital equivalent of physical credentials such as a driver's license or university degree. VCs are cryptographically secure, privacy-respecting, and instantly verifiable. [**OID4VP**](https://docs.walt.id/concepts/data-exchange-protocols/openid4vp.md): A protocol specifying how parties can present VCs in a way that's consistent and secure across platforms ensuring interoperability. **Note:** If you're migrating from the original Verifier (v1), see the [Migration Guide](https://docs.walt.id/community-stack/verifier2/migration-from-verifier1.md). ## Prerequisites Before you begin, ensure you have: - **Verifier2 API running** — Follow the [Getting Started](https://docs.walt.id/community-stack/verifier2/getting-started.md) or [Setup](https://docs.walt.id/community-stack/verifier2/setup.md) guide - **A test wallet with credentials** — Use the [walt.id Wallet](https://docs.walt.id/community-stack/wallet/getting-started.md) for testing, or any OID4VP-compatible wallet - **Publicly accessible URL** (for local development) — Use [ngrok](https://ngrok.com) or similar to expose `http://localhost:7003` if testing with a mobile wallet **Note:** **Local Development:** When running locally via Docker, the API is available at `http://localhost:7003` (standalone) or `http://localhost:7004` (Docker Compose). Use this as your base URL in the examples below. --- ## Example 1: Basic Verification This example requests a single W3C Verifiable Credential (OpenBadgeCredential) and verifies its cryptographic signature using the default signature policy. **Option: CURL** **Endpoint:** `POST /verification-session/create` | [API Reference](https://verifier2.portal.test.waltid.cloud/swagger/index.html#/Verification%20Session%20Management/post_verification_session_create) ##### Example Request ```bash curl -X 'POST' \ 'http://localhost:7003/verification-session/create' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -d '{ "flow_type": "cross_device", "core_flow": { "dcql_query": { "credentials": [ { "id": "credential_1", "format": "jwt_vc_json", "meta": { "type_values": [ [ "VerifiableCredential", "UniversityDegree" ] ] } } ] }, "policies": { "vc_policies": [ { "policy": "signature" } ] } } }' ``` **Body Parameters** - **flow_type**: _String_ (required) - Specifies how the credential request will be delivered to the holder's wallet. Options: - `"cross_device"` — User scans a QR code on a different device (e.g. desktop browser → phone wallet) - `"same_device"` — User clicks a deep link on the same device (e.g. phone browser → wallet app on same phone) - `"dc_api"` — Browser/OS-native Digital Credentials API (Chrome, Safari, iOS/Android native) over OpenID4VP 1.0 (Annex D). - `"dc_api-annex-c"` — Browser/OS-native Digital Credentials API (Chrome, Safari, iOS/Android native) over 18013-7 (Annex C). - **core_flow**: _Object_ (required) - Defines what credentials to request and which policies to apply. Contains: - **dcql_query**: _Object_ (required) - Specifies the credential type, format, and claims to request using the [DCQL query language](https://identity.foundation/credential-query-language/). You can also use the DCQL builder [here](https://dcql.walt.id/). - **credentials**: _Array_ - List of credential queries. Each credential query contains: - **id**: _String_ - Unique identifier for this credential in the request (used in policy results) - **format**: _String_ - Credential format. For W3C VCs, use the `"jwt_vc_json"` (JWT format). - **meta.type_values**: _Array_ - List of acceptable credential type arrays. The credential must match at least one type array. Example: `[["VerifiableCredential", "OpenBadgeCredential"]]`. - **claims**: _Array_ (optional) - Specific claims to request from the credential. Each claim has a `path` (JSON path to the claim). If omitted, all claims that are non-selectively disclosable are provided by the wallet. - **policies**: _Object_ (optional) - Validation rules applied to the received credential. Defaults to signature verification only if omitted. - **vc_policies**: _Array_ - Policies applied to individual credentials. Each policy has a `policy` field specifying the policy name. Available policies: `signature`, `expired`, `not-before`, `revoked-status-list`, `schema`, and more. See [Policies](https://docs.walt.id/community-stack/verifier2/policies/overview.md) for the complete list. - **vp_policies**: _Array_ (optional) - Policies applied to the entire presentation (not shown in this example) - **signedRequest**: _Boolean_ (optional) - Whether to sign the authorization request JWT. Required for HAIP profile compliance and DC API Annex C (ISO 18013-7) flows. Defaults to `false`. See [Signed Requests](#signed-requests) to learn more. - **encryptedResponse**: _Boolean_ (optional) - Whether the wallet should encrypt the VP token response. Defaults to `false`. See [Encrypted Responses](#encrypted-responses) to learn more. - **key**: _Object_ (optional) - Signing key for this verification session. Overrides the service-wide default `key` configured via the[`verifier-service.conf`](https://docs.walt.id/community-stack/verifier2/configurations/config-files/verifier-service.md#key-optional). Must be a serialized key in the format `{"type":"jwk","jwk":{...}}`. Required when `signedRequest: true` or `encryptedResponse: true` and no service-wide default is configured. See [Signed Requests](#signed-requests) and [Encrypted Responses](#encrypted-responses) for when this is needed. - **x5c**: _Array of Strings_ (optional) - X.509 certificate chain for this verification session. Overrides the service-wide default `x5c` configured via [`verifier-service.conf`](https://docs.walt.id/community-stack/verifier2/configurations/config-files/verifier-service.md#x5c-optional). Must be an ordered chain in DER-encoded, Base64-encoded format where the first certificate corresponds to the `key`. Required for X.509-based client authentication (`x509_san_dns:` or `x509_hash:` as `clientId`) and signed Annex C requests. See [Signed Requests](#signed-requests) to learn more. - **verifier_info**: _Object_ (optional) - An array of attestations about the Verifier relevant to the Credential Request. Each object is a JSON object with the following fields: - **format**: _String_ - The format of the attestation. Currently only `"jwt"` is supported. - **data**: _String_ - The attestation data in JWT format. - **credentialIds**: _Array_ (optional) - Specifies which credentials in your DCQL query this attestation applies to. References the `id` fields from your `credentials` array in the DCQL query. If omitted, the attestation applies to all requested credentials. - **url_config**: _Object_ (optional) - URL configuration for this session. Only applies to `cross_device` and `same_device` flows (not `dc_api`). - **url_prefix**: _String_ (optional) - The publicly accessible base URL where the wallet will fetch the authorization request and post its response. Overrides the `urlPrefix` from `verifier-service.conf`. - The service constructs: Request URI: `{urlPrefix}/{sessionId}/request` and Response URI: `{urlPrefix}/{sessionId}/response` - **Must be reachable by the wallet** (use ngrok or public URL for local development) - **url_host**: _String_ (optional) - The base URL used to construct the authorization request link. Overrides the `urlHost` from `verifier-service.conf`. - For standard OID4VP flows, use `"openid4vp://authorize"` (default) - For custom authorization endpoints (e.g. conformance testing), provide a full HTTPS URL - **redirects**: _Object_ (optional) - Success and error redirect URLs (only for `cross_device` and `same_device` flows) - **successRedirectUri**: _String_ - URL to redirect the user after successful verification - **errorRedirectUri**: _String_ - URL to redirect the user if verification fails **Note:** Many parameters have defaults configured in `verifier-service.conf`. Request-level parameters override these defaults for that specific session only. See [Configuration Defaults](#configuration-defaults) below. --- ##### Example Response The response contains the session ID and authorization URLs to present to the user. ```json { "sessionId": "7c66c445-63e6-4a6a-8351-23ec78791b4e", "bootstrapAuthorizationRequestUrl": "openid4vp://authorize?client_id=verifier2&request_uri=https%3A%2F%2Ff5dc-2a02-8388-17c1-5580-bddd-6845-3edf-388f.ngrok-free.app%2Fverification-session%2F7c66c445-63e6-4a6a-8351-23ec78791b4e%2Frequest", "fullAuthorizationRequestUrl": "openid4vp://authorize?response_type=vp_token&client_id=verifier2&state=467f4e16-8c42-489a-a17c-cb6175063df3&response_mode=direct_post&nonce=ae4a922a-255f-4118-b9c6-35031c3546ff&response_uri=https%3A%2F%2Ff5dc-2a02-8388-17c1-5580-bddd-6845-3edf-388f.ngrok-free.app%2Fverification-session%2F7c66c445-63e6-4a6a-8351-23ec78791b4e%2Fresponse&dcql_query=%7B%22credentials%22%3A%5B%7B%22id%22%3A%22credential_1%22%2C%22format%22%3A%22jwt_vc_json%22%2C%22meta%22%3A%7B%22type_values%22%3A%5B%5B%22VerifiableCredential%22%2C%22UniversityDegree%22%5D%5D%7D%7D%5D%7D&client_metadata=%7B%22vp_formats_supported%22%3A%7B%22mso_mdoc%22%3A%7B%22issuerauth_alg_values%22%3A%5B-7%2C-9%2C-50%5D%2C%22deviceauth_alg_values%22%3A%5B-7%2C-9%2C-50%2C-65537%5D%7D%7D%2C%22client_name%22%3A%22Verifier2%22%2C%22logo_uri%22%3A%22https%3A%2F%2Fimages.squarespace-cdn.com%2Fcontent%22%2C%22tos_uri%22%3A%22https%3A%2F%2Fexample.com%2Fterms%22%2C%22policy_uri%22%3A%22https%3A%2F%2Fexample.com%2Fprivacy%22%2C%22client_uri%22%3A%22https%3A%2F%2Fverifier.example.com%22%2C%22client_name%23fr-FR%22%3A%22V%C3%A9rificateur2%22%2C%22logo_uri%23fr-FR%22%3A%22https%3A%2F%2Fexample.com%2Flogo-fr.png%22%2C%22tos_uri%23fr-FR%22%3A%22https%3A%2F%2Fexample.com%2Fterms-fr%22%2C%22policy_uri%23fr-FR%22%3A%22https%3A%2F%2Fexample.com%2Fprivacy-fr%22%2C%22client_uri%23fr-FR%22%3A%22https%3A%2F%2Fverifier.example.com%2Ffr%22%7D" } ``` **Response Fields:** - **sessionId**: _String_ - The unique ID of this verification session. Use this to query the session status. - **bootstrapAuthorizationRequestUrl**: _String_ - Shortened URL using `request_uri` by reference. **Use this for QR codes** to minimize QR code density. - **fullAuthorizationRequestUrl**: _String_ - Complete URL with all parameters embedded. Use this if the wallet doesn't support `request_uri` (rare). --- ## Presenting the Request to the User After creating the session, you need to present the authorization request to the user based on the flow type you chose. ### Cross-Device Flow (QR Code) 1. **Generate a QR code** from the `bootstrapAuthorizationRequestUrl` 2. **Display the QR code** to the user on your web page or application 3. **User scans the QR code** with their wallet app 4. **Wallet presents the credential** and posts the response to your verifier 5. **Monitor the session status** by polling the session endpoint or using SSE/callbacks (see below) ### Same-Device Flow (Deep Link) 1. **Redirect the user** to the `bootstrapAuthorizationRequestUrl` 2. **User's wallet app opens** automatically (via deep link) 3. **Wallet presents the credential** and posts the response to your verifier 4. **User is redirected** to your `successRedirectUri` or `errorRedirectUri` 5. **Query the session status** to retrieve the verification results --- ## Checking Verification Results You can query the verification session to check if the user has presented their credential and whether validation succeeded. **Option: CURL** **Endpoint:** `GET /verification-session/{sessionId}/info` | [API Reference](https://verifier2.portal.test.waltid.cloud/swagger/index.html#/Verification%20Session%20Management/get_verification_session__verification_session__info) ##### Example Request ```bash curl -X 'GET' \ 'http://localhost:7003/verification-session/{sessionId}/info' \ -H 'accept: application/json' ``` **Path Parameters** - **sessionId**: _String_ (required) - The unique ID of the verification session returned when you created the session. Use this to query the current status and results. --- ##### Example Response The response contains the complete session information including setup, status, policy results, and presented credentials. ```json { "id": "7c66c445-63e6-4a6a-8351-23ec78791b4e", "setup": { "flow_type": "cross_device", "core_flow": { "dcql_query": { "credentials": [ { "id": "credential_1", "format": "jwt_vc_json", "meta": { "type_values": [ ["VerifiableCredential", "UniversityDegree"] ] } } ] }, "policies": { "vc_policies": [ { "policy": "signature", "id": "signature" } ] } } }, "creationDate": "2026-05-13T13:55:38.766985012Z", "expirationDate": "2026-05-13T14:00:38.766985012Z", "retentionDate": "2036-05-13T13:55:38.766985012Z", "status": "SUCCESSFUL", "attempted": true, "reattemptable": true, "bootstrapAuthorizationRequest": { "client_id": "verifier2", "request_uri": "https://f5dc-2a02-8388-17c1-5580-bddd-6845-3edf-388f.ngrok-free.app/verification-session/7c66c445-63e6-4a6a-8351-23ec78791b4e/request" }, "bootstrapAuthorizationRequestUrl": "openid4vp://authorize?client_id=verifier2&request_uri=https%3A%2F%2Ff5dc-2a02-8388-17c1-5580-bddd-6845-3edf-388f.ngrok-free.app%2Fverification-session%2F7c66c445-63e6-4a6a-8351-23ec78791b4e%2Frequest", "authorizationRequest": { "response_type": "vp_token", "client_id": "verifier2", "state": "467f4e16-8c42-489a-a17c-cb6175063df3", "response_mode": "direct_post", "nonce": "ae4a922a-255f-4118-b9c6-35031c3546ff", "response_uri": "https://f5dc-2a02-8388-17c1-5580-bddd-6845-3edf-388f.ngrok-free.app/verification-session/7c66c445-63e6-4a6a-8351-23ec78791b4e/response", "dcql_query": { "credentials": [ { "id": "credential_1", "format": "jwt_vc_json", "meta": { "type_values": [ ["VerifiableCredential", "UniversityDegree"] ] } } ] } }, "authorizationRequestUrl": "openid4vp://authorize?response_type=vp_token&client_id=verifier2&state=467f4e16-8c42-489a-a17c-cb6175063df3&response_mode=direct_post&nonce=ae4a922a-255f-4118-b9c6-35031c3546ff&response_uri=https%3A%2F%2Ff5dc-2a02-8388-17c1-5580-bddd-6845-3edf-388f.ngrok-free.app%2Fverification-session%2F7c66c445-63e6-4a6a-8351-23ec78791b4e%2Fresponse&dcql_query=%7B%22credentials%22%3A%5B%7B%22id%22%3A%22credential_1%22%2C%22format%22%3A%22jwt_vc_json%22%2C%22meta%22%3A%7B%22type_values%22%3A%5B%5B%22VerifiableCredential%22%2C%22UniversityDegree%22%5D%5D%7D%7D%5D%7D&client_metadata=%7B%22vp_formats_supported%22%3A%7B%22mso_mdoc%22%3A%7B%22issuerauth_alg_values%22%3A%5B-7%2C-9%2C-50%5D%2C%22deviceauth_alg_values%22%3A%5B-7%2C-9%2C-50%2C-65537%5D%7D%7D%2C%22client_name%22%3A%22Verifier2%22%2C%22logo_uri%22%3A%22https%3A%2F%2Fimages.squarespace-cdn.com%2Fcontent%22%2C%22tos_uri%22%3A%22https%3A%2F%2Fexample.com%2Fterms%22%2C%22policy_uri%22%3A%22https%3A%2F%2Fexample.com%2Fprivacy%22%2C%22client_uri%22%3A%22https%3A%2F%2Fverifier.example.com%22%2C%22client_name%23fr-FR%22%3A%22V%C3%A9rificateur2%22%2C%22logo_uri%23fr-FR%22%3A%22https%3A%2F%2Fexample.com%2Flogo-fr.png%22%2C%22tos_uri%23fr-FR%22%3A%22https%3A%2F%2Fexample.com%2Fterms-fr%22%2C%22policy_uri%23fr-FR%22%3A%22https%3A%2F%2Fexample.com%2Fprivacy-fr%22%2C%22client_uri%23fr-FR%22%3A%22https%3A%2F%2Fverifier.example.com%2Ffr%22%7D", "requestMode": "REQUEST_URI", "policies": { "vp_policies": { "jwt_vc_json": [ "jwt_vc_json/audience-check", "jwt_vc_json/nonce-check", "jwt_vc_json/envelope_signature" ] }, "vc_policies": [ { "policy": "signature", "id": "signature" } ] }, "policy_results": { "vp_policies": { "credential_1": { "jwt_vc_json/audience-check": { "policy_executed": { "policy": "jwt_vc_json/audience-check", "description": "Check if presentation audience matches expected audience for session" }, "success": true, "results": { "presentation_audience": "verifier2", "expected_audience": "verifier2" } }, "jwt_vc_json/nonce-check": { "policy_executed": { "policy": "jwt_vc_json/nonce-check", "description": "Check if presentation nonce matches expected nonce for session" }, "success": true }, "jwt_vc_json/envelope_signature": { "policy_executed": { "policy": "jwt_vc_json/envelope_signature", "description": "Verify signature using holders public key" }, "success": true } } }, "vc_policies": [ { "policy": { "policy": "signature", "id": "signature" }, "success": true, "result": { "verification_result": true, "verified_data": { "iss": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Ii...", "sub": "did:key:zmYg9bgKmRiCqTTd9MA1ufVE9tfzUptwQp4GMRxptXquJWw...", "vc": { "type": ["VerifiableCredential", "UniversityDegree"], "issuer": { "id": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Ii..." }, "credentialSubject": { "id": "did:key:zmYg9bgKmRiCqTTd9MA1ufVE9tfzUptwQp4GMRxptXquJWw...", "degree": { "type": "BachelorDegree", "name": "Bachelor of Science and Arts" } } } } } } ], "overallSuccess": true }, "presented_credentials": { "credential_1": [ { "type": "vc-w3c_1_1", "credentialData": { "type": ["VerifiableCredential", "UniversityDegree"], "issuer": { "id": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Ii..." }, "issuanceDate": "2026-05-13T13:03:43.475820800Z", "credentialSubject": { "id": "did:key:zmYg9bgKmRiCqTTd9MA1ufVE9tfzUptwQp4GMRxptXquJWw...", "degree": { "type": "BachelorDegree", "name": "Bachelor of Science and Arts" } }, "expirationDate": "2027-05-13T13:03:43.475849300Z" }, "issuer": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Ii...", "subject": "did:key:zmYg9bgKmRiCqTTd9MA1ufVE9tfzUptwQp4GMRxptXquJWw..." } ] } } ``` **Key Response Fields:** - **id**: _String_ - The verification session ID - **setup**: _Object_ - The complete session configuration that was used to create this verification session (the `VerificationSessionSetup` from the create request) - **creationDate**: _String_ - ISO 8601 timestamp when the session was created - **expirationDate**: _String_ - ISO 8601 timestamp when the session expires (default: 10 minutes after creation). Session will expire if left unused (no presentation pushed). Once a presentation is received, the session is no longer eligible for expiry. - **status**: _String_ - The current status of the verification session. Possible values: - `"UNKNOWN"` — Session ended up in unknown flow (should be avoided) - `"ACTIVE"` — Session was created and is active (can be used) - `"UNUSED"` — Session was not used yet, but not expired (can be used) - `"IN_USE"` — AuthorizationRequest was requested by wallet - `"VALIDATING_RECEIVED_REQUEST"` — Checking if received presentation will be processed - `"PROCESSING_FLOW"` — Received presentation is being processed (validation + policy execution) - `"EXPIRED"` — Verification request expired without being utilized - `"SUCCESSFUL"` — Verification completed fully successfully (all validation & policies passed) - `"FAILED"` — Verification was unsuccessful (presentation validation or policies failed) - **attempted**: _Boolean_ - Whether the wallet has attempted to respond to the verification request - **reattemptable**: _Boolean_ - Whether the user can retry this verification session if it failed - **policies**: _Object_ - The policies configured for this session: - **vp_policies**: _Object_ (optional) - Policies to run on presentations - **vc_policies**: _Array_ (optional) - Policies to run on credentials - **specific_vc_policies**: _Object_ (optional) - Policies to run on specific credential IDs - **policy_results**: _Object_ (optional) - Detailed results for all policies applied to the verification (only present after presentation is received): - **vp_policies**: _Object_ - Results for presentation-level policies (audience check, nonce check, envelope signature), organized by credential ID. Structure: `{ "credential_id": { "policy_name": { ... } } }` - **vc_policies**: _Array_ - Results for credential-level policies (signature, expiry, schema, etc.). Each item contains `policy`, `success`, and `result`. - **specific_vc_policies**: _Object_ - Results for credential-specific policies - **overallSuccess**: _Boolean_ (computed) - Whether all policies passed - **redirects**: _Object_ (optional) - Success and error redirect URIs if configured **Note:** Instead of polling the session status, you can use **callbacks** or **Server Sent Events (SSE)** to be notified when the verification completes. See [Callbacks & SSE](https://docs.walt.id/community-stack/verifier2/callback-and-sse.md) for details. --- ## Example 2: Requesting Multiple Credentials You can request multiple credentials in a single verification session. The wallet will present all matching credentials, and policies will be applied to each one. This example requests both an OpenBadgeCredential and a VerifiableId in JWT format. **Option: CURL** **Endpoint:** `POST /verification-session/create` | [API Reference](https://verifier2.portal.test.waltid.cloud/swagger/index.html#/Verification%20Session%20Management/post_verification_session_create) ##### Example Request ```bash curl -X 'POST' \ 'http://localhost:7003/verification-session/create' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -d '{ "flow_type": "cross_device", "core_flow": { "dcql_query": { "credentials": [ { "id": "example_openbadge_jwt_vc", "format": "jwt_vc_json", "meta": { "type_values": [ [ "VerifiableCredential", "OpenBadgeCredential" ] ] }, "claims": [ { "path": [ "name" ] } ] }, { "id": "example_verifiableid_jwt_vc", "format": "jwt_vc_json", "meta": { "type_values": [ [ "VerifiableCredential", "VerifiableAttestation", "VerifiableId" ] ] }, "claims": [ { "path": [ "issuanceDate" ] } ] } ] }, "policies": { "vc_policies": [ { "policy": "signature" } ] } } }' ``` The wallet will present both credentials (if available), and the signature policy will be applied to each one. The `policyResults` in the session info response will contain separate results for each credential using their `id` values. --- ## Understanding Flow Types and Core Concepts Now that you've seen verification in action, let's understand the underlying structure. ### Verification Session Structure A `VerificationSession` defines the full lifecycle: authorization request → user presentation → policy validation → final result. Every verification request follows this structure: ```json { "flow_type": "cross_device | same_device | dc_api", "core_flow": { /* common to all flows */ }, "url_config": { /* specific to cross_device and same_device */ }, "redirects": { /* optional */ } } ``` **Why this structure?** - **flow_type** determines how the credential request is delivered (QR code vs deep link vs browser API) - **core_flow** contains the credential query and policies (same regardless of delivery method) - **url_config** and **redirects** are flow-specific attributes This separation makes it easy to switch between flows without changing your credential requirements. ### Flow Type Comparison | Flow Type | Use Case | Required Attributes | User Experience | |-----------|----------|---------------------|-----------------| | `cross_device` | User scans QR code from desktop | None beyond core flow | Desktop shows QR → User scans with phone → Phone wallet opens | | `same_device` | User clicks link on mobile | `redirects` (success/error URLs) | Browser redirects to wallet → Wallet opens → User returns to browser | | `dc_api` | Browser-native credential exchange | `expectedOrigins` (array of allowed origins) | Browser shows native credential picker → User selects → Instant return | --- ## Configuration Defaults Many session parameters can be configured globally in `verifier-service.conf` and overridden per-request when needed. | Parameter | Config File Location | Request Override Path | When to Override | |-----------|---------------------|----------------------|------------------| | `urlPrefix` | `verifier-service.conf` → `urlPrefix` | `url_config.url_prefix` | Testing with ngrok, different environments per session | | `urlHost` | `verifier-service.conf` → `urlHost` | `url_config.url_host` | Custom authorization endpoints, conformance testing | | `clientId` | `verifier-service.conf` → `clientId` | `core_flow.clientId` | Different verifier identities per use case | | `clientMetadata` | `verifier-service.conf` → `clientMetadata` | `core_flow.client_metadata` | Session-specific branding, multi-tenant scenarios | | Signing key | `verifier-service.conf` → `key` | `core_flow.key` | Different signing keys per session (advanced) | | Certificate chain | `verifier-service.conf` → `x5c` | `core_flow.x5c` | Different certificate chains per session (x509 flows) | **Note:** **Best Practice:** Set defaults in `verifier-service.conf` for your primary use case, and override per-request only when necessary. This keeps your API calls clean and reduces duplication. See [Verifier Service Configuration](https://docs.walt.id/community-stack/verifier2/configurations/config-files/verifier-service.md) for detailed configuration documentation. --- ## Advanced Options ### Signed Requests Sign the authorization request JWT using a verifier signing key. This is required for: - **HAIP profile** compliance - **DC API Annex C** (ISO 18013-7) flows Enable by setting `signedRequest: true` in the request body. **Key Configuration Required:** Signed requests require a signing key (`key`) to be configured. You can either: - Set the `key` parameter in the [`verifier-service.conf`](https://docs.walt.id/community-stack/verifier2/configurations/config-files/verifier-service.md#key-optional) as a service-wide default - Override or provide the `key` directly in the verification request body **Certificate Chain (`x5c`) Required for:** - X.509-based client authentication when using `x509_san_dns:` or `x509_hash:` as the `clientId` format - Signed Annex C (ISO 18013-7) requests The `x5c` parameter can be configured the same way as `key` — as a service-wide default or per-session override. ### Encrypted Responses The wallet can encrypt the VP token response. Enable by setting `encryptedResponse: true` in the request body. **Key Configuration Required:** Encrypted responses require a key (`key`) for decrypting the VP token response. Configure it via [`verifier-service.conf`](https://docs.walt.id/community-stack/verifier2/configurations/config-files/verifier-service.md#key-optional) or provide it in the verification request body. ### Custom Policies Beyond signature verification, you can apply additional policies: ```json "policies": { "vc_policies": [ { "policy": "signature" }, { "policy": "expired" }, { "policy": "not-before" }, { "policy": "schema" } ] } ``` See [Policies](https://docs.walt.id/community-stack/verifier2/policies/overview.md) for the complete list and configuration options. --- ## Next Steps - **Apply custom policies** — See [Policies](https://docs.walt.id/community-stack/verifier2/policies/overview.md) for validation rules beyond signature verification - **Set up notifications** — Use [Callbacks & SSE](https://docs.walt.id/community-stack/verifier2/callback-and-sse.md) instead of polling for session status - **Verify other credential types** — Try [SD-JWT VC](https://docs.walt.id/community-stack/verifier2/credential-verification/sd-jwt-vc-oid4vp.md) or [mDL](https://docs.walt.id/community-stack/verifier2/credential-verification/mdl-oid4vp.md) - **Use DC API** — Try the [Digital Credentials API](https://docs.walt.id/community-stack/verifier2/credential-verification/dc-api.md) for browser-native credential exchange -------------------------------------------------------------------------------- PAGE: Verifying SD-JWT VC Credentials via OID4VP URL: https://docs.walt.id/community-stack/verifier2/credential-verification/sd-jwt-vc-oid4vp RAW: https://docs.walt.id/community-stack/verifier2/credential-verification/sd-jwt-vc-oid4vp.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Verifying SD-JWT VC credentials via OID4VP This guide shows you how to verify SD-JWT VC credentials using the Verifier2 API via OID4VP (OpenID for Verifiable Presentations) [**SD-JWT VCs**](https://docs.walt.id/concepts/digital-credentials/sd-jwt-vc.md): Credentials based on the [IETF SD-JWT VC standard](https://datatracker.ietf.org/doc/draft-ietf-oauth-sd-jwt-vc/) which support selective disclosure, allowing users to share only specific claims from their credentials. [**OID4VP**](https://docs.walt.id/concepts/data-exchange-protocols/openid4vp.md): A protocol specifying how parties can present VCs in a way that's consistent and secure across platforms ensuring interoperability. **Note:** If you're migrating from the original Verifier (v1), see the [Migration Guide](https://docs.walt.id/community-stack/verifier2/migration-from-verifier1.md). ## Prerequisites Before you begin, ensure you have: - **Verifier2 API running** — Follow the [Getting Started](https://docs.walt.id/community-stack/verifier2/getting-started.md) or [Setup](https://docs.walt.id/community-stack/verifier2/setup.md) guide - **A test wallet with credentials** — Use the [walt.id Wallet](https://docs.walt.id/community-stack/wallet/getting-started.md) for testing, or any OID4VP-compatible wallet - **Publicly accessible URL** (for local development) — Use [ngrok](https://ngrok.com) or similar to expose `http://localhost:7003` if testing with a mobile wallet **Note:** **Local Development:** When running locally via Docker, the API is available at `http://localhost:7003` (standalone) or `http://localhost:7004` (Docker Compose). Use this as your base URL in the examples below. --- ## Example 1: Basic Verification This example requests an SD-JWT identity credential and verifies its signature. **Option: CURL** **Endpoint:** `POST /verification-session/create` | [API Reference](https://verifier2.portal.test.waltid.cloud/swagger/index.html#/Verification%20Session%20Management/post_verification_session_create) ##### Example Request ```bash curl -X 'POST' \ 'http://localhost:7003/verification-session/create' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -d '{ "flow_type": "cross_device", "core_flow": { "dcql_query": { "credentials": [ { "id": "credential_1", "format": "dc+sd-jwt", "meta": { "vct_values": [ "https://issuer.demo.walt.id/draft13/identity_credential" ] } } ] } } }' ``` **Body Parameters** - **flow_type**: _String_ (required) - Specifies how the credential request will be delivered to the holder's wallet. Options: - `"cross_device"` — User scans a QR code on a different device (e.g. desktop browser → phone wallet) - `"same_device"` — User clicks a deep link on the same device (e.g. phone browser → wallet app on same phone) - `"dc_api"` — Browser/OS-native Digital Credentials API (Chrome, Safari, iOS/Android native) over OpenID4VP 1.0 (Annex D). - `"dc_api-annex-c"` — Browser/OS-native Digital Credentials API (Chrome, Safari, iOS/Android native) over 18013-7 (Annex C). - **core_flow**: _Object_ (required) - Defines what credentials to request and which policies to apply. Contains: - **dcql_query**: _Object_ (required) - Specifies the credential type, format, and claims to request using the [DCQL query language](https://identity.foundation/credential-query-language/). You can also use the DCQL builder [here](https://dcql.walt.id/). - **credentials**: _Array_ - List of credential queries. Each credential query contains: - **id**: _String_ - Unique identifier for this credential in the request (used in policy results) - **format**: _String_ - Credential format. For SD-JWT VCs, use the `"dc+sd-jwt"`. - **meta.vct_values**: _Array_ - List of of credential types. Example: `["https://issuer.demo.walt.id/draft13/identity_credential"]`. - **claims**: _Array_ (optional) - Specific claims to request from the credential. Each claim has a `path` (JSON path to the claim). If omitted, all claims that are non-selectively disclosable are provided by the wallet. - **policies**: _Object_ (optional) - Validation rules applied to the received credential. Defaults to signature verification only if omitted. - **vc_policies**: _Array_ - Policies applied to individual credentials. Each policy has a `policy` field specifying the policy name. Available policies: `signature`, `expired`, `not-before`, `revoked-status-list`, `schema`, and more. See [Policies](https://docs.walt.id/community-stack/verifier2/policies/overview.md) for the complete list. - **vp_policies**: _Array_ (optional) - Policies applied to the entire presentation (not shown in this example) - **signedRequest**: _Boolean_ (optional) - Whether to sign the authorization request JWT. Required for HAIP profile compliance and DC API Annex C (ISO 18013-7) flows. Defaults to `false`. See [Signed Requests](#signed-requests) to learn more. - **encryptedResponse**: _Boolean_ (optional) - Whether the wallet should encrypt the VP token response. Defaults to `false`. See [Encrypted Responses](#encrypted-responses) to learn more. - **key**: _Object_ (optional) - Signing key for this verification session. Overrides the service-wide default `key` configured via the[`verifier-service.conf`](https://docs.walt.id/community-stack/verifier2/configurations/config-files/verifier-service.md#key-optional). Must be a serialized key in the format `{"type":"jwk","jwk":{...}}`. Required when `signedRequest: true` or `encryptedResponse: true` and no service-wide default is configured. See [Signed Requests](#signed-requests) and [Encrypted Responses](#encrypted-responses) for when this is needed. - **x5c**: _Array of Strings_ (optional) - X.509 certificate chain for this verification session. Overrides the service-wide default `x5c` configured via [`verifier-service.conf`](https://docs.walt.id/community-stack/verifier2/configurations/config-files/verifier-service.md#x5c-optional). Must be an ordered chain in DER-encoded, Base64-encoded format where the first certificate corresponds to the `key`. Required for X.509-based client authentication (`x509_san_dns:` or `x509_hash:` as `clientId`) and signed Annex C requests. See [Signed Requests](#signed-requests) to learn more. - **verifier_info**: _Object_ (optional) - An array of attestations about the Verifier relevant to the Credential Request. Each object is a JSON object with the following fields: - **format**: _String_ - The format of the attestation. Currently only `"jwt"` is supported. - **data**: _String_ - The attestation data in JWT format. - **credentialIds**: _Array_ (optional) - Specifies which credentials in your DCQL query this attestation applies to. References the `id` fields from your `credentials` array in the DCQL query. If omitted, the attestation applies to all requested credentials. - **openid**: _Object_ (optional) - OpenID4VP-specific extensions - **transactionData**: _Array_ (optional) - Bind the credential presentation to a specific transaction (e.g., payment authorization, account access). Each entry references credential IDs and includes transaction details. See [Transaction Data Authorization](#transaction-data-authorization) for full documentation. - **url_config**: _Object_ (optional) - URL configuration for this session. Only applies to `cross_device` and `same_device` flows (not `dc_api`). - **url_prefix**: _String_ (optional) - The publicly accessible base URL where the wallet will fetch the authorization request and post its response. Overrides the `urlPrefix` from `verifier-service.conf`. - The service constructs: Request URI: `{urlPrefix}/{sessionId}/request` and Response URI: `{urlPrefix}/{sessionId}/response` - **Must be reachable by the wallet** (use ngrok or public URL for local development) - **url_host**: _String_ (optional) - The base URL used to construct the authorization request link. Overrides the `urlHost` from `verifier-service.conf`. - For standard OID4VP flows, use `"openid4vp://authorize"` (default) - For custom authorization endpoints (e.g. conformance testing), provide a full HTTPS URL - **redirects**: _Object_ (optional) - Success and error redirect URLs (only for `cross_device` and `same_device` flows) - **successRedirectUri**: _String_ - URL to redirect the user after successful verification - **errorRedirectUri**: _String_ - URL to redirect the user if verification fails **Note:** Many parameters have defaults configured in `verifier-service.conf`. Request-level parameters override these defaults for that specific session only. See [Configuration Defaults](#configuration-defaults) below. --- ##### Example Response ```json { "sessionId": "14d60fa2-4e7d-4a34-a291-80da0e053fab", "bootstrapAuthorizationRequestUrl": "openid4vp://authorize?client_id=verifier2&request_uri=https%3A%2F%2Ff5dc-2a02-8388-17c1-5580-bddd-6845-3edf-388f.ngrok-free.app%2Fverification-session%2F14d60fa2-4e7d-4a34-a291-80da0e053fab%2Frequest", "fullAuthorizationRequestUrl": "openid4vp://authorize?response_type=vp_token&client_id=verifier2&state=a697ef7d-fc66-4ccd-b79a-3aa1b93836df&response_mode=direct_post&nonce=4c44c117-2d26-4838-b42f-52b0e0faa070&response_uri=https%3A%2F%2Ff5dc-2a02-8388-17c1-5580-bddd-6845-3edf-388f.ngrok-free.app%2Fverification-session%2F14d60fa2-4e7d-4a34-a291-80da0e053fab%2Fresponse&dcql_query=%7B%22credentials%22%3A%5B%7B%22id%22%3A%22credential_1%22%2C%22format%22%3A%22dc%2Bsd-jwt%22%2C%22meta%22%3A%7B%22vct_values%22%3A%5B%22https%3A%2F%2Fissuer.demo.walt.id%2Fdraft13%2Fidentity_credential%22%5D%7D%7D%5D%7D&client_metadata=%7B%22vp_formats_supported%22%3A%7B%22mso_mdoc%22%3A%7B%22issuerauth_alg_values%22%3A%5B-7%2C-9%2C-50%5D%2C%22deviceauth_alg_values%22%3A%5B-7%2C-9%2C-50%2C-65537%5D%7D%7D%2C%22client_name%22%3A%22Verifier2%22%2C%22logo_uri%22%3A%22https%3A%2F%2Fimages.squarespace-cdn.com%2Fcontent%22%2C%22tos_uri%22%3A%22https%3A%2F%2Fexample.com%2Fterms%22%2C%22policy_uri%22%3A%22https%3A%2F%2Fexample.com%2Fprivacy%22%2C%22client_uri%22%3A%22https%3A%2F%2Fverifier.example.com%22%2C%22client_name%23fr-FR%22%3A%22V%C3%A9rificateur2%22%2C%22logo_uri%23fr-FR%22%3A%22https%3A%2F%2Fexample.com%2Flogo-fr.png%22%2C%22tos_uri%23fr-FR%22%3A%22https%3A%2F%2Fexample.com%2Fterms-fr%22%2C%22policy_uri%23fr-FR%22%3A%22https%3A%2F%2Fexample.com%2Fprivacy-fr%22%2C%22client_uri%23fr-FR%22%3A%22https%3A%2F%2Fverifier.example.com%2Ffr%22%7D" } ``` **Response Fields:** - **sessionId**: _String_ - The unique ID of this verification session. Use this to query the session status. - **bootstrapAuthorizationRequestUrl**: _String_ - Shortened URL using `request_uri` by reference. **Use this for QR codes** to minimize QR code density. - **fullAuthorizationRequestUrl**: _String_ - Complete URL with all parameters embedded. Use this if the wallet doesn't support `request_uri` (rare). ## Presenting the Request to the User After creating the session, you need to present the authorization request to the user based on the flow type you chose. ### Cross-Device Flow (QR Code) 1. **Generate a QR code** from the `bootstrapAuthorizationRequestUrl` 2. **Display the QR code** to the user on your web page or application 3. **User scans the QR code** with their wallet app 4. **Wallet presents the credential** and posts the response to your verifier 5. **Monitor the session status** by polling the session endpoint or using SSE/callbacks (see below) ### Same-Device Flow (Deep Link) 1. **Redirect the user** to the `bootstrapAuthorizationRequestUrl` 2. **User's wallet app opens** automatically (via deep link) 3. **Wallet presents the credential** and posts the response to your verifier 4. **User is redirected** to your `successRedirectUri` or `errorRedirectUri` 5. **Query the session status** to retrieve the verification results ## Checking Verification Results You can query the verification session to check if the user has presented their credential and whether validation succeeded. **Option: CURL** **Endpoint:** `GET /verification-session/{sessionId}/info` | [API Reference](https://verifier2.portal.test.waltid.cloud/swagger/index.html#/Verification%20Session%20Management/get_verification_session__sessionId__info) ##### Example Request ```bash curl -X 'GET' \ 'http://localhost:7003/verification-session/14d60fa2-4e7d-4a34-a291-80da0e053fab/info' \ -H 'accept: application/json' ``` **Path Parameters** - **sessionId**: _String_ (required) - The unique ID of the verification session returned when you created the session. Use this to query the current status and results. --- ##### Example Response ```json { "id": "14d60fa2-4e7d-4a34-a291-80da0e053fab", "setup": { "flow_type": "cross_device", "core_flow": { "dcql_query": { "credentials": [ { "id": "credential_1", "format": "dc+sd-jwt", "multiple": false, "meta": { "vct_values": [ "https://issuer.demo.walt.id/draft13/identity_credential" ], "format": "dc+sd-jwt" }, "require_cryptographic_holder_binding": true } ] }, "signed_request": false, "encrypted_response": false, "policies": {} }, "url_config": {} }, "creationDate": "2026-05-13T14:14:52.688020004Z", "expirationDate": "2026-05-13T14:19:52.688020004Z", "retentionDate": "2036-05-13T14:14:52.688020004Z", "status": "SUCCESSFUL", "attempted": true, "reattemptable": true, "bootstrapAuthorizationRequest": { "client_id": "verifier2", "request_uri": "https://f5dc-2a02-8388-17c1-5580-bddd-6845-3edf-388f.ngrok-free.app/verification-session/14d60fa2-4e7d-4a34-a291-80da0e053fab/request" }, "bootstrapAuthorizationRequestUrl": "openid4vp://authorize?client_id=verifier2&request_uri=https%3A%2F%2Ff5dc-2a02-8388-17c1-5580-bddd-6845-3edf-388f.ngrok-free.app%2Fverification-session%2F14d60fa2-4e7d-4a34-a291-80da0e053fab%2Frequest", "authorizationRequest": { "response_type": "vp_token", "client_id": "verifier2", "state": "a697ef7d-fc66-4ccd-b79a-3aa1b93836df", "response_mode": "direct_post", "nonce": "4c44c117-2d26-4838-b42f-52b0e0faa070", "response_uri": "https://f5dc-2a02-8388-17c1-5580-bddd-6845-3edf-388f.ngrok-free.app/verification-session/14d60fa2-4e7d-4a34-a291-80da0e053fab/response", "dcql_query": { "credentials": [ { "id": "credential_1", "format": "dc+sd-jwt", "multiple": false, "meta": { "vct_values": [ "https://issuer.demo.walt.id/draft13/identity_credential" ], "format": "dc+sd-jwt" }, "require_cryptographic_holder_binding": true } ] }, "client_metadata": { "vp_formats_supported": { "mso_mdoc": { "issuerauth_alg_values": [-7, -9, -50], "deviceauth_alg_values": [-7, -9, -50, -65537] } }, "client_name": "Verifier2", "logo_uri": "https://images.squarespace-cdn.com/content", "tos_uri": "https://example.com/terms", "policy_uri": "https://example.com/privacy", "client_uri": "https://verifier.example.com" } }, "authorizationRequestUrl": "openid4vp://authorize?response_type=vp_token&client_id=verifier2&state=a697ef7d-fc66-4ccd-b79a-3aa1b93836df&response_mode=direct_post&nonce=4c44c117-2d26-4838-b42f-52b0e0faa070&response_uri=https%3A%2F%2Ff5dc-2a02-8388-17c1-5580-bddd-6845-3edf-388f.ngrok-free.app%2Fverification-session%2F14d60fa2-4e7d-4a34-a291-80da0e053fab%2Fresponse&dcql_query=%7B%22credentials%22%3A%5B%7B%22id%22%3A%22credential_1%22%2C%22format%22%3A%22dc%2Bsd-jwt%22%2C%22meta%22%3A%7B%22vct_values%22%3A%5B%22https%3A%2F%2Fissuer.demo.walt.id%2Fdraft13%2Fidentity_credential%22%5D%7D%7D%5D%7D&client_metadata=%7B%22vp_formats_supported%22%3A%7B%22mso_mdoc%22%3A%7B%22issuerauth_alg_values%22%3A%5B-7%2C-9%2C-50%5D%2C%22deviceauth_alg_values%22%3A%5B-7%2C-9%2C-50%2C-65537%5D%7D%7D%2C%22client_name%22%3A%22Verifier2%22%2C%22logo_uri%22%3A%22https%3A%2F%2Fimages.squarespace-cdn.com%2Fcontent%22%2C%22tos_uri%22%3A%22https%3A%2F%2Fexample.com%2Fterms%22%2C%22policy_uri%22%3A%22https%3A%2F%2Fexample.com%2Fprivacy%22%2C%22client_uri%22%3A%22https%3A%2F%2Fverifier.example.com%22%7D", "requestMode": "REQUEST_URI", "policies": { "vp_policies": { "dc+sd-jwt": [ "dc+sd-jwt/audience-check", "dc+sd-jwt/kb-jwt_signature", "dc+sd-jwt/nonce-check", "dc+sd-jwt/sd_hash-check" ] }, "vc_policies": [ { "policy": "signature", "id": "signature" } ] }, "policy_results": { "vp_policies": { "credential_1": { "dc+sd-jwt/audience-check": { "policy_executed": { "policy": "dc+sd-jwt/audience-check", "id": "dc+sd-jwt/audience-check", "description": "Check if presentation audience matches expected audience for session" }, "success": true, "results": { "presentation_audience": "verifier2", "expected_audience": "verifier2" }, "errors": [], "execution_time": "PT0.000992042S" }, "dc+sd-jwt/kb-jwt_signature": { "policy_executed": { "policy": "dc+sd-jwt/kb-jwt_signature", "id": "dc+sd-jwt/kb-jwt_signature", "description": "Verify the KB-JWTs signature with the holders key" }, "success": true, "results": { "holder_key_jwk": { "kty": "OKP", "crv": "Ed25519", "kid": "v1sbluyFJrMT6Lt46S4N_CBH1g7OWHpm_f3uK93dSLE", "x": "jxQwRV7tHc3W_JkKVsDZL7_DtP4zyNJtQ4Yr1z7-Md4" } }, "errors": [], "execution_time": "PT0.002432834S" }, "dc+sd-jwt/nonce-check": { "policy_executed": { "policy": "dc+sd-jwt/nonce-check", "description": "Check if presentation nonce matches expected nonce for session" }, "success": true, "results": {}, "errors": [], "execution_time": "PT0.001029042S" }, "dc+sd-jwt/sd_hash-check": { "policy_executed": { "policy": "dc+sd-jwt/sd_hash-check", "description": "Verify SD-JWT Key Binding by recalculating SD Hashes" }, "success": true, "results": { "presentation_sdhash": "KQKyptFb7obYaGPDRQvNHEKCSispCoFYGmbe1WUR_wI", "recalculated_sdhash": "KQKyptFb7obYaGPDRQvNHEKCSispCoFYGmbe1WUR_wI" }, "errors": [], "execution_time": "PT0.003427750S" } } }, "vc_policies": [ { "policy": { "policy": "signature", "id": "signature" }, "success": true, "result": { "verification_result": true, "verified_data": { "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, "vct": "https://issuer.demo.walt.id/draft13/identity_credential" } } } ], "overallSuccess": true }, "presented_credentials": { "credential_1": [ { "type": "vc-sd_jwt", "format": "dc+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" }, "is_over_18": true, "is_over_21": true, "is_over_65": true, "vct": "https://issuer.demo.walt.id/draft13/identity_credential" }, "issuer": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp" } ] } } ``` **Key Response Fields:** - **id**: _String_ - The verification session ID - **setup**: _Object_ - The complete session configuration that was used to create this verification session (the `VerificationSessionSetup` from the create request) - **creationDate**: _String_ - ISO 8601 timestamp when the session was created - **expirationDate**: _String_ - ISO 8601 timestamp when the session expires (default: 10 minutes after creation). Session will expire if left unused (no presentation pushed). Once a presentation is received, the session is no longer eligible for expiry. - **status**: _String_ - The current status of the verification session. Possible values: - `"UNKNOWN"` — Session ended up in unknown flow (should be avoided) - `"ACTIVE"` — Session was created and is active (can be used) - `"UNUSED"` — Session was not used yet, but not expired (can be used) - `"IN_USE"` — AuthorizationRequest was requested by wallet - `"VALIDATING_RECEIVED_REQUEST"` — Checking if received presentation will be processed - `"PROCESSING_FLOW"` — Received presentation is being processed (validation + policy execution) - `"EXPIRED"` — Verification request expired without being utilized - `"SUCCESSFUL"` — Verification completed fully successfully (all validation & policies passed) - `"FAILED"` — Verification was unsuccessful (presentation validation or policies failed) - **attempted**: _Boolean_ - Whether the wallet has attempted to respond to the verification request - **reattemptable**: _Boolean_ - Whether the user can retry this verification session if it failed - **policies**: _Object_ - The policies configured for this session: - **vp_policies**: _Object_ (optional) - Policies to run on presentations - **vc_policies**: _Array_ (optional) - Policies to run on credentials - **specific_vc_policies**: _Object_ (optional) - Policies to run on specific credential IDs - **policy_results**: _Object_ (optional) - Detailed results for all policies applied to the verification (only present after presentation is received): - **vp_policies**: _Object_ - Results for presentation-level policies (audience check, nonce check, envelope signature), organized by credential ID. Structure: `{ "credential_id": { "policy_name": { ... } } }` - **vc_policies**: _Array_ - Results for credential-level policies (signature, expiry, schema, etc.). Each item contains `policy`, `success`, and `result`. - **specific_vc_policies**: _Object_ - Results for credential-specific policies - **overallSuccess**: _Boolean_ (computed) - Whether all policies passed - **redirects**: _Object_ (optional) - Success and error redirect URIs if configured **Note:** Instead of polling the session status, you can use **callbacks** or **Server Sent Events (SSE)** to be notified when the verification completes. See [Callbacks & SSE](https://docs.walt.id/community-stack/verifier2/callback-and-sse.md) for details. ## Example 2: Requesting Multiple Credentials You can request multiple credentials in a single verification session. The wallet will present all matching credentials, and policies will be applied to each one. This example requests both an Identity and a Education credential. **Option: CURL** **Endpoint:** `POST /verification-session/create` | [API Reference](https://verifier2.portal.test.waltid.cloud/swagger/index.html#/Verification%20Session%20Management/post_verification_session_create) ##### Example Request ```bash curl -X 'POST' \ 'http://localhost:7003/verification-session/create' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -d '{ "flow_type": "cross_device", "core_flow": { "dcql_query": { "credentials": [ { "id": "identity", "format": "dc+sd-jwt", "meta": { "vct_values": [ "https://issuer.demo.walt.id/draft13/identity_credential" ] } }, { "id": "education", "format": "dc+sd-jwt", "meta": { "vct_values": [ "https://issuer.demo.walt.id/draft13/educational_credential" ] } } ] }, "policies": { "vc_policies": [ { "policy": "signature" } ] } } }' ``` The wallet will present both credentials (if available), and the signature policy will be applied to each one. The `policyResults` in the session info response will contain separate results for each credential using their `id` values. ## Understanding Flow Types and Core Concepts Now that you've seen verification in action, let's understand the underlying structure. ### Verification Session Structure A `VerificationSession` defines the full lifecycle: authorization request → user presentation → policy validation → final result. Every verification request follows this structure: ```json { "flow_type": "cross_device | same_device | dc_api", "core_flow": { /* common to all flows */ }, "url_config": { /* specific to cross_device and same_device */ }, "redirects": { /* optional */ } } ``` **Why this structure?** - **flow_type** determines how the credential request is delivered (QR code vs deep link vs browser API) - **core_flow** contains the credential query and policies (same regardless of delivery method) - **url_config** and **redirects** are flow-specific attributes This separation makes it easy to switch between flows without changing your credential requirements. ### Flow Type Comparison | Flow Type | Use Case | Required Attributes | User Experience | |-----------|----------|---------------------|-----------------| | `cross_device` | User scans QR code from desktop | None beyond core flow | Desktop shows QR → User scans with phone → Phone wallet opens | | `same_device` | User clicks link on mobile | `redirects` (success/error URLs) | Browser redirects to wallet → Wallet opens → User returns to browser | | `dc_api` | Browser-native credential exchange | `expectedOrigins` (array of allowed origins) | Browser shows native credential picker → User selects → Instant return | ## Configuration Defaults Many session parameters can be configured globally in `verifier-service.conf` and overridden per-request when needed. | Parameter | Config File Location | Request Override Path | When to Override | |-----------|---------------------|----------------------|------------------| | `urlPrefix` | `verifier-service.conf` → `urlPrefix` | `url_config.url_prefix` | Testing with ngrok, different environments per session | | `urlHost` | `verifier-service.conf` → `urlHost` | `url_config.url_host` | Custom authorization endpoints, conformance testing | | `clientId` | `verifier-service.conf` → `clientId` | `core_flow.clientId` | Different verifier identities per use case | | `clientMetadata` | `verifier-service.conf` → `clientMetadata` | `core_flow.client_metadata` | Session-specific branding, multi-tenant scenarios | | Signing key | `verifier-service.conf` → `key` | `core_flow.key` | Different signing keys per session (advanced) | | Certificate chain | `verifier-service.conf` → `x5c` | `core_flow.x5c` | Different certificate chains per session (x509 flows) | **Note:** **Best Practice:** Set defaults in `verifier-service.conf` for your primary use case, and override per-request only when necessary. This keeps your API calls clean and reduces duplication. See [Verifier Service Configuration](https://docs.walt.id/community-stack/verifier2/configurations/config-files/verifier-service.md) for detailed configuration documentation. ## Advanced Options ### Requesting Specific Claims SD-JWT VCs support selective disclosure. You can request specific claims using the `claims` array: ```json { "flow_type": "cross_device", "core_flow": { "dcql_query": { "credentials": [ { "id": "credential_1", "format": "dc+sd-jwt", "meta": { "vct_values": [ "https://issuer.demo.walt.id/draft13/identity_credential" ] }, "claims": [ { "path": ["given_name"] }, { "path": ["family_name"] }, { "path": ["address", "street_address"] } ] } ] } } } ``` This requests only `given_name`, `family_name`, and `address.street_address` from the identity credential. The user's wallet will only disclose these specific claims. ### Custom Policies Override default validation policies: ```json { "flow_type": "cross_device", "core_flow": { "dcql_query": { /* ... */ }, "policies": { "vc_policies": [ { "policy": "signature" }, { "policy": "expired" }, { "policy": "not-before" } ] } } } ``` See [Policies overview](https://docs.walt.id/community-stack/verifier2/policies/overview.md) for all available policies. ### Transaction Data Authorization Verifier2 supports OpenID4VP `transaction_data` to bind credential presentations to specific transactions (e.g., payment authorization, account access). For SD-JWT VC credentials, the wallet signs the requested transaction data hashes in the KB-JWT, and the verifier evaluates the `dc+sd-jwt/transaction-data-hash-check` policy automatically to validate the binding. #### Example Request ```json { "flow_type": "cross_device", "core_flow": { "dcql_query": { "credentials": [ { "id": "pid", "format": "dc+sd-jwt", "meta": { "vct_values": [ "https://issuer.demo.walt.id/draft13/identity_credential" ] }, "claims": [ { "path": ["given_name"] }, { "path": ["family_name"] } ] } ] } }, "openid": { "transactionData": [ { "type": "org.waltid.transaction-data.payment-authorization", "credential_ids": ["pid"], "require_cryptographic_holder_binding": true, "transaction_data_hashes_alg": ["sha-256"], "amount": "42.00", "currency": "EUR", "payee": "ACME Corp" } ] } } ``` #### Parameters - **type**: _String_ (required) - The transaction data type identifier. Must match a profile registered via the [`transaction-data-profiles` configuration](https://docs.walt.id/community-stack/verifier2/configurations/config-files/transaction-data-profiles.md). Common types: - `"org.waltid.transaction-data.payment-authorization"` — For payment transactions (fields: `amount`, `currency`, `payee`) - `"org.waltid.transaction-data.account-access"` — For account access requests (fields: `account_identifier`, `access_scope`) - **credential_ids**: _Array of Strings_ (required) - References one or more credential IDs from your DCQL query (`credentials[].id`). The wallet will bind this transaction data to presentations of these credentials. - **require_cryptographic_holder_binding**: _Boolean_ (optional) - If provided, it must be set to `true`. Transaction data requires cryptographic holder binding (KB-JWT for SD-JWT VC, DeviceAuth for mDL). Requests with `false` are rejected per [OpenID4VP §B.3.3](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html#appendix-B.3.3). - **transaction_data_hashes_alg**: _Array of Strings_ (optional) — Hash algorithms the wallet uses to bind transaction data. e.g. `["sha-256"]`. If omitted "sha-256" is used. **Type-Specific Parameters:** For `"org.waltid.transaction-data.payment-authorization"`: - **amount**: _String_ (required) - Payment amount (e.g., `"42.00"`) - **currency**: _String_ (required) - Currency code (e.g., `"EUR"`, `"USD"`) - **payee**: _String_ (required) - Payee identifier (e.g., `"ACME Corp"`) For `"org.waltid.transaction-data.account-access"`: - **account_identifier**: _String_ (required) - Account identifier (e.g., account number, user ID) - **access_scope**: _String_ (required) - Scope of access being requested (e.g., `"read"`, `"write"`, `"admin"`) **Note:** **Encoding Behavior:** You pass transaction data entries as plain JSON objects in your verification request. The verifier service automatically base64url-encodes each entry before placing it in the authorization request sent to the wallet. Do not base64-encode the entries yourself. #### Constraints - `transaction_data` requires a `dcql_query`. Requests without one are rejected with `invalid_request`. - Each entry's `credential_ids` must reference DCQL credential query ids declared in the same request. - Each referenced credential query must use a transaction-data-capable format: `dc+sd-jwt` or `mso_mdoc`. - Transaction data with `require_cryptographic_holder_binding: false` is rejected. See [OpenID4VP §B.3.3](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html#appendix-B.3.3). - Transaction-data types are configurable via profiles (e.g. `payment-authorization`, `account-access`). Custom types can be added through the [`transaction-data-profiles` configuration](https://docs.walt.id/community-stack/verifier2/configurations/config-files/transaction-data-profiles.md), and the active profiles are exposed via `GET /transaction-data-profiles`. - Available on `cross_device`, `same_device`, and `dc_api` (Annex D) flows via the `openid.transactionData` field. **Note:** **Automatic Policy Enforcement:** When `transactionData` is present, the verifier automatically adds the appropriate transaction-data hash-check policy (`dc+sd-jwt/transaction-data-hash-check` for SD-JWT, `mso_mdoc/transaction-data-hash-check` for mDL) to ensure the wallet properly binds the transaction data. This happens even if you specify custom `vp_policies`. Your custom policies are preserved; the hash-check policy is added on top. #### Discovery & Configuration Transaction data types are configurable via profiles. To see which transaction data types your verifier instance supports: ```bash curl -X 'GET' \ 'http://localhost:7003/transaction-data-profiles' \ -H 'accept: application/json' ``` Custom transaction data types can be added through the [`transaction-data-profiles` configuration](https://docs.walt.id/community-stack/verifier2/configurations/config-files/transaction-data-profiles.md). ### Signed Requests Sign the authorization request JWT using a verifier signing key. This is required for: - **HAIP profile** compliance - **DC API Annex C** (ISO 18013-7) flows Enable by setting `signedRequest: true` in the request body. **Key Configuration Required:** Signed requests require a signing key (`key`) to be configured. You can either: - Set the `key` parameter in the [`verifier-service.conf`](https://docs.walt.id/community-stack/verifier2/configurations/config-files/verifier-service.md#key-optional) as a service-wide default - Override or provide the `key` directly in the verification request body **Certificate Chain (`x5c`) Required for:** - X.509-based client authentication when using `x509_san_dns:` or `x509_hash:` as the `clientId` format - Signed Annex C (ISO 18013-7) requests The `x5c` parameter can be configured the same way as `key` — as a service-wide default or per-session override. ### Encrypted Responses The wallet can encrypt the VP token response. Enable by setting `encryptedResponse: true` in the request body. **Key Configuration Required:** Encrypted responses require a key (`key`) for decrypting the VP token response. Configure it via [`verifier-service.conf`](https://docs.walt.id/community-stack/verifier2/configurations/config-files/verifier-service.md#key-optional) or provide it in the verification request body. ## Next Steps - **Apply custom policies** — See [Policies](https://docs.walt.id/community-stack/verifier2/policies/overview.md) for validation rules beyond signature verification - **Set up notifications** — Use [Callbacks & SSE](https://docs.walt.id/community-stack/verifier2/callback-and-sse.md) instead of polling for session status - **Verify other credential types** — Try [W3C VC](https://docs.walt.id/community-stack/verifier2/credential-verification/vc-oid4vp.md) or [mDL](https://docs.walt.id/community-stack/verifier2/credential-verification/mdl-oid4vp.md) - **Use DC API** — Try the [Digital Credentials API](https://docs.walt.id/community-stack/verifier2/credential-verification/dc-api.md) for browser-native credential exchange -------------------------------------------------------------------------------- PAGE: Verifying Mobile Driver's Licenses (mDL, ISO/IEC 18013-5) via OID4VP URL: https://docs.walt.id/community-stack/verifier2/credential-verification/mdl-oid4vp RAW: https://docs.walt.id/community-stack/verifier2/credential-verification/mdl-oid4vp.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Verifying Mobile Driver's Licenses (mDL, ISO/IEC 18013-5) via OID4VP This guide shows you how to verify ISO/IEC 18013-5 mobile driver's licenses (mDL) using the walt.id Verifier2 API and the OID4VP protocol. The verification process sends a credential request to a holder's wallet, receives the signed presentation, and validates it against your policies. [**mDL (Mobile Driver's License)**](https://docs.walt.id/concepts/digital-credentials/mdoc-mdl-iso.md): A digital representation of a driver's license based on the [ISO/IEC 18013-5 standard](https://www.iso.org/standard/69084.html), stored securely on digital ID wallet. [**OID4VP**](https://docs.walt.id/concepts/data-exchange-protocols/openid4vp.md): A protocol specifying how parties can present digital credentials in a way that's consistent and secure across platforms ensuring interoperability. **Note:** If you're migrating from the original Verifier (v1), see the [Migration Guide](https://docs.walt.id/community-stack/verifier2/migration-from-verifier1.md). ## Prerequisites Before you begin, ensure you have: - **Verifier2 API running** — Follow the [Getting Started](https://docs.walt.id/community-stack/verifier2/getting-started.md) or [Setup](https://docs.walt.id/community-stack/verifier2/setup.md) guide - **A test wallet with credentials** — Use the [walt.id Wallet](https://docs.walt.id/community-stack/wallet/getting-started.md) for testing, or any OID4VP-compatible wallet - **Publicly accessible URL** (for local development) — Use [ngrok](https://ngrok.com) or similar to expose `http://localhost:7003`. **Note:** **Local Development:** When running locally via Docker, the API is available at `http://localhost:7003` (standalone) or `http://localhost:7004` (Docker Compose). Use this as your base URL in the examples below. --- ## Example 1: Basic Verification This example requests a mobile driver's license (mDL) and verifies its cryptographic signature using the default signature policy. **Option: CURL** **Endpoint:** `POST /verification-session/create` | [API Reference](https://verifier2.portal.test.waltid.cloud/swagger/index.html#/Verification%20Session%20Management/post_verification_session_create) ##### Example Request ```bash curl -X 'POST' \ 'http://localhost:7003/verification-session/create' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -d '{ "flow_type": "cross_device", "core_flow": { "dcql_query": { "credentials": [ { "id": "credential_1", "format": "mso_mdoc", "meta": { "doctype_value": "org.iso.18013.5.1.mDL" }, "claims": [ { "path": [ "org.iso.18013.5.1", "family_name" ] }, { "path": [ "org.iso.18013.5.1", "given_name" ] }, { "path": [ "org.iso.18013.5.1", "birth_date" ] } ] } ] }, "policies": { "vc_policies": [ { "policy": "signature" } ] } } }' ``` **Body Parameters** - **flow_type**: _String_ (required) - Specifies how the credential request will be delivered to the holder's wallet. Options: - `"cross_device"` — User scans a QR code on a different device (e.g. desktop browser → phone wallet) - `"same_device"` — User clicks a deep link on the same device (e.g. phone browser → wallet app on same phone) - `"dc_api"` — Browser/OS-native Digital Credentials API (Chrome, Safari, iOS/Android native) over OpenID4VP 1.0 (Annex D). - `"dc_api-annex-c"` — Browser/OS-native Digital Credentials API (Chrome, Safari, iOS/Android native) over 18013-7 (Annex C). - **core_flow**: _Object_ (required) - Defines what credentials to request and which policies to apply. Contains: - **dcql_query**: _Object_ (required) - Specifies the credential type, format, and claims to request using the [DCQL query language](https://docs.walt.id/concepts/data-exchange-protocols/dcql.md). You can also use the DCQL builder [here](https://dcql.walt.id/). - **credentials**: _Array_ - List of credential queries. Each credential query contains: - **id**: _String_ - Unique identifier for this credential in the request (used in policy results) - **format**: _String_ - Credential format. For mDL, use `"mso_mdoc"` (mobile security object document). - **meta.doctype_value**: _String_ - The ISO mDL document type. For mobile driver's licenses, use `"org.iso.18013.5.1.mDL"`. - **claims**: _Array_ (optional) - Specific claims to request from the credential. Each claim has a `path` array with namespace and claim name (e.g. `["org.iso.18013.5.1", "family_name"]`). If omitted, all claims that are non-selectively disclosable are provided by the wallet. - **policies**: _Object_ (optional) - Validation rules applied to the received credential. Defaults to signature verification only if omitted. - **vc_policies**: _Array_ - Policies applied to individual credentials. Each policy has a `policy` field specifying the policy name. Available policies: `signature`, `expired`, `not-before`, `revoked-status-list`, `schema`, and more. See [Policies](https://docs.walt.id/community-stack/verifier2/policies/overview.md) for the complete list. - **vp_policies**: _Array_ (optional) - Policies applied to the entire presentation (not shown in this example) - **signedRequest**: _Boolean_ (optional) - Whether to sign the authorization request JWT. Required for HAIP profile compliance and DC API Annex C (ISO 18013-7) flows. Defaults to `false`. See [Signed Requests](#signed-requests) to learn more. - **encryptedResponse**: _Boolean_ (optional) - Whether the wallet should encrypt the VP token response. Defaults to `false`. See [Encrypted Responses](#encrypted-responses) to learn more. - **key**: _Object_ (optional) - Signing key for this verification session. Overrides the service-wide default `key` configured via the[`verifier-service.conf`](https://docs.walt.id/community-stack/verifier2/configurations/config-files/verifier-service.md#key-optional). Must be a serialized key in the format `{"type":"jwk","jwk":{...}}`. Required when `signedRequest: true` or `encryptedResponse: true` and no service-wide default is configured. See [Signed Requests](#signed-requests) and [Encrypted Responses](#encrypted-responses) for when this is needed. - **x5c**: _Array of Strings_ (optional) - X.509 certificate chain for this verification session. Overrides the service-wide default `x5c` configured via [`verifier-service.conf`](https://docs.walt.id/community-stack/verifier2/configurations/config-files/verifier-service.md#x5c-optional). Must be an ordered chain in DER-encoded, Base64-encoded format where the first certificate corresponds to the `key`. Required for X.509-based client authentication (`x509_san_dns:` or `x509_hash:` as `clientId`) and signed Annex C requests. See [Signed Requests](#signed-requests) to learn more. - **verifier_info**: _Object_ (optional) - An array of attestations about the Verifier relevant to the Credential Request. Each object is a JSON object with the following fields: - **format**: _String_ - The format of the attestation. Currently only `"jwt"` is supported. - **data**: _String_ - The attestation data in JWT format. - **credentialIds**: _Array_ (optional) - Specifies which credentials in your DCQL query this attestation applies to. References the `id` fields from your `credentials` array in the DCQL query. If omitted, the attestation applies to all requested credentials. - **openid**: _Object_ (optional) - OpenID4VP-specific extensions - **transactionData**: _Array_ (optional) - Bind the credential presentation to a specific transaction (e.g., payment authorization, account access). Each entry references credential IDs and includes transaction details. See [Transaction Data Authorization](#transaction-data-authorization) for full documentation. - **url_config**: _Object_ (optional) - URL configuration for this session. Only applies to `cross_device` and `same_device` flows (not `dc_api`). - **url_prefix**: _String_ (optional) - The publicly accessible base URL where the wallet will fetch the authorization request and post its response. Overrides the `urlPrefix` from `verifier-service.conf`. - The service constructs: Request URI: `{urlPrefix}/{sessionId}/request` and Response URI: `{urlPrefix}/{sessionId}/response` - **Must be reachable by the wallet** (use ngrok or public URL for local development) - **url_host**: _String_ (optional) - The base URL used to construct the authorization request link. Overrides the `urlHost` from `verifier-service.conf`. - For standard OID4VP flows, use `"openid4vp://authorize"` (default) - For custom authorization endpoints (e.g. conformance testing), provide a full HTTPS URL - **redirects**: _Object_ (optional) - Success and error redirect URLs (only for `cross_device` and `same_device` flows) - **successRedirectUri**: _String_ - URL to redirect the user after successful verification - **errorRedirectUri**: _String_ - URL to redirect the user if verification fails **Note:** Many parameters have defaults configured in `verifier-service.conf`. Request-level parameters override these defaults for that specific session only. See [Configuration Defaults](#configuration-defaults) below. --- ##### Example Response The response contains the session ID and authorization URLs to present to the user. ```json { "sessionId": "4a803c4b-1dc3-4210-806d-3fb52f1780c8", "bootstrapAuthorizationRequestUrl": "openid4vp://authorize?client_id=verifier2&request_uri=https%3A%2F%2F575e-2a02-8388-17c1-5580-b97f-5031-3eee-c8a4.ngrok-free.app%2Fverification-session%2F4a803c4b-1dc3-4210-806d-3fb52f1780c8%2Frequest", "fullAuthorizationRequestUrl": "openid4vp://authorize?response_type=vp_token&client_id=verifier2&state=da82aed7-ea36-4303-9c4a-3ded03cdedb4&response_mode=direct_post&nonce=74b0efc4-331c-488f-9489-7ce72b1c698f&response_uri=https%3A%2F%2F575e-2a02-8388-17c1-5580-b97f-5031-3eee-c8a4.ngrok-free.app%2Fverification-session%2F4a803c4b-1dc3-4210-806d-3fb52f1780c8%2Fresponse&dcql_query=%7B%22credentials%22%3A%5B%7B%22id%22%3A%22credential_1%22%2C%22format%22%3A%22mso_mdoc%22%2C%22meta%22%3A%7B%22doctype_value%22%3A%22org.iso.18013.5.1.mDL%22%7D%2C%22claims%22%3A%5B%7B%22path%22%3A%5B%22org.iso.18013.5.1%22%2C%22family_name%22%5D%7D%2C%7B%22path%22%3A%5B%22org.iso.18013.5.1%22%2C%22given_name%22%5D%7D%2C%7B%22path%22%3A%5B%22org.iso.18013.5.1%22%2C%22birth_date%22%5D%7D%5D%7D%5D%7D&client_metadata=%7B%22vp_formats_supported%22%3A%7B%22mso_mdoc%22%3A%7B%22issuerauth_alg_values%22%3A%5B-7%2C-9%2C-50%5D%2C%22deviceauth_alg_values%22%3A%5B-7%2C-9%2C-50%2C-65537%5D%7D%7D%2C%22client_name%22%3A%22Verifier2%22%2C%22logo_uri%22%3A%22https%3A%2F%2Fimages.squarespace-cdn.com%2Fcontent%22%2C%22tos_uri%22%3A%22https%3A%2F%2Fexample.com%2Fterms%22%2C%22policy_uri%22%3A%22https%3A%2F%2Fexample.com%2Fprivacy%22%2C%22client_uri%22%3A%22https%3A%2F%2Fverifier.example.com%22%2C%22client_name%23fr-FR%22%3A%22V%C3%A9rificateur2%22%2C%22logo_uri%23fr-FR%22%3A%22https%3A%2F%2Fexample.com%2Flogo-fr.png%22%2C%22tos_uri%23fr-FR%22%3A%22https%3A%2F%2Fexample.com%2Fterms-fr%22%2C%22policy_uri%23fr-FR%22%3A%22https%3A%2F%2Fexample.com%2Fprivacy-fr%22%2C%22client_uri%23fr-FR%22%3A%22https%3A%2F%2Fverifier.example.com%2Ffr%22%7D" } ``` **Response Fields:** - **sessionId**: _String_ - The unique ID of this verification session. Use this to query the session status. - **bootstrapAuthorizationRequestUrl**: _String_ - Shortened URL using `request_uri` by reference. **Use this for QR codes** to minimize QR code density. - **fullAuthorizationRequestUrl**: _String_ - Complete URL with all parameters embedded. Use this if the wallet doesn't support `request_uri` (rare). ## Presenting the Request to the User After creating the session, you need to present the authorization request to the user based on the flow type you chose. ### Cross-Device Flow (QR Code) 1. **Generate a QR code** from the `bootstrapAuthorizationRequestUrl` 2. **Display the QR code** to the user on your web page or application 3. **User scans the QR code** with their wallet app 4. **Wallet presents the credential** and posts the response to your verifier 5. **Monitor the session status** by polling the session endpoint or using SSE/callbacks (see below) ### Same-Device Flow (Deep Link) 1. **Redirect the user** to the `bootstrapAuthorizationRequestUrl` 2. **User's wallet app opens** automatically (via deep link) 3. **Wallet presents the credential** and posts the response to your verifier 4. **User is redirected** to your `successRedirectUri` or `errorRedirectUri` 5. **Query the session status** to retrieve the verification results --- ## Checking Verification Results You can query the verification session to check if the user has presented their credential and whether validation succeeded. **Option: CURL** **Endpoint:** `GET /verification-session/{sessionId}/info` | [API Reference](https://verifier2.portal.test.waltid.cloud/swagger/index.html#/Verification%20Session%20Management/get_verification_session__sessionId__info) ##### Example Request ```bash curl -X 'GET' \ 'http://localhost:7003/verification-session/{sessionId}/info' \ -H 'accept: application/json' ``` **Path Parameters** - **sessionId**: _String_ (required) - The unique ID of the verification session returned when you created the session. Use this to query the current status and results. --- ##### Example Response The response contains the complete session information including setup, status, policy results, and presented credentials. ```json { "id": "4a803c4b-1dc3-4210-806d-3fb52f1780c8", "setup": { "flow_type": "cross_device", "core_flow": { "dcql_query": { "credentials": [ { "id": "credential_1", "format": "mso_mdoc", "multiple": false, "meta": { "doctype_value": "org.iso.18013.5.1.mDL", "format": "mso_mdoc" }, "require_cryptographic_holder_binding": true, "claims": [ { "path": [ "org.iso.18013.5.1", "family_name" ] }, { "path": [ "org.iso.18013.5.1", "given_name" ] }, { "path": [ "org.iso.18013.5.1", "birth_date" ] } ] } ] }, "signed_request": false, "encrypted_response": false, "policies": { "vc_policies": [ { "policy": "signature", "id": "signature" } ] } }, "url_config": {} }, "creationDate": "2026-05-18T07:48:39.335523256Z", "expirationDate": "2026-05-18T07:53:39.335523256Z", "retentionDate": "2036-05-18T07:48:39.335523256Z", "status": "SUCCESSFUL", "attempted": true, "reattemptable": true, "bootstrapAuthorizationRequest": { "client_id": "verifier2", "request_uri": "https://575e-2a02-8388-17c1-5580-b97f-5031-3eee-c8a4.ngrok-free.app/verification-session/4a803c4b-1dc3-4210-806d-3fb52f1780c8/request" }, "bootstrapAuthorizationRequestUrl": "openid4vp://authorize?client_id=verifier2&request_uri=https%3A%2F%2F575e-2a02-8388-17c1-5580-b97f-5031-3eee-c8a4.ngrok-free.app%2Fverification-session%2F4a803c4b-1dc3-4210-806d-3fb52f1780c8%2Frequest", "authorizationRequest": { "response_type": "vp_token", "client_id": "verifier2", "state": "da82aed7-ea36-4303-9c4a-3ded03cdedb4", "response_mode": "direct_post", "nonce": "74b0efc4-331c-488f-9489-7ce72b1c698f", "response_uri": "https://575e-2a02-8388-17c1-5580-b97f-5031-3eee-c8a4.ngrok-free.app/verification-session/4a803c4b-1dc3-4210-806d-3fb52f1780c8/response", "dcql_query": { "credentials": [ { "id": "credential_1", "format": "mso_mdoc", "multiple": false, "meta": { "doctype_value": "org.iso.18013.5.1.mDL", "format": "mso_mdoc" }, "require_cryptographic_holder_binding": true, "claims": [ { "path": [ "org.iso.18013.5.1", "family_name" ] }, { "path": [ "org.iso.18013.5.1", "given_name" ] }, { "path": [ "org.iso.18013.5.1", "birth_date" ] } ] } ] }, "client_metadata": { "vp_formats_supported": { "mso_mdoc": { "issuerauth_alg_values": [ -7, -9, -50 ], "deviceauth_alg_values": [ -7, -9, -50, -65537 ] } }, "client_name": "Verifier2", "logo_uri": "https://images.squarespace-cdn.com/content", "tos_uri": "https://example.com/terms", "policy_uri": "https://example.com/privacy", "client_uri": "https://verifier.example.com", "client_name#fr-FR": "Vérificateur2", "logo_uri#fr-FR": "https://example.com/logo-fr.png", "tos_uri#fr-FR": "https://example.com/terms-fr", "policy_uri#fr-FR": "https://example.com/privacy-fr", "client_uri#fr-FR": "https://verifier.example.com/fr" } }, "authorizationRequestUrl": "openid4vp://authorize?response_type=vp_token&client_id=verifier2&state=da82aed7-ea36-4303-9c4a-3ded03cdedb4&response_mode=direct_post&nonce=74b0efc4-331c-488f-9489-7ce72b1c698f&response_uri=https%3A%2F%2F575e-2a02-8388-17c1-5580-b97f-5031-3eee-c8a4.ngrok-free.app%2Fverification-session%2F4a803c4b-1dc3-4210-806d-3fb52f1780c8%2Fresponse&dcql_query=%7B%22credentials%22%3A%5B%7B%22id%22%3A%22credential_1%22%2C%22format%22%3A%22mso_mdoc%22%2C%22meta%22%3A%7B%22doctype_value%22%3A%22org.iso.18013.5.1.mDL%22%7D%2C%22claims%22%3A%5B%7B%22path%22%3A%5B%22org.iso.18013.5.1%22%2C%22family_name%22%5D%7D%2C%7B%22path%22%3A%5B%22org.iso.18013.5.1%22%2C%22given_name%22%5D%7D%2C%7B%22path%22%3A%5B%22org.iso.18013.5.1%22%2C%22birth_date%22%5D%7D%5D%7D%5D%7D&client_metadata=%7B%22vp_formats_supported%22%3A%7B%22mso_mdoc%22%3A%7B%22issuerauth_alg_values%22%3A%5B-7%2C-9%2C-50%5D%2C%22deviceauth_alg_values%22%3A%5B-7%2C-9%2C-50%2C-65537%5D%7D%7D%2C%22client_name%22%3A%22Verifier2%22%2C%22logo_uri%22%3A%22https%3A%2F%2Fimages.squarespace-cdn.com%2Fcontent%22%2C%22tos_uri%22%3A%22https%3A%2F%2Fexample.com%2Fterms%22%2C%22policy_uri%22%3A%22https%3A%2F%2Fexample.com%2Fprivacy%22%2C%22client_uri%22%3A%22https%3A%2F%2Fverifier.example.com%22%2C%22client_name%23fr-FR%22%3A%22V%C3%A9rificateur2%22%2C%22logo_uri%23fr-FR%22%3A%22https%3A%2F%2Fexample.com%2Flogo-fr.png%22%2C%22tos_uri%23fr-FR%22%3A%22https%3A%2F%2Fexample.com%2Fterms-fr%22%2C%22policy_uri%23fr-FR%22%3A%22https%3A%2F%2Fexample.com%2Fprivacy-fr%22%2C%22client_uri%23fr-FR%22%3A%22https%3A%2F%2Fverifier.example.com%2Ffr%22%7D", "requestMode": "REQUEST_URI", "policies": { "vp_policies": { "jwt_vc_json": [ "jwt_vc_json/audience-check", "jwt_vc_json/nonce-check", "jwt_vc_json/envelope_signature" ], "dc+sd-jwt": [ "dc+sd-jwt/audience-check", "dc+sd-jwt/kb-jwt_signature", "dc+sd-jwt/nonce-check", "dc+sd-jwt/sd_hash-check" ], "mso_mdoc": [ "mso_mdoc/device-auth", "mso_mdoc/device_key_auth", "mso_mdoc/issuer_auth", "mso_mdoc/issuer_signed_integrity", "mso_mdoc/mso" ] }, "vc_policies": [ { "policy": "signature", "id": "signature" } ] }, "policy_results": { "vp_policies": { "credential_1": { "mso_mdoc/device-auth": { "policy_executed": { "policy": "mso_mdoc/device-auth", "id": "mso_mdoc/device-auth", "description": "Verify device authentication" }, "success": true, "results": { "device_public_jwk": { "kty": "OKP", "crv": "Ed25519", "x": "jxQwRV7tHc3W_JkKVsDZL7_DtP4zyNJtQ4Yr1z7-Md4" }, "device_auth_bytes_hex": "d8185868847444657669636541757468656e7469636174696f6e83f6f682714f70656e494434565048616e646f7665725820bdb670da65dae163a8fe31c591c82da53c6da1b38232c8f4d3657405e4220a43756f72672e69736f2e31383031332e352e312e6d444cd81841a0" }, "errors": [], "execution_time": "PT0.002658542S" }, "mso_mdoc/device_key_auth": { "policy_executed": { "policy": "mso_mdoc/device_key_auth", "id": "mso_mdoc/device_key_auth", "description": "Verify holder-verified data" }, "success": true, "results": { "empty_device_signed_namespaces": true }, "errors": [], "execution_time": "PT0.000800334S" }, "mso_mdoc/issuer_auth": { "policy_executed": { "policy": "mso_mdoc/issuer_auth", "id": "mso_mdoc/issuer_auth", "description": "Verify issuer authentication" }, "success": true, "results": { "certificate_chain": [ "3082022b308201d1a0030201020214440fe81f511c691ef21f46d51ef752bbde666996300a06082a8648ce3d040302302c310b3009060355040613024154311d301b06035504030c146d444c20497373756572204941434120526f6f74301e170d3236303531383036313932365a170d3237303831383036313932365a3032310b30090603550406130241543123302106035504030c1a6d444c2049737375657220446f63756d656e74205369676e65723059301306072a8648ce3d020106082a8648ce3d03010703420004c9f99bb42b1d410ff81033e0d06b7df20258935ce434ea4633d6c76048b8f667301080b22778927ef1e8a3f7d5bc33469d3cd03e9795b4761b19a673bec228f9a381ca3081c7301f0603551d2304183016801472b2f76f49d0bcb3dd70cc2d6499a3378002e8e3301d0603551d0e041604145c64d6b94b4538b14534525b3942eef8f841827d300e0603551d0f0101ff04040302078030270603551d120420301e861c68747470733a2f2f696163612e74656e616e74312e77616c742e696430150603551d250101ff040b3009060728818c5d05010230350603551d1f042e302c302aa028a026862468747470733a2f2f63726c2e74656e616e74312e77616c742e69642f696163612e63726c300a06082a8648ce3d0403020348003045022006c7eaed38d41d53d35224d5b420ca41109d5f3a7afde473a8e287ed8688a3d7022100eb7882937823711f9bc909e3f88861436da296b882066ced7fdf66f057f0af39" ], "signer_pem": "-----BEGIN CERTIFICATE-----\nMIICKzCCAdGgAwIBAgIURA/oH1EcaR7yH0bVHvdSu95maZYwCgYIKoZIzj0EAwIw\r\nLDELMAkGA1UEBhMCQVQxHTAbBgNVBAMMFG1ETCBJc3N1ZXIgSUFDQSBSb290MB4X\r\nDTI2MDUxODA2MTkyNloXDTI3MDgxODA2MTkyNlowMjELMAkGA1UEBhMCQVQxIzAh\r\nBgNVBAMMGm1ETCBJc3N1ZXIgRG9jdW1lbnQgU2lnbmVyMFkwEwYHKoZIzj0CAQYI\r\nKoZIzj0DAQcDQgAEyfmbtCsdQQ/4EDPg0Gt98gJYk1zkNOpGM9bHYEi49mcwEICy\r\nJ3iSfvHoo/fVvDNGnTzQPpeVtHYbGaZzvsIo+aOByjCBxzAfBgNVHSMEGDAWgBRy\r\nsvdvSdC8s91wzC1kmaM3gALo4zAdBgNVHQ4EFgQUXGTWuUtFOLFFNFJbOULu+PhB\r\ngn0wDgYDVR0PAQH/BAQDAgeAMCcGA1UdEgQgMB6GHGh0dHBzOi8vaWFjYS50ZW5h\r\nbnQxLndhbHQuaWQwFQYDVR0lAQH/BAswCQYHKIGMXQUBAjA1BgNVHR8ELjAsMCqg\r\nKKAmhiRodHRwczovL2NybC50ZW5hbnQxLndhbHQuaWQvaWFjYS5jcmwwCgYIKoZI\r\nzj0EAwIDSAAwRQIgBsfq7TjUHVPTUiTVtCDKQRCdXzp6/eRzqOKH7YaIo9cCIQDr\r\neIKTeCNxH5vJCeP4iGFDbaKWuIIGbO1/32bwV/CvOQ==\n-----END CERTIFICATE-----", "signer_jwk": { "kty": "EC", "crv": "P-256", "x": "yfmbtCsdQQ_4EDPg0Gt98gJYk1zkNOpGM9bHYEi49mc", "y": "MBCAsid4kn7x6KP31bwzRp080D6XlbR2Gxmmc77CKPk" } }, "errors": [], "execution_time": "PT0.004215542S" }, "mso_mdoc/issuer_signed_integrity": { "policy_executed": { "policy": "mso_mdoc/issuer_signed_integrity", "id": "mso_mdoc/issuer_signed_integrity", "description": "Verify issuer-verified data integrity" }, "success": true, "results": { "namespace": { "org.iso.18013.5.1": [ { "id": "family_name", "digest_id": 0, "value": "Doe", "value_type": "CborString", "random_hex": "736b060d21fa4e37f17b0b0568285add9d064ba883eb6c35", "serialized_hex": "a4686469676573744944006672616e646f6d5818736b060d21fa4e37f17b0b0568285add9d064ba883eb6c3571656c656d656e744964656e7469666965726b66616d696c795f6e616d656c656c656d656e7456616c756563446f65" }, { "id": "given_name", "digest_id": 1, "value": "John", "value_type": "CborString", "random_hex": "312e734c986fdf279c7d75130b0da4794d16d44bbb3cfd61", "serialized_hex": "a4686469676573744944016672616e646f6d5818312e734c986fdf279c7d75130b0da4794d16d44bbb3cfd6171656c656d656e744964656e7469666965726a676976656e5f6e616d656c656c656d656e7456616c7565644a6f686e" }, { "id": "birth_date", "digest_id": 2, "value": "1986-03-22", "value_type": "CborString", "random_hex": "e6864d13d13a9fb1a7fccb729a6330dec975bac5e79fa023", "serialized_hex": "a4686469676573744944026672616e646f6d5818e6864d13d13a9fb1a7fccb729a6330dec975bac5e79fa02371656c656d656e744964656e7469666965726a62697274685f646174656c656c656d656e7456616c7565d903ec6a313938362d30332d3232" } ] }, "matching_digest": { "org.iso.18013.5.1": [ "family_name", "given_name", "birth_date" ] } }, "errors": [], "execution_time": "PT0.003104834S" }, "mso_mdoc/mso": { "policy_executed": { "policy": "mso_mdoc/mso", "id": "mso_mdoc/mso", "description": "Verify MSO" }, "success": true, "results": { "signed": "2026-05-18T07:48:27.870905006Z", "valid_from": "2027-05-18T07:48:27.852289522Z" }, "errors": [], "execution_time": "PT0.004245834S" } } }, "vc_policies": [ { "policy": { "policy": "signature", "id": "signature" }, "success": true, "result": { "verification_result": true, "signed_credential": "o2d2ZXJzaW9uYzEuMGlkb2N1bWVudHOBo2dkb2NUeXBldW9yZy5pc28uMTgwMTMuNS4xLm1ETGxpc3N1ZXJTaWduZWSiam5hbWVTcGFjZXOhcW9yZy5pc28uMTgwMTMuNS4xg9gYWFukaGRpZ2VzdElEAGZyYW5kb21YGHNrBg0h-k438XsLBWgoWt2dBkuog-tsNXFlbGVtZW50SWRlbnRpZmllcmtmYW1pbHlfbmFtZWxlbGVtZW50VmFsdWVjRG9l2BhYW6RoZGlnZXN0SUQBZnJhbmRvbVgYMS5zTJhv3yecfXUTCw2keU0W1Eu7PP1hcWVsZW1lbnRJZGVudGlmaWVyamdpdmVuX25hbWVsZWxlbWVudFZhbHVlZEpvaG7YGFhkpGhkaWdlc3RJRAJmcmFuZG9tWBjmhk0T0Tqfsaf8y3KaYzDeyXW6xeefoCNxZWxlbWVudElkZW50aWZpZXJqYmlydGhfZGF0ZWxlbGVtZW50VmFsdWXZA-xqMTk4Ni0wMy0yMmppc3N1ZXJBdXRohEOhASahGCFZAi8wggIrMIIB0aADAgECAhRED-gfURxpHvIfRtUe91K73mZpljAKBggqhkjOPQQDAjAsMQswCQYDVQQGEwJBVDEdMBsGA1UEAwwUbURMIElzc3VlciBJQUNBIFJvb3QwHhcNMjYwNTE4MDYxOTI2WhcNMjcwODE4MDYxOTI2WjAyMQswCQYDVQQGEwJBVDEjMCEGA1UEAwwabURMIElzc3VlciBEb2N1bWVudCBTaWduZXIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATJ-Zu0Kx1BD_gQM-DQa33yAliTXOQ06kYz1sdgSLj2ZzAQgLIneJJ-8eij99W8M0adPNA-l5W0dhsZpnO-wij5o4HKMIHHMB8GA1UdIwQYMBaAFHKy929J0Lyz3XDMLWSZozeAAujjMB0GA1UdDgQWBBRcZNa5S0U4sUU0Uls5Qu74-EGCfTAOBgNVHQ8BAf8EBAMCB4AwJwYDVR0SBCAwHoYcaHR0cHM6Ly9pYWNhLnRlbmFudDEud2FsdC5pZDAVBgNVHSUBAf8ECzAJBgcogYxdBQECMDUGA1UdHwQuMCwwKqAooCaGJGh0dHBzOi8vY3JsLnRlbmFudDEud2FsdC5pZC9pYWNhLmNybDAKBggqhkjOPQQDAgNIADBFAiAGx-rtONQdU9NSJNW0IMpBEJ1fOnr95HOo4ofthoij1wIhAOt4gpN4I3Efm8kJ4_iIYUNtopa4ggZs7X_fZvBX8K85WQKV2BhZApCmZ3ZlcnNpb25jMS4wb2RpZ2VzdEFsZ29yaXRobWdTSEEtMjU2bHZhbHVlRGlnZXN0c6Fxb3JnLmlzby4xODAxMy41LjGqAFgglIt2JC3rDbrr6azX5GWPMGyGqyD5gTnyASk37pDhZQYBWCCMQ7Ph45igVnLbvCtlSqLPlaMTXSk2kLoy4Mxn1x37kwJYIOjGjCHBlfvZi74lANZXAWt_FDdDf6NU591eYMGbFladA1ggcdl99l7hzcOFDYUeghGX0gDXYYgEN7WyRFrgSSFVvv4EWCCKwt6H37DEK8kJFcs__AiFZzLDc-4GPFKuYYEJzv_X1QVYIMybLDIPi3G7go7Bq4ggn0UYXEpmKMazxXZH25mnl0RQBlggofgVz7C7i4z0Iz8zrEPhNBRMlZcr4EhqQCR2pjo3qsoHWCDaJ4DszfMYhI0HfyOcSgMEgx_K12dx34khKW6ohKRhZghYIPTE7TeLsXVDSX9l3DTPB4zDhzF1WDTmCw3Ecxv1deNJCVggqueG-PUmdP8sJdsQ6XbuByQzAZXoRdwB-7dn0hxtkCdtZGV2aWNlS2V5SW5mb6FpZGV2aWNlS2V5owEBIAYhWCCPFDBFXu0dzdb8mQpWwNkvv8O0_jPI0m1DhivXPv4x3mdkb2NUeXBldW9yZy5pc28uMTgwMTMuNS4xLm1ETGx2YWxpZGl0eUluZm-jZnNpZ25lZMB4HjIwMjYtMDUtMThUMDc6NDg6MjcuODcwOTA1MDA2Wml2YWxpZEZyb23AeB4yMDI2LTA1LTE4VDA3OjQ4OjI3Ljg3MDkwNTAwNlpqdmFsaWRVbnRpbMB4HjIwMjctMDUtMThUMDc6NDg6MjcuODUyMjg5NTIyWlhAmRC5DQjAwaNaozniXwKmOoMscEyNwA65Y31zyoCJpiGiZxs-fk9bcsZnrDSu5ATPVEIKrf_4hjY8ODArK76q2WxkZXZpY2VTaWduZWSiam5hbWVTcGFjZXPYGEGgamRldmljZUF1dGihb2RldmljZVNpZ25hdHVyZYRDoQEnoPZYQGQUayxOBk2Wp7g7UhumPbtFSQ5h1h4jArb60UGZOsFMvG6MXHBBPd4JdOX40auhbmKNmBjinPfw96QN9XfV-Q9mc3RhdHVzAA", "credential_signature": { "type": "signature-cose", "signerKey": { "type": "jwk", "jwk": { "kty": "EC", "crv": "P-256", "x": "yfmbtCsdQQ_4EDPg0Gt98gJYk1zkNOpGM9bHYEi49mc", "y": "MBCAsid4kn7x6KP31bwzRp080D6XlbR2Gxmmc77CKPk" } }, "x5cList": [ "MIICKzCCAdGgAwIBAgIURA/oH1EcaR7yH0bVHvdSu95maZYwCgYIKoZIzj0EAwIwLDELMAkGA1UEBhMCQVQxHTAbBgNVBAMMFG1ETCBJc3N1ZXIgSUFDQSBSb290MB4XDTI2MDUxODA2MTkyNloXDTI3MDgxODA2MTkyNlowMjELMAkGA1UEBhMCQVQxIzAhBgNVBAMMGm1ETCBJc3N1ZXIgRG9jdW1lbnQgU2lnbmVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEyfmbtCsdQQ/4EDPg0Gt98gJYk1zkNOpGM9bHYEi49mcwEICyJ3iSfvHoo/fVvDNGnTzQPpeVtHYbGaZzvsIo+aOByjCBxzAfBgNVHSMEGDAWgBRysvdvSdC8s91wzC1kmaM3gALo4zAdBgNVHQ4EFgQUXGTWuUtFOLFFNFJbOULu+PhBgn0wDgYDVR0PAQH/BAQDAgeAMCcGA1UdEgQgMB6GHGh0dHBzOi8vaWFjYS50ZW5hbnQxLndhbHQuaWQwFQYDVR0lAQH/BAswCQYHKIGMXQUBAjA1BgNVHR8ELjAsMCqgKKAmhiRodHRwczovL2NybC50ZW5hbnQxLndhbHQuaWQvaWFjYS5jcmwwCgYIKoZIzj0EAwIDSAAwRQIgBsfq7TjUHVPTUiTVtCDKQRCdXzp6/eRzqOKH7YaIo9cCIQDreIKTeCNxH5vJCeP4iGFDbaKWuIIGbO1/32bwV/CvOQ==" ] }, "verified_data": { "docType": "org.iso.18013.5.1.mDL", "org.iso.18013.5.1": { "family_name": "Doe", "given_name": "John", "birth_date": "1986-03-22" } }, "successful_issuer_public_key": { "kty": "EC", "crv": "P-256", "x": "yfmbtCsdQQ_4EDPg0Gt98gJYk1zkNOpGM9bHYEi49mc", "y": "MBCAsid4kn7x6KP31bwzRp080D6XlbR2Gxmmc77CKPk" }, "successful_issuer_public_key_id": "FOWc2X5ZSle6u66RwXQoVUwm86vnTnbZ61s_Golepz0" } } ], "specific_vc_policies": {}, "overallSuccess": true }, "presentation_validation_results": { "credential_1": { "mso_mdoc/device-auth": { "policy_executed": { "policy": "mso_mdoc/device-auth", "id": "mso_mdoc/device-auth", "description": "Verify device authentication" }, "success": true, "results": { "device_public_jwk": { "kty": "OKP", "crv": "Ed25519", "x": "jxQwRV7tHc3W_JkKVsDZL7_DtP4zyNJtQ4Yr1z7-Md4" }, "device_auth_bytes_hex": "d8185868847444657669636541757468656e7469636174696f6e83f6f682714f70656e494434565048616e646f7665725820bdb670da65dae163a8fe31c591c82da53c6da1b38232c8f4d3657405e4220a43756f72672e69736f2e31383031332e352e312e6d444cd81841a0" }, "errors": [], "execution_time": "PT0.002658542S" }, "mso_mdoc/device_key_auth": { "policy_executed": { "policy": "mso_mdoc/device_key_auth", "id": "mso_mdoc/device_key_auth", "description": "Verify holder-verified data" }, "success": true, "results": { "empty_device_signed_namespaces": true }, "errors": [], "execution_time": "PT0.000800334S" }, "mso_mdoc/issuer_auth": { "policy_executed": { "policy": "mso_mdoc/issuer_auth", "id": "mso_mdoc/issuer_auth", "description": "Verify issuer authentication" }, "success": true, "results": { "certificate_chain": [ "3082022b308201d1a0030201020214440fe81f511c691ef21f46d51ef752bbde666996300a06082a8648ce3d040302302c310b3009060355040613024154311d301b06035504030c146d444c20497373756572204941434120526f6f74301e170d3236303531383036313932365a170d3237303831383036313932365a3032310b30090603550406130241543123302106035504030c1a6d444c2049737375657220446f63756d656e74205369676e65723059301306072a8648ce3d020106082a8648ce3d03010703420004c9f99bb42b1d410ff81033e0d06b7df20258935ce434ea4633d6c76048b8f667301080b22778927ef1e8a3f7d5bc33469d3cd03e9795b4761b19a673bec228f9a381ca3081c7301f0603551d2304183016801472b2f76f49d0bcb3dd70cc2d6499a3378002e8e3301d0603551d0e041604145c64d6b94b4538b14534525b3942eef8f841827d300e0603551d0f0101ff04040302078030270603551d120420301e861c68747470733a2f2f696163612e74656e616e74312e77616c742e696430150603551d250101ff040b3009060728818c5d05010230350603551d1f042e302c302aa028a026862468747470733a2f2f63726c2e74656e616e74312e77616c742e69642f696163612e63726c300a06082a8648ce3d0403020348003045022006c7eaed38d41d53d35224d5b420ca41109d5f3a7afde473a8e287ed8688a3d7022100eb7882937823711f9bc909e3f88861436da296b882066ced7fdf66f057f0af39" ], "signer_pem": "-----BEGIN CERTIFICATE-----\nMIICKzCCAdGgAwIBAgIURA/oH1EcaR7yH0bVHvdSu95maZYwCgYIKoZIzj0EAwIw\r\nLDELMAkGA1UEBhMCQVQxHTAbBgNVBAMMFG1ETCBJc3N1ZXIgSUFDQSBSb290MB4X\r\nDTI2MDUxODA2MTkyNloXDTI3MDgxODA2MTkyNlowMjELMAkGA1UEBhMCQVQxIzAh\r\nBgNVBAMMGm1ETCBJc3N1ZXIgRG9jdW1lbnQgU2lnbmVyMFkwEwYHKoZIzj0CAQYI\r\nKoZIzj0DAQcDQgAEyfmbtCsdQQ/4EDPg0Gt98gJYk1zkNOpGM9bHYEi49mcwEICy\r\nJ3iSfvHoo/fVvDNGnTzQPpeVtHYbGaZzvsIo+aOByjCBxzAfBgNVHSMEGDAWgBRy\r\nsvdvSdC8s91wzC1kmaM3gALo4zAdBgNVHQ4EFgQUXGTWuUtFOLFFNFJbOULu+PhB\r\ngn0wDgYDVR0PAQH/BAQDAgeAMCcGA1UdEgQgMB6GHGh0dHBzOi8vaWFjYS50ZW5h\r\nbnQxLndhbHQuaWQwFQYDVR0lAQH/BAswCQYHKIGMXQUBAjA1BgNVHR8ELjAsMCqg\r\nKKAmhiRodHRwczovL2NybC50ZW5hbnQxLndhbHQuaWQvaWFjYS5jcmwwCgYIKoZI\r\nzj0EAwIDSAAwRQIgBsfq7TjUHVPTUiTVtCDKQRCdXzp6/eRzqOKH7YaIo9cCIQDr\r\neIKTeCNxH5vJCeP4iGFDbaKWuIIGbO1/32bwV/CvOQ==\n-----END CERTIFICATE-----", "signer_jwk": { "kty": "EC", "crv": "P-256", "x": "yfmbtCsdQQ_4EDPg0Gt98gJYk1zkNOpGM9bHYEi49mc", "y": "MBCAsid4kn7x6KP31bwzRp080D6XlbR2Gxmmc77CKPk" } }, "errors": [], "execution_time": "PT0.004215542S" }, "mso_mdoc/issuer_signed_integrity": { "policy_executed": { "policy": "mso_mdoc/issuer_signed_integrity", "id": "mso_mdoc/issuer_signed_integrity", "description": "Verify issuer-verified data integrity" }, "success": true, "results": { "namespace": { "org.iso.18013.5.1": [ { "id": "family_name", "digest_id": 0, "value": "Doe", "value_type": "CborString", "random_hex": "736b060d21fa4e37f17b0b0568285add9d064ba883eb6c35", "serialized_hex": "a4686469676573744944006672616e646f6d5818736b060d21fa4e37f17b0b0568285add9d064ba883eb6c3571656c656d656e744964656e7469666965726b66616d696c795f6e616d656c656c656d656e7456616c756563446f65" }, { "id": "given_name", "digest_id": 1, "value": "John", "value_type": "CborString", "random_hex": "312e734c986fdf279c7d75130b0da4794d16d44bbb3cfd61", "serialized_hex": "a4686469676573744944016672616e646f6d5818312e734c986fdf279c7d75130b0da4794d16d44bbb3cfd6171656c656d656e744964656e7469666965726a676976656e5f6e616d656c656c656d656e7456616c7565644a6f686e" }, { "id": "birth_date", "digest_id": 2, "value": "1986-03-22", "value_type": "CborString", "random_hex": "e6864d13d13a9fb1a7fccb729a6330dec975bac5e79fa023", "serialized_hex": "a4686469676573744944026672616e646f6d5818e6864d13d13a9fb1a7fccb729a6330dec975bac5e79fa02371656c656d656e744964656e7469666965726a62697274685f646174656c656c656d656e7456616c7565d903ec6a313938362d30332d3232" } ] }, "matching_digest": { "org.iso.18013.5.1": [ "family_name", "given_name", "birth_date" ] } }, "errors": [], "execution_time": "PT0.003104834S" }, "mso_mdoc/mso": { "policy_executed": { "policy": "mso_mdoc/mso", "id": "mso_mdoc/mso", "description": "Verify MSO" }, "success": true, "results": { "signed": "2026-05-18T07:48:27.870905006Z", "valid_from": "2027-05-18T07:48:27.852289522Z" }, "errors": [], "execution_time": "PT0.004245834S" } } }, "presented_raw_data": { "vpToken": { "credential_1": [ "o2d2ZXJzaW9uYzEuMGlkb2N1bWVudHOBo2dkb2NUeXBldW9yZy5pc28uMTgwMTMuNS4xLm1ETGxpc3N1ZXJTaWduZWSiam5hbWVTcGFjZXOhcW9yZy5pc28uMTgwMTMuNS4xg9gYWFukaGRpZ2VzdElEAGZyYW5kb21YGHNrBg0h-k438XsLBWgoWt2dBkuog-tsNXFlbGVtZW50SWRlbnRpZmllcmtmYW1pbHlfbmFtZWxlbGVtZW50VmFsdWVjRG9l2BhYW6RoZGlnZXN0SUQBZnJhbmRvbVgYMS5zTJhv3yecfXUTCw2keU0W1Eu7PP1hcWVsZW1lbnRJZGVudGlmaWVyamdpdmVuX25hbWVsZWxlbWVudFZhbHVlZEpvaG7YGFhkpGhkaWdlc3RJRAJmcmFuZG9tWBjmhk0T0Tqfsaf8y3KaYzDeyXW6xeefoCNxZWxlbWVudElkZW50aWZpZXJqYmlydGhfZGF0ZWxlbGVtZW50VmFsdWXZA-xqMTk4Ni0wMy0yMmppc3N1ZXJBdXRohEOhASahGCFZAi8wggIrMIIB0aADAgECAhRED-gfURxpHvIfRtUe91K73mZpljAKBggqhkjOPQQDAjAsMQswCQYDVQQGEwJBVDEdMBsGA1UEAwwUbURMIElzc3VlciBJQUNBIFJvb3QwHhcNMjYwNTE4MDYxOTI2WhcNMjcwODE4MDYxOTI2WjAyMQswCQYDVQQGEwJBVDEjMCEGA1UEAwwabURMIElzc3VlciBEb2N1bWVudCBTaWduZXIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATJ-Zu0Kx1BD_gQM-DQa33yAliTXOQ06kYz1sdgSLj2ZzAQgLIneJJ-8eij99W8M0adPNA-l5W0dhsZpnO-wij5o4HKMIHHMB8GA1UdIwQYMBaAFHKy929J0Lyz3XDMLWSZozeAAujjMB0GA1UdDgQWBBRcZNa5S0U4sUU0Uls5Qu74-EGCfTAOBgNVHQ8BAf8EBAMCB4AwJwYDVR0SBCAwHoYcaHR0cHM6Ly9pYWNhLnRlbmFudDEud2FsdC5pZDAVBgNVHSUBAf8ECzAJBgcogYxdBQECMDUGA1UdHwQuMCwwKqAooCaGJGh0dHBzOi8vY3JsLnRlbmFudDEud2FsdC5pZC9pYWNhLmNybDAKBggqhkjOPQQDAgNIADBFAiAGx-rtONQdU9NSJNW0IMpBEJ1fOnr95HOo4ofthoij1wIhAOt4gpN4I3Efm8kJ4_iIYUNtopa4ggZs7X_fZvBX8K85WQKV2BhZApCmZ3ZlcnNpb25jMS4wb2RpZ2VzdEFsZ29yaXRobWdTSEEtMjU2bHZhbHVlRGlnZXN0c6Fxb3JnLmlzby4xODAxMy41LjGqAFgglIt2JC3rDbrr6azX5GWPMGyGqyD5gTnyASk37pDhZQYBWCCMQ7Ph45igVnLbvCtlSqLPlaMTXSk2kLoy4Mxn1x37kwJYIOjGjCHBlfvZi74lANZXAWt_FDdDf6NU591eYMGbFladA1ggcdl99l7hzcOFDYUeghGX0gDXYYgEN7WyRFrgSSFVvv4EWCCKwt6H37DEK8kJFcs__AiFZzLDc-4GPFKuYYEJzv_X1QVYIMybLDIPi3G7go7Bq4ggn0UYXEpmKMazxXZH25mnl0RQBlggofgVz7C7i4z0Iz8zrEPhNBRMlZcr4EhqQCR2pjo3qsoHWCDaJ4DszfMYhI0HfyOcSgMEgx_K12dx34khKW6ohKRhZghYIPTE7TeLsXVDSX9l3DTPB4zDhzF1WDTmCw3Ecxv1deNJCVggqueG-PUmdP8sJdsQ6XbuByQzAZXoRdwB-7dn0hxtkCdtZGV2aWNlS2V5SW5mb6FpZGV2aWNlS2V5owEBIAYhWCCPFDBFXu0dzdb8mQpWwNkvv8O0_jPI0m1DhivXPv4x3mdkb2NUeXBldW9yZy5pc28uMTgwMTMuNS4xLm1ETGx2YWxpZGl0eUluZm-jZnNpZ25lZMB4HjIwMjYtMDUtMThUMDc6NDg6MjcuODcwOTA1MDA2Wml2YWxpZEZyb23AeB4yMDI2LTA1LTE4VDA3OjQ4OjI3Ljg3MDkwNTAwNlpqdmFsaWRVbnRpbMB4HjIwMjctMDUtMThUMDc6NDg6MjcuODUyMjg5NTIyWlhAmRC5DQjAwaNaozniXwKmOoMscEyNwA65Y31zyoCJpiGiZxs-fk9bcsZnrDSu5ATPVEIKrf_4hjY8ODArK76q2WxkZXZpY2VTaWduZWSiam5hbWVTcGFjZXPYGEGgamRldmljZUF1dGihb2RldmljZVNpZ25hdHVyZYRDoQEnoPZYQGQUayxOBk2Wp7g7UhumPbtFSQ5h1h4jArb60UGZOsFMvG6MXHBBPd4JdOX40auhbmKNmBjinPfw96QN9XfV-Q9mc3RhdHVzAA" ] }, "state": "da82aed7-ea36-4303-9c4a-3ded03cdedb4" }, "presented_presentations": { "credential_1": { "type": "mso_mdoc", "format": "mso_mdoc", "mdoc": { "credentialData": { "docType": "org.iso.18013.5.1.mDL", "org.iso.18013.5.1": { "family_name": "Doe", "given_name": "John", "birth_date": "1986-03-22" } }, "signed": "o2d2ZXJzaW9uYzEuMGlkb2N1bWVudHOBo2dkb2NUeXBldW9yZy5pc28uMTgwMTMuNS4xLm1ETGxpc3N1ZXJTaWduZWSiam5hbWVTcGFjZXOhcW9yZy5pc28uMTgwMTMuNS4xg9gYWFukaGRpZ2VzdElEAGZyYW5kb21YGHNrBg0h-k438XsLBWgoWt2dBkuog-tsNXFlbGVtZW50SWRlbnRpZmllcmtmYW1pbHlfbmFtZWxlbGVtZW50VmFsdWVjRG9l2BhYW6RoZGlnZXN0SUQBZnJhbmRvbVgYMS5zTJhv3yecfXUTCw2keU0W1Eu7PP1hcWVsZW1lbnRJZGVudGlmaWVyamdpdmVuX25hbWVsZWxlbWVudFZhbHVlZEpvaG7YGFhkpGhkaWdlc3RJRAJmcmFuZG9tWBjmhk0T0Tqfsaf8y3KaYzDeyXW6xeefoCNxZWxlbWVudElkZW50aWZpZXJqYmlydGhfZGF0ZWxlbGVtZW50VmFsdWXZA-xqMTk4Ni0wMy0yMmppc3N1ZXJBdXRohEOhASahGCFZAi8wggIrMIIB0aADAgECAhRED-gfURxpHvIfRtUe91K73mZpljAKBggqhkjOPQQDAjAsMQswCQYDVQQGEwJBVDEdMBsGA1UEAwwUbURMIElzc3VlciBJQUNBIFJvb3QwHhcNMjYwNTE4MDYxOTI2WhcNMjcwODE4MDYxOTI2WjAyMQswCQYDVQQGEwJBVDEjMCEGA1UEAwwabURMIElzc3VlciBEb2N1bWVudCBTaWduZXIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATJ-Zu0Kx1BD_gQM-DQa33yAliTXOQ06kYz1sdgSLj2ZzAQgLIneJJ-8eij99W8M0adPNA-l5W0dhsZpnO-wij5o4HKMIHHMB8GA1UdIwQYMBaAFHKy929J0Lyz3XDMLWSZozeAAujjMB0GA1UdDgQWBBRcZNa5S0U4sUU0Uls5Qu74-EGCfTAOBgNVHQ8BAf8EBAMCB4AwJwYDVR0SBCAwHoYcaHR0cHM6Ly9pYWNhLnRlbmFudDEud2FsdC5pZDAVBgNVHSUBAf8ECzAJBgcogYxdBQECMDUGA1UdHwQuMCwwKqAooCaGJGh0dHBzOi8vY3JsLnRlbmFudDEud2FsdC5pZC9pYWNhLmNybDAKBggqhkjOPQQDAgNIADBFAiAGx-rtONQdU9NSJNW0IMpBEJ1fOnr95HOo4ofthoij1wIhAOt4gpN4I3Efm8kJ4_iIYUNtopa4ggZs7X_fZvBX8K85WQKV2BhZApCmZ3ZlcnNpb25jMS4wb2RpZ2VzdEFsZ29yaXRobWdTSEEtMjU2bHZhbHVlRGlnZXN0c6Fxb3JnLmlzby4xODAxMy41LjGqAFgglIt2JC3rDbrr6azX5GWPMGyGqyD5gTnyASk37pDhZQYBWCCMQ7Ph45igVnLbvCtlSqLPlaMTXSk2kLoy4Mxn1x37kwJYIOjGjCHBlfvZi74lANZXAWt_FDdDf6NU591eYMGbFladA1ggcdl99l7hzcOFDYUeghGX0gDXYYgEN7WyRFrgSSFVvv4EWCCKwt6H37DEK8kJFcs__AiFZzLDc-4GPFKuYYEJzv_X1QVYIMybLDIPi3G7go7Bq4ggn0UYXEpmKMazxXZH25mnl0RQBlggofgVz7C7i4z0Iz8zrEPhNBRMlZcr4EhqQCR2pjo3qsoHWCDaJ4DszfMYhI0HfyOcSgMEgx_K12dx34khKW6ohKRhZghYIPTE7TeLsXVDSX9l3DTPB4zDhzF1WDTmCw3Ecxv1deNJCVggqueG-PUmdP8sJdsQ6XbuByQzAZXoRdwB-7dn0hxtkCdtZGV2aWNlS2V5SW5mb6FpZGV2aWNlS2V5owEBIAYhWCCPFDBFXu0dzdb8mQpWwNkvv8O0_jPI0m1DhivXPv4x3mdkb2NUeXBldW9yZy5pc28uMTgwMTMuNS4xLm1ETGx2YWxpZGl0eUluZm-jZnNpZ25lZMB4HjIwMjYtMDUtMThUMDc6NDg6MjcuODcwOTA1MDA2Wml2YWxpZEZyb23AeB4yMDI2LTA1LTE4VDA3OjQ4OjI3Ljg3MDkwNTAwNlpqdmFsaWRVbnRpbMB4HjIwMjctMDUtMThUMDc6NDg6MjcuODUyMjg5NTIyWlhAmRC5DQjAwaNaozniXwKmOoMscEyNwA65Y31zyoCJpiGiZxs-fk9bcsZnrDSu5ATPVEIKrf_4hjY8ODArK76q2WxkZXZpY2VTaWduZWSiam5hbWVTcGFjZXPYGEGgamRldmljZUF1dGihb2RldmljZVNpZ25hdHVyZYRDoQEnoPZYQGQUayxOBk2Wp7g7UhumPbtFSQ5h1h4jArb60UGZOsFMvG6MXHBBPd4JdOX40auhbmKNmBjinPfw96QN9XfV-Q9mc3RhdHVzAA", "docType": "org.iso.18013.5.1.mDL", "signature": { "type": "signature-cose", "signerKey": { "type": "jwk", "jwk": { "kty": "EC", "crv": "P-256", "x": "yfmbtCsdQQ_4EDPg0Gt98gJYk1zkNOpGM9bHYEi49mc", "y": "MBCAsid4kn7x6KP31bwzRp080D6XlbR2Gxmmc77CKPk" } }, "x5cList": [ "MIICKzCCAdGgAwIBAgIURA/oH1EcaR7yH0bVHvdSu95maZYwCgYIKoZIzj0EAwIwLDELMAkGA1UEBhMCQVQxHTAbBgNVBAMMFG1ETCBJc3N1ZXIgSUFDQSBSb290MB4XDTI2MDUxODA2MTkyNloXDTI3MDgxODA2MTkyNlowMjELMAkGA1UEBhMCQVQxIzAhBgNVBAMMGm1ETCBJc3N1ZXIgRG9jdW1lbnQgU2lnbmVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEyfmbtCsdQQ/4EDPg0Gt98gJYk1zkNOpGM9bHYEi49mcwEICyJ3iSfvHoo/fVvDNGnTzQPpeVtHYbGaZzvsIo+aOByjCBxzAfBgNVHSMEGDAWgBRysvdvSdC8s91wzC1kmaM3gALo4zAdBgNVHQ4EFgQUXGTWuUtFOLFFNFJbOULu+PhBgn0wDgYDVR0PAQH/BAQDAgeAMCcGA1UdEgQgMB6GHGh0dHBzOi8vaWFjYS50ZW5hbnQxLndhbHQuaWQwFQYDVR0lAQH/BAswCQYHKIGMXQUBAjA1BgNVHR8ELjAsMCqgKKAmhiRodHRwczovL2NybC50ZW5hbnQxLndhbHQuaWQvaWFjYS5jcmwwCgYIKoZIzj0EAwIDSAAwRQIgBsfq7TjUHVPTUiTVtCDKQRCdXzp6/eRzqOKH7YaIo9cCIQDreIKTeCNxH5vJCeP4iGFDbaKWuIIGbO1/32bwV/CvOQ==" ] }, "format": "mso_mdoc", "mso": { "version": "1.0", "digestAlgorithm": "SHA-256", "valueDigests": { "org.iso.18013.5.1": { "0": [ -108, -117, 118, 36, 45, -21, 13, -70, -21, -23, -84, -41, -28, 101, -113, 48, 108, -122, -85, 32, -7, -127, 57, -14, 1, 41, 55, -18, -112, -31, 101, 6 ], "1": [ -116, 67, -77, -31, -29, -104, -96, 86, 114, -37, -68, 43, 101, 74, -94, -49, -107, -93, 19, 93, 41, 54, -112, -70, 50, -32, -52, 103, -41, 29, -5, -109 ], "2": [ -24, -58, -116, 33, -63, -107, -5, -39, -117, -66, 37, 0, -42, 87, 1, 107, 127, 20, 55, 67, 127, -93, 84, -25, -35, 94, 96, -63, -101, 22, 86, -99 ], "3": [ 113, -39, 125, -10, 94, -31, -51, -61, -123, 13, -123, 30, -126, 17, -105, -46, 0, -41, 97, -120, 4, 55, -75, -78, 68, 90, -32, 73, 33, 85, -66, -2 ], "4": [ -118, -62, -34, -121, -33, -80, -60, 43, -55, 9, 21, -53, 63, -4, 8, -123, 103, 50, -61, 115, -18, 6, 60, 82, -82, 97, -127, 9, -50, -1, -41, -43 ], "5": [ -52, -101, 44, 50, 15, -117, 113, -69, -126, -114, -63, -85, -120, 32, -97, 69, 24, 92, 74, 102, 40, -58, -77, -59, 118, 71, -37, -103, -89, -105, 68, 80 ], "6": [ -95, -8, 21, -49, -80, -69, -117, -116, -12, 35, 63, 51, -84, 67, -31, 52, 20, 76, -107, -105, 43, -32, 72, 106, 64, 36, 118, -90, 58, 55, -86, -54 ], "7": [ -38, 39, -128, -20, -51, -13, 24, -124, -115, 7, 127, 35, -100, 74, 3, 4, -125, 31, -54, -41, 103, 113, -33, -119, 33, 41, 110, -88, -124, -92, 97, 102 ], "8": [ -12, -60, -19, 55, -117, -79, 117, 67, 73, 127, 101, -36, 52, -49, 7, -116, -61, -121, 49, 117, 88, 52, -26, 11, 13, -60, 115, 27, -11, 117, -29, 73 ], "9": [ -86, -25, -122, -8, -11, 38, 116, -1, 44, 37, -37, 16, -23, 118, -18, 7, 36, 51, 1, -107, -24, 69, -36, 1, -5, -73, 103, -46, 28, 109, -112, 39 ] } }, "deviceKeyInfo": { "deviceKey": { "kty": 1, "crv": 6, "x": [ -113, 20, 48, 69, 94, -19, 29, -51, -42, -4, -103, 10, 86, -64, -39, 47, -65, -61, -76, -2, 51, -56, -46, 109, 67, -122, 43, -41, 62, -2, 49, -34 ] } }, "docType": "org.iso.18013.5.1.mDL", "validityInfo": { "signed": "2026-05-18T07:48:27.870905006Z", "validFrom": "2026-05-18T07:48:27.870905006Z", "validUntil": "2027-05-18T07:48:27.852289522Z" } } } } }, "presented_credentials": { "credential_1": [ { "type": "vc-mdocs", "credentialData": { "docType": "org.iso.18013.5.1.mDL", "org.iso.18013.5.1": { "family_name": "Doe", "given_name": "John", "birth_date": "1986-03-22" } }, "signed": "o2d2ZXJzaW9uYzEuMGlkb2N1bWVudHOBo2dkb2NUeXBldW9yZy5pc28uMTgwMTMuNS4xLm1ETGxpc3N1ZXJTaWduZWSiam5hbWVTcGFjZXOhcW9yZy5pc28uMTgwMTMuNS4xg9gYWFukaGRpZ2VzdElEAGZyYW5kb21YGHNrBg0h-k438XsLBWgoWt2dBkuog-tsNXFlbGVtZW50SWRlbnRpZmllcmtmYW1pbHlfbmFtZWxlbGVtZW50VmFsdWVjRG9l2BhYW6RoZGlnZXN0SUQBZnJhbmRvbVgYMS5zTJhv3yecfXUTCw2keU0W1Eu7PP1hcWVsZW1lbnRJZGVudGlmaWVyamdpdmVuX25hbWVsZWxlbWVudFZhbHVlZEpvaG7YGFhkpGhkaWdlc3RJRAJmcmFuZG9tWBjmhk0T0Tqfsaf8y3KaYzDeyXW6xeefoCNxZWxlbWVudElkZW50aWZpZXJqYmlydGhfZGF0ZWxlbGVtZW50VmFsdWXZA-xqMTk4Ni0wMy0yMmppc3N1ZXJBdXRohEOhASahGCFZAi8wggIrMIIB0aADAgECAhRED-gfURxpHvIfRtUe91K73mZpljAKBggqhkjOPQQDAjAsMQswCQYDVQQGEwJBVDEdMBsGA1UEAwwUbURMIElzc3VlciBJQUNBIFJvb3QwHhcNMjYwNTE4MDYxOTI2WhcNMjcwODE4MDYxOTI2WjAyMQswCQYDVQQGEwJBVDEjMCEGA1UEAwwabURMIElzc3VlciBEb2N1bWVudCBTaWduZXIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATJ-Zu0Kx1BD_gQM-DQa33yAliTXOQ06kYz1sdgSLj2ZzAQgLIneJJ-8eij99W8M0adPNA-l5W0dhsZpnO-wij5o4HKMIHHMB8GA1UdIwQYMBaAFHKy929J0Lyz3XDMLWSZozeAAujjMB0GA1UdDgQWBBRcZNa5S0U4sUU0Uls5Qu74-EGCfTAOBgNVHQ8BAf8EBAMCB4AwJwYDVR0SBCAwHoYcaHR0cHM6Ly9pYWNhLnRlbmFudDEud2FsdC5pZDAVBgNVHSUBAf8ECzAJBgcogYxdBQECMDUGA1UdHwQuMCwwKqAooCaGJGh0dHBzOi8vY3JsLnRlbmFudDEud2FsdC5pZC9pYWNhLmNybDAKBggqhkjOPQQDAgNIADBFAiAGx-rtONQdU9NSJNW0IMpBEJ1fOnr95HOo4ofthoij1wIhAOt4gpN4I3Efm8kJ4_iIYUNtopa4ggZs7X_fZvBX8K85WQKV2BhZApCmZ3ZlcnNpb25jMS4wb2RpZ2VzdEFsZ29yaXRobWdTSEEtMjU2bHZhbHVlRGlnZXN0c6Fxb3JnLmlzby4xODAxMy41LjGqAFgglIt2JC3rDbrr6azX5GWPMGyGqyD5gTnyASk37pDhZQYBWCCMQ7Ph45igVnLbvCtlSqLPlaMTXSk2kLoy4Mxn1x37kwJYIOjGjCHBlfvZi74lANZXAWt_FDdDf6NU591eYMGbFladA1ggcdl99l7hzcOFDYUeghGX0gDXYYgEN7WyRFrgSSFVvv4EWCCKwt6H37DEK8kJFcs__AiFZzLDc-4GPFKuYYEJzv_X1QVYIMybLDIPi3G7go7Bq4ggn0UYXEpmKMazxXZH25mnl0RQBlggofgVz7C7i4z0Iz8zrEPhNBRMlZcr4EhqQCR2pjo3qsoHWCDaJ4DszfMYhI0HfyOcSgMEgx_K12dx34khKW6ohKRhZghYIPTE7TeLsXVDSX9l3DTPB4zDhzF1WDTmCw3Ecxv1deNJCVggqueG-PUmdP8sJdsQ6XbuByQzAZXoRdwB-7dn0hxtkCdtZGV2aWNlS2V5SW5mb6FpZGV2aWNlS2V5owEBIAYhWCCPFDBFXu0dzdb8mQpWwNkvv8O0_jPI0m1DhivXPv4x3mdkb2NUeXBldW9yZy5pc28uMTgwMTMuNS4xLm1ETGx2YWxpZGl0eUluZm-jZnNpZ25lZMB4HjIwMjYtMDUtMThUMDc6NDg6MjcuODcwOTA1MDA2Wml2YWxpZEZyb23AeB4yMDI2LTA1LTE4VDA3OjQ4OjI3Ljg3MDkwNTAwNlpqdmFsaWRVbnRpbMB4HjIwMjctMDUtMThUMDc6NDg6MjcuODUyMjg5NTIyWlhAmRC5DQjAwaNaozniXwKmOoMscEyNwA65Y31zyoCJpiGiZxs-fk9bcsZnrDSu5ATPVEIKrf_4hjY8ODArK76q2WxkZXZpY2VTaWduZWSiam5hbWVTcGFjZXPYGEGgamRldmljZUF1dGihb2RldmljZVNpZ25hdHVyZYRDoQEnoPZYQGQUayxOBk2Wp7g7UhumPbtFSQ5h1h4jArb60UGZOsFMvG6MXHBBPd4JdOX40auhbmKNmBjinPfw96QN9XfV-Q9mc3RhdHVzAA", "docType": "org.iso.18013.5.1.mDL", "signature": { "type": "signature-cose", "signerKey": { "type": "jwk", "jwk": { "kty": "EC", "crv": "P-256", "x": "yfmbtCsdQQ_4EDPg0Gt98gJYk1zkNOpGM9bHYEi49mc", "y": "MBCAsid4kn7x6KP31bwzRp080D6XlbR2Gxmmc77CKPk" } }, "x5cList": [ "MIICKzCCAdGgAwIBAgIURA/oH1EcaR7yH0bVHvdSu95maZYwCgYIKoZIzj0EAwIwLDELMAkGA1UEBhMCQVQxHTAbBgNVBAMMFG1ETCBJc3N1ZXIgSUFDQSBSb290MB4XDTI2MDUxODA2MTkyNloXDTI3MDgxODA2MTkyNlowMjELMAkGA1UEBhMCQVQxIzAhBgNVBAMMGm1ETCBJc3N1ZXIgRG9jdW1lbnQgU2lnbmVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEyfmbtCsdQQ/4EDPg0Gt98gJYk1zkNOpGM9bHYEi49mcwEICyJ3iSfvHoo/fVvDNGnTzQPpeVtHYbGaZzvsIo+aOByjCBxzAfBgNVHSMEGDAWgBRysvdvSdC8s91wzC1kmaM3gALo4zAdBgNVHQ4EFgQUXGTWuUtFOLFFNFJbOULu+PhBgn0wDgYDVR0PAQH/BAQDAgeAMCcGA1UdEgQgMB6GHGh0dHBzOi8vaWFjYS50ZW5hbnQxLndhbHQuaWQwFQYDVR0lAQH/BAswCQYHKIGMXQUBAjA1BgNVHR8ELjAsMCqgKKAmhiRodHRwczovL2NybC50ZW5hbnQxLndhbHQuaWQvaWFjYS5jcmwwCgYIKoZIzj0EAwIDSAAwRQIgBsfq7TjUHVPTUiTVtCDKQRCdXzp6/eRzqOKH7YaIo9cCIQDreIKTeCNxH5vJCeP4iGFDbaKWuIIGbO1/32bwV/CvOQ==" ] }, "format": "mso_mdoc", "mso": { "version": "1.0", "digestAlgorithm": "SHA-256", "valueDigests": { "org.iso.18013.5.1": { "0": [ -108, -117, 118, 36, 45, -21, 13, -70, -21, -23, -84, -41, -28, 101, -113, 48, 108, -122, -85, 32, -7, -127, 57, -14, 1, 41, 55, -18, -112, -31, 101, 6 ], "1": [ -116, 67, -77, -31, -29, -104, -96, 86, 114, -37, -68, 43, 101, 74, -94, -49, -107, -93, 19, 93, 41, 54, -112, -70, 50, -32, -52, 103, -41, 29, -5, -109 ], "2": [ -24, -58, -116, 33, -63, -107, -5, -39, -117, -66, 37, 0, -42, 87, 1, 107, 127, 20, 55, 67, 127, -93, 84, -25, -35, 94, 96, -63, -101, 22, 86, -99 ], "3": [ 113, -39, 125, -10, 94, -31, -51, -61, -123, 13, -123, 30, -126, 17, -105, -46, 0, -41, 97, -120, 4, 55, -75, -78, 68, 90, -32, 73, 33, 85, -66, -2 ], "4": [ -118, -62, -34, -121, -33, -80, -60, 43, -55, 9, 21, -53, 63, -4, 8, -123, 103, 50, -61, 115, -18, 6, 60, 82, -82, 97, -127, 9, -50, -1, -41, -43 ], "5": [ -52, -101, 44, 50, 15, -117, 113, -69, -126, -114, -63, -85, -120, 32, -97, 69, 24, 92, 74, 102, 40, -58, -77, -59, 118, 71, -37, -103, -89, -105, 68, 80 ], "6": [ -95, -8, 21, -49, -80, -69, -117, -116, -12, 35, 63, 51, -84, 67, -31, 52, 20, 76, -107, -105, 43, -32, 72, 106, 64, 36, 118, -90, 58, 55, -86, -54 ], "7": [ -38, 39, -128, -20, -51, -13, 24, -124, -115, 7, 127, 35, -100, 74, 3, 4, -125, 31, -54, -41, 103, 113, -33, -119, 33, 41, 110, -88, -124, -92, 97, 102 ], "8": [ -12, -60, -19, 55, -117, -79, 117, 67, 73, 127, 101, -36, 52, -49, 7, -116, -61, -121, 49, 117, 88, 52, -26, 11, 13, -60, 115, 27, -11, 117, -29, 73 ], "9": [ -86, -25, -122, -8, -11, 38, 116, -1, 44, 37, -37, 16, -23, 118, -18, 7, 36, 51, 1, -107, -24, 69, -36, 1, -5, -73, 103, -46, 28, 109, -112, 39 ] } }, "deviceKeyInfo": { "deviceKey": { "kty": 1, "crv": 6, "x": [ -113, 20, 48, 69, 94, -19, 29, -51, -42, -4, -103, 10, 86, -64, -39, 47, -65, -61, -76, -2, 51, -56, -46, 109, 67, -122, 43, -41, 62, -2, 49, -34 ] } }, "docType": "org.iso.18013.5.1.mDL", "validityInfo": { "signed": "2026-05-18T07:48:27.870905006Z", "validFrom": "2026-05-18T07:48:27.870905006Z", "validUntil": "2027-05-18T07:48:27.852289522Z" } } } ] } } ``` **Key Response Fields:** - **id**: _String_ - The verification session ID - **setup**: _Object_ - The complete session configuration that was used to create this verification session (the `VerificationSessionSetup` from the create request) - **creationDate**: _String_ - ISO 8601 timestamp when the session was created - **expirationDate**: _String_ - ISO 8601 timestamp when the session expires (default: 10 minutes after creation). Session will expire if left unused (no presentation pushed). Once a presentation is received, the session is no longer eligible for expiry. - **status**: _String_ - The current status of the verification session. Possible values: - `"UNKNOWN"` — Session ended up in unknown flow (should be avoided) - `"ACTIVE"` — Session was created and is active (can be used) - `"UNUSED"` — Session was not used yet, but not expired (can be used) - `"IN_USE"` — AuthorizationRequest was requested by wallet - `"VALIDATING_RECEIVED_REQUEST"` — Checking if received presentation will be processed - `"PROCESSING_FLOW"` — Received presentation is being processed (validation + policy execution) - `"EXPIRED"` — Verification request expired without being utilized - `"SUCCESSFUL"` — Verification completed fully successfully (all validation & policies passed) - `"FAILED"` — Verification was unsuccessful (presentation validation or policies failed) - **attempted**: _Boolean_ - Whether the wallet has attempted to respond to the verification request - **reattemptable**: _Boolean_ - Whether the user can retry this verification session if it failed - **policies**: _Object_ - The policies configured for this session: - **vp_policies**: _Object_ (optional) - Policies to run on presentations - **vc_policies**: _Array_ (optional) - Policies to run on credentials - **specific_vc_policies**: _Object_ (optional) - Policies to run on specific credential IDs - **policy_results**: _Object_ (optional) - Detailed results for all policies applied to the verification (only present after presentation is received): - **vp_policies**: _Object_ - Results for presentation-level policies (audience check, nonce check, envelope signature), organized by credential ID. Structure: `{ "credential_id": { "policy_name": { ... } } }` - **vc_policies**: _Array_ - Results for credential-level policies (signature, expiry, schema, etc.). Each item contains `policy`, `success`, and `result`. - **specific_vc_policies**: _Object_ - Results for credential-specific policies - **overallSuccess**: _Boolean_ (computed) - Whether all policies passed - **redirects**: _Object_ (optional) - Success and error redirect URIs if configured **Note:** Instead of polling the session status, you can use **callbacks** or **Server Sent Events (SSE)** to be notified when the verification completes. See [Callbacks & SSE](https://docs.walt.id/community-stack/verifier2/callback-and-sse.md) for details. --- ## Understanding Flow Types and Core Concepts Now that you've seen verification in action, let's understand the underlying structure. ### Verification Session Structure A `VerificationSession` defines the full lifecycle: authorization request → user presentation → policy validation → final result. Every verification request follows this structure: ```json { "flow_type": "cross_device | same_device | dc_api", "core_flow": { /* common to all flows */ }, "url_config": { /* specific to cross_device and same_device */ }, "redirects": { /* optional */ } } ``` **Why this structure?** - **flow_type** determines how the credential request is delivered (QR code vs deep link vs browser API) - **core_flow** contains the credential query and policies (same regardless of delivery method) - **url_config** and **redirects** are flow-specific attributes This separation makes it easy to switch between flows without changing your credential requirements. ### Flow Type Comparison | Flow Type | Use Case | Required Attributes | User Experience | |-----------|----------|---------------------|-----------------| | `cross_device` | User scans QR code from desktop | None beyond core flow | Desktop shows QR → User scans with phone → Phone wallet opens | | `same_device` | User clicks link on mobile | `redirects` (success/error URLs) | Browser redirects to wallet → Wallet opens → User returns to browser | | `dc_api` | Browser-native credential exchange | `expectedOrigins` (array of allowed origins) | Browser shows native credential picker → User selects → Instant return | --- ## Configuration Defaults Many session parameters can be configured globally in `verifier-service.conf` and overridden per-request when needed. | Parameter | Config File Location | Request Override Path | When to Override | |-----------|---------------------|----------------------|------------------| | `urlPrefix` | `verifier-service.conf` → `urlPrefix` | `url_config.url_prefix` | Testing with ngrok, different environments per session | | `urlHost` | `verifier-service.conf` → `urlHost` | `url_config.url_host` | Custom authorization endpoints, conformance testing | | `clientId` | `verifier-service.conf` → `clientId` | `core_flow.clientId` | Different verifier identities per use case | | `clientMetadata` | `verifier-service.conf` → `clientMetadata` | `core_flow.client_metadata` | Session-specific branding, multi-tenant scenarios | | Signing key | `verifier-service.conf` → `key` | `core_flow.key` | Different signing keys per session (advanced) | | Certificate chain | `verifier-service.conf` → `x5c` | `core_flow.x5c` | Different certificate chains per session (x509 flows) | **Note:** **Best Practice:** Set defaults in `verifier-service.conf` for your primary use case, and override per-request only when necessary. This keeps your API calls clean and reduces duplication. See [Verifier Service Configuration](https://docs.walt.id/community-stack/verifier2/configurations/config-files/verifier-service.md) for detailed configuration documentation. --- ## Advanced Options ### Requesting Specific Claims Mobile driver's licenses support selective disclosure. You can request specific claims using the `claims` array with namespace-qualified paths: ```json { "flow_type": "cross_device", "core_flow": { "dcql_query": { "credentials": [ { "id": "credential_1", "format": "mso_mdoc", "meta": { "doctype_value": "org.iso.18013.5.1.mDL" }, "claims": [ { "path": ["org.iso.18013.5.1", "family_name"] }, { "path": ["org.iso.18013.5.1", "given_name"] }, { "path": ["org.iso.18013.5.1", "birth_date"] }, { "path": ["org.iso.18013.5.1", "portrait"] } ] } ] } } } ``` This requests only specific claims from the mDL. The user's wallet will only disclose these specific data elements. ### Claim Values You can also request claims with specific values (for filtering): ```json { "claims": [ { "path": ["org.iso.18013.5.1", "issuing_country"], "values": ["US", "CA"] } ] } ``` This requests only mDLs issued by US or Canadian authorities. ### Custom Policies Beyond signature verification, you can apply additional policies: ```json "policies": { "vc_policies": [ { "policy": "signature" }, { "policy": "expired" }, { "policy": "not-before" } ] } ``` See [Policies](https://docs.walt.id/community-stack/verifier2/policies/overview.md) for the complete list and configuration options. ### VICAL Trust Validation For mDL credentials, you can apply the VICAL (Verifiable Issuer Certificate Authority List) policy for trust validation: ```json "policies": { "vc_policies": [ { "policy": "signature" }, { "policy": "vical", "vical": "", "enableDocumentTypeValidation": false, "enableTrustedChainRoot": false, "enableSystemTrustAnchors": false, "enableRevocation": false } ] } ``` See the [VICAL policy documentation](https://docs.walt.id/community-stack/verifier2/policies/available-policies.md#vical) for detailed parameter descriptions and configuration examples. ### Transaction Data Authorization Verifier2 supports OpenID4VP `transaction_data` to bind credential presentations to specific transactions (e.g., payment authorization, account access). For mDL credentials, the wallet embeds transaction data hashes in the DeviceSigned namespaces, and the verifier evaluates the `mso_mdoc/transaction-data-hash-check` policy automatically to validate the binding. #### Example Request ```json { "flow_type": "cross_device", "core_flow": { "dcql_query": { "credentials": [ { "id": "mdl", "format": "mso_mdoc", "meta": { "doctype_value": "org.iso.18013.5.1.mDL" }, "claims": [ { "path": ["org.iso.18013.5.1", "family_name"] }, { "path": ["org.iso.18013.5.1", "given_name"] } ] } ] } }, "openid": { "transactionData": [ { "type": "org.waltid.transaction-data.payment-authorization", "credential_ids": ["mdl"], "require_cryptographic_holder_binding": true, "transaction_data_hashes_alg": ["sha-256"], "amount": "42.00", "currency": "EUR", "payee": "ACME Corp" } ] } } ``` #### Parameters - **type**: _String_ (required) - The transaction data type identifier. Must match a profile registered via the [`transaction-data-profiles` configuration](https://docs.walt.id/community-stack/verifier2/configurations/config-files/transaction-data-profiles.md). Common types: - `"org.waltid.transaction-data.payment-authorization"` — For payment transactions (fields: `amount`, `currency`, `payee`) - `"org.waltid.transaction-data.account-access"` — For account access requests (fields: `account_identifier`, `access_scope`) - **credential_ids**: _Array of Strings_ (required) - References one or more credential IDs from your DCQL query (`credentials[].id`). The wallet will bind this transaction data to presentations of these credentials. For mDL credentials, use the credential ID you assigned (e.g., `"mdl"`). - **require_cryptographic_holder_binding**: _Boolean_ (optional) - If provided, it must be set to `true`. Transaction data requires cryptographic holder binding (KB-JWT for SD-JWT VC, DeviceAuth for mDL). Requests with `false` are rejected per [OpenID4VP §B.3.3](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html#appendix-B.3.3). - **transaction_data_hashes_alg**: _Array of Strings_ (optional) — Hash algorithms the wallet uses to bind transaction data. e.g. `["sha-256"]`. If omitted "sha-256" is used. **Type-Specific Parameters:** For `"org.waltid.transaction-data.payment-authorization"`: - **amount**: _String_ (required) - Payment amount (e.g., `"42.00"`) - **currency**: _String_ (required) - Currency code (e.g., `"EUR"`, `"USD"`) - **payee**: _String_ (required) - Payee identifier (e.g., `"ACME Corp"`) For `"org.waltid.transaction-data.account-access"`: - **account_identifier**: _String_ (required) - Account identifier (e.g., account number, user ID) - **access_scope**: _String_ (required) - Scope of access being requested (e.g., `"read"`, `"write"`, `"admin"`) **Note:** **Encoding Behavior:** You pass transaction data entries as plain JSON objects in your verification request. The verifier service automatically base64url-encodes each entry before placing it in the authorization request sent to the wallet. Do not base64-encode the entries yourself. #### Constraints - `transaction_data` requires a `dcql_query`. Requests without one are rejected with `invalid_request`. - Each entry's `credential_ids` must reference DCQL credential query ids declared in the same request. - Each referenced credential query must use a transaction-data-capable format: `dc+sd-jwt` or `mso_mdoc`. - Transaction data with `require_cryptographic_holder_binding: false` is rejected. See [OpenID4VP §B.3.3](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html#appendix-B.3.3). - Transaction-data types are configurable via profiles (e.g. `payment-authorization`, `account-access`). Custom types can be added through the [`transaction-data-profiles` configuration](https://docs.walt.id/community-stack/verifier2/configurations/config-files/transaction-data-profiles.md), and the active profiles are exposed via `GET /transaction-data-profiles`. - Available on `cross_device`, `same_device`, and `dc_api` (Annex D) flows via the `openid.transactionData` field. **Note:** **Automatic Policy Enforcement:** When `transactionData` is present, the verifier automatically adds the appropriate transaction-data hash-check policy (`dc+sd-jwt/transaction-data-hash-check` for SD-JWT, `mso_mdoc/transaction-data-hash-check` for mDL) to ensure the wallet properly binds the transaction data. This happens even if you specify custom `vp_policies`. Your custom policies are preserved; the hash-check policy is added on top. #### Discovery & Configuration Transaction data types are configurable via profiles. To see which transaction data types your verifier instance supports: ```bash curl -X 'GET' \ 'http://localhost:7003/transaction-data-profiles' \ -H 'accept: application/json' ``` Custom transaction data types can be added through the [`transaction-data-profiles` configuration](https://docs.walt.id/community-stack/verifier2/configurations/config-files/transaction-data-profiles.md). ### Signed Requests Sign the authorization request JWT using a verifier signing key. This is required for: - **HAIP profile** compliance - **DC API Annex C** (ISO 18013-7) flows Enable by setting `signedRequest: true` in the request body. **Key Configuration Required:** Signed requests require a signing key (`key`) to be configured. You can either: - Set the `key` parameter in the [`verifier-service.conf`](https://docs.walt.id/community-stack/verifier2/configurations/config-files/verifier-service.md#key-optional) as a service-wide default - Override or provide the `key` directly in the verification request body **Certificate Chain (`x5c`) Required for:** - X.509-based client authentication when using `x509_san_dns:` or `x509_hash:` as the `clientId` format - Signed Annex C (ISO 18013-7) requests The `x5c` parameter can be configured the same way as `key` — as a service-wide default or per-session override. ### Encrypted Responses The wallet can encrypt the VP token response. Enable by setting `encryptedResponse: true` in the request body. **Key Configuration Required:** Encrypted responses require a key (`key`) for decrypting the VP token response. Configure it via [`verifier-service.conf`](https://docs.walt.id/community-stack/verifier2/configurations/config-files/verifier-service.md#key-optional) or provide it in the verification request body. --- ## Next Steps - **Apply custom policies** — See [Policies](https://docs.walt.id/community-stack/verifier2/policies/overview.md) for validation rules beyond signature verification - **Set up notifications** — Use [Callbacks & SSE](https://docs.walt.id/community-stack/verifier2/callback-and-sse.md) instead of polling for session status - **Verify other credential types** — Try [W3C VC](https://docs.walt.id/community-stack/verifier2/credential-verification/vc-oid4vp.md) or [SD-JWT VC](https://docs.walt.id/community-stack/verifier2/credential-verification/sd-jwt-vc-oid4vp.md) - **Use DC API** — Try the [Digital Credentials API](https://docs.walt.id/community-stack/verifier2/credential-verification/dc-api.md) for browser-native credential exchange -------------------------------------------------------------------------------- PAGE: Verifying Verifiable Credentials via Digital Credentials API URL: https://docs.walt.id/community-stack/verifier2/credential-verification/dc-api RAW: https://docs.walt.id/community-stack/verifier2/credential-verification/dc-api.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Verifying Verifiable Credentials via Digital Credentials API This guide shows you how to verify credentials using the browser-native Digital Credentials API (DC API). The DC API enables secure, native credential sharing directly through the user's browser or operating system, providing a seamless user experience without redirects or QR codes. We recommend checking out our [Digital Credential Button project](https://github.com/walt-id/waltid-identity/tree/main/waltid-applications/waltid-digital-credentials), which provides a reference for how to invoke the Browser-based DC API. It contains up to date tutorials for setting up and end to end demo utilizing the DC API. This guide will just focus on how to configure the Verifier API for this use case. ## Prerequisites Before you begin, ensure you have: - **Verifier2 API running** — Follow the [Getting Started](https://docs.walt.id/community-stack/verifier2/getting-started.md) or [Setup](https://docs.walt.id/community-stack/verifier2/setup.md) guide - **DC API-compatible browser** — Chrome 121+, Safari 18+, or Edge 121+ (desktop or mobile) - **HTTPS enabled** — The DC API only works over secure connections (required for production; localhost is allowed for development) - **Test credentials** — Use the [walt.id Wallet](https://docs.walt.id/community-stack/wallet/getting-started.md) or any DC API-compatible wallet for testing - **X.509 certificate and key** (for signed requests) — Required for HAIP profile and ISO 18013-7 Annex C flows **Note:** **Local Development:** When running locally via Docker, the API is available at `http://localhost:7003` (standalone) or `http://localhost:7004` (Docker Compose). The DC API will work with localhost URLs during development. ## Understanding DC API Protocols The Verifier2 API supports two DC API protocols: - Over 18013-7 (Annex C) [using flow_type = "dc_api-annex-c"] - Over OpenID4VP 1.0 (Annex D) [using flow_type = "dc_api"] For each of these, we support both same device and cross device flows. You can find many examples in the Digital Credential Button project as well as the swagger documentation for the Verifier API. ### Step 1: Preparing the Verification Session This example requests an mDL credential using the ISO 18013-7 Annex C protocol with signed requests. The user will see a native browser credential picker to select and share their credential. **Option: CURL** Endpoint: `/verification-session/create` | [API Reference](https://verifier2.portal.test.waltid.cloud/swagger/index.html#/Verification%20Session%20Management/post_verification_session_create) **Example Request** ```bash curl -X 'POST' \ 'http://localhost:7003/verification-session/create' \ -H 'accept: */*' \ -H 'Content-Type: application/json' \ -d '{ "flow_type": "dc_api", "core": { "dcql_query": { "credentials": [ { "id": "my_mdl", "format": "mso_mdoc", "meta": { "doctype_value": "org.iso.18013.5.1.mDL" }, "claims": [ { "path": [ "org.iso.18013.5.1", "family_name" ] }, { "path": [ "org.iso.18013.5.1", "given_name" ] }, { "path": [ "org.iso.18013.5.1", "age_over_21" ] } ] } ] }, "signed_request": true, "encrypted_response": true, "clientId": "x509_hash:abc-xyz-base64url-sha256-hash-of-der-x509-leaf", "key": { "type": "jwk", "jwk": { "kty": "EC", "d": "AEb4k1BeTR9xt2NxYZggdzkFLLUkhyyWvyUOq3qSiwA", "crv": "P-256", "kid": "_nd-T2YRYLSmuKkJZlRI641zrCIJLTpiHeqMwXuvdug", "x": "G_TgBc0BkmMipiQ_6gkamIn3mmp7hcTrZuyrLTmknP0", "y": "VkRMZdXYXSMff5AJLrnHiN0x5MV6u_8vrAcytGUe4z4" } } }, "expectedOrigins": [ "http://localhost:7003" ] }' ``` **Body Parameters** - **flow_type**: _String_ (required) - Specifies how the credential request will be delivered to the holder's wallet. Options: - `"cross_device"` — User scans a QR code on a different device (e.g. desktop browser → phone wallet) - `"same_device"` — User clicks a deep link on the same device (e.g. phone browser → wallet app on same phone) - `"dc_api"` — Browser/OS-native Digital Credentials API (Chrome, Safari, iOS/Android native) over OpenID4VP 1.0 (Annex D). - `"dc_api-annex-c"` — Browser/OS-native Digital Credentials API (Chrome, Safari, iOS/Android native) over 18013-7 (Annex C). - **core**: _Object_ (required) - Defines what credentials to request and which policies to apply. Contains: - **dcql_query**: _Object_ (required) - Specifies the credential type, format, and claims to request using the [DCQL query language](https://docs.walt.id/concepts/data-exchange-protocols/dcql.md). You can also use the DCQL builder [here](https://dcql.walt.id/). - **credentials**: _Array_ - List of credential queries. Each credential query contains: - **id**: _String_ - Unique identifier for this credential in the request (used in policy results) - **format**: _String_ - Credential format. For mDL, use `"mso_mdoc"` (mobile security object document). - **meta.doctype_value**: _String_ - The ISO mDL document type. For mobile driver's licenses, use `"org.iso.18013.5.1.mDL"`. - **claims**: _Array_ (optional) - Specific claims to request from the credential. Each claim has a `path` array with namespace and claim name (e.g. `["org.iso.18013.5.1", "family_name"]`). If omitted, all claims that are non-selectively disclosable are provided by the wallet. - **policies**: _Object_ (optional) - Validation rules applied to the received credential. Defaults to signature verification only if omitted. - **vc_policies**: _Array_ - Policies applied to individual credentials. Each policy has a `policy` field specifying the policy name. Available policies: `signature`, `expired`, `not-before`, `revoked-status-list`, `schema`, and more. See [Policies](https://docs.walt.id/community-stack/verifier2/policies/overview.md) for the complete list. - **vp_policies**: _Array_ (optional) - Policies applied to the entire presentation (not shown in this example) - **signed_request** - Whether the request should be signed. - **encrypted_response** - Whether the response should be encrypted. - **clientId**: The client ID to be used for the verification as per the HAIP specification. - **key**: The key used by the wallet to encrypt the response to the verifier (optional based on if you are using encrypted_response). - **expectedOrigins**: The expected origins for the verification. This is a requirement of the DC API. **Note:** Many parameters have defaults configured in `verifier-service.conf`. Request-level parameters override these defaults for that specific session only. See [Configuration Defaults](#configuration-defaults) below. **Response** ```json { "sessionId": "ff4f0c86-56ce-4e4a-a137-dd5569049ff3", "bootstrapAuthorizationRequestUrl": "openid4vp://authorize?client_id=did%3Ajwk%3AeyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiU05JZ1ItTHZzSEUtNkhUUGxYYUpQVDVubHRZMFpqZlA0VkpTWEwzQ2ZkMCIsIngiOiJIVWM1ZHY1eFREV3VMVmZQcFdvVXBWTjZ1QTNJNzBsVzVnNEhoMG5VaDQ0In0&request_uri=http%3A%2F%2Forg.enterprise.localhost%3A3000%2Fv1%2Forg.tenant.verifier%2Fverifier2-service-api%2Fff4f0c86-56ce-4e4a-a137-dd5569049ff3%2Frequest", "fullAuthorizationRequestUrl": "openid4vp://authorize?response_type=vp_token&client_id=did%3Ajwk%3AeyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiU05JZ1ItTHZzSEUtNkhUUGxYYUpQVDVubHRZMFpqZlA0VkpTWEwzQ2ZkMCIsIngiOiJIVWM1ZHY1eFREV3VMVmZQcFdvVXBWTjZ1QTNJNzBsVzVnNEhoMG5VaDQ0In0&state=8faa6212-465f-4e3f-9a44-649d3d65845c&response_mode=direct_post&nonce=9a054852-46ab-4bbe-8fc1-b96882afcc4f&response_uri=http%3A%2F%2Forg.enterprise.localhost%3A3000%2Fv1%2Forg.tenant.verifier%2Fverifier2-service-api%2Fff4f0c86-56ce-4e4a-a137-dd5569049ff3%2Fresponse&dcql_query=%7B%22credentials%22%3A%5B%7B%22id%22%3A%22pid%22%2C%22format%22%3A%22dc%2Bsd-jwt%22%2C%22meta%22%3A%7B%22vct_values%22%3A%5B%22http%3A%2F%2Forg.enterprise.localhost%3A3000%2Fv1%2Fwaltid.issuer%2Fissuer-service-api%2Fopenid4vc%2Fdraft13%2Fidentity_credential%22%5D%7D%2C%22claims%22%3A%5B%7B%22path%22%3A%5B%22given_name%22%5D%7D%2C%7B%22path%22%3A%5B%22family_name%22%5D%7D%2C%7B%22path%22%3A%5B%22address%22%2C%22street_address%22%5D%7D%5D%7D%5D%7D&client_metadata=%7B%22client_name%22%3A%22walt.id+Enterprise+Verifier%22%2C%22logo_uri%22%3A%22https%3A%2F%2Fcdn.walt.id%2Fverifier%2Flogo.png%22%7D" } ``` You will receive a response containing the following fields: - **sessionId**: _String_ - The unique ID of this verification session. Use this to query the session status. - **bootstrapAuthorizationRequestUrl**: _String_ - Shortened URL using `request_uri` by reference. **Use this for QR codes** to minimize QR code density. - **fullAuthorizationRequestUrl**: _String_ - Complete URL with all parameters embedded. Use this if the wallet doesn't support `request_uri` (rare). ### Step 2: Generating the DC API Request For the next step, take the sessionId and make the following request to get the DC API request URL. **Option: CURL** ```bash curl -X 'GET' \ ''http://localhost:7003/verification-session/{sessionId}/request' \ -H 'accept: application/json' ``` Which will return the DC API request URL, which can be used with the Browser API. We recommend checking out our [Digital Credential Button project](https://github.com/walt-id/waltid-identity/tree/main/waltid-applications/waltid-digital-credentials), which provides a reference for how to invoke the Browser-based DC API. ```json { "digital": { "requests": [ { "protocol": "openid4vp-v1-signed", "data": { "client_id": "x509_hash:abc-xyz-base64url-sha256-hash-of-der-x509-leaf", "expected_origins": [ "https://digital-credentials.walt.id" ], "request": "eyJ0eXAiOiJvYXV0aC1hdXRoei1yZXErand0IiwiZXhwIjoxNzY0Nzc4NjE3LCJpYXQiOjE3NjQ3NzgzMTcsImFsZyI6IkVTMjU2IiwieDVjIjpbIk1JSUJWekNCL2FBREFnRUNBZ2dOS1pBdlVydGltekFLQmdncWhrak9QUVFEQWpBZk1SMHdHd1lEVlFRRERCUjJaWEpwWm1sbGNpNWxlR0Z0Y0d4bExtTnZiVEFlRncweU5URXdNVFF3TmpJME1qQmFGdzB5TmpFd01UUXdOakkwTWpCYU1COHhIVEFiQmdOVkJBTU1GSFpsY21sbWFXVnlMbVY0WVcxd2JHVXVZMjl0TUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFRy9UZ0JjMEJrbU1pcGlRLzZna2FtSW4zbW1wN2hjVHJadXlyTFRta25QMVdSRXhsMWRoZEl4OS9rQWt1dWNlSTNUSGt4WHE3L3krc0J6SzBaUjdqUHFNak1DRXdId1lEVlIwUkJCZ3dGb0lVZG1WeWFXWnBaWEl1WlhoaGJYQnNaUzVqYjIwd0NnWUlLb1pJemowRUF3SURTUUF3UmdJaEFPdTBSR002QmpWUVVlcGVMQm9ndytaRDNNUTl2RnBwYlBJR01QanRuL3FkQWlFQXR0ZmRmeVhIZnpKMnRyK1Bjenlja3p2M05sTTQzNDYxY3ZQOTZzSXpPUUE9Il19.eyJyZXNwb25zZV90eXBlIjoidnBfdG9rZW4iLCJjbGllbnRfaWQiOiJ4NTA5X2hhc2g6YWJjLXh5ei1iYXNlNjR1cmwtc2hhMjU2LWhhc2gtb2YtZGVyLXg1MDktbGVhZiIsInJlc3BvbnNlX21vZGUiOiJkY19hcGkiLCJub25jZSI6IjE1ZmI2YTQ2LTQyNTktNDVlYy05NDFkLWU0NWQwYjA4YTlhZiIsImRjcWxfcXVlcnkiOnsiY3JlZGVudGlhbHMiOlt7ImlkIjoibXlfbWRsIiwiZm9ybWF0IjoibXNvX21kb2MiLCJtZXRhIjp7ImRvY3R5cGVfdmFsdWUiOiJvcmcuaXNvLjE4MDEzLjUuMS5tREwifSwiY2xhaW1zIjpbeyJwYXRoIjpbIm9yZy5pc28uMTgwMTMuNS4xIiwiZmFtaWx5X25hbWUiXX0seyJwYXRoIjpbIm9yZy5pc28uMTgwMTMuNS4xIiwiZ2l2ZW5fbmFtZSJdfSx7InBhdGgiOlsib3JnLmlzby4xODAxMy41LjEiLCJhZ2Vfb3Zlcl8yMSJdfV19XX0sImNsaWVudF9tZXRhZGF0YSI6e30sImV4cGVjdGVkX29yaWdpbnMiOlsiaHR0cHM6Ly9kaWdpdGFsLWNyZWRlbnRpYWxzLndhbHQuaWQiXX0.ESqmoJ_vpD-GQMSmw8CCdQWbiZQ9CBiuHnVi87DO3Jd94HPb48n-Zz9CwDw7R_ytd6_pPlZTUnpKLuVHMfWlog" } } ] } } ``` ### Step 3: Handling the response Once the wallet presents the credential, you will receive a response from the DC API containing the verifiable presentation like so from the wallet ```json { "protocol": "openid4vp-v1-signed", "data": { "vp_token": { "my_mdl": [ "o2d2ZXJzaW9uYzEuMGlkb2N1bWVudHOBo2dkb2NUeXBldW9yZy5pc28uMTgwMTMuNS4xLm1ETGxpc3N1ZXJTaWduZWSiam5hbWVTcGFjZXOhcW9yZy5pc28uMTgwMTMuNS4xg9gYWFSkaGRpZ2VzdElEAGZyYW5kb21QEloNL96oPMkJmnXv9ESbW3FlbGVtZW50SWRlbnRpZmllcmtmYW1pbHlfbmFtZWxlbGVtZW50VmFsdWVlU21pdGjYGFhRpGhkaWdlc3RJRAFmcmFuZG9tUKQwy6zsJN0wZkMPQxjbyWJxZWxlbWVudElkZW50aWZpZXJqZ2l2ZW5fbmFtZWxlbGVtZW50VmFsdWVjSm9u2BhYT6RoZGlnZXN0SUQCZnJhbmRvbVBfiTVUPq1IQvzu0cugg7b9cWVsZW1lbnRJZGVudGlmaWVya2FnZV9vdmVyXzIxbGVsZW1lbnRWYWx1ZfVqaXNzdWVyQXV0aIRDoQEmoRghWQLEMIICwDCCAmegAwIBAgIUHn8bMq1PNO_ksMwHt7DjM6cLGE0wCgYIKoZIzj0EAwIweTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxHDAaBgNVBAoME0RpZ2l0YWwgQ3JlZGVudGlhbHMxHzAdBgNVBAMMFmRpZ2l0YWxjcmVkZW50aWFscy5kZXYwHhcNMjUwMjE5MjMzMDE4WhcNMjYwMjE5MjMzMDE4WjB5MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEcMBoGA1UECgwTRGlnaXRhbCBDcmVkZW50aWFsczEfMB0GA1UEAwwWZGlnaXRhbGNyZWRlbnRpYWxzLmRldjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOt5Nivi1_OXw1AEfYPh42Is41VrNg9qaMdYuw3cavhsCa-aXV0NmTl2EsNaJ5GWmMoAD8ikwAFszYhIeNgF42mjgcwwgckwHwYDVR0jBBgwFoAUok_0idl8Ruhuo4bZR0jOzL7cz_UwHQYDVR0OBBYEFN_-aloS6cBixLyYpyXS2XD3emAoMDQGA1UdHwQtMCswKaAnoCWGI2h0dHBzOi8vZGlnaXRhbC1jcmVkZW50aWFscy5kZXYvY3JsMCoGA1UdEgQjMCGGH2h0dHBzOi8vZGlnaXRhbC1jcmVkZW50aWFscy5kZXYwDgYDVR0PAQH_BAQDAgeAMBUGA1UdJQEB_wQLMAkGByiBjF0FAQIwCgYIKoZIzj0EAwIDRwAwRAIgYcXL9XzB43vy4LEz2h8gMQRdcJtaIRQOemgwm8sHQucCIHCvouHEm_unjBXMCeUZ7QR_ympjGyHITw25_B9H9QsCWQOk2BhZA5-mZ3ZlcnNpb25jMS4wb2RpZ2VzdEFsZ29yaXRobWdTSEEtMjU2Z2RvY1R5cGV1b3JnLmlzby4xODAxMy41LjEubURMbHZhbHVlRGlnZXN0c6Fxb3JnLmlzby4xODAxMy41LjGxAFggng2tWJR7fp49froXSRnsklR_sI-cX_vtNAgkCpdH7KcBWCC0FPEWRP0z6_Rt-ttqzJN5g6hoLJ3nrVljFBcO7RybfwJYIO6B7hZWOTAt0Kz_o7zCJclTcb6SJr_404PWx8RAzjN_A1ggeLRVnN98xkCw3ysIv4PDTCAhZkTqgDSVi-jkN9poMYUEWCDowY3mzymtYR69jFIoHo-NrNdCCxD9k9OogqIHrU3m0QVYILN8AZMmJ9Qq9bwUbsVAQVUP-QtoPmlYdIBTmXJNbr1oBlggzqXSfF8t4hF5hU5wGmxZa0he4VTpAQOCeUTNKP5rA5wHWCATqvZD_qvPdBVcFyUxmIRAK3CgDFrAdzfLMkkRyIIDzAhYIKzeNnZGdWcMC2mXpck7pWMHiVOMDGqSA5M_1lfqO7-wCVggVOyCZq3s73xJMoLhL4b1zoa_hV_twW4xUtyyoWm98nMKWCAzTGC3kG1f25U5RLQUOPNZ1fslnEvfMeLrRXgj19yFxgtYII0QK_PhhkC_VYFaCysXkdtwBGJb6Z3I8fFufwgIGo2NDFggQHxV4YQJLdbpxcfZiPIuocbCZDozc1f0fe9m4-lwVnENWCCY0f4Pabks2V2efyNUpA-Bc0qSG88o0gYgg6mZ-d48ww5YIHDHCfY18n5O6_740xNe_5HMn7D0jHFFymsk5FCNRSQ2D1gg4Qu7JMWCmI1bO1L8kN59jpJyZ9QXNgsG2p679UzAomAQWCAbGPycoabYvTGW6hX0onS3jiCLdi874pBb-hzB1STJ4W1kZXZpY2VLZXlJbmZvoWlkZXZpY2VLZXmkAQIgASFYIEgd6IsLll1JJy7WL7pRu_fcGKaJ8xCKt6klhXBcQJ9PIlggni7iZtdNwuuORu3f_5AeypnVHIu_U5rm4FtrGy-YyIJsdmFsaWRpdHlJbmZvo2ZzaWduZWTAeBsyMDI1LTA5LTI5VDAwOjE5OjUzLjYwODQ3M1ppdmFsaWRGcm9twHgbMjAyNS0wOS0yOVQwMDoxOTo1My42MDg0OTFaanZhbGlkVW50aWzAeBsyMDM1LTA5LTE3VDAwOjE5OjUzLjYwODQ5MlpYQCm_oEaEsbGd7vTDkT1DyLfJ8AYw4AVYYH5mvypCWe09qV2-iLNWF_Q5QMUwOotaJrblStdSZQK-HQydqYXumaxsZGV2aWNlU2lnbmVkompuYW1lU3BhY2Vz2BhBoGpkZXZpY2VBdXRooW9kZXZpY2VTaWduYXR1cmWEQ6EBJqD2WEAsvLdoWQ6ZxizNT8YFz3Kl0nkZDPv1UGjMTDuvCJcH7QCetj6_obSYXIQik5bBY4Pw1CmEsOwMMccuCincIxkoZnN0YXR1cwA" ] } } } ``` Which you pass to the following verifier endpoint to verify the credential and run any policies you have configured. **Option: CURL** ```bash curl -X 'POST' \ ''http://localhost:7003/verification-session/{sessionId}/response' \ -H 'accept: application/json' \ -d ' "protocol": "openid4vp-v1-signed", "data": { "vp_token": { "my_mdl": [ "o2d2ZXJzaW9uYzEuMGlkb2N1bWVudHOBo2dkb2NUeXBldW9yZy5pc28uMTgwMTMuNS4xLm1ETGxpc3N1ZXJTaWduZWSiam5hbWVTcGFjZXOhcW9yZy5pc28uMTgwMTMuNS4xg9gYWFSkaGRpZ2VzdElEAGZyYW5kb21QEloNL96oPMkJmnXv9ESbW3FlbGVtZW50SWRlbnRpZmllcmtmYW1pbHlfbmFtZWxlbGVtZW50VmFsdWVlU21pdGjYGFhRpGhkaWdlc3RJRAFmcmFuZG9tUKQwy6zsJN0wZkMPQxjbyWJxZWxlbWVudElkZW50aWZpZXJqZ2l2ZW5fbmFtZWxlbGVtZW50VmFsdWVjSm9u2BhYT6RoZGlnZXN0SUQCZnJhbmRvbVBfiTVUPq1IQvzu0cugg7b9cWVsZW1lbnRJZGVudGlmaWVya2FnZV9vdmVyXzIxbGVsZW1lbnRWYWx1ZfVqaXNzdWVyQXV0aIRDoQEmoRghWQLEMIICwDCCAmegAwIBAgIUHn8bMq1PNO_ksMwHt7DjM6cLGE0wCgYIKoZIzj0EAwIweTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxHDAaBgNVBAoME0RpZ2l0YWwgQ3JlZGVudGlhbHMxHzAdBgNVBAMMFmRpZ2l0YWxjcmVkZW50aWFscy5kZXYwHhcNMjUwMjE5MjMzMDE4WhcNMjYwMjE5MjMzMDE4WjB5MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEcMBoGA1UECgwTRGlnaXRhbCBDcmVkZW50aWFsczEfMB0GA1UEAwwWZGlnaXRhbGNyZWRlbnRpYWxzLmRldjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOt5Nivi1_OXw1AEfYPh42Is41VrNg9qaMdYuw3cavhsCa-aXV0NmTl2EsNaJ5GWmMoAD8ikwAFszYhIeNgF42mjgcwwgckwHwYDVR0jBBgwFoAUok_0idl8Ruhuo4bZR0jOzL7cz_UwHQYDVR0OBBYEFN_-aloS6cBixLyYpyXS2XD3emAoMDQGA1UdHwQtMCswKaAnoCWGI2h0dHBzOi8vZGlnaXRhbC1jcmVkZW50aWFscy5kZXYvY3JsMCoGA1UdEgQjMCGGH2h0dHBzOi8vZGlnaXRhbC1jcmVkZW50aWFscy5kZXYwDgYDVR0PAQH_BAQDAgeAMBUGA1UdJQEB_wQLMAkGByiBjF0FAQIwCgYIKoZIzj0EAwIDRwAwRAIgYcXL9XzB43vy4LEz2h8gMQRdcJtaIRQOemgwm8sHQucCIHCvouHEm_unjBXMCeUZ7QR_ympjGyHITw25_B9H9QsCWQOk2BhZA5-mZ3ZlcnNpb25jMS4wb2RpZ2VzdEFsZ29yaXRobWdTSEEtMjU2Z2RvY1R5cGV1b3JnLmlzby4xODAxMy41LjEubURMbHZhbHVlRGlnZXN0c6Fxb3JnLmlzby4xODAxMy41LjGxAFggng2tWJR7fp49froXSRnsklR_sI-cX_vtNAgkCpdH7KcBWCC0FPEWRP0z6_Rt-ttqzJN5g6hoLJ3nrVljFBcO7RybfwJYIO6B7hZWOTAt0Kz_o7zCJclTcb6SJr_404PWx8RAzjN_A1ggeLRVnN98xkCw3ysIv4PDTCAhZkTqgDSVi-jkN9poMYUEWCDowY3mzymtYR69jFIoHo-NrNdCCxD9k9OogqIHrU3m0QVYILN8AZMmJ9Qq9bwUbsVAQVUP-QtoPmlYdIBTmXJNbr1oBlggzqXSfF8t4hF5hU5wGmxZa0he4VTpAQOCeUTNKP5rA5wHWCATqvZD_qvPdBVcFyUxmIRAK3CgDFrAdzfLMkkRyIIDzAhYIKzeNnZGdWcMC2mXpck7pWMHiVOMDGqSA5M_1lfqO7-wCVggVOyCZq3s73xJMoLhL4b1zoa_hV_twW4xUtyyoWm98nMKWCAzTGC3kG1f25U5RLQUOPNZ1fslnEvfMeLrRXgj19yFxgtYII0QK_PhhkC_VYFaCysXkdtwBGJb6Z3I8fFufwgIGo2NDFggQHxV4YQJLdbpxcfZiPIuocbCZDozc1f0fe9m4-lwVnENWCCY0f4Pabks2V2efyNUpA-Bc0qSG88o0gYgg6mZ-d48ww5YIHDHCfY18n5O6_740xNe_5HMn7D0jHFFymsk5FCNRSQ2D1gg4Qu7JMWCmI1bO1L8kN59jpJyZ9QXNgsG2p679UzAomAQWCAbGPycoabYvTGW6hX0onS3jiCLdi874pBb-hzB1STJ4W1kZXZpY2VLZXlJbmZvoWlkZXZpY2VLZXmkAQIgASFYIEgd6IsLll1JJy7WL7pRu_fcGKaJ8xCKt6klhXBcQJ9PIlggni7iZtdNwuuORu3f_5AeypnVHIu_U5rm4FtrGy-YyIJsdmFsaWRpdHlJbmZvo2ZzaWduZWTAeBsyMDI1LTA5LTI5VDAwOjE5OjUzLjYwODQ3M1ppdmFsaWRGcm9twHgbMjAyNS0wOS0yOVQwMDoxOTo1My42MDg0OTFaanZhbGlkVW50aWzAeBsyMDM1LTA5LTE3VDAwOjE5OjUzLjYwODQ5MlpYQCm_oEaEsbGd7vTDkT1DyLfJ8AYw4AVYYH5mvypCWe09qV2-iLNWF_Q5QMUwOotaJrblStdSZQK-HQydqYXumaxsZGV2aWNlU2lnbmVkompuYW1lU3BhY2Vz2BhBoGpkZXZpY2VBdXRooW9kZXZpY2VTaWduYXR1cmWEQ6EBJqD2WEAsvLdoWQ6ZxizNT8YFz3Kl0nkZDPv1UGjMTDuvCJcH7QCetj6_obSYXIQik5bBY4Pw1CmEsOwMMccuCincIxkoZnN0YXR1cwA" ] } } } ' ``` ## Validating the Verification Session You can query the verification session to check if the user has presented their credential and whether validation succeeded. **Option: CURL** **Endpoint:** `GET /verification-session/{sessionId}/info` | [API Reference](https://verifier2.portal.test.waltid.cloud/swagger/index.html#/Verification%20Session%20Management/get_verification_session__sessionId__info) ##### Example Request ```bash curl -X 'GET' \ 'http://localhost:7003/verification-session/{sessionId}/info' \ -H 'accept: application/json' ``` **Path Parameters** - **sessionId**: _String_ (required) - The unique ID of the verification session returned when you created the session. Use this to query the current status and results. --- ##### Example Response The response contains the complete session information including setup, status, policy results, and presented credentials. ```json { "id": "4a803c4b-1dc3-4210-806d-3fb52f1780c8", "setup":{ "flow_type": "dc_api", "core_flow": { "dcql_query": { "credentials": [ { "id": "my_mdl", "format": "mso_mdoc", "meta": { "doctype_value": "org.iso.18013.5.1.mDL" }, "claims": [ { "path": [ "org.iso.18013.5.1", "family_name" ] }, { "path": [ "org.iso.18013.5.1", "given_name" ] }, { "path": [ "org.iso.18013.5.1", "age_over_21" ] } ] } ] }, "signed_request": true, "encrypted_response": true, "clientId": "x509_hash:abc-xyz-base64url-sha256-hash-of-der-x509-leaf", "key": { "type": "jwk", "jwk": { "kty": "EC", "d": "AEb4k1BeTR9xt2NxYZggdzkFLLUkhyyWvyUOq3qSiwA", "crv": "P-256", "kid": "_nd-T2YRYLSmuKkJZlRI641zrCIJLTpiHeqMwXuvdug", "x": "G_TgBc0BkmMipiQ_6gkamIn3mmp7hcTrZuyrLTmknP0", "y": "VkRMZdXYXSMff5AJLrnHiN0x5MV6u_8vrAcytGUe4z4" } } }, "expectedOrigins": [ "http://localhost:7003" ] }, "creationDate": "2026-05-18T07:48:39.335523256Z", "expirationDate": "2026-05-18T07:53:39.335523256Z", "retentionDate": "2036-05-18T07:48:39.335523256Z", "status": "SUCCESSFUL", "attempted": true, "reattemptable": true, "bootstrapAuthorizationRequest": { "client_id": "verifier2", "request_uri": "https://575e-2a02-8388-17c1-5580-b97f-5031-3eee-c8a4.ngrok-free.app/verification-session/4a803c4b-1dc3-4210-806d-3fb52f1780c8/request" }, "bootstrapAuthorizationRequestUrl": "openid4vp://authorize?client_id=verifier2&request_uri=https%3A%2F%2F575e-2a02-8388-17c1-5580-b97f-5031-3eee-c8a4.ngrok-free.app%2Fverification-session%2F4a803c4b-1dc3-4210-806d-3fb52f1780c8%2Frequest", "authorizationRequest": { "response_type": "vp_token", "client_id": "verifier2", "state": "da82aed7-ea36-4303-9c4a-3ded03cdedb4", "response_mode": "direct_post", "nonce": "74b0efc4-331c-488f-9489-7ce72b1c698f", "response_uri": "https://575e-2a02-8388-17c1-5580-b97f-5031-3eee-c8a4.ngrok-free.app/verification-session/4a803c4b-1dc3-4210-806d-3fb52f1780c8/response", "dcql_query": { "credentials": [ { "id": "credential_1", "format": "mso_mdoc", "multiple": false, "meta": { "doctype_value": "org.iso.18013.5.1.mDL", "format": "mso_mdoc" }, "require_cryptographic_holder_binding": true, "claims": [ { "path": [ "org.iso.18013.5.1", "family_name" ] }, { "path": [ "org.iso.18013.5.1", "given_name" ] }, { "path": [ "org.iso.18013.5.1", "birth_date" ] } ] } ] }, "client_metadata": { "vp_formats_supported": { "mso_mdoc": { "issuerauth_alg_values": [ -7, -9, -50 ], "deviceauth_alg_values": [ -7, -9, -50, -65537 ] } }, "client_name": "Verifier2", "logo_uri": "https://images.squarespace-cdn.com/content", "tos_uri": "https://example.com/terms", "policy_uri": "https://example.com/privacy", "client_uri": "https://verifier.example.com", "client_name#fr-FR": "Vérificateur2", "logo_uri#fr-FR": "https://example.com/logo-fr.png", "tos_uri#fr-FR": "https://example.com/terms-fr", "policy_uri#fr-FR": "https://example.com/privacy-fr", "client_uri#fr-FR": "https://verifier.example.com/fr" } }, "authorizationRequestUrl": "openid4vp://authorize?response_type=vp_token&client_id=verifier2&state=da82aed7-ea36-4303-9c4a-3ded03cdedb4&response_mode=direct_post&nonce=74b0efc4-331c-488f-9489-7ce72b1c698f&response_uri=https%3A%2F%2F575e-2a02-8388-17c1-5580-b97f-5031-3eee-c8a4.ngrok-free.app%2Fverification-session%2F4a803c4b-1dc3-4210-806d-3fb52f1780c8%2Fresponse&dcql_query=%7B%22credentials%22%3A%5B%7B%22id%22%3A%22credential_1%22%2C%22format%22%3A%22mso_mdoc%22%2C%22meta%22%3A%7B%22doctype_value%22%3A%22org.iso.18013.5.1.mDL%22%7D%2C%22claims%22%3A%5B%7B%22path%22%3A%5B%22org.iso.18013.5.1%22%2C%22family_name%22%5D%7D%2C%7B%22path%22%3A%5B%22org.iso.18013.5.1%22%2C%22given_name%22%5D%7D%2C%7B%22path%22%3A%5B%22org.iso.18013.5.1%22%2C%22birth_date%22%5D%7D%5D%7D%5D%7D&client_metadata=%7B%22vp_formats_supported%22%3A%7B%22mso_mdoc%22%3A%7B%22issuerauth_alg_values%22%3A%5B-7%2C-9%2C-50%5D%2C%22deviceauth_alg_values%22%3A%5B-7%2C-9%2C-50%2C-65537%5D%7D%7D%2C%22client_name%22%3A%22Verifier2%22%2C%22logo_uri%22%3A%22https%3A%2F%2Fimages.squarespace-cdn.com%2Fcontent%22%2C%22tos_uri%22%3A%22https%3A%2F%2Fexample.com%2Fterms%22%2C%22policy_uri%22%3A%22https%3A%2F%2Fexample.com%2Fprivacy%22%2C%22client_uri%22%3A%22https%3A%2F%2Fverifier.example.com%22%2C%22client_name%23fr-FR%22%3A%22V%C3%A9rificateur2%22%2C%22logo_uri%23fr-FR%22%3A%22https%3A%2F%2Fexample.com%2Flogo-fr.png%22%2C%22tos_uri%23fr-FR%22%3A%22https%3A%2F%2Fexample.com%2Fterms-fr%22%2C%22policy_uri%23fr-FR%22%3A%22https%3A%2F%2Fexample.com%2Fprivacy-fr%22%2C%22client_uri%23fr-FR%22%3A%22https%3A%2F%2Fverifier.example.com%2Ffr%22%7D", "requestMode": "REQUEST_URI", "policies": { "vp_policies": { "jwt_vc_json": [ "jwt_vc_json/audience-check", "jwt_vc_json/nonce-check", "jwt_vc_json/envelope_signature" ], "dc+sd-jwt": [ "dc+sd-jwt/audience-check", "dc+sd-jwt/kb-jwt_signature", "dc+sd-jwt/nonce-check", "dc+sd-jwt/sd_hash-check" ], "mso_mdoc": [ "mso_mdoc/device-auth", "mso_mdoc/device_key_auth", "mso_mdoc/issuer_auth", "mso_mdoc/issuer_signed_integrity", "mso_mdoc/mso" ] }, "vc_policies": [ { "policy": "signature", "id": "signature" } ] }, "policy_results": { "vp_policies": { "credential_1": { "mso_mdoc/device-auth": { "policy_executed": { "policy": "mso_mdoc/device-auth", "id": "mso_mdoc/device-auth", "description": "Verify device authentication" }, "success": true, "results": { "device_public_jwk": { "kty": "OKP", "crv": "Ed25519", "x": "jxQwRV7tHc3W_JkKVsDZL7_DtP4zyNJtQ4Yr1z7-Md4" }, "device_auth_bytes_hex": "d8185868847444657669636541757468656e7469636174696f6e83f6f682714f70656e494434565048616e646f7665725820bdb670da65dae163a8fe31c591c82da53c6da1b38232c8f4d3657405e4220a43756f72672e69736f2e31383031332e352e312e6d444cd81841a0" }, "errors": [], "execution_time": "PT0.002658542S" }, "mso_mdoc/device_key_auth": { "policy_executed": { "policy": "mso_mdoc/device_key_auth", "id": "mso_mdoc/device_key_auth", "description": "Verify holder-verified data" }, "success": true, "results": { "empty_device_signed_namespaces": true }, "errors": [], "execution_time": "PT0.000800334S" }, "mso_mdoc/issuer_auth": { "policy_executed": { "policy": "mso_mdoc/issuer_auth", "id": "mso_mdoc/issuer_auth", "description": "Verify issuer authentication" }, "success": true, "results": { "certificate_chain": [ "3082022b308201d1a0030201020214440fe81f511c691ef21f46d51ef752bbde666996300a06082a8648ce3d040302302c310b3009060355040613024154311d301b06035504030c146d444c20497373756572204941434120526f6f74301e170d3236303531383036313932365a170d3237303831383036313932365a3032310b30090603550406130241543123302106035504030c1a6d444c2049737375657220446f63756d656e74205369676e65723059301306072a8648ce3d020106082a8648ce3d03010703420004c9f99bb42b1d410ff81033e0d06b7df20258935ce434ea4633d6c76048b8f667301080b22778927ef1e8a3f7d5bc33469d3cd03e9795b4761b19a673bec228f9a381ca3081c7301f0603551d2304183016801472b2f76f49d0bcb3dd70cc2d6499a3378002e8e3301d0603551d0e041604145c64d6b94b4538b14534525b3942eef8f841827d300e0603551d0f0101ff04040302078030270603551d120420301e861c68747470733a2f2f696163612e74656e616e74312e77616c742e696430150603551d250101ff040b3009060728818c5d05010230350603551d1f042e302c302aa028a026862468747470733a2f2f63726c2e74656e616e74312e77616c742e69642f696163612e63726c300a06082a8648ce3d0403020348003045022006c7eaed38d41d53d35224d5b420ca41109d5f3a7afde473a8e287ed8688a3d7022100eb7882937823711f9bc909e3f88861436da296b882066ced7fdf66f057f0af39" ], "signer_pem": "-----BEGIN CERTIFICATE-----\nMIICKzCCAdGgAwIBAgIURA/oH1EcaR7yH0bVHvdSu95maZYwCgYIKoZIzj0EAwIw\r\nLDELMAkGA1UEBhMCQVQxHTAbBgNVBAMMFG1ETCBJc3N1ZXIgSUFDQSBSb290MB4X\r\nDTI2MDUxODA2MTkyNloXDTI3MDgxODA2MTkyNlowMjELMAkGA1UEBhMCQVQxIzAh\r\nBgNVBAMMGm1ETCBJc3N1ZXIgRG9jdW1lbnQgU2lnbmVyMFkwEwYHKoZIzj0CAQYI\r\nKoZIzj0DAQcDQgAEyfmbtCsdQQ/4EDPg0Gt98gJYk1zkNOpGM9bHYEi49mcwEICy\r\nJ3iSfvHoo/fVvDNGnTzQPpeVtHYbGaZzvsIo+aOByjCBxzAfBgNVHSMEGDAWgBRy\r\nsvdvSdC8s91wzC1kmaM3gALo4zAdBgNVHQ4EFgQUXGTWuUtFOLFFNFJbOULu+PhB\r\ngn0wDgYDVR0PAQH/BAQDAgeAMCcGA1UdEgQgMB6GHGh0dHBzOi8vaWFjYS50ZW5h\r\nbnQxLndhbHQuaWQwFQYDVR0lAQH/BAswCQYHKIGMXQUBAjA1BgNVHR8ELjAsMCqg\r\nKKAmhiRodHRwczovL2NybC50ZW5hbnQxLndhbHQuaWQvaWFjYS5jcmwwCgYIKoZI\r\nzj0EAwIDSAAwRQIgBsfq7TjUHVPTUiTVtCDKQRCdXzp6/eRzqOKH7YaIo9cCIQDr\r\neIKTeCNxH5vJCeP4iGFDbaKWuIIGbO1/32bwV/CvOQ==\n-----END CERTIFICATE-----", "signer_jwk": { "kty": "EC", "crv": "P-256", "x": "yfmbtCsdQQ_4EDPg0Gt98gJYk1zkNOpGM9bHYEi49mc", "y": "MBCAsid4kn7x6KP31bwzRp080D6XlbR2Gxmmc77CKPk" } }, "errors": [], "execution_time": "PT0.004215542S" }, "mso_mdoc/issuer_signed_integrity": { "policy_executed": { "policy": "mso_mdoc/issuer_signed_integrity", "id": "mso_mdoc/issuer_signed_integrity", "description": "Verify issuer-verified data integrity" }, "success": true, "results": { "namespace": { "org.iso.18013.5.1": [ { "id": "family_name", "digest_id": 0, "value": "Doe", "value_type": "CborString", "random_hex": "736b060d21fa4e37f17b0b0568285add9d064ba883eb6c35", "serialized_hex": "a4686469676573744944006672616e646f6d5818736b060d21fa4e37f17b0b0568285add9d064ba883eb6c3571656c656d656e744964656e7469666965726b66616d696c795f6e616d656c656c656d656e7456616c756563446f65" }, { "id": "given_name", "digest_id": 1, "value": "John", "value_type": "CborString", "random_hex": "312e734c986fdf279c7d75130b0da4794d16d44bbb3cfd61", "serialized_hex": "a4686469676573744944016672616e646f6d5818312e734c986fdf279c7d75130b0da4794d16d44bbb3cfd6171656c656d656e744964656e7469666965726a676976656e5f6e616d656c656c656d656e7456616c7565644a6f686e" }, { "id": "birth_date", "digest_id": 2, "value": "1986-03-22", "value_type": "CborString", "random_hex": "e6864d13d13a9fb1a7fccb729a6330dec975bac5e79fa023", "serialized_hex": "a4686469676573744944026672616e646f6d5818e6864d13d13a9fb1a7fccb729a6330dec975bac5e79fa02371656c656d656e744964656e7469666965726a62697274685f646174656c656c656d656e7456616c7565d903ec6a313938362d30332d3232" } ] }, "matching_digest": { "org.iso.18013.5.1": [ "family_name", "given_name", "birth_date" ] } }, "errors": [], "execution_time": "PT0.003104834S" }, "mso_mdoc/mso": { "policy_executed": { "policy": "mso_mdoc/mso", "id": "mso_mdoc/mso", "description": "Verify MSO" }, "success": true, "results": { "signed": "2026-05-18T07:48:27.870905006Z", "valid_from": "2027-05-18T07:48:27.852289522Z" }, "errors": [], "execution_time": "PT0.004245834S" } } }, "vc_policies": [ { "policy": { "policy": "signature", "id": "signature" }, "success": true, "result": { "verification_result": true, "signed_credential": "o2d2ZXJzaW9uYzEuMGlkb2N1bWVudHOBo2dkb2NUeXBldW9yZy5pc28uMTgwMTMuNS4xLm1ETGxpc3N1ZXJTaWduZWSiam5hbWVTcGFjZXOhcW9yZy5pc28uMTgwMTMuNS4xg9gYWFukaGRpZ2VzdElEAGZyYW5kb21YGHNrBg0h-k438XsLBWgoWt2dBkuog-tsNXFlbGVtZW50SWRlbnRpZmllcmtmYW1pbHlfbmFtZWxlbGVtZW50VmFsdWVjRG9l2BhYW6RoZGlnZXN0SUQBZnJhbmRvbVgYMS5zTJhv3yecfXUTCw2keU0W1Eu7PP1hcWVsZW1lbnRJZGVudGlmaWVyamdpdmVuX25hbWVsZWxlbWVudFZhbHVlZEpvaG7YGFhkpGhkaWdlc3RJRAJmcmFuZG9tWBjmhk0T0Tqfsaf8y3KaYzDeyXW6xeefoCNxZWxlbWVudElkZW50aWZpZXJqYmlydGhfZGF0ZWxlbGVtZW50VmFsdWXZA-xqMTk4Ni0wMy0yMmppc3N1ZXJBdXRohEOhASahGCFZAi8wggIrMIIB0aADAgECAhRED-gfURxpHvIfRtUe91K73mZpljAKBggqhkjOPQQDAjAsMQswCQYDVQQGEwJBVDEdMBsGA1UEAwwUbURMIElzc3VlciBJQUNBIFJvb3QwHhcNMjYwNTE4MDYxOTI2WhcNMjcwODE4MDYxOTI2WjAyMQswCQYDVQQGEwJBVDEjMCEGA1UEAwwabURMIElzc3VlciBEb2N1bWVudCBTaWduZXIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATJ-Zu0Kx1BD_gQM-DQa33yAliTXOQ06kYz1sdgSLj2ZzAQgLIneJJ-8eij99W8M0adPNA-l5W0dhsZpnO-wij5o4HKMIHHMB8GA1UdIwQYMBaAFHKy929J0Lyz3XDMLWSZozeAAujjMB0GA1UdDgQWBBRcZNa5S0U4sUU0Uls5Qu74-EGCfTAOBgNVHQ8BAf8EBAMCB4AwJwYDVR0SBCAwHoYcaHR0cHM6Ly9pYWNhLnRlbmFudDEud2FsdC5pZDAVBgNVHSUBAf8ECzAJBgcogYxdBQECMDUGA1UdHwQuMCwwKqAooCaGJGh0dHBzOi8vY3JsLnRlbmFudDEud2FsdC5pZC9pYWNhLmNybDAKBggqhkjOPQQDAgNIADBFAiAGx-rtONQdU9NSJNW0IMpBEJ1fOnr95HOo4ofthoij1wIhAOt4gpN4I3Efm8kJ4_iIYUNtopa4ggZs7X_fZvBX8K85WQKV2BhZApCmZ3ZlcnNpb25jMS4wb2RpZ2VzdEFsZ29yaXRobWdTSEEtMjU2bHZhbHVlRGlnZXN0c6Fxb3JnLmlzby4xODAxMy41LjGqAFgglIt2JC3rDbrr6azX5GWPMGyGqyD5gTnyASk37pDhZQYBWCCMQ7Ph45igVnLbvCtlSqLPlaMTXSk2kLoy4Mxn1x37kwJYIOjGjCHBlfvZi74lANZXAWt_FDdDf6NU591eYMGbFladA1ggcdl99l7hzcOFDYUeghGX0gDXYYgEN7WyRFrgSSFVvv4EWCCKwt6H37DEK8kJFcs__AiFZzLDc-4GPFKuYYEJzv_X1QVYIMybLDIPi3G7go7Bq4ggn0UYXEpmKMazxXZH25mnl0RQBlggofgVz7C7i4z0Iz8zrEPhNBRMlZcr4EhqQCR2pjo3qsoHWCDaJ4DszfMYhI0HfyOcSgMEgx_K12dx34khKW6ohKRhZghYIPTE7TeLsXVDSX9l3DTPB4zDhzF1WDTmCw3Ecxv1deNJCVggqueG-PUmdP8sJdsQ6XbuByQzAZXoRdwB-7dn0hxtkCdtZGV2aWNlS2V5SW5mb6FpZGV2aWNlS2V5owEBIAYhWCCPFDBFXu0dzdb8mQpWwNkvv8O0_jPI0m1DhivXPv4x3mdkb2NUeXBldW9yZy5pc28uMTgwMTMuNS4xLm1ETGx2YWxpZGl0eUluZm-jZnNpZ25lZMB4HjIwMjYtMDUtMThUMDc6NDg6MjcuODcwOTA1MDA2Wml2YWxpZEZyb23AeB4yMDI2LTA1LTE4VDA3OjQ4OjI3Ljg3MDkwNTAwNlpqdmFsaWRVbnRpbMB4HjIwMjctMDUtMThUMDc6NDg6MjcuODUyMjg5NTIyWlhAmRC5DQjAwaNaozniXwKmOoMscEyNwA65Y31zyoCJpiGiZxs-fk9bcsZnrDSu5ATPVEIKrf_4hjY8ODArK76q2WxkZXZpY2VTaWduZWSiam5hbWVTcGFjZXPYGEGgamRldmljZUF1dGihb2RldmljZVNpZ25hdHVyZYRDoQEnoPZYQGQUayxOBk2Wp7g7UhumPbtFSQ5h1h4jArb60UGZOsFMvG6MXHBBPd4JdOX40auhbmKNmBjinPfw96QN9XfV-Q9mc3RhdHVzAA", "credential_signature": { "type": "signature-cose", "signerKey": { "type": "jwk", "jwk": { "kty": "EC", "crv": "P-256", "x": "yfmbtCsdQQ_4EDPg0Gt98gJYk1zkNOpGM9bHYEi49mc", "y": "MBCAsid4kn7x6KP31bwzRp080D6XlbR2Gxmmc77CKPk" } }, "x5cList": [ "MIICKzCCAdGgAwIBAgIURA/oH1EcaR7yH0bVHvdSu95maZYwCgYIKoZIzj0EAwIwLDELMAkGA1UEBhMCQVQxHTAbBgNVBAMMFG1ETCBJc3N1ZXIgSUFDQSBSb290MB4XDTI2MDUxODA2MTkyNloXDTI3MDgxODA2MTkyNlowMjELMAkGA1UEBhMCQVQxIzAhBgNVBAMMGm1ETCBJc3N1ZXIgRG9jdW1lbnQgU2lnbmVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEyfmbtCsdQQ/4EDPg0Gt98gJYk1zkNOpGM9bHYEi49mcwEICyJ3iSfvHoo/fVvDNGnTzQPpeVtHYbGaZzvsIo+aOByjCBxzAfBgNVHSMEGDAWgBRysvdvSdC8s91wzC1kmaM3gALo4zAdBgNVHQ4EFgQUXGTWuUtFOLFFNFJbOULu+PhBgn0wDgYDVR0PAQH/BAQDAgeAMCcGA1UdEgQgMB6GHGh0dHBzOi8vaWFjYS50ZW5hbnQxLndhbHQuaWQwFQYDVR0lAQH/BAswCQYHKIGMXQUBAjA1BgNVHR8ELjAsMCqgKKAmhiRodHRwczovL2NybC50ZW5hbnQxLndhbHQuaWQvaWFjYS5jcmwwCgYIKoZIzj0EAwIDSAAwRQIgBsfq7TjUHVPTUiTVtCDKQRCdXzp6/eRzqOKH7YaIo9cCIQDreIKTeCNxH5vJCeP4iGFDbaKWuIIGbO1/32bwV/CvOQ==" ] }, "verified_data": { "docType": "org.iso.18013.5.1.mDL", "org.iso.18013.5.1": { "family_name": "Doe", "given_name": "John", "birth_date": "1986-03-22" } }, "successful_issuer_public_key": { "kty": "EC", "crv": "P-256", "x": "yfmbtCsdQQ_4EDPg0Gt98gJYk1zkNOpGM9bHYEi49mc", "y": "MBCAsid4kn7x6KP31bwzRp080D6XlbR2Gxmmc77CKPk" }, "successful_issuer_public_key_id": "FOWc2X5ZSle6u66RwXQoVUwm86vnTnbZ61s_Golepz0" } } ], "specific_vc_policies": {}, "overallSuccess": true }, "presentation_validation_results": { "credential_1": { "mso_mdoc/device-auth": { "policy_executed": { "policy": "mso_mdoc/device-auth", "id": "mso_mdoc/device-auth", "description": "Verify device authentication" }, "success": true, "results": { "device_public_jwk": { "kty": "OKP", "crv": "Ed25519", "x": "jxQwRV7tHc3W_JkKVsDZL7_DtP4zyNJtQ4Yr1z7-Md4" }, "device_auth_bytes_hex": "d8185868847444657669636541757468656e7469636174696f6e83f6f682714f70656e494434565048616e646f7665725820bdb670da65dae163a8fe31c591c82da53c6da1b38232c8f4d3657405e4220a43756f72672e69736f2e31383031332e352e312e6d444cd81841a0" }, "errors": [], "execution_time": "PT0.002658542S" }, "mso_mdoc/device_key_auth": { "policy_executed": { "policy": "mso_mdoc/device_key_auth", "id": "mso_mdoc/device_key_auth", "description": "Verify holder-verified data" }, "success": true, "results": { "empty_device_signed_namespaces": true }, "errors": [], "execution_time": "PT0.000800334S" }, "mso_mdoc/issuer_auth": { "policy_executed": { "policy": "mso_mdoc/issuer_auth", "id": "mso_mdoc/issuer_auth", "description": "Verify issuer authentication" }, "success": true, "results": { "certificate_chain": [ "3082022b308201d1a0030201020214440fe81f511c691ef21f46d51ef752bbde666996300a06082a8648ce3d040302302c310b3009060355040613024154311d301b06035504030c146d444c20497373756572204941434120526f6f74301e170d3236303531383036313932365a170d3237303831383036313932365a3032310b30090603550406130241543123302106035504030c1a6d444c2049737375657220446f63756d656e74205369676e65723059301306072a8648ce3d020106082a8648ce3d03010703420004c9f99bb42b1d410ff81033e0d06b7df20258935ce434ea4633d6c76048b8f667301080b22778927ef1e8a3f7d5bc33469d3cd03e9795b4761b19a673bec228f9a381ca3081c7301f0603551d2304183016801472b2f76f49d0bcb3dd70cc2d6499a3378002e8e3301d0603551d0e041604145c64d6b94b4538b14534525b3942eef8f841827d300e0603551d0f0101ff04040302078030270603551d120420301e861c68747470733a2f2f696163612e74656e616e74312e77616c742e696430150603551d250101ff040b3009060728818c5d05010230350603551d1f042e302c302aa028a026862468747470733a2f2f63726c2e74656e616e74312e77616c742e69642f696163612e63726c300a06082a8648ce3d0403020348003045022006c7eaed38d41d53d35224d5b420ca41109d5f3a7afde473a8e287ed8688a3d7022100eb7882937823711f9bc909e3f88861436da296b882066ced7fdf66f057f0af39" ], "signer_pem": "-----BEGIN CERTIFICATE-----\nMIICKzCCAdGgAwIBAgIURA/oH1EcaR7yH0bVHvdSu95maZYwCgYIKoZIzj0EAwIw\r\nLDELMAkGA1UEBhMCQVQxHTAbBgNVBAMMFG1ETCBJc3N1ZXIgSUFDQSBSb290MB4X\r\nDTI2MDUxODA2MTkyNloXDTI3MDgxODA2MTkyNlowMjELMAkGA1UEBhMCQVQxIzAh\r\nBgNVBAMMGm1ETCBJc3N1ZXIgRG9jdW1lbnQgU2lnbmVyMFkwEwYHKoZIzj0CAQYI\r\nKoZIzj0DAQcDQgAEyfmbtCsdQQ/4EDPg0Gt98gJYk1zkNOpGM9bHYEi49mcwEICy\r\nJ3iSfvHoo/fVvDNGnTzQPpeVtHYbGaZzvsIo+aOByjCBxzAfBgNVHSMEGDAWgBRy\r\nsvdvSdC8s91wzC1kmaM3gALo4zAdBgNVHQ4EFgQUXGTWuUtFOLFFNFJbOULu+PhB\r\ngn0wDgYDVR0PAQH/BAQDAgeAMCcGA1UdEgQgMB6GHGh0dHBzOi8vaWFjYS50ZW5h\r\nbnQxLndhbHQuaWQwFQYDVR0lAQH/BAswCQYHKIGMXQUBAjA1BgNVHR8ELjAsMCqg\r\nKKAmhiRodHRwczovL2NybC50ZW5hbnQxLndhbHQuaWQvaWFjYS5jcmwwCgYIKoZI\r\nzj0EAwIDSAAwRQIgBsfq7TjUHVPTUiTVtCDKQRCdXzp6/eRzqOKH7YaIo9cCIQDr\r\neIKTeCNxH5vJCeP4iGFDbaKWuIIGbO1/32bwV/CvOQ==\n-----END CERTIFICATE-----", "signer_jwk": { "kty": "EC", "crv": "P-256", "x": "yfmbtCsdQQ_4EDPg0Gt98gJYk1zkNOpGM9bHYEi49mc", "y": "MBCAsid4kn7x6KP31bwzRp080D6XlbR2Gxmmc77CKPk" } }, "errors": [], "execution_time": "PT0.004215542S" }, "mso_mdoc/issuer_signed_integrity": { "policy_executed": { "policy": "mso_mdoc/issuer_signed_integrity", "id": "mso_mdoc/issuer_signed_integrity", "description": "Verify issuer-verified data integrity" }, "success": true, "results": { "namespace": { "org.iso.18013.5.1": [ { "id": "family_name", "digest_id": 0, "value": "Doe", "value_type": "CborString", "random_hex": "736b060d21fa4e37f17b0b0568285add9d064ba883eb6c35", "serialized_hex": "a4686469676573744944006672616e646f6d5818736b060d21fa4e37f17b0b0568285add9d064ba883eb6c3571656c656d656e744964656e7469666965726b66616d696c795f6e616d656c656c656d656e7456616c756563446f65" }, { "id": "given_name", "digest_id": 1, "value": "John", "value_type": "CborString", "random_hex": "312e734c986fdf279c7d75130b0da4794d16d44bbb3cfd61", "serialized_hex": "a4686469676573744944016672616e646f6d5818312e734c986fdf279c7d75130b0da4794d16d44bbb3cfd6171656c656d656e744964656e7469666965726a676976656e5f6e616d656c656c656d656e7456616c7565644a6f686e" }, { "id": "birth_date", "digest_id": 2, "value": "1986-03-22", "value_type": "CborString", "random_hex": "e6864d13d13a9fb1a7fccb729a6330dec975bac5e79fa023", "serialized_hex": "a4686469676573744944026672616e646f6d5818e6864d13d13a9fb1a7fccb729a6330dec975bac5e79fa02371656c656d656e744964656e7469666965726a62697274685f646174656c656c656d656e7456616c7565d903ec6a313938362d30332d3232" } ] }, "matching_digest": { "org.iso.18013.5.1": [ "family_name", "given_name", "birth_date" ] } }, "errors": [], "execution_time": "PT0.003104834S" }, "mso_mdoc/mso": { "policy_executed": { "policy": "mso_mdoc/mso", "id": "mso_mdoc/mso", "description": "Verify MSO" }, "success": true, "results": { "signed": "2026-05-18T07:48:27.870905006Z", "valid_from": "2027-05-18T07:48:27.852289522Z" }, "errors": [], "execution_time": "PT0.004245834S" } } }, "presented_raw_data": { "vpToken": { "credential_1": [ "o2d2ZXJzaW9uYzEuMGlkb2N1bWVudHOBo2dkb2NUeXBldW9yZy5pc28uMTgwMTMuNS4xLm1ETGxpc3N1ZXJTaWduZWSiam5hbWVTcGFjZXOhcW9yZy5pc28uMTgwMTMuNS4xg9gYWFukaGRpZ2VzdElEAGZyYW5kb21YGHNrBg0h-k438XsLBWgoWt2dBkuog-tsNXFlbGVtZW50SWRlbnRpZmllcmtmYW1pbHlfbmFtZWxlbGVtZW50VmFsdWVjRG9l2BhYW6RoZGlnZXN0SUQBZnJhbmRvbVgYMS5zTJhv3yecfXUTCw2keU0W1Eu7PP1hcWVsZW1lbnRJZGVudGlmaWVyamdpdmVuX25hbWVsZWxlbWVudFZhbHVlZEpvaG7YGFhkpGhkaWdlc3RJRAJmcmFuZG9tWBjmhk0T0Tqfsaf8y3KaYzDeyXW6xeefoCNxZWxlbWVudElkZW50aWZpZXJqYmlydGhfZGF0ZWxlbGVtZW50VmFsdWXZA-xqMTk4Ni0wMy0yMmppc3N1ZXJBdXRohEOhASahGCFZAi8wggIrMIIB0aADAgECAhRED-gfURxpHvIfRtUe91K73mZpljAKBggqhkjOPQQDAjAsMQswCQYDVQQGEwJBVDEdMBsGA1UEAwwUbURMIElzc3VlciBJQUNBIFJvb3QwHhcNMjYwNTE4MDYxOTI2WhcNMjcwODE4MDYxOTI2WjAyMQswCQYDVQQGEwJBVDEjMCEGA1UEAwwabURMIElzc3VlciBEb2N1bWVudCBTaWduZXIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATJ-Zu0Kx1BD_gQM-DQa33yAliTXOQ06kYz1sdgSLj2ZzAQgLIneJJ-8eij99W8M0adPNA-l5W0dhsZpnO-wij5o4HKMIHHMB8GA1UdIwQYMBaAFHKy929J0Lyz3XDMLWSZozeAAujjMB0GA1UdDgQWBBRcZNa5S0U4sUU0Uls5Qu74-EGCfTAOBgNVHQ8BAf8EBAMCB4AwJwYDVR0SBCAwHoYcaHR0cHM6Ly9pYWNhLnRlbmFudDEud2FsdC5pZDAVBgNVHSUBAf8ECzAJBgcogYxdBQECMDUGA1UdHwQuMCwwKqAooCaGJGh0dHBzOi8vY3JsLnRlbmFudDEud2FsdC5pZC9pYWNhLmNybDAKBggqhkjOPQQDAgNIADBFAiAGx-rtONQdU9NSJNW0IMpBEJ1fOnr95HOo4ofthoij1wIhAOt4gpN4I3Efm8kJ4_iIYUNtopa4ggZs7X_fZvBX8K85WQKV2BhZApCmZ3ZlcnNpb25jMS4wb2RpZ2VzdEFsZ29yaXRobWdTSEEtMjU2bHZhbHVlRGlnZXN0c6Fxb3JnLmlzby4xODAxMy41LjGqAFgglIt2JC3rDbrr6azX5GWPMGyGqyD5gTnyASk37pDhZQYBWCCMQ7Ph45igVnLbvCtlSqLPlaMTXSk2kLoy4Mxn1x37kwJYIOjGjCHBlfvZi74lANZXAWt_FDdDf6NU591eYMGbFladA1ggcdl99l7hzcOFDYUeghGX0gDXYYgEN7WyRFrgSSFVvv4EWCCKwt6H37DEK8kJFcs__AiFZzLDc-4GPFKuYYEJzv_X1QVYIMybLDIPi3G7go7Bq4ggn0UYXEpmKMazxXZH25mnl0RQBlggofgVz7C7i4z0Iz8zrEPhNBRMlZcr4EhqQCR2pjo3qsoHWCDaJ4DszfMYhI0HfyOcSgMEgx_K12dx34khKW6ohKRhZghYIPTE7TeLsXVDSX9l3DTPB4zDhzF1WDTmCw3Ecxv1deNJCVggqueG-PUmdP8sJdsQ6XbuByQzAZXoRdwB-7dn0hxtkCdtZGV2aWNlS2V5SW5mb6FpZGV2aWNlS2V5owEBIAYhWCCPFDBFXu0dzdb8mQpWwNkvv8O0_jPI0m1DhivXPv4x3mdkb2NUeXBldW9yZy5pc28uMTgwMTMuNS4xLm1ETGx2YWxpZGl0eUluZm-jZnNpZ25lZMB4HjIwMjYtMDUtMThUMDc6NDg6MjcuODcwOTA1MDA2Wml2YWxpZEZyb23AeB4yMDI2LTA1LTE4VDA3OjQ4OjI3Ljg3MDkwNTAwNlpqdmFsaWRVbnRpbMB4HjIwMjctMDUtMThUMDc6NDg6MjcuODUyMjg5NTIyWlhAmRC5DQjAwaNaozniXwKmOoMscEyNwA65Y31zyoCJpiGiZxs-fk9bcsZnrDSu5ATPVEIKrf_4hjY8ODArK76q2WxkZXZpY2VTaWduZWSiam5hbWVTcGFjZXPYGEGgamRldmljZUF1dGihb2RldmljZVNpZ25hdHVyZYRDoQEnoPZYQGQUayxOBk2Wp7g7UhumPbtFSQ5h1h4jArb60UGZOsFMvG6MXHBBPd4JdOX40auhbmKNmBjinPfw96QN9XfV-Q9mc3RhdHVzAA" ] }, "state": "da82aed7-ea36-4303-9c4a-3ded03cdedb4" }, "presented_presentations": { "credential_1": { "type": "mso_mdoc", "format": "mso_mdoc", "mdoc": { "credentialData": { "docType": "org.iso.18013.5.1.mDL", "org.iso.18013.5.1": { "family_name": "Doe", "given_name": "John", "birth_date": "1986-03-22" } }, "signed": "o2d2ZXJzaW9uYzEuMGlkb2N1bWVudHOBo2dkb2NUeXBldW9yZy5pc28uMTgwMTMuNS4xLm1ETGxpc3N1ZXJTaWduZWSiam5hbWVTcGFjZXOhcW9yZy5pc28uMTgwMTMuNS4xg9gYWFukaGRpZ2VzdElEAGZyYW5kb21YGHNrBg0h-k438XsLBWgoWt2dBkuog-tsNXFlbGVtZW50SWRlbnRpZmllcmtmYW1pbHlfbmFtZWxlbGVtZW50VmFsdWVjRG9l2BhYW6RoZGlnZXN0SUQBZnJhbmRvbVgYMS5zTJhv3yecfXUTCw2keU0W1Eu7PP1hcWVsZW1lbnRJZGVudGlmaWVyamdpdmVuX25hbWVsZWxlbWVudFZhbHVlZEpvaG7YGFhkpGhkaWdlc3RJRAJmcmFuZG9tWBjmhk0T0Tqfsaf8y3KaYzDeyXW6xeefoCNxZWxlbWVudElkZW50aWZpZXJqYmlydGhfZGF0ZWxlbGVtZW50VmFsdWXZA-xqMTk4Ni0wMy0yMmppc3N1ZXJBdXRohEOhASahGCFZAi8wggIrMIIB0aADAgECAhRED-gfURxpHvIfRtUe91K73mZpljAKBggqhkjOPQQDAjAsMQswCQYDVQQGEwJBVDEdMBsGA1UEAwwUbURMIElzc3VlciBJQUNBIFJvb3QwHhcNMjYwNTE4MDYxOTI2WhcNMjcwODE4MDYxOTI2WjAyMQswCQYDVQQGEwJBVDEjMCEGA1UEAwwabURMIElzc3VlciBEb2N1bWVudCBTaWduZXIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATJ-Zu0Kx1BD_gQM-DQa33yAliTXOQ06kYz1sdgSLj2ZzAQgLIneJJ-8eij99W8M0adPNA-l5W0dhsZpnO-wij5o4HKMIHHMB8GA1UdIwQYMBaAFHKy929J0Lyz3XDMLWSZozeAAujjMB0GA1UdDgQWBBRcZNa5S0U4sUU0Uls5Qu74-EGCfTAOBgNVHQ8BAf8EBAMCB4AwJwYDVR0SBCAwHoYcaHR0cHM6Ly9pYWNhLnRlbmFudDEud2FsdC5pZDAVBgNVHSUBAf8ECzAJBgcogYxdBQECMDUGA1UdHwQuMCwwKqAooCaGJGh0dHBzOi8vY3JsLnRlbmFudDEud2FsdC5pZC9pYWNhLmNybDAKBggqhkjOPQQDAgNIADBFAiAGx-rtONQdU9NSJNW0IMpBEJ1fOnr95HOo4ofthoij1wIhAOt4gpN4I3Efm8kJ4_iIYUNtopa4ggZs7X_fZvBX8K85WQKV2BhZApCmZ3ZlcnNpb25jMS4wb2RpZ2VzdEFsZ29yaXRobWdTSEEtMjU2bHZhbHVlRGlnZXN0c6Fxb3JnLmlzby4xODAxMy41LjGqAFgglIt2JC3rDbrr6azX5GWPMGyGqyD5gTnyASk37pDhZQYBWCCMQ7Ph45igVnLbvCtlSqLPlaMTXSk2kLoy4Mxn1x37kwJYIOjGjCHBlfvZi74lANZXAWt_FDdDf6NU591eYMGbFladA1ggcdl99l7hzcOFDYUeghGX0gDXYYgEN7WyRFrgSSFVvv4EWCCKwt6H37DEK8kJFcs__AiFZzLDc-4GPFKuYYEJzv_X1QVYIMybLDIPi3G7go7Bq4ggn0UYXEpmKMazxXZH25mnl0RQBlggofgVz7C7i4z0Iz8zrEPhNBRMlZcr4EhqQCR2pjo3qsoHWCDaJ4DszfMYhI0HfyOcSgMEgx_K12dx34khKW6ohKRhZghYIPTE7TeLsXVDSX9l3DTPB4zDhzF1WDTmCw3Ecxv1deNJCVggqueG-PUmdP8sJdsQ6XbuByQzAZXoRdwB-7dn0hxtkCdtZGV2aWNlS2V5SW5mb6FpZGV2aWNlS2V5owEBIAYhWCCPFDBFXu0dzdb8mQpWwNkvv8O0_jPI0m1DhivXPv4x3mdkb2NUeXBldW9yZy5pc28uMTgwMTMuNS4xLm1ETGx2YWxpZGl0eUluZm-jZnNpZ25lZMB4HjIwMjYtMDUtMThUMDc6NDg6MjcuODcwOTA1MDA2Wml2YWxpZEZyb23AeB4yMDI2LTA1LTE4VDA3OjQ4OjI3Ljg3MDkwNTAwNlpqdmFsaWRVbnRpbMB4HjIwMjctMDUtMThUMDc6NDg6MjcuODUyMjg5NTIyWlhAmRC5DQjAwaNaozniXwKmOoMscEyNwA65Y31zyoCJpiGiZxs-fk9bcsZnrDSu5ATPVEIKrf_4hjY8ODArK76q2WxkZXZpY2VTaWduZWSiam5hbWVTcGFjZXPYGEGgamRldmljZUF1dGihb2RldmljZVNpZ25hdHVyZYRDoQEnoPZYQGQUayxOBk2Wp7g7UhumPbtFSQ5h1h4jArb60UGZOsFMvG6MXHBBPd4JdOX40auhbmKNmBjinPfw96QN9XfV-Q9mc3RhdHVzAA", "docType": "org.iso.18013.5.1.mDL", "signature": { "type": "signature-cose", "signerKey": { "type": "jwk", "jwk": { "kty": "EC", "crv": "P-256", "x": "yfmbtCsdQQ_4EDPg0Gt98gJYk1zkNOpGM9bHYEi49mc", "y": "MBCAsid4kn7x6KP31bwzRp080D6XlbR2Gxmmc77CKPk" } }, "x5cList": [ "MIICKzCCAdGgAwIBAgIURA/oH1EcaR7yH0bVHvdSu95maZYwCgYIKoZIzj0EAwIwLDELMAkGA1UEBhMCQVQxHTAbBgNVBAMMFG1ETCBJc3N1ZXIgSUFDQSBSb290MB4XDTI2MDUxODA2MTkyNloXDTI3MDgxODA2MTkyNlowMjELMAkGA1UEBhMCQVQxIzAhBgNVBAMMGm1ETCBJc3N1ZXIgRG9jdW1lbnQgU2lnbmVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEyfmbtCsdQQ/4EDPg0Gt98gJYk1zkNOpGM9bHYEi49mcwEICyJ3iSfvHoo/fVvDNGnTzQPpeVtHYbGaZzvsIo+aOByjCBxzAfBgNVHSMEGDAWgBRysvdvSdC8s91wzC1kmaM3gALo4zAdBgNVHQ4EFgQUXGTWuUtFOLFFNFJbOULu+PhBgn0wDgYDVR0PAQH/BAQDAgeAMCcGA1UdEgQgMB6GHGh0dHBzOi8vaWFjYS50ZW5hbnQxLndhbHQuaWQwFQYDVR0lAQH/BAswCQYHKIGMXQUBAjA1BgNVHR8ELjAsMCqgKKAmhiRodHRwczovL2NybC50ZW5hbnQxLndhbHQuaWQvaWFjYS5jcmwwCgYIKoZIzj0EAwIDSAAwRQIgBsfq7TjUHVPTUiTVtCDKQRCdXzp6/eRzqOKH7YaIo9cCIQDreIKTeCNxH5vJCeP4iGFDbaKWuIIGbO1/32bwV/CvOQ==" ] }, "format": "mso_mdoc", "mso": { "version": "1.0", "digestAlgorithm": "SHA-256", "valueDigests": { "org.iso.18013.5.1": { "0": [ -108, -117, 118, 36, 45, -21, 13, -70, -21, -23, -84, -41, -28, 101, -113, 48, 108, -122, -85, 32, -7, -127, 57, -14, 1, 41, 55, -18, -112, -31, 101, 6 ], "1": [ -116, 67, -77, -31, -29, -104, -96, 86, 114, -37, -68, 43, 101, 74, -94, -49, -107, -93, 19, 93, 41, 54, -112, -70, 50, -32, -52, 103, -41, 29, -5, -109 ], "2": [ -24, -58, -116, 33, -63, -107, -5, -39, -117, -66, 37, 0, -42, 87, 1, 107, 127, 20, 55, 67, 127, -93, 84, -25, -35, 94, 96, -63, -101, 22, 86, -99 ], "3": [ 113, -39, 125, -10, 94, -31, -51, -61, -123, 13, -123, 30, -126, 17, -105, -46, 0, -41, 97, -120, 4, 55, -75, -78, 68, 90, -32, 73, 33, 85, -66, -2 ], "4": [ -118, -62, -34, -121, -33, -80, -60, 43, -55, 9, 21, -53, 63, -4, 8, -123, 103, 50, -61, 115, -18, 6, 60, 82, -82, 97, -127, 9, -50, -1, -41, -43 ], "5": [ -52, -101, 44, 50, 15, -117, 113, -69, -126, -114, -63, -85, -120, 32, -97, 69, 24, 92, 74, 102, 40, -58, -77, -59, 118, 71, -37, -103, -89, -105, 68, 80 ], "6": [ -95, -8, 21, -49, -80, -69, -117, -116, -12, 35, 63, 51, -84, 67, -31, 52, 20, 76, -107, -105, 43, -32, 72, 106, 64, 36, 118, -90, 58, 55, -86, -54 ], "7": [ -38, 39, -128, -20, -51, -13, 24, -124, -115, 7, 127, 35, -100, 74, 3, 4, -125, 31, -54, -41, 103, 113, -33, -119, 33, 41, 110, -88, -124, -92, 97, 102 ], "8": [ -12, -60, -19, 55, -117, -79, 117, 67, 73, 127, 101, -36, 52, -49, 7, -116, -61, -121, 49, 117, 88, 52, -26, 11, 13, -60, 115, 27, -11, 117, -29, 73 ], "9": [ -86, -25, -122, -8, -11, 38, 116, -1, 44, 37, -37, 16, -23, 118, -18, 7, 36, 51, 1, -107, -24, 69, -36, 1, -5, -73, 103, -46, 28, 109, -112, 39 ] } }, "deviceKeyInfo": { "deviceKey": { "kty": 1, "crv": 6, "x": [ -113, 20, 48, 69, 94, -19, 29, -51, -42, -4, -103, 10, 86, -64, -39, 47, -65, -61, -76, -2, 51, -56, -46, 109, 67, -122, 43, -41, 62, -2, 49, -34 ] } }, "docType": "org.iso.18013.5.1.mDL", "validityInfo": { "signed": "2026-05-18T07:48:27.870905006Z", "validFrom": "2026-05-18T07:48:27.870905006Z", "validUntil": "2027-05-18T07:48:27.852289522Z" } } } } }, "presented_credentials": { "credential_1": [ { "type": "vc-mdocs", "credentialData": { "docType": "org.iso.18013.5.1.mDL", "org.iso.18013.5.1": { "family_name": "Doe", "given_name": "John", "birth_date": "1986-03-22" } }, "signed": "o2d2ZXJzaW9uYzEuMGlkb2N1bWVudHOBo2dkb2NUeXBldW9yZy5pc28uMTgwMTMuNS4xLm1ETGxpc3N1ZXJTaWduZWSiam5hbWVTcGFjZXOhcW9yZy5pc28uMTgwMTMuNS4xg9gYWFukaGRpZ2VzdElEAGZyYW5kb21YGHNrBg0h-k438XsLBWgoWt2dBkuog-tsNXFlbGVtZW50SWRlbnRpZmllcmtmYW1pbHlfbmFtZWxlbGVtZW50VmFsdWVjRG9l2BhYW6RoZGlnZXN0SUQBZnJhbmRvbVgYMS5zTJhv3yecfXUTCw2keU0W1Eu7PP1hcWVsZW1lbnRJZGVudGlmaWVyamdpdmVuX25hbWVsZWxlbWVudFZhbHVlZEpvaG7YGFhkpGhkaWdlc3RJRAJmcmFuZG9tWBjmhk0T0Tqfsaf8y3KaYzDeyXW6xeefoCNxZWxlbWVudElkZW50aWZpZXJqYmlydGhfZGF0ZWxlbGVtZW50VmFsdWXZA-xqMTk4Ni0wMy0yMmppc3N1ZXJBdXRohEOhASahGCFZAi8wggIrMIIB0aADAgECAhRED-gfURxpHvIfRtUe91K73mZpljAKBggqhkjOPQQDAjAsMQswCQYDVQQGEwJBVDEdMBsGA1UEAwwUbURMIElzc3VlciBJQUNBIFJvb3QwHhcNMjYwNTE4MDYxOTI2WhcNMjcwODE4MDYxOTI2WjAyMQswCQYDVQQGEwJBVDEjMCEGA1UEAwwabURMIElzc3VlciBEb2N1bWVudCBTaWduZXIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATJ-Zu0Kx1BD_gQM-DQa33yAliTXOQ06kYz1sdgSLj2ZzAQgLIneJJ-8eij99W8M0adPNA-l5W0dhsZpnO-wij5o4HKMIHHMB8GA1UdIwQYMBaAFHKy929J0Lyz3XDMLWSZozeAAujjMB0GA1UdDgQWBBRcZNa5S0U4sUU0Uls5Qu74-EGCfTAOBgNVHQ8BAf8EBAMCB4AwJwYDVR0SBCAwHoYcaHR0cHM6Ly9pYWNhLnRlbmFudDEud2FsdC5pZDAVBgNVHSUBAf8ECzAJBgcogYxdBQECMDUGA1UdHwQuMCwwKqAooCaGJGh0dHBzOi8vY3JsLnRlbmFudDEud2FsdC5pZC9pYWNhLmNybDAKBggqhkjOPQQDAgNIADBFAiAGx-rtONQdU9NSJNW0IMpBEJ1fOnr95HOo4ofthoij1wIhAOt4gpN4I3Efm8kJ4_iIYUNtopa4ggZs7X_fZvBX8K85WQKV2BhZApCmZ3ZlcnNpb25jMS4wb2RpZ2VzdEFsZ29yaXRobWdTSEEtMjU2bHZhbHVlRGlnZXN0c6Fxb3JnLmlzby4xODAxMy41LjGqAFgglIt2JC3rDbrr6azX5GWPMGyGqyD5gTnyASk37pDhZQYBWCCMQ7Ph45igVnLbvCtlSqLPlaMTXSk2kLoy4Mxn1x37kwJYIOjGjCHBlfvZi74lANZXAWt_FDdDf6NU591eYMGbFladA1ggcdl99l7hzcOFDYUeghGX0gDXYYgEN7WyRFrgSSFVvv4EWCCKwt6H37DEK8kJFcs__AiFZzLDc-4GPFKuYYEJzv_X1QVYIMybLDIPi3G7go7Bq4ggn0UYXEpmKMazxXZH25mnl0RQBlggofgVz7C7i4z0Iz8zrEPhNBRMlZcr4EhqQCR2pjo3qsoHWCDaJ4DszfMYhI0HfyOcSgMEgx_K12dx34khKW6ohKRhZghYIPTE7TeLsXVDSX9l3DTPB4zDhzF1WDTmCw3Ecxv1deNJCVggqueG-PUmdP8sJdsQ6XbuByQzAZXoRdwB-7dn0hxtkCdtZGV2aWNlS2V5SW5mb6FpZGV2aWNlS2V5owEBIAYhWCCPFDBFXu0dzdb8mQpWwNkvv8O0_jPI0m1DhivXPv4x3mdkb2NUeXBldW9yZy5pc28uMTgwMTMuNS4xLm1ETGx2YWxpZGl0eUluZm-jZnNpZ25lZMB4HjIwMjYtMDUtMThUMDc6NDg6MjcuODcwOTA1MDA2Wml2YWxpZEZyb23AeB4yMDI2LTA1LTE4VDA3OjQ4OjI3Ljg3MDkwNTAwNlpqdmFsaWRVbnRpbMB4HjIwMjctMDUtMThUMDc6NDg6MjcuODUyMjg5NTIyWlhAmRC5DQjAwaNaozniXwKmOoMscEyNwA65Y31zyoCJpiGiZxs-fk9bcsZnrDSu5ATPVEIKrf_4hjY8ODArK76q2WxkZXZpY2VTaWduZWSiam5hbWVTcGFjZXPYGEGgamRldmljZUF1dGihb2RldmljZVNpZ25hdHVyZYRDoQEnoPZYQGQUayxOBk2Wp7g7UhumPbtFSQ5h1h4jArb60UGZOsFMvG6MXHBBPd4JdOX40auhbmKNmBjinPfw96QN9XfV-Q9mc3RhdHVzAA", "docType": "org.iso.18013.5.1.mDL", "signature": { "type": "signature-cose", "signerKey": { "type": "jwk", "jwk": { "kty": "EC", "crv": "P-256", "x": "yfmbtCsdQQ_4EDPg0Gt98gJYk1zkNOpGM9bHYEi49mc", "y": "MBCAsid4kn7x6KP31bwzRp080D6XlbR2Gxmmc77CKPk" } }, "x5cList": [ "MIICKzCCAdGgAwIBAgIURA/oH1EcaR7yH0bVHvdSu95maZYwCgYIKoZIzj0EAwIwLDELMAkGA1UEBhMCQVQxHTAbBgNVBAMMFG1ETCBJc3N1ZXIgSUFDQSBSb290MB4XDTI2MDUxODA2MTkyNloXDTI3MDgxODA2MTkyNlowMjELMAkGA1UEBhMCQVQxIzAhBgNVBAMMGm1ETCBJc3N1ZXIgRG9jdW1lbnQgU2lnbmVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEyfmbtCsdQQ/4EDPg0Gt98gJYk1zkNOpGM9bHYEi49mcwEICyJ3iSfvHoo/fVvDNGnTzQPpeVtHYbGaZzvsIo+aOByjCBxzAfBgNVHSMEGDAWgBRysvdvSdC8s91wzC1kmaM3gALo4zAdBgNVHQ4EFgQUXGTWuUtFOLFFNFJbOULu+PhBgn0wDgYDVR0PAQH/BAQDAgeAMCcGA1UdEgQgMB6GHGh0dHBzOi8vaWFjYS50ZW5hbnQxLndhbHQuaWQwFQYDVR0lAQH/BAswCQYHKIGMXQUBAjA1BgNVHR8ELjAsMCqgKKAmhiRodHRwczovL2NybC50ZW5hbnQxLndhbHQuaWQvaWFjYS5jcmwwCgYIKoZIzj0EAwIDSAAwRQIgBsfq7TjUHVPTUiTVtCDKQRCdXzp6/eRzqOKH7YaIo9cCIQDreIKTeCNxH5vJCeP4iGFDbaKWuIIGbO1/32bwV/CvOQ==" ] }, "format": "mso_mdoc", "mso": { "version": "1.0", "digestAlgorithm": "SHA-256", "valueDigests": { "org.iso.18013.5.1": { "0": [ -108, -117, 118, 36, 45, -21, 13, -70, -21, -23, -84, -41, -28, 101, -113, 48, 108, -122, -85, 32, -7, -127, 57, -14, 1, 41, 55, -18, -112, -31, 101, 6 ], "1": [ -116, 67, -77, -31, -29, -104, -96, 86, 114, -37, -68, 43, 101, 74, -94, -49, -107, -93, 19, 93, 41, 54, -112, -70, 50, -32, -52, 103, -41, 29, -5, -109 ], "2": [ -24, -58, -116, 33, -63, -107, -5, -39, -117, -66, 37, 0, -42, 87, 1, 107, 127, 20, 55, 67, 127, -93, 84, -25, -35, 94, 96, -63, -101, 22, 86, -99 ], "3": [ 113, -39, 125, -10, 94, -31, -51, -61, -123, 13, -123, 30, -126, 17, -105, -46, 0, -41, 97, -120, 4, 55, -75, -78, 68, 90, -32, 73, 33, 85, -66, -2 ], "4": [ -118, -62, -34, -121, -33, -80, -60, 43, -55, 9, 21, -53, 63, -4, 8, -123, 103, 50, -61, 115, -18, 6, 60, 82, -82, 97, -127, 9, -50, -1, -41, -43 ], "5": [ -52, -101, 44, 50, 15, -117, 113, -69, -126, -114, -63, -85, -120, 32, -97, 69, 24, 92, 74, 102, 40, -58, -77, -59, 118, 71, -37, -103, -89, -105, 68, 80 ], "6": [ -95, -8, 21, -49, -80, -69, -117, -116, -12, 35, 63, 51, -84, 67, -31, 52, 20, 76, -107, -105, 43, -32, 72, 106, 64, 36, 118, -90, 58, 55, -86, -54 ], "7": [ -38, 39, -128, -20, -51, -13, 24, -124, -115, 7, 127, 35, -100, 74, 3, 4, -125, 31, -54, -41, 103, 113, -33, -119, 33, 41, 110, -88, -124, -92, 97, 102 ], "8": [ -12, -60, -19, 55, -117, -79, 117, 67, 73, 127, 101, -36, 52, -49, 7, -116, -61, -121, 49, 117, 88, 52, -26, 11, 13, -60, 115, 27, -11, 117, -29, 73 ], "9": [ -86, -25, -122, -8, -11, 38, 116, -1, 44, 37, -37, 16, -23, 118, -18, 7, 36, 51, 1, -107, -24, 69, -36, 1, -5, -73, 103, -46, 28, 109, -112, 39 ] } }, "deviceKeyInfo": { "deviceKey": { "kty": 1, "crv": 6, "x": [ -113, 20, 48, 69, 94, -19, 29, -51, -42, -4, -103, 10, 86, -64, -39, 47, -65, -61, -76, -2, 51, -56, -46, 109, 67, -122, 43, -41, 62, -2, 49, -34 ] } }, "docType": "org.iso.18013.5.1.mDL", "validityInfo": { "signed": "2026-05-18T07:48:27.870905006Z", "validFrom": "2026-05-18T07:48:27.870905006Z", "validUntil": "2027-05-18T07:48:27.852289522Z" } } } ] } } ``` **Key Response Fields:** - **id**: _String_ - The verification session ID - **setup**: _Object_ - The complete session configuration that was used to create this verification session (the `VerificationSessionSetup` from the create request) - **creationDate**: _String_ - ISO 8601 timestamp when the session was created - **expirationDate**: _String_ - ISO 8601 timestamp when the session expires (default: 10 minutes after creation). Session will expire if left unused (no presentation pushed). Once a presentation is received, the session is no longer eligible for expiry. - **status**: _String_ - The current status of the verification session. Possible values: - `"UNKNOWN"` — Session ended up in unknown flow (should be avoided) - `"ACTIVE"` — Session was created and is active (can be used) - `"UNUSED"` — Session was not used yet, but not expired (can be used) - `"IN_USE"` — AuthorizationRequest was requested by wallet - `"VALIDATING_RECEIVED_REQUEST"` — Checking if received presentation will be processed - `"PROCESSING_FLOW"` — Received presentation is being processed (validation + policy execution) - `"EXPIRED"` — Verification request expired without being utilized - `"SUCCESSFUL"` — Verification completed fully successfully (all validation & policies passed) - `"FAILED"` — Verification was unsuccessful (presentation validation or policies failed) - **attempted**: _Boolean_ - Whether the wallet has attempted to respond to the verification request - **reattemptable**: _Boolean_ - Whether the user can retry this verification session if it failed - **policies**: _Object_ - The policies configured for this session: - **vp_policies**: _Object_ (optional) - Policies to run on presentations - **vc_policies**: _Array_ (optional) - Policies to run on credentials - **specific_vc_policies**: _Object_ (optional) - Policies to run on specific credential IDs - **policy_results**: _Object_ (optional) - Detailed results for all policies applied to the verification (only present after presentation is received): - **vp_policies**: _Object_ - Results for presentation-level policies (audience check, nonce check, envelope signature), organized by credential ID. Structure: `{ "credential_id": { "policy_name": { ... } } }` - **vc_policies**: _Array_ - Results for credential-level policies (signature, expiry, schema, etc.). Each item contains `policy`, `success`, and `result`. - **specific_vc_policies**: _Object_ - Results for credential-specific policies - **overallSuccess**: _Boolean_ (computed) - Whether all policies passed - **redirects**: _Object_ (optional) - Success and error redirect URIs if configured **Note:** Instead of polling the session status, you can use **callbacks** or **Server Sent Events (SSE)** to be notified when the verification completes. See [Callbacks & SSE](https://docs.walt.id/community-stack/verifier2/callback-and-sse.md) for details. --- ## Understanding Flow Types and Core Concepts Now that you've seen verification in action, let's understand the underlying structure. ### Verification Session Structure A `VerificationSession` defines the full lifecycle: authorization request → user presentation → policy validation → final result. Every verification request follows this structure: ```json { "flow_type": "cross_device | same_device | dc_api", "core_flow": { /* common to all flows */ }, "url_config": { /* specific to cross_device and same_device */ }, "redirects": { /* optional */ } } ``` **Why this structure?** - **flow_type** determines how the credential request is delivered (QR code vs deep link vs browser API) - **core_flow** contains the credential query and policies (same regardless of delivery method) - **url_config** and **redirects** are flow-specific attributes This separation makes it easy to switch between flows without changing your credential requirements. ### Flow Type Comparison | Flow Type | Use Case | Required Attributes | User Experience | |-----------|----------|---------------------|-----------------| | `cross_device` | User scans QR code from desktop | None beyond core flow | Desktop shows QR → User scans with phone → Phone wallet opens | | `same_device` | User clicks link on mobile | `redirects` (success/error URLs) | Browser redirects to wallet → Wallet opens → User returns to browser | | `dc_api` | Browser-native credential exchange | `expectedOrigins` (array of allowed origins) | Browser shows native credential picker → User selects → Instant return | --- ## Configuration Defaults Many session parameters can be configured globally in `verifier-service.conf` and overridden per-request when needed. | Parameter | Config File Location | Request Override Path | When to Override | |-----------|---------------------|----------------------|------------------| | `urlPrefix` | `verifier-service.conf` → `urlPrefix` | `url_config.url_prefix` | Testing with ngrok, different environments per session | | `urlHost` | `verifier-service.conf` → `urlHost` | `url_config.url_host` | Custom authorization endpoints, conformance testing | | `clientId` | `verifier-service.conf` → `clientId` | `core_flow.clientId` | Different verifier identities per use case | | `clientMetadata` | `verifier-service.conf` → `clientMetadata` | `core_flow.client_metadata` | Session-specific branding, multi-tenant scenarios | | Signing key | `verifier-service.conf` → `key` | `core_flow.key` | Different signing keys per session (advanced) | | Certificate chain | `verifier-service.conf` → `x5c` | `core_flow.x5c` | Different certificate chains per session (x509 flows) | **Note:** **Best Practice:** Set defaults in `verifier-service.conf` for your primary use case, and override per-request only when necessary. This keeps your API calls clean and reduces duplication. See [Verifier Service Configuration](https://docs.walt.id/community-stack/verifier2/configurations/config-files/verifier-service.md) for detailed configuration documentation. ## Additional You can find more information on how to configure different DC API request flows, and how to interact with the Browser-based DC API in our [Digital Credential Button project](https://github.com/walt-id/waltid-identity/tree/main/waltid-applications/waltid-digital-credentials). ## Next Steps - **Apply custom policies** — See [Policies](https://docs.walt.id/community-stack/verifier2/policies/overview.md) for validation rules beyond signature verification - **Set up notifications** — Use [Callbacks & SSE](https://docs.walt.id/community-stack/verifier2/callback-and-sse.md) instead of polling for session status - **Verify other credential types** — Try [W3C VC](https://docs.walt.id/community-stack/verifier2/credential-verification/vc-oid4vp.md) or [SD-JWT VC](https://docs.walt.id/community-stack/verifier2/credential-verification/sd-jwt-vc-oid4vp.md) -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/community-stack/verifier2/policies/overview RAW: https://docs.walt.id/community-stack/verifier2/policies/overview.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Policies Verifier 2 introduces a fully parallelized validation process: - [Credential (VC) validation](https://docs.walt.id/community-stack/verifier2/policies/available-policies.md#available-policies-vc) - [Presentation (VP) validation](https://docs.walt.id/community-stack/verifier2/policies/available-policies.md#available-policies-vp) - Specific VC-Type Policies (if configured) Instead of sequential execution, VC-, VP-, and type-specific policies are executed concurrently, significantly improving performance for multi-credential presentations. Each of them can be defined in the policies object as shown below: ```json "policies": { "vp_policies": [...], "vc_policies": [...], "vc_type_policies": { "VerifiableID": [...], "BankStatementCredential": [...] } } ``` Learn more about the policy configuration [here](https://docs.walt.id/community-stack/verifier2/policies/configuration.md). -------------------------------------------------------------------------------- PAGE: Policy Configuration Levels URL: https://docs.walt.id/community-stack/verifier2/policies/configuration RAW: https://docs.walt.id/community-stack/verifier2/policies/configuration.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Policy Configuration Levels The verification policy system supports multiple levels of policy configuration, allowing fine-grained control over which policies are applied to which credentials and presentations. This document explains how policies can be configured at different scopes based on the implementation in the verifier libraries. ## Policy Configuration Structure Policies are organized into a hierarchical structure ```kotlin data class DefinedVerificationPolicies( /** Policies to run on the presentations (VP-level) */ val vp_policies: VPPolicyList? = null, /** Policies to run on all credentials (Global VC-level) */ val vc_policies: VCPolicyList? = null, /** Policies to run on specific credential query IDs (Specific VC-level) */ val specific_vc_policies: Map? = null ) ``` ## Policy Levels Explained ### 1. VP-Level Policies (`vp_policies`) **Scope:** Applied to Verifiable Presentations themselves, before credential extraction. **Purpose:** Validate presentation-level properties such as: - Presentation signature (holder binding) - Audience matching - Nonce validation - Presentation format-specific checks (e.g., SD hash for SD-JWT, device auth for mdoc) **When Applied:** - First in the verification flow - Applied to each presentation in the `vp_token` response - Format-specific: policies are organized by presentation format (`jwt_vc_json`, `dc+sd-jwt`, `mso_mdoc`) **Example Configuration:** ```json { "vp_policies": { "jwt_vc_json": [ "jwt_vc_json/audience-check", "jwt_vc_json/nonce-check", "jwt_vc_json/envelope_signature" ], "dc+sd-jwt": [ "dc+sd-jwt/audience-check", "dc+sd-jwt/nonce-check", "dc+sd-jwt/kb-jwt_signature", "dc+sd-jwt/sd_hash-check" ], "mso_mdoc": [ "mso_mdoc/device-auth", "mso_mdoc/issuer_auth", "mso_mdoc/issuer_signed_integrity", "mso_mdoc/mso" ] } } ``` **Default Behavior:** If not specified, default policies are applied based on the presentation format: - `jwt_vc_json`: audience-check, nonce-check, envelope_signature - `dc+sd-jwt`: audience-check, nonce-check, kb-jwt_signature, sd_hash-check - `mso_mdoc`: device-auth, device_key_auth, issuer_auth, issuer_signed_integrity, mso **Available Policies:** - [Available Policies (VP)](https://docs.walt.id/community-stack/verifier2/policies/available-policies.md#available-policies-vp) --- ### 2. Global VC-Level Policies (`vc_policies`) **Scope:** Applied to **all credentials** extracted from all presentations, regardless of which query they satisfy. **Purpose:** Enforce universal credential requirements such as: - Signature verification - Expiration checks - Not-before validation - Issuer allowlists - Schema validation - Webhook verification **When Applied:** - After presentations are validated and credentials are extracted - Applied to every credential in parallel - Runs regardless of which DCQL query the credential satisfies **Example Configuration:** ```json { "vc_policies": [ "signature", "expiration", "not-before", { "policy": "allowed-issuer", "allowed_issuer": ["did:example:trusted-issuer"] }, { "policy": "schema", "schema": { "type": "object", "properties": { "credentialSubject": { "type": "object" } } } } ] } ``` **Default Behavior:** If not specified, defaults to `["signature"]` (signature verification only). **Available Policies:** - [Available Policies (VC)](https://docs.walt.id/community-stack/verifier2/policies/available-policies.md#available-policies-vc) --- ### 3. Specific VC-Level Policies (`specific_vc_policies`) **Scope:** Applied to credentials that satisfy **specific DCQL credential queries**, identified by query ID. **Purpose:** Enforce credential-type-specific or query-specific requirements such as: - Schema validation for specific credential types - Issuer restrictions for specific credential queries - Custom validation rules for particular credential formats - Field-specific regex patterns **When Applied:** - After global VC policies - Only to credentials that match the specified query ID - Allows different policies for different credential types in the same session **Example Configuration:** ```json { "specific_vc_policies": { "identity_credential": [ { "policy": "schema", "schema": { "type": "object", "required": ["credentialSubject"], "properties": { "credentialSubject": { "type": "object", "required": ["name", "email"] } } } }, { "policy": "allowed-issuer", "allowed_issuer": ["did:example:identity-issuer"] } ], "drivers_license": [ { "policy": "vical", "vical": "", "enableDocumentTypeValidation": true, "enableRevocation": true } ], "age_verification": [ { "policy": "regex", "path": "$.credentialSubject.age", "regex": "^[0-9]{1,3}$" } ] } } ``` **Available Policies:** - [Available Policies (Specific VC)](https://docs.walt.id/community-stack/verifier2/policies/available-policies.md#available-policies-specific-vc) --- ## Policy Execution Flow The verification engine executes policies in the following order: ``` 1. Parse vp_token → Extract presentations ↓ 2. VP-Level Policies (vp_policies) - Validate presentation signatures, audience, nonce - Format-specific checks (SD hash, device auth, etc.) ↓ 3. Extract credentials from validated presentations ↓ 4. Global VC-Level Policies (vc_policies) - Applied to ALL credentials in parallel ↓ 5. Specific VC-Level Policies (specific_vc_policies) - Applied to credentials matching specific query IDs ↓ 6. Aggregate results and determine overall success ``` **Key Points:** - Policies at each level run **in parallel** (using coroutines) - If any policy fails, the overall verification may fail (depending on configuration) - Results are collected and aggregated into `Verifier2PolicyResults` - The `overallSuccess` flag requires all policies at all levels to succeed --- ## Complete Example Here's a complete example showing all policy levels: ```json { "dcql_query": { "credentials": [ { "id": "identity_credential", "format": "jwt_vc_json", "claims": [...] }, { "id": "drivers_license", "format": "mso_mdoc", "meta": { "doctype_value": "org.iso.18013.5.1.mDL" } } ] }, "policies": { "vp_policies": { "jwt_vc_json": [ "jwt_vc_json/audience-check", "jwt_vc_json/nonce-check", "jwt_vc_json/envelope_signature" ], "mso_mdoc": [ "mso_mdoc/device-auth", "mso_mdoc/issuer_auth", "mso_mdoc/mso" ] }, "vc_policies": [ "signature", "expiration", "not-before", { "policy": "allowed-issuer", "allowed_issuer": [ "did:example:trusted-issuer-1", "did:example:trusted-issuer-2" ] } ], "specific_vc_policies": { "identity_credential": [ { "policy": "schema", "schema": { "type": "object", "required": ["credentialSubject"], "properties": { "credentialSubject": { "type": "object", "required": ["name", "email"] } } } }, { "policy": "regex", "path": "$.credentialSubject.email", "regex": "^.+@example\.com$" } ], "drivers_license": [ { "policy": "vical", "vical": "", "enableDocumentTypeValidation": true, "enableRevocation": true } ] } } } ``` **What happens:** 1. **VP Policies:** - JWT VC JSON presentations are validated for audience, nonce, and signature - mdoc presentations are validated for device auth, issuer auth, and MSO validity 2. **Global VC Policies:** - ALL credentials (both identity and drivers license) are checked for: - Signature validity - Expiration status - Not-before validity - Issuer is in the trusted list 3. **Specific VC Policies:** - Credentials matching `identity_credential` query additionally checked for: - Schema compliance (must have credentialSubject with name and email) - Email format matches `@example.com` pattern - Credentials matching `drivers_license` query additionally checked for: - VICAL trust anchor validation - Document type validation - Revocation status --- ## Policy Result Structure Results are collected into `Verifier2PolicyResults`: ```kotlin data class Verifier2PolicyResults( val vpPolicies: Map>, val vcPolicies: List, val specificVcPolicies: Map> ) { val overallSuccess: Boolean = vpPolicies.all { it.value.all { it.value.success } } && vcPolicies.all { it.success } && specificVcPolicies.values.all { policies -> policies.all { it.success } } } ``` The `overallSuccess` property requires **all policies at all levels** to succeed for the verification to be considered successful. --- ## Use Cases ### Use Case 1: Basic Verification Apply only essential checks to all credentials: ```json { "vc_policies": ["signature", "expiration"] } ``` ### Use Case 2: Format-Specific Requirements Different policies for different credential types: ```json { "vc_policies": ["signature"], "specific_vc_policies": { "mdoc_credential": [ {"policy": "vical", "vical": "..."} ], "jwt_credential": [ {"policy": "schema", "schema": {...}} ] } } ``` ### Use Case 3: Strict Verification Comprehensive checks at all levels: ```json { "vp_policies": {...}, // All presentation checks "vc_policies": [ "signature", "expiration", "not-before", {"policy": "allowed-issuer", "allowed_issuer": [...]} ], "specific_vc_policies": { "each_query": [ {"policy": "schema", "schema": {...}} ] } } ``` --- ## Configuration via API When creating a verification session via the API (`POST /verification-session/create`), policies are specified in the `policies` field of `VerificationSessionSetup`: ```json { "flow_type": "cross_device", "core_flow": { "dcql_query": {...}, "policies": { "vp_policies": {...}, "vc_policies": [...], "specific_vc_policies": {...} } } } ``` If policies are not specified, defaults are applied: - **VP policies:** Format-specific defaults (see VP-Level Policies section) - **VC policies:** `["signature"]` (signature verification only) - **Specific VC policies:** `{}` (empty, no specific policies) -------------------------------------------------------------------------------- PAGE: Available Policies URL: https://docs.walt.id/community-stack/verifier2/policies/available-policies RAW: https://docs.walt.id/community-stack/verifier2/policies/available-policies.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Available Policies ## Available Policies (VC) ### `expiration` Checks if a credential has expired based on its `notAfter` claim. **Use case:** Ensure credentials are still valid and haven't expired. **Example:** ```json { "policy": "expiration" } ``` --- ### `not-before` Validates that a credential is not being used before its `notBefore` or `issuedAt` date. **Use case:** Prevent premature use of credentials that have future validity dates. **Example:** ```json { "policy": "not-before" } ``` --- ### `schema` Validates credential data against a JSON Schema. **Use case:** Ensure credential structure and data types match expected schema. **Example:** ```json { "policy": "schema", "schema": { "type": "object", "properties": { "credentialSubject": { "type": "object", "properties": { "name": { "type": "string" } } } } }, "defaultType": null } ``` --- ### `signature` Verifies the cryptographic signature of a credential. **Use case:** Ensure credential authenticity and integrity. **Example:** ```json { "policy": "signature" } ``` --- ### `allowed-issuer` Validates that the credential issuer is in an allowed list. **Use case:** Restrict credentials to trusted issuers only. **Example:** ```json { "policy": "allowed-issuer", "allowed_issuer": "did:example:issuer123" } ``` **Multiple issuers:** ```json { "policy": "allowed-issuer", "allowed_issuer": ["did:example:issuer1", "did:example:issuer2"] } ``` --- ### `regex` Validates credential data at a specific path using a regular expression. **Use case:** Pattern matching for specific credential fields (e.g., email format, ID numbers). **Example:** ```json { "policy": "regex", "path": "$.credentialSubject.email", "regex": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$", "regex_options": ["IGNORE_CASE"], "allowNull": false } ``` --- ### `credential-status` Checks credential status for different status list formats. **Use case:** Verify credential hasn't been revoked or suspended. **Example (W3C StatusList2021):** ```json { "policy": "credential-status", "argument": { "discriminator": "w3c", "value": 0, "purpose": "revocation", "type": "StatusList2021" } } ``` **Example (TokenStatusList):** ```json { "policy": "credential-status", "argument": { "discriminator": "ietf", "value": 0 } } ``` **Example (Multiple allowed status values):** You can specify multiple allowed status values using the `values` field. This is useful when you want to accept credentials that are in any of several valid states. ```json { "policy": "credential-status", "argument": { "discriminator": "ietf", "values": [0, 1] } } ``` ```json { "policy": "credential-status", "argument": { "discriminator": "w3c", "values": [0, 1], "purpose": "revocation", "type": "BitstringStatusList" } } ``` **Example (Multiple BitstringStatusList entries):** ```json { "policy": "credential-status", "argument": { "discriminator": "w3c-list", "list" : [ { "value": 0, "purpose": "revocation", "type": "BitstringStatusList" }, { "value": 0, "purpose": "suspension", "type": "BitstringStatusList" } ] } } ``` **Note:** **Note:** You can use either `value` (single value) or `values` (array of values), but at least one must be provided. When using `values`, the credential status is considered valid if it matches any of the specified values. --- ### `revoked-status-list` Checks revocation status using W3C StatusList2021 format. **Use case:** Quick revocation check for StatusList2021 credentials. **Example:** ```json { "policy": "revoked-status-list" } ``` --- ### `vical` Validates mdoc credentials using VICAL (Verified Issuer Certificate Authority List) data. **Use case:** Verify mdoc/mDL authenticity and certificate chain using trusted VICAL data. This policy validates the credential's X.509 certificate chain against a list of trusted Issuing Authority Certificate Authorities (IACAs). **Note:** **Background:** VICAL is part of the ISO/IEC 18013-5 standard trust infrastructure for mobile documents. It provides a cryptographically signed list of trusted issuer certificate authorities. Learn more about [VICALs and IACAs in the concepts section](https://docs.walt.id/concepts/trust-systems/iso-trust-vicals.md). **Info:** **mdoc/mDL Only:** This policy can currently only be applied to mdoc credentials (ISO 18013-5 format). It will fail if applied to other credential formats like JWT or SD-JWT. #### Configuration Parameters The VICAL policy requires either `vical` or `vicalUrl` (exactly one must be provided): **VICAL Source (choose one):** - `vical`: _String_ - Base64-encoded VICAL CBOR data. Use this for inline trust lists embedded directly in the policy configuration. - `vicalUrl`: _String (optional)_ - HTTP(S) URL from which to fetch the VICAL at verification time. The endpoint must return the raw VICAL CBOR bytes (either as binary `application/cbor` or hex-encoded text). This is the **preferred approach in production** as it decouples policy configuration from the trust list and allows updates without redeploying configuration. **Validation Options:** - `enableDocumentTypeValidation`: _Boolean_ - When `true`, filters VICAL trust anchors to only those that authorize the specific document type (e.g., `org.iso.18013.5.1.mDL`). When `false`, uses all trust anchors from the VICAL regardless of document type. Default: `false`. - `enableTrustedChainRoot`: _Boolean_ - When `true`, requires the certificate chain to include a trusted self-signed root certificate. When `false`, chain validation is more lenient. Default: `false`. - `enableSystemTrustAnchors`: _Boolean_ - When `true`, uses the system's built-in trust anchors (operating system certificate stores) in addition to VICAL trust anchors. When `false`, relies only on VICAL trust anchors. Default: `false`. - `enableRevocation`: _Boolean_ - When `true`, performs revocation checks on certificates in the chain (requires network access to CRL/OCSP endpoints). When `false`, skips revocation checking. Default: `false`. #### Example: Inline VICAL (Base64) ```json { "policy": "vical", "vical": "o2d2ZXJzaW9uAWx2aWNhbERhdGGCpmhkb2NUeXBlgXhfb3JnLmlzby4xODAxMy41LjEubURMamNlcnRpZmljYXRlWQOuMIID...", "enableDocumentTypeValidation": true, "enableTrustedChainRoot": true } ``` **Note:** If you have a VICAL CBOR file, encode it using: `base64 < austroads.cbor`. Remove any newlines from the output before using it in JSON. #### Example: VICAL URL (Recommended) ```json { "policy": "vical", "vicalUrl": "https://vical.example.com/api/latest", "enableDocumentTypeValidation": true, "enableTrustedChainRoot": true, "enableSystemTrustAnchors": false, "enableRevocation": false } ``` **Note:** **Enterprise Stack:** If you're using the walt.id Enterprise Stack, the [VICAL Service](https://docs.walt.id/enterprise-stack/services/vical-service/overview.md) provides managed VICAL endpoints. Use the `/v1/{target}/vical-service-api/latest` endpoint as your `vicalUrl`. --- ### `webhook` Calls an external HTTP endpoint to verify the credential. **Use case:** Delegate verification to external systems or custom validation logic. **Example:** ```json { "policy": "webhook", "url": "https://api.example.com/verify", "basicauth_username": "user", "basicauth_password": "pass", "bearerauth_token": null } ``` **Bearer token authentication:** ```json { "policy": "webhook", "url": "https://api.example.com/verify", "bearerauth_token": "your-token-here" } ``` ## Available Policies (VP) Policies are organized by presentation format. All policies can be referenced by their ID string or configured as JSON objects. ### JWT VC JSON Policies #### `jwt_vc_json/audience-check` Validates that the presentation audience matches the expected audience for the verification session. **Use case:** Ensure the presentation is intended for the correct verifier. **Example:** ```json { "policy": "jwt_vc_json/audience-check" } ``` --- #### `jwt_vc_json/nonce-check` Validates that the presentation nonce matches the expected nonce for the verification session. **Use case:** Prevent replay attacks by ensuring the presentation was created for this specific session. **Example:** ```json { "policy": "jwt_vc_json/nonce-check" } ``` --- #### `jwt_vc_json/envelope_signature` Verifies the presentation envelope signature using the holder's public key. **Use case:** Ensure the presentation was signed by the holder and hasn't been tampered with. **Example:** ```json { "policy": "jwt_vc_json/envelope_signature" } ``` --- ### SD-JWT Policies #### `dc+sd-jwt/audience-check` Validates that the SD-JWT presentation audience matches the expected audience for the verification session. **Use case:** Ensure the SD-JWT presentation is intended for the correct verifier. **Example:** ```json { "policy": "dc+sd-jwt/audience-check" } ``` --- #### `dc+sd-jwt/nonce-check` Validates that the SD-JWT presentation nonce matches the expected nonce for the verification session. **Use case:** Prevent replay attacks by ensuring the presentation was created for this specific session. **Example:** ```json { "policy": "dc+sd-jwt/nonce-check" } ``` --- #### `dc+sd-jwt/kb-jwt_signature` Verifies the Key Binding JWT (KB-JWT) signature using the holder's public key. **Use case:** Ensure the KB-JWT was signed by the holder and binds the presentation to the holder's key. **Example:** ```json { "policy": "dc+sd-jwt/kb-jwt_signature" } ``` --- #### `dc+sd-jwt/sd_hash-check` Verifies SD-JWT key binding by recalculating and comparing the SD hash. **Use case:** Ensure the presentation hash matches the expected hash, validating key binding integrity. **Example:** ```json { "policy": "dc+sd-jwt/sd_hash-check" } ``` --- ### mdoc Policies #### `mso_mdoc/device-auth` Verifies device authentication using device signature or MAC. **Use case:** Ensure the mdoc presentation was authenticated by the device that holds the credential. **Example:** ```json { "policy": "mso_mdoc/device-auth" } ``` --- #### `mso_mdoc/device_key_auth` Verifies holder-verified data authorization using KeyAuthorization from the MSO. **Use case:** Ensure holder-verified data elements are authorized according to the issuer's KeyAuthorization. **Example:** ```json { "policy": "mso_mdoc/device_key_auth" } ``` --- #### `mso_mdoc/issuer_auth` Verifies issuer authentication by validating the COSE_Sign1 signature using the issuer's certificate chain. **Use case:** Ensure the issuer-signed data was authenticated by the credential issuer. **Example:** ```json { "policy": "mso_mdoc/issuer_auth" } ``` --- #### `mso_mdoc/issuer_signed_integrity` Verifies issuer-signed data integrity by comparing value digests with the MSO. **Use case:** Ensure issuer-signed data hasn't been tampered with by validating digest hashes. **Example:** ```json { "policy": "mso_mdoc/issuer_signed_integrity" } ``` --- #### `mso_mdoc/mso` Verifies the Mobile Security Object (MSO) validity, including timestamps and digest algorithm support. **Use case:** Ensure the MSO is valid and within its validity period. **Example:** ```json { "policy": "mso_mdoc/mso" } ``` -------------------------------------------------------------------------------- PAGE: Callback and SSE URL: https://docs.walt.id/community-stack/verifier2/callback-and-sse RAW: https://docs.walt.id/community-stack/verifier2/callback-and-sse.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Callback and SSE The verifier service can be configured to use a callback or Server Sent Events (SSE) to be notified when the verification session is completed. ## Callback The callback is a URL that will be called when the verification session is completed. The callback will be called with the verification session ID and the verification session object. **Note:** In previous versions of the verifier service, the callback information was passed through headers. This is no longer supported and the required configuration is now passed in the body of the request. Example Request: ```json "core_flow": { "dcql_query": { "credentials": [ { "id": "example_openbadge_jwt_vc", "format": "jwt_vc_json", "meta": { "type_values": [ [ "VerifiableCredential", "OpenBadgeCredential" ] ] }, "claims": [ { "path": [ "name" ] } ] } ] }, "webhook": { "url": "https://example.com/callback", "basicAuthUser": "1234567890", "basicAuthPass": "1234567890", "bearerToken": "1234567890" } } ``` **Body Parameters** - **dcql_query**: The DCQL query to be used for the verification. - **webhook**: The webhook configuration to be used for the verification. - **url**: The URL to be called when the verification session is completed. For authentication, you can use basic authentication or bearer token authentication. - **basicAuthUser**: The username for basic authentication. - **basicAuthPass**: The password for basic authentication. OR - **bearerToken**: The bearer token for bearer authentication. ## Server Sent Events (SSE) The SSE is a stream of events that will be sent to the client when the verification session is completed. The events will be sent to the client via a WebSocket connection. From the desired client, you can subscribe to the SSE stream by using the following endpoint: ```bash curl -X 'GET' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{verificationSessionID}/verifier2-service-api/verification-session/events' \ -H 'Authorization: Bearer {yourToken}' ``` **Path Parameters** - **orgID**: The organization ID. - **verificationSessionID**: The target indicates the verification session to target, generally given as {target}.{sessionID} (e.g. org.tenant.verifier.61566cec-23ea-4fca-9049-5c8b316e3271) which is returned in the `creationTarget` parameter when creating the verification session. ## Verification Session Info As is the case with polling the verification session info endpoint, both the callback andthe SSE stream will return the verficiation session object containing the following useful fields: - `status`: The status of the verification session. - `attempted`: Whether the verification session has been attempted. - `reattemptable`: Whether the verification session can be retried. - `policyResults`: The results of the policies applied to the verification session. - `presentedRawData`: The raw data verifiable presentation token. - `presentedCredentials`: The parsed credentials presented by the user. - `failure`: Structured detail of *why* a session ended in `FAILED` (see below). `null` for successful or in-progress sessions. Policy results will be split into vcPolicy and vpPolicy results. More information about the policy results can be found [here](https://docs.walt.id/community-stack/verifier2/policies/overview.md). ## Structured failure detail When `status` is `FAILED`, the session carries a `failure` object describing the cause. The `type` discriminator distinguishes the failure modes, and each subtype carries detail specific to that mode. `statusReason` may contain a human-readable summary for quick display; use `failure.reason` for the canonical failure explanation. All existing webhook fields keep their shape — `failure` is a new optional field. Consumers that only read `status` / `statusReason` / `policyResults` keep working unchanged. ### VC policy violation (per-credential attribution) When a `vc_policies` check fails, each affected policy result carries `query_id` + `credential_index` so the failure can be tied back to the specific credential that failed. The same attributed policy result entries are exposed in `failure.violations`. ```json { "event": "credential_policy_results_available", "session": { "status": "FAILED", "policy_results": { "vc_policies": [ { "query_id": "pid", "credential_index": 1, "policy": {"id": "signature"}, "success": false, "error": "Signature verification failed" } ] }, "failure": { "type": "vc_policy_violations", "reason": "1 credential policy violation(s)", "violations": [ { "query_id": "pid", "credential_index": 1, "policy": {"id": "signature"}, "success": false, "error": "Signature verification failed" } ] } } } ``` ### DCQL fulfillment failure When the presented credentials do not satisfy the DCQL query (missing required IDs or unsatisfied required `credential_set` options), `failure.failure` lists exactly which queries or sets were unmet. ```json { "event": "dcql_fulfillment_check_failed", "session": { "status": "FAILED", "failure": { "type": "dcql_fulfillment", "reason": "DCQL Fulfillment Failed: 1 required CredentialSet(s) not satisfied. Unsatisfied sets: [[[pid], [mdl]]], Successfully validated query IDs: [email]", "failure": { "missing_query_ids": [], "unsatisfied_sets": [ { "options": [["pid"], ["mdl"]] } ], "successfully_validated_query_ids": ["email"] } } } } ``` ### Presentation validation failure When one or more VP policies fail (e.g. bad signature, wrong audience), `failure.failed_policies` mirrors `presentation_validation_results` but filtered to only the failed entries. ```json { "event": "presentation_validation_failed", "session": { "status": "FAILED", "failure": { "type": "presentation_validation", "reason": "Presentation validation failed: ...", "failed_policies": { "pid": { "jwt_vc_json/envelope_signature": { "success": false, "errors": [ { "error": "InvalidSignature", "message": "..." } ] } } } } } } ``` ### Wallet-initiated rejection (OpenID4VP §8.5) Wallets can POST an OpenID4VP [§8.5 error response](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html#section-8.5) to the `direct_post` endpoint instead of a `vp_token` — for example when the user refuses to share credentials. `WalletPresentFunctionality2.walletRejectHandling` can create these rejection responses, and the verifier accepts both url-encoded (`error=access_denied&error_description=…&state=…`) and JSON bodies. When the original request included `state`, the error response must echo the same `state`. Session transitions to `FAILED` with `failure.type = "wallet_error_response"`, and the webhook emits the new `wallet_error_response_received` event. If the verifier session was configured with `redirects.errorRedirectUri`, the `direct_post` response contains that URL as `redirect_uri` so the wallet can send the user back to the verifier's error page. ```json { "event": "wallet_error_response_received", "session": { "status": "FAILED", "statusReason": "Wallet returned OID4VP error: access_denied", "failure": { "type": "wallet_error_response", "reason": "Wallet returned OID4VP error response per §8.5", "error": "access_denied", "error_description": "User denied", "state": "abc123" } } } ``` **State echo**: when the original Authorization Request included a `state`, the wallet error response **must** echo the same `state`. Missing or mismatched state is rejected with `invalid_request` (HTTP 400) — matching the OAuth 2.0 / §8.5 contract. **Idempotency**: once the session has reached a terminal state (`SUCCESSFUL` or `FAILED`), a duplicate or late wallet error POST is acknowledged with HTTP 200 but does **not** overwrite the existing `status` / `statusReason` / `failure`. This prevents a replayed or malicious error POST from flipping a successful verification to failed. **Wallet helper**: use `WalletPresentFunctionality2.walletRejectHandling(...)` to produce the rejection response on the wallet side. The helper supports `fragment`, `query`, `form_post`, `direct_post`, and `direct_post.jwt` response modes, and accepts either the typed `OID4VPErrorCode` enum (recommended) or a raw `String` error code for forward compatibility with codes not yet in the enum. For `direct_post.jwt`, the helper sends the unencrypted form-encoded error response to `response_uri`, which OpenID4VP 1.0 permits when the Wallet cannot generate an encrypted response. -------------------------------------------------------------------------------- PAGE: Feature Manager URL: https://docs.walt.id/community-stack/verifier2/configurations/feature-manager RAW: https://docs.walt.id/community-stack/verifier2/configurations/feature-manager.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Feature Manager Use the feature manager to control which Verifier2 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 always enabled and do not require additional configuration in most cases. They provide critical functionality expected to be available by default. ### Optional Features Optional features can be enabled or disabled based on your requirements. 1. **Enabled by default**: These features work without additional configuration (e.g. no extra config file needed). 2. **Disabled by default**: These features must be explicitly enabled in `_features.conf`. They may require their own configuration to function. **Note:** Each feature may have zero, one, or multiple configuration files associated with it. ## How the Feature Manager Works The Feature Manager ensures efficient and error-free service initialisation through the following steps: 1. **Initialisation with Feature Catalogs**: Services are passed one or more Feature Catalogs on startup. 2. **Determination of Active Features**: The initialiser identifies explicitly enabled or disabled features and resolves the default states of optional features. 3. **Loading Configurations**: Configurations for base features and explicitly enabled optional features are loaded. **This results in:** - Only necessary configurations being loaded, reducing complexity and avoiding unused features. - Minimal required configuration through sensible defaults. - Invalid configurations being caught during initialisation before they can reach production. ## Feature Configuration Configure features by updating the `_features.conf` file as explained [here](https://docs.walt.id/community-stack/verifier2/configurations/config-files/features.md). -------------------------------------------------------------------------------- PAGE: Features URL: https://docs.walt.id/community-stack/verifier2/configurations/config-files/features RAW: https://docs.walt.id/community-stack/verifier2/configurations/config-files/features.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Features With `_features.conf` you can control which Verifier2 API features are enabled. They are managed via the [feature manager](https://docs.walt.id/community-stack/verifier2/configurations/feature-manager.md). ## Available Features ### Base Features Base features are always enabled. They do not need to be listed in `_features.conf`. - `verifier-service` — Core verification service implementation. Handles verification session creation, authorization request generation, and VP response processing. ### Optional Features - `dev-mode` — Development mode. Disabled by default. When enabled, loads additional settings from `dev-mode.conf` (e.g. allows DID Web resolution over plain HTTP). See [dev-mode.conf](https://docs.walt.id/community-stack/verifier2/configurations/config-files/dev-mode.md) for details. - `transaction-data-profiles` — Transaction-data type registry and discovery endpoint. Enabled by default. Loads `transaction-data-profiles.conf` and exposes `GET /transaction-data-profiles`. See [transaction-data-profiles.conf](https://docs.walt.id/community-stack/verifier2/configurations/config-files/transaction-data-profiles.md) for details. ## Example Setup ```editorconfig[_features.conf] enabledFeatures = [ dev-mode ] disabledFeatures = [ # ... ] ``` -------------------------------------------------------------------------------- PAGE: Web URL: https://docs.walt.id/community-stack/verifier2/configurations/config-files/web RAW: https://docs.walt.id/community-stack/verifier2/configurations/config-files/web.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Web With `web.conf` you can configure the host and port the Verifier2 API binds to for local network connections. ### `webHost` _String — default: `"0.0.0.0"`_ The network interface the service binds to. `0.0.0.0` listens on all available interfaces. ### `webPort` _String — default: `"7003"`_ The port the service listens on. **Note:** When running via Docker Compose alongside Verifier (v1), Verifier2 is mapped to port `7004` on the host to avoid conflicts — but the service itself still binds to `7003` internally. When running Verifier2 standalone, the default port `7003` applies end-to-end. ## Example ```editorconfig[web.conf] webHost = "0.0.0.0" webPort = "7003" ``` -------------------------------------------------------------------------------- PAGE: Verifier Service URL: https://docs.walt.id/community-stack/verifier2/configurations/config-files/verifier-service RAW: https://docs.walt.id/community-stack/verifier2/configurations/config-files/verifier-service.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Verifier Service `verifier-service.conf` configures the service-level defaults for the Verifier2 API — its identity as presented to wallets, the URLs used in authorization requests, and the optional signing key and certificate chain. **Note:** All fields in this file act as **defaults**. Every value can be overridden per-request in the request body when creating a verification session. ## Fields ### `clientId` _String_ — The identifier the verifier presents to wallets during the credential exchange. The following formats are supported (defined in OID4VP 1.0, §5.9.3): - **Plain string** — e.g. `"verifier2"`: used as-is in the authorization request. Suitable for development and use cases where the verifier does not need a verifiable identity. - **`x509_san_dns:`** — e.g. `"x509_san_dns:verifier.example.com"`: the client identity is bound to the DNS name in the certificate's Subject Alternative Name extension. Wallets verify the signed request against the SAN in the provided certificate. Use together with `key` and `x5c`. - **`x509_hash:`** — the client identity is bound to the SHA-256 hash of the DER-encoded leaf certificate. Wallets verify the signed request and check that the certificate matches the hash. Use together with `key` and `x5c`. Required by the HAIP profile. Both `x509_san_dns` and `x509_hash` are general OID4VP client identifier schemes and can be used with any signed OID4VP request (cross-device, same-device, or DC API). For unsigned DC API flows (`signed_request: false`), `clientId` is automatically omitted from the authorization request regardless of this setting. ### `clientMetadata` _Object_ — Metadata about the verifier that wallets may display to users during the presentation flow. Corresponds to the `client_metadata` parameter defined in OID4VP and [RFC 7591](https://www.rfc-editor.org/rfc/rfc7591). All fields within `clientMetadata` are optional. Fields that support internationalisation can have language-tagged variants using the format `"field_name#language-tag"` (e.g. `"client_name#fr-FR"`). Language tags follow [BCP 47](https://www.rfc-editor.org/rfc/bcp/bcp47.txt). | Field | Type | Description | |---|---|---| | `client_name` | String | Human-readable name of the verifier. | | `logo_uri` | String | URL pointing to the verifier's logo image. | | `tos_uri` | String | URL to a human-readable Terms of Service document. | | `policy_uri` | String | URL to a human-readable Privacy Policy document. | | `client_uri` | String | URL to the verifier's homepage. | | `vp_formats_supported` | Object | Credential and proof formats the verifier accepts (per OID4VP §11.1). If not set, the service automatically injects a default declaring `mso_mdoc` support with ES256, -9, and -50 issuer auth algorithms. | | `jwks` | Object | JWK Set for wallet response encryption (per RFC 7517). For DC API flows with encrypted response (`encrypted_response: true`), this field is **ignored** — the service generates an ephemeral P-256 ECDH-ES key automatically. | | `encrypted_response_enc_values_supported` | Array | JWE `enc` algorithms supported for encrypting the VP response. For DC API encrypted response flows, this is **hardcoded to `["A128GCM"]`** by the service and any configured value is overridden. | ### `urlPrefix` _String_ — The base URL the service will use to construct the `request_uri` and `response_uri` endpoints returned to wallets. Wallets fetch the authorization request from `{urlPrefix}/{sessionId}/request` and post their VP response to `{urlPrefix}/{sessionId}/response`. Must be publicly reachable by the wallet. **Error:** `urlPrefix` is only used for OID4VP flows (cross-device and same-device). For DC API flows, this field must be absent — passing it with a DC API request will cause session creation to fail with a validation error. Example: `"http://localhost:7003/verification-session"` ### `urlHost` _String_ — The base URL used to construct the QR code link or deep link handed to the wallet. The authorization request URL is built by appending the authorization request parameters to this value. - Use `"openid4vp://authorize"` for OID4VP flows where the wallet is invoked via a deep link or QR code scan. - Use a full HTTPS URL for conformance testing with a browser-based authorization endpoint. For DC API flows, `urlHost` is not used from config — it is derived from `expectedOrigins` (Annex D) or `origin` (Annex C) provided in the request body. ### `key` _(optional)_ _JWK Object_ — A JSON Web Key used to sign the authorization request JWT. The key must be provided in JWK format wrapped in a `{"type":"jwk","jwk":{...}}` object. **Note:** The `key` field does not control whether requests are signed. Signing is enabled per-request by setting `signed_request: true` in the verification session setup. When `signed_request: true`, a signing key is **required** — either configured here or supplied per-request. If neither is present, session creation will fail. ### `x5c` _(optional)_ _Array of Strings_ — An ordered X.509 certificate chain in DER-encoded, Base64-encoded format. The first certificate must correspond to the `key`. Required when using `x509_san_dns:` or `x509_hash:` as the `clientId` format, as wallets will use the certificate chain to verify the verifier's identity. For signed Annex C (ISO 18013-7) requests, `x5c` is explicitly required — session creation will fail without it. ## Full Example ```editorconfig[verifier-service.conf] clientId: "verifier2" # clientId: "x509_san_dns:verifier.example.com" # clientId: "x509_hash:" clientMetadata: { client_name: "My Verifier" "client_name#fr-FR": "Mon Vérificateur" logo_uri: "https://example.com/logo.png" tos_uri: "https://example.com/terms" "tos_uri#fr-FR": "https://example.com/terms-fr" policy_uri: "https://example.com/privacy" "policy_uri#fr-FR": "https://example.com/privacy-fr" client_uri: "https://example.com" } # Used for OID4VP flows only. Not applicable to DC API flows. urlPrefix: "http://localhost:7003/verification-session" urlHost: "openid4vp://authorize" # Required when signed_request: true is set in the verification session setup. # key: {"type":"jwk","jwk":{"kty":"EC","crv":"P-256",...}} # Required when using x509_san_dns or x509_hash clientId formats. # x5c: ["MIIBVz..."] ``` -------------------------------------------------------------------------------- PAGE: Dev Mode URL: https://docs.walt.id/community-stack/verifier2/configurations/config-files/dev-mode RAW: https://docs.walt.id/community-stack/verifier2/configurations/config-files/dev-mode.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Dev Mode `dev-mode.conf` contains settings that are only relevant in local development and testing environments. This file is only loaded when the `dev-mode` feature is explicitly enabled in [`_features.conf`](https://docs.walt.id/community-stack/verifier2/configurations/config-files/features.md). ## Fields ### `enableDidWebResolverHttps` _Boolean — default: `true`_ Controls whether the DID Web resolver enforces HTTPS. When `true`, only HTTPS DID Web endpoints are accepted. Set to `false` in local development environments where HTTPS is not available. This field has no effect unless the `dev-mode` feature is enabled in `_features.conf`. **Note:** The code default is `true` (HTTPS enforced). The example `dev-mode.conf` shipped in the repository sets this to `false`, which is the typical local development value. If you enable dev-mode but do not provide a `dev-mode.conf`, HTTPS will be enforced. ## Example ```editorconfig[dev-mode.conf] # Allow HTTP for local DID Web resolution (typical dev setup) enableDidWebResolverHttps = false ``` ## Enabling Dev Mode Add `dev-mode` to the `enabledFeatures` list in `_features.conf`. The `dev-mode.conf` file is only loaded when this feature is active — it has no effect otherwise. ```editorconfig[_features.conf] enabledFeatures = [ dev-mode ] ``` -------------------------------------------------------------------------------- PAGE: Transaction Data Profiles URL: https://docs.walt.id/community-stack/verifier2/configurations/config-files/transaction-data-profiles RAW: https://docs.walt.id/community-stack/verifier2/configurations/config-files/transaction-data-profiles.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Transaction Data Profiles `transaction-data-profiles.conf` defines the transaction data types the verifier and wallet recognize for binding presentations to specific actions (e.g., payment authorization, account access). Transaction data types act as namespaces that: - Enforce which types the wallet will accept in authorization requests - Define the structure and fields for UI/discovery endpoints - Are validated by the wallet's `TransactionDataTypeRegistry` The `transaction-data-profiles` feature is enabled by default for Verifier2 and backs the runtime discovery endpoint: `GET /transaction-data-profiles` The same profile format is also used by the walt.id Wallet API, which exposes: `GET /wallet-api/transaction-data-profiles` ## Fields ### `transactionDataProfiles` _Array of Objects_ — List of recognized transaction data types. Each profile defines: | Field | Type | Description | |---|---|---| | `type` | String | **Required.** The transaction data type identifier. Used as the `type` field in transaction data entries and as the namespace for mdoc DeviceSigned binding. Example: `"org.waltid.transaction-data.payment-authorization"` | | `displayName` | String | Human-readable name for this type. Used in UIs and discovery endpoints. Defaults to `type` if not specified. | | `fields` | Array | List of expected field names for this type. Used for documentation and UI generation. Example: `["amount", "currency", "payee"]` | ## Built-in Profiles The default configuration includes two profiles: ```editorconfig[transaction-data-profiles.conf] transactionDataProfiles = [ { type = "org.waltid.transaction-data.payment-authorization" displayName = "Payment Authorization" fields = ["amount", "currency", "payee"] }, { type = "org.waltid.transaction-data.account-access" displayName = "Account Access" fields = ["account_identifier", "access_scope"] } ] ``` ## Adding Custom Types To add a custom transaction data type: 1. Add a new entry to the `transactionDataProfiles` array in both the verifier and wallet config files: - Verifier: `waltid-services/waltid-verifier-api2/config/transaction-data-profiles.conf` - Wallet: `waltid-services/waltid-wallet-api/config/transaction-data-profiles.conf` 2. Restart both services to load the new configuration **Example:** Adding a subscription authorization type: ```editorconfig[transaction-data-profiles.conf] transactionDataProfiles = [ { type = "org.waltid.transaction-data.payment-authorization" displayName = "Payment Authorization" fields = ["amount", "currency", "payee"] }, { type = "org.waltid.transaction-data.account-access" displayName = "Account Access" fields = ["account_identifier", "access_scope"] }, { type = "org.example.subscription-authorization" displayName = "Subscription Authorization" fields = ["service_name", "billing_cycle", "price"] } ] ``` **Info:** **Important:** Transaction data types must be configured on **both** the verifier and wallet. Mismatched configurations will cause the wallet to reject authorization requests with unknown types. ## Usage in Verification Requests Once configured, use the `type` in your verification session transaction data: ```json { "flow_type": "cross_device", "core_flow": { "dcql_query": { ... } }, "openid": { "transactionData": [ { "type": "org.example.subscription-authorization", "credential_ids": ["pid"], "require_cryptographic_holder_binding": true, "transaction_data_hashes_alg": ["sha-256"], "service_name": "Premium Streaming", "billing_cycle": "monthly", "price": "12.99" } ] } } ``` See [Transaction Data Authorization](https://docs.walt.id/community-stack/verifier2/credential-verification/sd-jwt-vc-oid4vp.md#transaction-data-authorization) for complete usage details. -------------------------------------------------------------------------------- PAGE: Full API Reference URL: https://docs.walt.id/community-stack/verifier2/full-api-reference RAW: https://docs.walt.id/community-stack/verifier2/full-api-reference.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Full API Reference Find it [here](https://verifier2.demo.walt.id/swagger/index.html) -------------------------------------------------------------------------------- PAGE: Migrating from Verifier (v1) URL: https://docs.walt.id/community-stack/verifier2/migration-from-verifier1 RAW: https://docs.walt.id/community-stack/verifier2/migration-from-verifier1.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Migrating from Verifier (v1) to Verifier2 This guide helps you migrate from the original Verifier service (which supports OID4VP Draft 14 and Draft 20) to Verifier2 (which supports OID4VP 1.0 final specification). **Error:** The original Verifier service is deprecated and will be removed in Q2 2026. We recommend migrating to Verifier2 for all new projects and planning migration for existing production deployments. --- ## Key Differences at a Glance | Aspect | Verifier (v1) | Verifier2 | |--------|--------------|-----------| | **OID4VP Version** | Draft 14, Draft 20 | 1.0 (final specification) | | **Port (Docker Compose)** | 7003 | 7004 | | **Port (Standalone)** | 7003 | 7003 | | **Request Structure** | Single flat request body | Structured: `flow_type` + `core_flow` + flow-specific attributes | | **Credential Query** | `request_credentials` | `dcql_query` (DCQL standard) | | **Policies** | Separate `vp_policies` and `vc_policies` arrays | Nested under `policies` object | | **URL Configuration** | Headers (`authorizeBaseUrl`, `successRedirectUri`, `errorRedirectUri`) | Request body fields (`url_config`, `redirects`) | | **Flow Selection** | Implicit (via `preset` or config) | Explicit via `flow_type` field | | **Session Endpoint** | `POST /openid4vc/verify` | `POST /verification-session/create` | --- ## Request Structure Changes ### Verifier (v1) Request Example ```bash curl -X 'POST' \ 'http://localhost:7003/openid4vc/verify' \ -H 'authorizeBaseUrl: openid4vp://authorize' \ -H 'successRedirectUri: https://example.com/success' \ -H 'errorRedirectUri: https://example.com/error' \ -H 'Content-Type: application/json' \ -d '{ "request_credentials": [ { "format": "jwt_vc_json", "type": ["VerifiableCredential", "OpenBadgeCredential"] } ], "vp_policies": [], "vc_policies": [ "signature", "expired" ] }' ``` ### Verifier2 Equivalent ```bash curl -X 'POST' \ 'http://localhost:7004/verification-session/create' \ -H 'Content-Type: application/json' \ -d '{ "flow_type": "cross_device", "url_config": { "url_host": "openid4vp://authorize", "url_prefix": "http://localhost:7004/verification-session" }, "redirects": { "successRedirectUri": "https://example.com/success", "errorRedirectUri": "https://example.com/error" }, "core_flow": { "dcql_query": { "credentials": [ { "id": "my_credential", "format": "jwt_vc_json", "meta": { "type_values": [ ["VerifiableCredential", "OpenBadgeCredential"] ] } } ] }, "policies": { "vc_policies": [ { "policy": "signature" }, { "policy": "expired" } ] } } }' ``` --- ## Breaking Changes ### 1. Request Body Structure **v1:** Single flat JSON object **v2:** Structured with `flow_type`, `core_flow`, and flow-specific sections **Migration:** Decompose your v1 request into: - `flow_type` — Choose `cross_device`, `same_device`, or `dc_api` - `core_flow` — Move credential queries and policies here - `url_config` — Move URL settings here (no longer in headers) - `redirects` — Move redirect URLs here (no longer in headers) --- ### 2. Credential Query Format **v1:** `request_credentials` with simple `type` array ```json { "request_credentials": [ { "format": "jwt_vc_json", "type": ["VerifiableCredential", "OpenBadgeCredential"] } ] } ``` **v2:** `dcql_query` using the DCQL standard ```json { "dcql_query": { "credentials": [ { "id": "my_credential", "format": "jwt_vc_json", "meta": { "type_values": [ ["VerifiableCredential", "OpenBadgeCredential"] ] } } ] } } ``` **Key Changes:** - Each credential query requires a unique `id` - `type` becomes `meta.type_values` (array of arrays to support alternatives) - DCQL adds support for claim-level queries via `claims` array --- ### 3. Policy Configuration **v1:** Separate top-level arrays ```json { "vp_policies": [], "vc_policies": ["signature", "expired"] } ``` **v2:** Nested under `policies` object with explicit policy objects ```json { "policies": { "vc_policies": [ { "policy": "signature" }, { "policy": "expired" } ], "vp_policies": [] } } ``` **Key Changes:** - Each policy is now an object with a `policy` field (allows for policy arguments in future) - Nested under `policies` object instead of top-level --- ### 4. URL Configuration **v1:** Passed via HTTP headers ```bash -H 'authorizeBaseUrl: openid4vp://authorize' \ -H 'successRedirectUri: https://example.com/success' \ -H 'errorRedirectUri: https://example.com/error' ``` **v2:** Passed in request body ```json { "url_config": { "url_host": "openid4vp://authorize", "url_prefix": "http://localhost:7004/verification-session" }, "redirects": { "successRedirectUri": "https://example.com/success", "errorRedirectUri": "https://example.com/error" } } ``` **Key Changes:** - `authorizeBaseUrl` → `url_config.url_host` - `successRedirectUri` and `errorRedirectUri` → nested under `redirects` object - New `url_config.url_prefix` field (was implicit in v1) --- ### 5. Endpoint Changes | Operation | Verifier (v1) | Verifier2 | |-----------|--------------|-----------| | Create verification session | `POST /openid4vc/verify` | `POST /verification-session/create` | | Get session result | `GET /openid4vc/session/{id}` | `GET /verification-session/{id}/info` | --- ### 6. Response Format Changes **v1 Session Response:** ```json "openid4vp://authorize?..." ``` **v2 Session Response:** ```json { "sessionId": "abc123", "bootstrapAuthorizationRequestUrl": "openid4vp://authorize?...", "fullAuthorizationRequestUrl": "openid4vp://authorize?..." } ``` **Key Changes:** - `url` field split into `bootstrapAuthorizationRequestUrl` (short) and `fullAuthorizationRequestUrl` (with all params) --- ## Configuration File Changes ### Verifier (v1) Config ```editorconfig[verifier.conf] baseUrl: "http://localhost:7003" authorizeBaseUrl: "openid4vp://authorize" ``` ### Verifier2 Config ```editorconfig[verifier-service.conf] clientId: "verifier2" clientMetadata: { client_name: "My Verifier" logo_uri: "https://example.com/logo.png" } urlPrefix: "http://localhost:7003/verification-session" urlHost: "openid4vp://authorize" ``` See [Verifier Service Configuration](https://docs.walt.id/community-stack/verifier2/configurations/config-files/verifier-service.md) for complete configuration documentation. --- -------------------------------------------------------------------------------- PAGE: Getting Started URL: https://docs.walt.id/community-stack/issuer2/getting-started RAW: https://docs.walt.id/community-stack/issuer2/getting-started.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Getting Started **Issuer2** is an open-source HTTP API service for creating, signing, and distributing verifiable digital credentials. It implements the [OID4VCI v1.0](https://docs.walt.id/concepts/data-exchange-protocols.md) specification and introduces a profile-based architecture for streamlined credential issuance. Supported credential formats include [W3C VC](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), and [ISO 18013-5 mDL](https://docs.walt.id/concepts/digital-credentials/mdoc-mdl-iso.md). **Note:** The original issuer service, which supports Draft 11/13 of the OID4VCI specification, remains available for existing projects. For new projects, we recommend using **Issuer2** with its profile-based architecture and OID4VCI v1.0 support. **Note:** **Coming from the original Issuer (v1)?** The biggest change is that each credential type must be declared ahead of time as a **profile** in a config file — including a default issuer key and credential data — and the service must be restarted to pick up new or changed profiles. You no longer need to supply a signing key or credential data with every issuance request, though you can still override either (and more) per-offer at request time via [`runtimeOverrides`](https://docs.walt.id/community-stack/issuer2/credential-offers/create-offer.md#runtime-overrides). See [Core Concepts](#core-concepts) below. ## 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) (v1.0) | | **Signing Algorithms:** | ed25519, secp256k1, secp256r1, RSA | ## Setup If you are new to the stack, the **Docker Compose quick start** is the fastest way to get running — it spins up the Issuer, Verifier, and Wallet together in one command, which mirrors a real issuance flow. Once running, the API is available at `http://localhost:7005`. **Note:** When running via Docker Compose, the original Issuer (v1) runs on port `7002` and Issuer2 runs on port `7005` to avoid conflicts. When running Issuer2 standalone with Docker, it uses the default port `7002`. - [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. _(Recommended for first-time users)_ - [Run standalone with Docker](https://docs.walt.id/community-stack/issuer2/setup.md) - Pull and run the issuer container in isolation using the [walt.id identity repo](https://github.com/walt-id/waltid-identity.git). See the [configuration reference](https://docs.walt.id/community-stack/issuer2/configurations/feature-manager.md) for available config options. - [Full API reference](https://docs.walt.id/community-stack/issuer2/full-api-reference.md) - Explore all available endpoints via Swagger. ## Core Concepts ### Profile-Based Issuance Issuer2 introduces a **profile-based architecture** that simplifies credential issuance: - **Credential Profiles** – Define reusable configurations for each credential type, including issuer keys, credential data templates, selective disclosure settings, and notification webhooks. - **Credential Offers** – Create offers from profiles with optional runtime overrides, supporting both pre-authorized and authorization code flows. ### Credential Exchange The Issuer2 API supports credential exchange protocols based on: - **OID4VCI v1.0:** Flows such as Pre-Authorized Code Flow (with or without PIN/Transaction Code) and Authorization Code Flow (with external authorization servers like Keycloak). - **mDoc Issuance:** Remote issuance of Mobile Driver's Licenses (mDL) and other ISO 18013-5 credentials via OID4VCI (ISO 18013-7). ### Credential Data Collection Flexible data collection options allow populating credentials before or after an offer has been created: - **Before Credential Offer Creation** – Provide all subject data upfront when creating the profile or offer. - **After Credential Offer Creation & Before Credential Signing** – Enrich credentials dynamically using [data functions](https://docs.walt.id/community-stack/issuer2/data-functions.md) such as webhooks or timestamps. - **During User Authentication** – When using the authorization code flow, the subject can authenticate against an external IdP and the retrieved claims are mapped to credential fields via `idTokenClaimsMapping`. ## Features - [Credential Profiles](https://docs.walt.id/community-stack/issuer2/credential-profiles/overview.md) - Create and manage reusable credential configurations. - [Credential Offers](https://docs.walt.id/community-stack/issuer2/credential-offers/overview.md) - Create credential offers from profiles with runtime overrides. - [Data Functions](https://docs.walt.id/community-stack/issuer2/data-functions.md) - Populate credentials with dynamic data at claim time. - [Notifications](https://docs.walt.id/community-stack/issuer2/notifications.md) - Configure webhook notifications to stay informed about on the issuance. ## Start Issuing Choose the credential type that matches your use case: - [SD-JWT VC Issuance](https://docs.walt.id/community-stack/issuer2/credential-issuance/sd-jwt-vc.md) - Issue IETF SD-JWT Credentials. - [W3C VC Issuance](https://docs.walt.id/community-stack/issuer2/credential-issuance/w3c-vc.md) - Issue W3C Verifiable Credentials with JWT signatures. - [mDL Issuance](https://docs.walt.id/community-stack/issuer2/credential-issuance/mdl.md) - Issue ISO 18013-5 Mobile Driver's Licenses. -------------------------------------------------------------------------------- PAGE: Setup URL: https://docs.walt.id/community-stack/issuer2/setup RAW: https://docs.walt.id/community-stack/issuer2/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. Clone the Identity Repo** Clone the repository to your local machine. The repo contains the default configuration files that will be mounted into the container in the next step. ```bash git clone https://github.com/walt-id/waltid-identity.git && cd waltid-identity ``` **2. Pull the Docker Image** Pull the pre-built Docker image from Docker Hub: ```bash docker pull waltid/issuer-api2 ``` **3. Run the Docker Image** Run the container, mounting the config directory from the cloned repository. This gives the service its default configuration — see the note below for details. ```bash docker run -p 7002:7002 \ -itv $(pwd)/waltid-services/waltid-issuer-api2/config:/waltid-issuer-api2/config \ -t waltid/issuer-api2 ``` The `-v` flag mounts `waltid-services/waltid-issuer-api2/config` from the cloned repository into the container. This directory contains the service configuration files (e.g. server port, credential profiles, metadata). You can edit these files before starting the container to customise the service behaviour. **Note:** See the [configuration reference](https://docs.walt.id/community-stack/issuer2/configurations/feature-manager.md) for a full description of all available config files and their fields. Once the container starts, the API is available at `http://localhost:7002`. You can confirm it is running by opening `http://localhost:7002/swagger` in your browser. ### Option 2: Building the Image Yourself **Note:** This option requires **Java (JDK 17+)**, **Gradle**, and **Docker** to be installed on your machine. **1. Clone the Identity Repo** Clone the repository to your local machine: ```bash git clone https://github.com/walt-id/waltid-identity.git && cd waltid-identity ``` **2. Build 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-api2:publishImageToLocalRegistry # image: waltid/issuer-api2: ``` ```bash # Production (multi-arch push to your registry) export DOCKER_USERNAME= export DOCKER_PASSWORD= ./gradlew :waltid-services:waltid-issuer-api2:publishImage # image: docker.io//issuer-api2: ``` Note: multi-arch images require a registry push. Local tar output is single-arch only. **3. Run 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-api2/config:/waltid-issuer-api2/config \ -t waltid/issuer-api2 ``` The `-v` flag mounts `waltid-services/waltid-issuer-api2/config` from the cloned repository into the container. This directory contains the service configuration files (e.g. server port, credential profiles, metadata). You can edit these files before starting the container to customise the service behaviour. **Note:** See the [configuration reference](https://docs.walt.id/community-stack/issuer2/configurations/feature-manager.md) for a full description of all available config files and their fields. Once the container starts, the API is available at `http://localhost:7002`. You can confirm it is running by opening `http://localhost:7002/swagger` in your browser. ## Start Issuing Credentials Now that the Issuer2 API is running, you can start issuing credentials: 1. [Issue a Credential](https://docs.walt.id/community-stack/issuer2/credential-issuance/sd-jwt-vc.md) - Issue your first credential from a ready-made profile, no configuration needed. 2. [Configure Credential Profiles](https://docs.walt.id/community-stack/issuer2/credential-profiles/overview.md) - Define reusable configurations for your own credential types. 3. [Create Credential Offers](https://docs.walt.id/community-stack/issuer2/credential-offers/overview.md) - Generate OID4VCI credential offer URLs. 4. [Full API Reference](https://docs.walt.id/community-stack/issuer2/full-api-reference.md) - Explore all available endpoints via Swagger. -------------------------------------------------------------------------------- PAGE: Issuing SD-JWT VC Credentials via OID4VCI URL: https://docs.walt.id/community-stack/issuer2/credential-issuance/sd-jwt-vc RAW: https://docs.walt.id/community-stack/issuer2/credential-issuance/sd-jwt-vc.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Issuing SD-JWT VC Credentials via OID4VCI This guide shows you how to issue an [IETF SD-JWT VC](https://docs.walt.id/concepts/digital-credentials/sd-jwt-vc.md) credential using the walt.id Issuer2 API. You'll issue a credential from a ready-made [profile](https://docs.walt.id/community-stack/issuer2/credential-profiles/overview.md) in seconds, then see how to define your own. [**SD-JWT VC**](https://docs.walt.id/concepts/digital-credentials/sd-jwt-vc.md): An IETF credential format supporting selective disclosure, letting holders reveal only specific claims of a credential rather than the whole thing. [**OID4VCI**](https://docs.walt.id/concepts/data-exchange-protocols/openid4vci.md): The protocol used to deliver the credential to a wallet. **Note:** Issuer2 issues credentials from **profiles** — reusable configurations declared ahead of time in a config file, with sensible defaults for the issuer key and credential data. If you're used to the original Issuer (v1), where the key and credential data were posted directly to an issuance endpoint every time, see [Getting Started](https://docs.walt.id/community-stack/issuer2/getting-started.md#core-concepts) for what changed — you can still override a profile's defaults per-offer via [`runtimeOverrides`](https://docs.walt.id/community-stack/issuer2/credential-offers/create-offer.md#runtime-overrides). ## Prerequisites Before you begin, ensure you have: - **Issuer2 API running** — Follow the [Setup](https://docs.walt.id/community-stack/issuer2/setup.md) guide - **A test wallet** — Use the [walt.id Wallet](https://wallet.demo.walt.id/) for testing, or any OID4VCI-compatible wallet **Note:** **Local Development:** When running locally via Docker, the API is available at `http://localhost:7002` (standalone) or `http://localhost:7005` (Docker Compose). Use this as your base URL in the examples below. --- ## Quick Start: Issue Using the Default Profile The waltid-identity repository ships with a ready-made profile called `identityCredentialSdJwt`, bound to the `identity_credential` credential type. You can issue from it immediately — no configuration file editing required. ### Step 1: Confirm the Profile Is Loaded **Option: CURL** **Endpoint:** `GET /issuer2/profiles/{profileId}` | [API Reference](https://issuer2.demo.walt.id/swagger/index.html) ##### Example Request ```bash curl -X 'GET' \ 'http://localhost:7002/issuer2/profiles/identityCredentialSdJwt' \ -H 'accept: application/json' ``` --- ##### Example Response ```json { "profileId": "identityCredentialSdJwt", "name": "Identity Credential", "credentialConfigurationId": "identity_credential", "issuerKey": { "type": "jwk", "jwk": { "d": "KJ4k3Vcl5Sj9Mfq4rrNXBm2MoPoY3_Ak_PIR_EgsFhQ", "crv": "P-256", "x": "G0RINBiF-oQUD3d5DGnegQuXenI29JDaMGoMvioKRBM", "y": "ed3eFGs2pEtrp7vAZ7BLcbrUtpKkYWAT2JPUQK4lN4E", "kty": "EC" } }, "issuerDid": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6IkcwUklOQmlGLW9RVUQzZDVER25lZ1F1WGVuSTI5SkRhTUdvTXZpb0tSQk0iLCJ5IjoiZWQzZUZHczJwRXRycDd2QVo3QkxjYnJVdHBLa1lXQVQySlBVUUs0bE40RSJ9", "credentialData": { "address": { "region": "Anystate", "street_address": "123 Main St", "country": "US", "locality": "Anytown" }, "is_over_18": true, "phone_number": "+1-202-555-0101", "given_name": "John", "family_name": "Doe", "is_over_21": true, "is_over_65": true, "email": "johndoe@example.com", "birthdate": "1940-01-01" }, "mapping": { "exp": "", "nbf": "", "id": "", "iat": "" }, "selectiveDisclosure": { "fields": { "birthdate": { "sd": true }, "family_name": { "sd": false } }, "decoyMode": "NONE", "decoys": 0 }, "idTokenClaimsMapping": { "$.given_name": "$.given_name", "$.family_name": "$.family_name" } } ``` The profile already has selective disclosure and issuer key/DID configured — you don't need to understand every field yet to issue your first credential. We'll come back to them in [Defining Your Own Profile](#defining-your-own-profile) below. ### Step 2: Create a Credential Offer **Option: CURL** **Endpoint:** `POST /issuer2/credential-offers` | [API Reference](https://issuer2.demo.walt.id/swagger/index.html) ##### Example Request ```bash curl -X 'POST' \ 'http://localhost:7002/issuer2/credential-offers' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -d '{ "profileId": "identityCredentialSdJwt", "authMethod": "PRE_AUTHORIZED" }' ``` **Body Parameters** - **profileId**: _String_ (required) - The profile to issue from. Here, the shipped default `identityCredentialSdJwt`. - **authMethod**: _String_ (required) - The OID4VCI flow used to claim the offer. `PRE_AUTHORIZED` requires no user login and is the simplest way to get started. See [Using the Authorization Code Flow Instead](#using-the-authorization-code-flow-instead) for the alternative. --- ##### Example Response ```json { "offerId": "48f1dc54-134c-4e10-b889-17f3c0a595bf", "profileId": "identityCredentialSdJwt", "authMethod": "PRE_AUTHORIZED", "expiresAt": 1782898162344, "credentialOffer": "openid-credential-offer://?credential_offer_uri=http%3A%2F%2Flocalhost%3A7002%2Fopenid4vci%2Fcredential-offer%3Fid%3D48f1dc54-134c-4e10-b889-17f3c0a595bf" } ``` **Response Fields** - **offerId**: _String_ - Unique identifier for this offer. - **expiresAt**: _Long_ - Unix timestamp (ms) when the offer expires (default: 300 seconds after creation). - **credentialOffer**: _String_ - The OID4VCI credential offer URL. Turn this into a QR code or deep link for the wallet. --- ## Claiming the Offer 1. **Generate a QR code** from the `credentialOffer` URL, or use it directly as a deep link 2. **Open the [walt.id Wallet](https://wallet.demo.walt.id/)** and log in 3. **Click "Request Credential"** and paste the offer URL into the field below the camera 4. **The wallet resolves the offer**, requests the credential, and stores it 🎉 You've issued an SD-JWT VC credential using OID4VCI. To learn how to accept this credential offer programmatically instead, 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). --- ## Defining Your Own Profile To issue your own SD-JWT VC credential type instead of the shipped default, two config files need an entry, and the service needs a restart to pick them up. ### Step 1: Declare the Credential Type Add an entry to `credential-issuer-metadata.conf`: ```hocon credentialConfigurations = { "my_identity_credential" = { format = "dc+sd-jwt" scope = "my_identity_credential" vct = "vctBaseUrl" cryptographic_binding_methods_supported = ["jwk", "did:key", "did:web"] credential_signing_alg_values_supported = ["ES256"] proof_types_supported = { jwt = { proof_signing_alg_values_supported = ["ES256"] } } } } ``` See [Credential Issuer Metadata](https://docs.walt.id/community-stack/issuer2/configurations/config-files/credential-issuer-metadata.md) for the full field reference. ### Step 2: Define a Profile Add a matching profile to `issuer2-profiles.conf`: ```hocon profiles { myIdentityCredential { name = "My Identity Credential" credentialConfigurationId = "my_identity_credential" issuerKey = ${defaultIssuerKey} issuerDid = ${defaultIssuerDid} credentialData = { given_name = "Jane" family_name = "Smith" } mapping = { iat = "" nbf = "" } selectiveDisclosure = { fields = { family_name = { sd = true } } } } } ``` See [Issuer Profiles Configuration](https://docs.walt.id/community-stack/issuer2/configurations/config-files/issuer-profiles.md) for every available profile field, including notifications and credential status. ### Step 3: Restart and Verify **Error:** Profiles are loaded once, at startup. **Restart the Issuer2 container** after editing either config file, then confirm the new profile is loaded. ```bash curl -X 'GET' 'http://localhost:7002/issuer2/profiles/myIdentityCredential' ``` Then repeat the two steps from [Quick Start](#quick-start-issue-using-the-default-profile) above, substituting your own `profileId`. --- ## Using the Authorization Code Flow Instead The example above used `PRE_AUTHORIZED` — no user login required. Issuer2 also supports the **Authorization Code Flow**, where the holder authenticates against an external IdP (e.g. Keycloak) before claiming the credential, and the resulting ID token claims can be mapped into the credential via `idTokenClaimsMapping`. This requires registering an external OAuth/OIDC provider first. See [Authorization Code Flow](https://docs.walt.id/community-stack/issuer2/protocol-flows/authorization-code-flow.md) for the full sequence and setup. ## Selective Disclosure The default profile already marks `birthdate` as selectively disclosable. To configure which claims a holder can choose to hide, see [Selective Disclosure](https://docs.walt.id/community-stack/issuer2/configurations/config-files/issuer-profiles.md#selective-disclosure) in the profile reference. --- ## Next Steps **Info:** The `vct` published for `identity_credential` is derived from your issuer's `baseUrl` (see [VCT handling](https://docs.walt.id/community-stack/issuer2/configurations/config-files/credential-issuer-metadata.md#vct-handling)). When verifying, use the `vct` from **your own** `/.well-known/openid-credential-issuer/openid4vci` response rather than assuming a fixed value. - **Verify the credential you just issued** — Try [Verifying SD-JWT VC Credentials](https://docs.walt.id/community-stack/verifier2/credential-verification/sd-jwt-vc-oid4vp.md) with the walt.id Verifier2 API - **Populate credentials dynamically** — See [Data Functions](https://docs.walt.id/community-stack/issuer2/data-functions.md) for webhooks, timestamps, and more - **Track issuance events** — See [Notifications](https://docs.walt.id/community-stack/issuer2/notifications.md) for webhook and SSE options - **Issue other credential types** — Try [W3C VC](https://docs.walt.id/community-stack/issuer2/credential-issuance/w3c-vc.md) or [mDL](https://docs.walt.id/community-stack/issuer2/credential-issuance/mdl.md) -------------------------------------------------------------------------------- PAGE: Issuing W3C Verifiable Credentials via OID4VCI URL: https://docs.walt.id/community-stack/issuer2/credential-issuance/w3c-vc RAW: https://docs.walt.id/community-stack/issuer2/credential-issuance/w3c-vc.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Issuing W3C Verifiable Credentials via OID4VCI This guide shows you how to issue a [W3C Verifiable Credential](https://docs.walt.id/concepts/digital-credentials/verifiable-credentials-w3c.md) with a JWT signature using the walt.id Issuer2 API. You'll issue a credential from a ready-made profile in seconds, then see how to define your own. [**W3C VC**](https://docs.walt.id/concepts/digital-credentials/verifiable-credentials-w3c.md): A digital equivalent of physical credentials such as a driver's license or university degree, defined by the W3C Verifiable Credentials Data Model. [**OID4VCI**](https://docs.walt.id/concepts/data-exchange-protocols/openid4vci.md): The protocol used to deliver the credential to a wallet. **Note:** Issuer2 issues credentials from **profiles** — reusable configurations declared ahead of time in a config file, with sensible defaults for the issuer key and credential data. If you're used to the original Issuer (v1), where the key and credential data were posted directly to an issuance endpoint every time, see [Getting Started](https://docs.walt.id/community-stack/issuer2/getting-started.md#core-concepts) for what changed — you can still override a profile's defaults per-offer via [`runtimeOverrides`](https://docs.walt.id/community-stack/issuer2/credential-offers/create-offer.md#runtime-overrides). ## Prerequisites Before you begin, ensure you have: - **Issuer2 API running** — Follow the [Setup](https://docs.walt.id/community-stack/issuer2/setup.md) guide - **A test wallet** — Use the [walt.id Wallet](https://wallet.demo.walt.id/) for testing, or any OID4VCI-compatible wallet **Note:** **Local Development:** When running locally via Docker, the API is available at `http://localhost:7002` (standalone) or `http://localhost:7005` (Docker Compose). Use this as your base URL in the examples below. --- ## Quick Start: Issue Using the Default Profile The waltid-identity repository ships with a ready-made profile called `universityDegree`, bound to the `UniversityDegree_jwt_vc_json` credential type. You can issue from it immediately — no configuration file editing required. ### Step 1: Confirm the Profile Is Loaded **Option: CURL** **Endpoint:** `GET /issuer2/profiles/{profileId}` | [API Reference](https://issuer2.demo.walt.id/swagger/index.html) ##### Example Request ```bash curl -X 'GET' \ 'http://localhost:7002/issuer2/profiles/universityDegree' \ -H 'accept: application/json' ``` --- ##### Example Response ```json { "profileId": "universityDegree", "name": "UniversityDegree", "credentialConfigurationId": "UniversityDegree_jwt_vc_json", "issuerKey": { "type": "jwk", "jwk": { "d": "KJ4k3Vcl5Sj9Mfq4rrNXBm2MoPoY3_Ak_PIR_EgsFhQ", "crv": "P-256", "x": "G0RINBiF-oQUD3d5DGnegQuXenI29JDaMGoMvioKRBM", "y": "ed3eFGs2pEtrp7vAZ7BLcbrUtpKkYWAT2JPUQK4lN4E", "kty": "EC" } }, "issuerDid": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6IkcwUklOQmlGLW9RVUQzZDVER25lZ1F1WGVuSTI5SkRhTUdvTXZpb0tSQk0iLCJ5IjoiZWQzZUZHczJwRXRycDd2QVo3QkxjYnJVdHBLa1lXQVQySlBVUUs0bE40RSJ9", "credentialData": { "issuanceDate": "THIS WILL BE REPLACED WITH DYNAMIC DATA FUNCTION", "issuer": { "id": "THIS WILL BE REPLACED WITH DYNAMIC DATA FUNCTION" }, "@context": [ "https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1" ], "id": "http://example.gov/credentials/3732", "credentialSubject": { "degree": { "name": "Bachelor of Science and Arts", "type": "BachelorDegree" }, "id": "THIS WILL BE REPLACED WITH DYNAMIC DATA FUNCTION" }, "type": [ "VerifiableCredential", "UniversityDegree" ] }, "mapping": { "issuanceDate": "", "credentialSubject": { "id": "" }, "id": "", "issuer": { "id": "" } } } ``` This is a minimal profile — no selective disclosure, no status list — a good first look at the plain shape of a profile before you customize one. ### Step 2: Create a Credential Offer **Option: CURL** **Endpoint:** `POST /issuer2/credential-offers` | [API Reference](https://issuer2.demo.walt.id/swagger/index.html) ##### Example Request ```bash curl -X 'POST' \ 'http://localhost:7002/issuer2/credential-offers' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -d '{ "profileId": "universityDegree", "authMethod": "PRE_AUTHORIZED" }' ``` **Body Parameters** - **profileId**: _String_ (required) - The profile to issue from. Here, the shipped default `universityDegree`. - **authMethod**: _String_ (required) - The OID4VCI flow used to claim the offer. `PRE_AUTHORIZED` requires no user login and is the simplest way to get started. See [Using the Authorization Code Flow Instead](#using-the-authorization-code-flow-instead) for the alternative. --- ##### Example Response ```json { "offerId": "477ecf7e-e783-4326-85dd-a590e94d06e6", "profileId": "universityDegree", "authMethod": "PRE_AUTHORIZED", "expiresAt": 1782909743958, "credentialOffer": "openid-credential-offer://?credential_offer_uri=http%3A%2F%2Fissuer-api2%3A7002%2Fopenid4vci%2Fcredential-offer%3Fid%3D477ecf7e-e783-4326-85dd-a590e94d06e6" } ``` **Response Fields** - **offerId**: _String_ - Unique identifier for this offer. - **expiresAt**: _Long_ - Unix timestamp (ms) when the offer expires (default: 300 seconds after creation). - **credentialOffer**: _String_ - The OID4VCI credential offer URL. Turn this into a QR code or deep link for the wallet. --- ## Claiming the Offer 1. **Generate a QR code** from the `credentialOffer` URL, or use it directly as a deep link 2. **Open the [walt.id Wallet](https://wallet.demo.walt.id/)** and log in 3. **Click "Request Credential"** and paste the offer URL into the field below the camera 4. **The wallet resolves the offer**, requests the credential, and stores it 🎉 You've issued a W3C Verifiable Credential using OID4VCI. To learn how to accept this credential offer programmatically instead, see [How to Accept W3C VCs via OID4VCI](https://docs.walt.id/community-stack/wallet/credential-exchange/guides/accept-w3c-vc-oid4vci.md). --- ## Defining Your Own Profile To issue your own W3C VC type instead of the shipped default, two config files need an entry, and the service needs a restart to pick them up. ### Step 1: Declare the Credential Type Add an entry to `credential-issuer-metadata.conf`: ```hocon credentialConfigurations = { "MyCredential_jwt_vc_json" = { format = "jwt_vc_json" scope = "MyCredential_jwt_vc_json" cryptographic_binding_methods_supported = ["jwk", "did:key", "did:web"] credential_signing_alg_values_supported = ["ES256"] proof_types_supported = { jwt = { proof_signing_alg_values_supported = ["ES256"] } } credential_definition = { type = ["VerifiableCredential", "MyCredential"] } } } ``` See [Credential Issuer Metadata](https://docs.walt.id/community-stack/issuer2/configurations/config-files/credential-issuer-metadata.md) for the full field reference. ### Step 2: Define a Profile Add a matching profile to `issuer2-profiles.conf`: ```hocon profiles { myCredential { name = "My Credential" credentialConfigurationId = "MyCredential_jwt_vc_json" issuerKey = ${defaultIssuerKey} issuerDid = ${defaultIssuerDid} credentialData = { "@context" = ["https://www.w3.org/2018/credentials/v1"] type = ["VerifiableCredential", "MyCredential"] credentialSubject = { name = "Jane Smith" } } mapping = { id = "" issuer = { id = "" } credentialSubject = { id = "" } issuanceDate = "" expirationDate = "" } } } ``` See [Issuer Profiles Configuration](https://docs.walt.id/community-stack/issuer2/configurations/config-files/issuer-profiles.md) for every available profile field, including notifications and credential status. ### Step 3: Restart and Verify **Error:** Profiles are loaded once, at startup. **Restart the Issuer2 container** after editing either config file, then confirm the new profile is loaded. ```bash curl -X 'GET' 'http://localhost:7002/issuer2/profiles/myCredential' ``` Then repeat the two steps from [Quick Start](#quick-start-issue-using-the-default-profile) above, substituting your own `profileId`. --- ## Using the Authorization Code Flow Instead The example above used `PRE_AUTHORIZED` — no user login required. Issuer2 also supports the **Authorization Code Flow**, where the holder authenticates against an external IdP (e.g. Keycloak) before claiming the credential, and the resulting ID token claims are mapped into the credential via `idTokenClaimsMapping`. This requires registering an external OAuth/OIDC provider first. See [Authorization Code Flow](https://docs.walt.id/community-stack/issuer2/protocol-flows/authorization-code-flow.md) for the full sequence and setup. ## Credential Status (Revocation) To make a W3C VC revocable, embed a Bitstring Status List reference in the profile. See [Credential Status](https://docs.walt.id/community-stack/issuer2/credential-offers/credential-status.md) for the full reference. --- ## Next Steps - **Verify the credential you just issued** — Try [Verifying W3C VCs](https://docs.walt.id/community-stack/verifier2/credential-verification/vc-oid4vp.md) with the walt.id Verifier2 API - **Populate credentials dynamically** — See [Data Functions](https://docs.walt.id/community-stack/issuer2/data-functions.md) for webhooks, timestamps, and more - **Track issuance events** — See [Notifications](https://docs.walt.id/community-stack/issuer2/notifications.md) for webhook and SSE options - **Issue other credential types** — Try [SD-JWT VC](https://docs.walt.id/community-stack/issuer2/credential-issuance/sd-jwt-vc.md) or [mDL](https://docs.walt.id/community-stack/issuer2/credential-issuance/mdl.md) -------------------------------------------------------------------------------- PAGE: Issuing Mobile Driver's Licenses (mDL) via OID4VCI URL: https://docs.walt.id/community-stack/issuer2/credential-issuance/mdl RAW: https://docs.walt.id/community-stack/issuer2/credential-issuance/mdl.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Issuing Mobile Driver's Licenses (mDL) via OID4VCI This guide shows you how to issue an [ISO/IEC 18013-5](https://docs.walt.id/concepts/digital-credentials/mdoc-mdl-iso.md) Mobile Driver's License (mDL) using the walt.id Issuer2 API. You'll issue a credential from a ready-made profile in seconds, then see how to define your own. [**mDL (Mobile Driver's License)**](https://docs.walt.id/concepts/digital-credentials/mdoc-mdl-iso.md): A digital representation of a driver's license based on the ISO/IEC 18013-5 standard, stored securely on a digital ID wallet. [**OID4VCI**](https://docs.walt.id/concepts/data-exchange-protocols/openid4vci.md): The protocol used to deliver the credential to a wallet (mDoc issuance follows OID4VCI per ISO 18013-7). **Note:** Issuer2 issues credentials from **profiles** — reusable configurations declared ahead of time in a config file, with sensible defaults for the issuer key and credential data. If you're used to the original Issuer (v1), where the key and credential data were posted directly to an issuance endpoint every time, see [Getting Started](https://docs.walt.id/community-stack/issuer2/getting-started.md#core-concepts) for what changed — you can still override a profile's defaults per-offer via [`runtimeOverrides`](https://docs.walt.id/community-stack/issuer2/credential-offers/create-offer.md#runtime-overrides). ## Prerequisites Before you begin, ensure you have: - **Issuer2 API running** — Follow the [Setup](https://docs.walt.id/community-stack/issuer2/setup.md) guide - **A test wallet** — Use the [walt.id Wallet](https://wallet.demo.walt.id/) for testing, or any OID4VCI-compatible wallet with mDoc support **Note:** **Local Development:** When running locally via Docker, the API is available at `http://localhost:7002` (standalone) or `http://localhost:7005` (Docker Compose). Use this as your base URL in the examples below. --- ## Quick Start: Issue Using the Default Profile The waltid-identity repository ships with a ready-made profile called `isoMdl`, bound to the `org.iso.18013.5.1.mDL` credential type. You can issue from it immediately — no configuration file editing required. ### Step 1: Confirm the Profile Is Loaded **Option: CURL** **Endpoint:** `GET /issuer2/profiles/{profileId}` | [API Reference](https://issuer2.demo.walt.id/swagger/index.html) ##### Example Request ```bash curl -X 'GET' \ 'http://localhost:7002/issuer2/profiles/isoMdl' \ -H 'accept: application/json' ``` --- ##### Example Response ```json { "profileId": "isoMdl", "name": "ISO 18013-5 Mobile Driving License", "credentialConfigurationId": "org.iso.18013.5.1.mDL", "issuerKey": { "type": "jwk", "jwk": { "d": "KJ4k3Vcl5Sj9Mfq4rrNXBm2MoPoY3_Ak_PIR_EgsFhQ", "crv": "P-256", "x": "G0RINBiF-oQUD3d5DGnegQuXenI29JDaMGoMvioKRBM", "y": "ed3eFGs2pEtrp7vAZ7BLcbrUtpKkYWAT2JPUQK4lN4E", "kty": "EC" } }, "credentialData": { "org.iso.18013.5.1": { "un_distinguishing_sign": "A", "biometric_template_signature_sign": null, "portrait_capture_date": "2024-12-20", "age_over_16": true, "age_over_18": true, "given_name_national_character": "Anna Maria", "given_name": "Anna Maria", "birth_place": "Graz", "family_name": "Musterfrau", "age_birth_year": 1988, "age_over_12": true, "nationality": "AUT", "age_over_13": true, "age_over_14": true, "weight": 63, "issuing_jurisdiction": "AT-9", "expiry_date": "2035-01-15", "signature_usual_mark": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO7Z0i8AAAAASUVORK5CYII=", "biometric_template_finger": null, "eye_colour": "brown", "resident_postal_code": "1070", "sex": 2, "age_over_62": false, "issuing_country": "AT", "hair_colour": "brown", "issue_date": "2025-01-15", "age_over_60": false, "age_in_years": 36, "administrative_number": "AT-FS-99817234", "age_over_65": false, "resident_state": "Wien", "resident_city": "Wien", "document_number": "DL-AT-2025-00018427", "portrait": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO7Z0i8AAAAASUVORK5CYII=", "resident_country": "AT", "resident_address": "Mariahilfer Straße 120/8", "age_over_68": false, "driving_privileges": [ { "issue_date": "2006-09-14", "vehicle_category_code": "B", "expiry_date": "2035-01-15" }, { "issue_date": "2004-08-25", "vehicle_category_code": "AM", "expiry_date": "2035-01-15" } ], "age_over_21": true, "height": 170, "biometric_template_iris": null, "birth_date": "1988-08-25", "age_over_25": true, "biometric_template_face": null, "issuing_authority": "Bundesministerium für Inneres", "family_name_national_character": "Musterfrau" } }, "idTokenClaimsMapping": { "$.family_name": "$.['org.iso.18013.5.1'].family_name", "$.given_name": "$.['org.iso.18013.5.1'].given_name" }, "mDocNameSpacesDataMappingConfig": { "org.iso.18013.5.1": { "entriesConfigMap": { "portrait_capture_date": { "type": "string", "conversionType": "stringToFullDate" }, "driving_privileges": { "type": "array", "arrayConfig": [ { "type": "object", "entriesConfigMap": { "issue_date": { "type": "string", "conversionType": "stringToFullDate" }, "expiry_date": { "type": "string", "conversionType": "stringToFullDate" } } }, { "type": "object", "entriesConfigMap": { "issue_date": { "type": "string", "conversionType": "stringToFullDate" }, "expiry_date": { "type": "string", "conversionType": "stringToFullDate" } } } ] }, "issue_date": { "type": "string", "conversionType": "stringToFullDate" }, "portrait": { "type": "string", "conversionType": "base64StringToByteString" }, "birth_date": { "type": "string", "conversionType": "stringToFullDate" }, "expiry_date": { "type": "string", "conversionType": "stringToFullDate" }, "signature_usual_mark": { "type": "string", "conversionType": "base64StringToByteString" } } } }, "x5Chain": [ "-----BEGIN CERTIFICATE-----\nMIIBeTCCAR8CFHrWgrGl5KdefSvRQhR+aoqdf48+MAoGCCqGSM49BAMCMBcxFTATBgNVBAMMDE1ET0MgUk9PVCBDQTAgFw0yNTA1MTQxNDA4MDlaGA8yMDc1MDUwMjE0MDgwOVowZTELMAkGA1UEBhMCQVQxDzANBgNVBAgMBlZpZW5uYTEPMA0GA1UEBwwGVmllbm5hMRAwDgYDVQQKDAd3YWx0LmlkMRAwDgYDVQQLDAd3YWx0LmlkMRAwDgYDVQQDDAd3YWx0LmlzMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEG0RINBiF+oQUD3d5DGnegQuXenI29JDaMGoMvioKRBN53d4UazakS2unu8BnsEtxutS2kqRhYBPYk9RAriU3gTAKBggqhkjOPQQDAgNIADBFAiAOMwM7hH7q9Di+mT6qCi4LvB+kH8OxMheIrZ2eRPxtDQIhALHzTxwvN8Udt0Z2Cpo8JBihqacfeXkIxVAO8XkxmXhB\n-----END CERTIFICATE-----" ] } ``` The profile already ships with `x5Chain` (for CBOR signing) and `mDocNameSpacesDataMappingConfig` (to encode dates and images to the correct CBOR types) pre-configured — you don't need to understand every field yet to issue your first credential. We'll come back to them in [Defining Your Own Profile](#defining-your-own-profile) below. ### Step 2: Create a Credential Offer **Option: CURL** **Endpoint:** `POST /issuer2/credential-offers` | [API Reference](https://issuer2.demo.walt.id/swagger/index.html) ##### Example Request ```bash curl -X 'POST' \ 'http://localhost:7002/issuer2/credential-offers' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -d '{ "profileId": "isoMdl", "authMethod": "PRE_AUTHORIZED" }' ``` **Body Parameters** - **profileId**: _String_ (required) - The profile to issue from. Here, the shipped default `isoMdl`. - **authMethod**: _String_ (required) - The OID4VCI flow used to claim the offer. `PRE_AUTHORIZED` requires no user login and is the simplest way to get started. See [Using the Authorization Code Flow Instead](#using-the-authorization-code-flow-instead) for the alternative. --- ##### Example Response ```json { "offerId": "99825a89-73d0-47f4-80cb-321fea52cd54", "profileId": "isoMdl", "authMethod": "PRE_AUTHORIZED", "expiresAt": 1782910641406, "credentialOffer": "openid-credential-offer://?credential_offer_uri=http%3A%2F%2Fissuer-api2%3A7002%2Fopenid4vci%2Fcredential-offer%3Fid%3D99825a89-73d0-47f4-80cb-321fea52cd54" } ``` **Response Fields** - **offerId**: _String_ - Unique identifier for this offer. - **expiresAt**: _Long_ - Unix timestamp (ms) when the offer expires (default: 300 seconds after creation). - **credentialOffer**: _String_ - The OID4VCI credential offer URL. Turn this into a QR code or deep link for the wallet. --- ## Claiming the Offer 1. **Generate a QR code** from the `credentialOffer` URL, or use it directly as a deep link 2. **Open the [walt.id Wallet](https://wallet.demo.walt.id/)** and log in 3. **Click "Request Credential"** and paste the offer URL into the field below the camera 4. **The wallet resolves the offer**, requests the credential, and stores it 🎉 You've issued a Mobile Driver's License using OID4VCI. To learn how to accept this credential offer programmatically instead, see [How to Accept mDL Credentials via OID4VCI](https://docs.walt.id/community-stack/wallet/credential-exchange/guides/accept-iso-18013-5-mdl-oid4vci.md). --- ## Defining Your Own Profile To issue your own mDoc credential type instead of the shipped default, two config files need an entry, and the service needs a restart to pick them up. ### Step 1: Declare the Credential Type Add an entry to `credential-issuer-metadata.conf`: ```hocon credentialConfigurations = { "org.iso.18013.5.1.mDL.custom" = { format = "mso_mdoc" scope = "org.iso.18013.5.1.mDL.custom" doctype = "org.iso.18013.5.1.mDL" cryptographic_binding_methods_supported = ["cose_key"] credential_signing_alg_values_supported = [-7, -9] proof_types_supported = { jwt = { proof_signing_alg_values_supported = ["ES256"] } } } } ``` See [Credential Issuer Metadata](https://docs.walt.id/community-stack/issuer2/configurations/config-files/credential-issuer-metadata.md) for the full field reference. ### Step 2: Define a Profile Add a matching profile to `issuer2-profiles.conf`. Note that mDoc profiles sign with `x5Chain` instead of `issuerDid`, and need `mDocNameSpacesDataMappingConfig` for any date or binary field (see [why this is needed](https://docs.walt.id/community-stack/issuer2/configurations/config-files/issuer-profiles.md#why-this-is-needed)): ```hocon profiles { myMdl { name = "My mDL" credentialConfigurationId = "org.iso.18013.5.1.mDL.custom" issuerKey = ${defaultIssuerKey} x5Chain = ${defaultIssuerX5chain} credentialData = { "org.iso.18013.5.1" = { family_name = "Doe" given_name = "John" birth_date = "1990-01-01" issue_date = "2024-01-01" expiry_date = "2029-01-01" } } mDocNameSpacesDataMappingConfig = { "org.iso.18013.5.1" = { entriesConfigMap = { birth_date = { type = "string", conversionType = "stringToFullDate" } issue_date = { type = "string", conversionType = "stringToFullDate" } expiry_date = { type = "string", conversionType = "stringToFullDate" } } } } } } ``` See [Issuer Profiles Configuration](https://docs.walt.id/community-stack/issuer2/configurations/config-files/issuer-profiles.md) for every available profile field, including notifications and credential status. ### Step 3: Restart and Verify **Error:** Profiles are loaded once, at startup. **Restart the Issuer2 container** after editing either config file, then confirm the new profile is loaded. ```bash curl -X 'GET' 'http://localhost:7002/issuer2/profiles/myMdl' ``` Then repeat the two steps from [Quick Start](#quick-start-issue-using-the-default-profile) above, substituting your own `profileId`. --- ## Using the Authorization Code Flow Instead The example above used `PRE_AUTHORIZED` — no user login required. Issuer2 also supports the **Authorization Code Flow**, where the holder authenticates against an external IdP (e.g. Keycloak) before claiming the credential, and the resulting ID token claims are mapped into the credential via `idTokenClaimsMapping`. This requires registering an external OAuth/OIDC provider first. See [Authorization Code Flow](https://docs.walt.id/community-stack/issuer2/protocol-flows/authorization-code-flow.md) for the full sequence and setup. ## mDoc Data Type Mapping CBOR requires specific types for dates and binary data (e.g. `portrait`) that JSON can't represent natively. See [mDocNameSpacesDataMappingConfig](https://docs.walt.id/community-stack/issuer2/configurations/config-files/issuer-profiles.md#mdocnamespacesdatamappingconfig) for the full conversion reference. --- ## Next Steps - **Verify the credential you just issued** — Try [Verifying mDLs](https://docs.walt.id/community-stack/verifier2/credential-verification/mdl-oid4vp.md) with the walt.id Verifier2 API - **Populate credentials dynamically** — See [Data Functions](https://docs.walt.id/community-stack/issuer2/data-functions.md) for webhooks, timestamps, and more - **Track issuance events** — See [Notifications](https://docs.walt.id/community-stack/issuer2/notifications.md) for webhook and SSE options - **Issue other credential types** — Try [SD-JWT VC](https://docs.walt.id/community-stack/issuer2/credential-issuance/sd-jwt-vc.md) or [W3C VC](https://docs.walt.id/community-stack/issuer2/credential-issuance/w3c-vc.md) -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/community-stack/issuer2/credential-profiles/overview RAW: https://docs.walt.id/community-stack/issuer2/credential-profiles/overview.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Overview **Credential Profiles** are the foundation of the Issuer2 API's profile-based architecture. A profile defines all the default values needed to issue a specific type of credential, making it easy to create consistent credential offers without repeating configuration. ## What is a Credential Profile? A credential profile is a reusable configuration that contains: - **Credential Configuration ID** – The type of credential this profile issues (must match a supported credential type in your issuer metadata) - **Issuer Identity** – The DID, key, and/or X.509 certificate chain used to sign credentials - **Credential Data** – Default data template for the credential - **Data Mapping** – Dynamic data functions for populating credential fields - **ID Token Claims Mapping** – Mapping from external IdP claims to credential data (for authorization code flow) - **mDoc Data Mapping** – Type conversions for mDoc/mDL credential fields - **Selective Disclosure** – Selective disclosure configuration for SD-JWT credentials - **Notifications** – Webhook settings for issuance events Profiles are defined in the `issuer2-profiles.conf` configuration file and loaded when the service starts. For the full field reference, examples for every credential format, and how to write and reload the file, see [Issuer Profiles Configuration](https://docs.walt.id/community-stack/issuer2/configurations/config-files/issuer-profiles.md). **Note:** The waltid-identity repository ships with a set of ready-to-use default profiles — you don't need to write your own to issue your first credential. See [Credential Issuance](https://docs.walt.id/community-stack/issuer2/credential-issuance/sd-jwt-vc.md) to try one immediately. ## Supported Credential Formats Profiles can be created for any credential format supported: | Format | Description | |--------|-------------| | `dc+sd-jwt` | IETF SD-JWT Verifiable Credentials | | `jwt_vc_json` | W3C Verifiable Credentials (JWT) | | `mso_mdoc` | ISO 18013-5 Mobile Documents (mDL) | ## Reading Profiles via the API Once configured, profiles are available to read (but not create or edit) via the API: ### List All Profiles ```bash curl -X GET 'http://localhost:7002/issuer2/profiles' ``` ### Get a Specific Profile ```bash curl -X GET 'http://localhost:7002/issuer2/profiles/{profileId}' ``` Use this to confirm a profile you added to `issuer2-profiles.conf` was picked up correctly after restarting the service. ## Next Steps - [Issue a Credential](https://docs.walt.id/community-stack/issuer2/credential-issuance/sd-jwt-vc.md) – Try issuing a credential using a ready-made default profile - [Issuer Profiles Configuration](https://docs.walt.id/community-stack/issuer2/configurations/config-files/issuer-profiles.md) – The full field reference for writing your own profiles - [Credential Offers](https://docs.walt.id/community-stack/issuer2/credential-offers/overview.md) – Create offers from your profiles -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/community-stack/issuer2/credential-offers/overview RAW: https://docs.walt.id/community-stack/issuer2/credential-offers/overview.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Overview Before a wallet can receive a credential, the issuer creates a credential offer from a [credential profile](https://docs.walt.id/community-stack/issuer2/credential-profiles/overview.md). The Issuer2 API exposes endpoints for creating and managing these offers, which generate an OID4VCI credential offer URL that any compliant wallet can claim. ## What is a Credential Offer? A credential offer is a one-time or limited-use invitation for a wallet to receive a credential. When you create an offer, you specify: - **Profile** – The credential profile to use as the base configuration - **Authentication Method** – Pre-authorized or authorization code flow - **Delivery Mode** – Credential offer by reference or by value - **Expiration** – How long the offer remains valid - **Runtime Overrides** – Any profile values to override for this specific offer ## Offer Flow 1. **Create Offer** – Call the offers endpoint with a profile ID 2. **Share URL** – Send the credential offer URL to the user (QR code, deep link, etc.) 3. **Wallet Claims** – User's wallet resolves the offer and receives the credential ## Authentication Methods | Method | Description | Use Case | |--------|-------------|----------| | `PRE_AUTHORIZED` | No user authentication required | Known users, pre-verified data | | `AUTHORIZED` | User must authenticate via IdP | Unknown users, dynamic data collection | See [Protocol Flows](https://docs.walt.id/community-stack/issuer2/protocol-flows/overview.md) for the full sequence diagrams and a more detailed breakdown of when to use each. ## Offer Delivery Modes | Mode | Description | |------|-------------| | `BY_REFERENCE` | Offer URL contains a reference; wallet fetches full offer from issuer (default) | | `BY_VALUE` | Full credential offer is embedded in the URL | **Note:** `BY_REFERENCE` is recommended for most use cases as it keeps URLs shorter and allows the issuer to track when offers are resolved. ## API Endpoints ### Create a Credential Offer ```bash curl -X POST 'http://localhost:7002/issuer2/credential-offers' \ -H 'Content-Type: application/json' \ -d '{ "profileId": "universityDegree", "authMethod": "PRE_AUTHORIZED" }' ``` **Response:** ```json { "offerId": "abc123-def456-ghi789", "profileId": "universityDegree", "authMethod": "PRE_AUTHORIZED", "expiresAt": 1704067500000, "credentialOffer": "openid-credential-offer://?credential_offer_uri=http%3A%2F%2Flocalhost%3A7002%2Fopenid4vci%2Fcredential-offer%3Fid%3Dabc123-def456-ghi789" } ``` ### List Sessions ```bash curl -X GET 'http://localhost:7002/issuer2/sessions' ``` ### Get Session Details ```bash curl -X GET 'http://localhost:7002/issuer2/sessions/{sessionId}' ``` ## Using the Credential Offer The `credentialOffer` URL in the response can be: 1. **Displayed as QR Code** – User scans with their wallet app 2. **Sent as Deep Link** – User clicks link on mobile device 3. **Embedded in Email/Message** – User clicks to open in wallet When the wallet resolves this URL, it will: 1. Fetch the full credential offer from the issuer 2. Display the credential details to the user 3. Request the credential using the appropriate flow ## Next Steps - [Create an Offer](https://docs.walt.id/community-stack/issuer2/credential-offers/create-offer.md) – Step-by-step guide to creating credential offers - [Protocol Flows](https://docs.walt.id/community-stack/issuer2/protocol-flows/overview.md) – Full sequence diagrams for both authentication methods - [Notifications](https://docs.walt.id/community-stack/issuer2/notifications.md) – Monitor issuance progress via webhooks or SSE -------------------------------------------------------------------------------- PAGE: Create an Offer URL: https://docs.walt.id/community-stack/issuer2/credential-offers/create-offer RAW: https://docs.walt.id/community-stack/issuer2/credential-offers/create-offer.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Create a Credential Offer This guide walks you through creating a credential offer from an existing profile. The offer generates an OID4VCI credential offer URL that can be claimed by any compliant wallet. ## Prerequisites Before creating an offer, ensure you have: 1. [Issuer2 API running](https://docs.walt.id/community-stack/issuer2/setup.md) 2. [Credential Profile configured](https://docs.walt.id/community-stack/issuer2/configurations/config-files/issuer-profiles.md) — or use one of the [ready-made default profiles](https://docs.walt.id/community-stack/issuer2/credential-issuance/sd-jwt-vc.md) ## Create an Offer **Option: CURL** **Endpoint:** `POST /issuer2/credential-offers` | [API Reference](https://issuer2.demo.walt.id/swagger/index.html) ##### Example Request ```bash curl -X 'POST' \ 'http://localhost:7002/issuer2/credential-offers' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -d '{ "profileId": "universityDegree", "authMethod": "PRE_AUTHORIZED" }' ``` **Body Parameters** - **profileId**: _String_ (required) - The ID of the credential profile to use. - **authMethod**: _String_ (required) - Authentication method: `PRE_AUTHORIZED` or `AUTHORIZED`. - **valueMode**: _String_ (optional) - How the credential offer is delivered (default: `BY_REFERENCE`). `BY_REFERENCE` — offer URL contains a reference, wallet fetches the full offer. `BY_VALUE` — full credential offer embedded in the URL. - **issuerStateMode**: _String_ (optional) - Whether to include issuer state for `AUTHORIZED` offers (default: `INCLUDE`). `OMIT` — no issuer state. `INCLUDE` — include issuer state for session correlation with authorization servers. - **expiresInSeconds**: _Integer_ (optional) - Offer validity duration in seconds (default: 300). Set to `-1` for no expiration. - **txCode**: _Object_ (optional) - Transaction code (PIN) configuration for pre-authorized flow. See [Transaction Code Configuration](#transaction-code-configuration). - **txCodeValue**: _String_ (optional) - Specific PIN value (if not provided, one is generated). - **runtimeOverrides**: _Object_ (optional) - Override profile values for this offer. See [Runtime Overrides](#runtime-overrides). - **sessionId**: _String_ (optional) - Session ID for the offer. If not provided, a random session ID will be generated. --- ##### Example Response ```json { "offerId": "abc123-def456-ghi789", "profileId": "universityDegree", "authMethod": "PRE_AUTHORIZED", "expiresAt": 1704067500000, "credentialOffer": "openid-credential-offer://?credential_offer_uri=http%3A%2F%2Flocalhost%3A7002%2Fopenid4vci%2Fcredential-offer%3Fid%3Dabc123-def456-ghi789" } ``` **Response Fields** - **offerId**: _String_ - Unique identifier for this offer. - **profileId**: _String_ - The profile used to create this offer. - **authMethod**: _String_ - The authentication method. - **issuerStateMode**: _String_ - The issuer state mode (for authorized flow). - **expiresAt**: _Long_ - Unix timestamp (ms) when the offer expires. - **txCodeValue**: _String_ - The PIN value (only for pre-authorized flow with txCode). - **credentialOffer**: _String_ - The OID4VCI credential offer URL. ## Examples ### Pre-Authorized Flow with PIN Require a PIN for additional security: ```bash curl -X POST 'http://localhost:7002/issuer2/credential-offers' \ -H 'Content-Type: application/json' \ -d '{ "profileId": "universityDegree", "authMethod": "PRE_AUTHORIZED", "txCode": { "input_mode": "numeric", "length": 6, "description": "Please enter the PIN sent to your email" } }' ``` **Response includes the PIN:** ```json { "offerId": "abc123-def456-ghi789", "profileId": "universityDegree", "authMethod": "PRE_AUTHORIZED", "expiresAt": 1704067500000, "txCodeValue": "123456", "credentialOffer": "openid-credential-offer://..." } ``` ### Pre-Authorized Flow with Custom PIN Specify your own PIN value: ```bash curl -X POST 'http://localhost:7002/issuer2/credential-offers' \ -H 'Content-Type: application/json' \ -d '{ "profileId": "universityDegree", "authMethod": "PRE_AUTHORIZED", "txCode": { "input_mode": "numeric", "length": 4 }, "txCodeValue": "1234" }' ``` ### Authorization Code Flow For user authentication via external IdP: ```bash curl -X POST 'http://localhost:7002/issuer2/credential-offers' \ -H 'Content-Type: application/json' \ -d '{ "profileId": "identityCredentialSdJwt", "authMethod": "AUTHORIZED", "issuerStateMode": "INCLUDE" }' ``` `issuerStateMode: "INCLUDE"` lets the Issuer2 API correlate the authorization request it receives back with this offer's issuance session. See [Authorization Code Flow](https://docs.walt.id/community-stack/issuer2/protocol-flows/authorization-code-flow.md) for the full sequence, including how (and when) `issuer_state` is forwarded to the external authorization server. **Note:** `issuer_state` can be viewed as a mechanism in OpenID4VCI for correlating a Credential Offer with the later Authorization Request in the Authorization Code Flow. The issuer creates it, the wallet treats it as opaque, and if it is present in the offer the wallet sends it back in the Authorization Request. Issuer2 includes `issuer_state` by default for `AUTHORIZED` offers. If you explicitly set `issuerStateMode` to `OMIT`, do not use `runtimeOverrides`; offer-specific overrides require issuer-state correlation and the request is rejected. **Note:** You can run this example without setting up an identity provider. Out of the box it uses a [hosted walt.id test provider](https://docs.walt.id/community-stack/issuer2/configurations/config-files/authentication-service.md#default-test-provider) — open the offer in a wallet and log in as `jane@walt.id` / `jane`. Because the `identityCredentialSdJwt` profile maps `given_name` and `family_name` from the ID token, the issued credential is populated with Jane's name from her login. ### Offer by Value Embed the full offer in the URL (useful for offline scenarios): ```bash curl -X POST 'http://localhost:7002/issuer2/credential-offers' \ -H 'Content-Type: application/json' \ -d '{ "profileId": "universityDegree", "authMethod": "PRE_AUTHORIZED", "valueMode": "BY_VALUE" }' ``` ### Custom Expiration Set a longer expiration time: ```bash curl -X POST 'http://localhost:7002/issuer2/credential-offers' \ -H 'Content-Type: application/json' \ -d '{ "profileId": "universityDegree", "authMethod": "PRE_AUTHORIZED", "expiresInSeconds": 3600 }' ``` ### Runtime Overrides Override profile values for a specific offer — including the signing key, not just the credential data: ```bash curl -X POST 'http://localhost:7002/issuer2/credential-offers' \ -H 'Content-Type: application/json' \ -d '{ "profileId": "universityDegree", "authMethod": "PRE_AUTHORIZED", "runtimeOverrides": { "issuerKey": { "type": "jwk", "jwk": { "kty": "OKP", "d": "5bx6BJCRTtKmXacRtnhjQ6i07UWaAWRixZvFD3t9f00", "crv": "Ed25519", "kid": "N4dxEvjiNg9RwEqySdHm2VTjfB0M0JyO9UV5_SS9sQE", "x": "u2fhaRWUv-Uo24he12WLHTbcdJHWis7VCBA8PJkB0o0" } }, "issuerDid": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiTjRkeEV2amlOZzlSd0VxeVNkSG0yVlRqZkIwTTBKeU85VVY1X1NTOXNRRSIsIngiOiJ1MmZoYVJXVXYtVW8yNGhlMTJXTEhUYmNkSkhXaXM3VkNCQThQSmtCMG8wIn0", "credentialData": { "@context": [ "https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1" ], "type": ["VerifiableCredential", "UniversityDegree"], "credentialSubject": { "degree": { "type": "MasterDegree", "name": "Master of Computer Science" } } }, "notifications": { "webhook": { "url": "https://my-server.com/webhook/special-offer" } } } }' ``` **Note:** `issuerKey` (and `issuerDid`/`x5Chain`) override the profile's default signing key for this offer only — useful for issuing a one-off credential with a different key without touching the profile itself. Provide both together, since a mismatched key/DID pair will fail to sign. ### Available Override Fields | Field | Description | |-----------------------------------|-------------------------------------------| | `issuerDid` | Override the issuer DID | | `issuerKey` | Override the signing key | | `x5Chain` | Override the X.509 certificate chain | | `credentialData` | Override the credential data | | `mapping` | Override the data mapping | | `selectiveDisclosure` | Override the SD-JWT selective disclosure configuration | | `idTokenClaimsMapping` | Override ID token claims mapping | | `mDocNameSpacesDataMappingConfig` | Override mDoc data mapping | | `credentialStatus` | Override credential status configuration | | `notifications` | Override notification settings | ## Transaction Code Configuration The `txCode` object configures PIN requirements: | Property | Type | Description | |----------|------|-------------| | `input_mode` | String | Input type: `numeric` or `text` | | `length` | Integer | Length of the PIN | | `description` | String | Description shown to the user | ## Using the Credential Offer URL The `credentialOffer` URL can be: 1. **Displayed as QR Code** – User scans with their wallet app 2. **Sent as Deep Link** – User clicks link on mobile device 3. **Embedded in Email/Message** – User clicks to open in wallet ### Example Decoded Offer URL ``` openid-credential-offer://?credential_offer_uri=http://localhost:7002/openid4vci/credential-offer?id=abc123-def456-ghi789 ``` ## Next Steps - [Notifications](https://docs.walt.id/community-stack/issuer2/notifications.md) – Configure webhook notifications - [Data Functions](https://docs.walt.id/community-stack/issuer2/data-functions.md) – Populate credentials with dynamic data -------------------------------------------------------------------------------- PAGE: Credential Status URL: https://docs.walt.id/community-stack/issuer2/credential-offers/credential-status RAW: https://docs.walt.id/community-stack/issuer2/credential-offers/credential-status.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Credential Status Unlike the [Enterprise Stack](https://docs.walt.id/enterprise-stack/home.md), where we offer a credential status service to allow you to create, manage and serve status lists to enable credential revocation - within the Community Stack you are responsible for hosting and managing the status lists yourself. However, you are still able to embed the status information within a credential profile or credential offer override like so: ## Profile Configuration ### Bitstring Status List with W3C JWT ```json { "profileId": "university-degree-with-status", "name": "University Degree with Revocation", "credentialConfigurationId": "UniversityDegree_jwt_vc_json", "issuerKey": { "type": "jwk", ... }, "issuerDid": "did:key:...", "credentialData": { ... }, "credentialStatus": { "id": "https://issuer.example.com/status/1#94567", "type": "BitstringStatusListEntry", "statusPurpose": "revocation", "statusListIndex": "94567", "statusListCredential": "https://issuer.example.com/status/1" } } ``` ### Token Status List with IETF SD-JWT ```json { "profileId": "identity-sdjwt-with-status", "name": "Identity Credential with Status", "credentialConfigurationId": "identity_credential", "issuerKey": { "type": "jwk", "..." }, "issuerDid": "did:key:...", "credentialData": { "vct": "https://example.com/identity/1", "given_name": "John", "family_name": "Smith", "birthdate": "1980-01-01" }, "credentialStatus": { "status_list": { "idx": 12345, "uri": "https://issuer.example.com/status/sdjwt/1" } } } ``` ### Token Status List with mDocs ```json { "profileId": "mdl-with-status", "name": "Mobile Driving License with Status", "credentialConfigurationId": "org.iso.18013.5.1.mDL", "issuerKey": { "type": "jwk", "..." }, "issuerDid": "did:key:...", "credentialData": { "org.iso.18013.5.1": { "family_name": "Smith", "given_name": "John", "birth_date": "1980-01-01" } }, "credentialStatus": { "status_list": { "idx": 94567, "uri": "https://issuer.example.com/status/mdoc/1" } } } ``` ## Credential Offer Override ### Bitstring Status List with W3C JWT ```json { "profileId": "universityDegree", "authMethod": "PRE_AUTHORIZED", "runtimeOverrides": { "credentialStatus": { "id": "https://issuer.example.com/status/2#12345", "type": "BitstringStatusListEntry", "statusPurpose": "suspension", "statusListIndex": "12345", "statusListCredential": "https://issuer.example.com/status/2" } } } ``` ### Token Status List with IETF SD-JWT ```json { "profileId": "identityCredentialSdJwt", "authMethod": "PRE_AUTHORIZED", "runtimeOverrides": { "credentialStatus": { "status_list": { "idx": 12345, "uri": "https://issuer.example.com/status/sdjwt/2" } } } } ``` ### Token Status List with mDocs ```json { "profileId": "isoMdl", "authMethod": "PRE_AUTHORIZED", "runtimeOverrides": { "credentialStatus": { "status_list": { "idx": 12345, "uri": "https://issuer.example.com/status/mdoc/2" } } } } ``` -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/community-stack/issuer2/protocol-flows/overview RAW: https://docs.walt.id/community-stack/issuer2/protocol-flows/overview.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Overview Issuer2 supports two OID4VCI flows for claiming a credential offer, set via the `authMethod` field when [creating a credential offer](https://docs.walt.id/community-stack/issuer2/credential-offers/create-offer.md). This section walks through the full sequence for each so you know what's actually happening between the wallet, your issuer, and (for one of them) an external identity provider. ## Which Flow Should I Use? | | Pre-Authorized Code Flow | Authorization Code Flow | |---|---|---| | **`authMethod`** | `PRE_AUTHORIZED` | `AUTHORIZED` | | **User authentication** | None (optionally a PIN/`txCode`) | Required — user logs in at an external IdP | | **Best for** | Known users (already logged in) and data already verified before the offer is created | Unknown users, or credential data that should come from the user's own IdP login (e.g. corporate SSO) | | **Extra setup required** | None | None to try it — a [hosted test provider](https://docs.walt.id/community-stack/issuer2/configurations/config-files/authentication-service.md#default-test-provider) with ready-made logins is used by default. For production, register your own OAuth/OIDC provider in [`authentication-service.conf`](https://docs.walt.id/community-stack/issuer2/configurations/config-files/authentication-service.md) | | **Credential data source** | Fully defined in the profile (or runtime overrides) ahead of time | Profile defaults (or runtime overrides), enriched with claims from the IdP's ID token via `idTokenClaimsMapping` | ## Available Flows - [Pre-Authorized Code Flow](https://docs.walt.id/community-stack/issuer2/protocol-flows/pre-authorized-flow.md) — Sequence diagram for issuing a credential with no user login. - [Authorization Code Flow](https://docs.walt.id/community-stack/issuer2/protocol-flows/authorization-code-flow.md) — Sequence diagram for issuing a credential after the holder authenticates against an external IdP. -------------------------------------------------------------------------------- PAGE: Pre-Authorized Code Flow URL: https://docs.walt.id/community-stack/issuer2/protocol-flows/pre-authorized-flow RAW: https://docs.walt.id/community-stack/issuer2/protocol-flows/pre-authorized-flow.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Pre-Authorized Code Flow This flow covers your application creating a credential offer, and the wallet claiming it via OID4VCI with no user login. Set `"authMethod": "PRE_AUTHORIZED"` when [creating the offer](https://docs.walt.id/community-stack/issuer2/credential-offers/create-offer.md) to use it. **Components involved:** - Your Application — creates the credential offer and displays the QR code - Wallet (Holder) — scans the QR code, completes the OID4VCI flow, stores the credential - Issuer2 API — manages the issuance session, validates the code/PIN, and signs the credential ```mermaid sequenceDiagram participant App as Your Application participant Wallet as Wallet
(Holder) participant Issuer as Issuer2 API App->>Issuer: POST /issuer2/credential-offers
{profileId, authMethod: PRE_AUTHORIZED} activate Issuer Issuer->>Issuer: Create issuance session and
pre-authorized code (+ optional txCode/PIN) Issuer-->>App: 201 Created
{credentialOffer: "openid-credential-offer://..."} deactivate Issuer App->>App: Generate QR code with
credential offer URL App-->>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/openid4vci Issuer-->>Wallet: Issuer metadata Wallet->>Issuer: POST /token
(pre-authorized_code [+ tx_code]) activate Issuer Issuer->>Issuer: Validate code (+ PIN if configured) Issuer-->>Wallet: access_token deactivate Issuer Wallet->>Issuer: POST /nonce activate Issuer Issuer->>Issuer: Generate fresh c_nonce Issuer-->>Wallet: {c_nonce} deactivate Issuer Wallet->>Wallet: Generate proof of possession
(incl. sign c_nonce with holder key) Wallet->>Issuer: POST /credential
(access_token, proof) activate Issuer Issuer->>Issuer: Resolve profile, apply
mapping / data functions Issuer->>Issuer: Sign credential
with issuerKey Issuer-->>Wallet: 200 OK
{credential} deactivate Issuer Wallet->>Wallet: Store credential ``` **Note:** This diagram is a simplified overview focused on how the Issuer2 API behaves. It omits some protocol details (e.g. metadata discovery of the Nonce Endpoint, PKCE, and exact `.well-known` paths). For the complete, normative sequence see the [OpenID for Verifiable Credential Issuance 1.0 specification](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html). ## Related - [Credential Issuance](https://docs.walt.id/community-stack/issuer2/credential-issuance/sd-jwt-vc.md) — Try this flow end-to-end - [Create an Offer](https://docs.walt.id/community-stack/issuer2/credential-offers/create-offer.md) — `txCode` and other offer parameters - [Authorization Code Flow](https://docs.walt.id/community-stack/issuer2/protocol-flows/authorization-code-flow.md) — The alternative flow, when user login is required -------------------------------------------------------------------------------- PAGE: Authorization Code Flow URL: https://docs.walt.id/community-stack/issuer2/protocol-flows/authorization-code-flow RAW: https://docs.walt.id/community-stack/issuer2/protocol-flows/authorization-code-flow.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Authorization Code Flow This flow covers your application creating a credential offer, the wallet redirecting the holder to an external IdP to authenticate, and the Issuer2 API mapping the returned ID token claims into the credential. Set `"authMethod": "AUTHORIZED"` when [creating the offer](https://docs.walt.id/community-stack/issuer2/credential-offers/create-offer.md) to use it. **Components involved:** - Your Application — creates the credential offer and displays the QR code - Wallet (Holder) — scans the QR code, redirects the holder to log in, completes the flow - Issuer2 API — manages the issuance session, correlates `issuer_state`, applies `idTokenClaimsMapping`, and signs the credential - External IdP — authenticates the holder and issues the ID token (registered in [`authentication-service.conf`](https://docs.walt.id/community-stack/issuer2/configurations/config-files/authentication-service.md)) **Note:** **Trying this out?** You don't need your own identity provider to start. If you haven't configured `authentication-service.conf`, Issuer2 uses a [hosted walt.id test provider](https://docs.walt.id/community-stack/issuer2/configurations/config-files/authentication-service.md#default-test-provider) with ready-made login accounts (e.g. `jane@walt.id` / `jane`), so you can walk through the whole flow immediately. **Error:** For production, this flow requires **your own** external OAuth/OIDC provider (e.g. Keycloak, Microsoft Entra ID, Auth0) registered in `authentication-service.conf` before you create an `AUTHORIZED` offer. See [Authentication Service Configuration](https://docs.walt.id/community-stack/issuer2/configurations/config-files/authentication-service.md) to set one up. ```mermaid sequenceDiagram participant App as Your Application participant Wallet as Wallet
(Holder) participant Issuer as Issuer2 API participant IdP as External IdP
(e.g. Keycloak) App->>Issuer: POST /issuer2/credential-offers
{profileId, authMethod: AUTHORIZED, issuerStateMode: INCLUDE} activate Issuer Issuer->>Issuer: Create issuance session
and issuer_state Issuer-->>App: 201 Created
{credentialOffer: "openid-credential-offer://..."} deactivate Issuer App->>App: Generate QR code with
credential offer URL App-->>Wallet: Display QR code Wallet->>Wallet: Scan QR code Wallet->>Issuer: GET /.well-known/openid-credential-issuer/openid4vci Issuer-->>Wallet: Issuer metadata Wallet->>Issuer: GET /authorize
(response_type=code, issuer_state) activate Issuer Issuer->>Issuer: Resolve issuance session from issuer_state Issuer-->>Wallet: 302 redirect to IdP authorizeUrl deactivate Issuer Wallet-->>IdP: Follow redirect to authorizeUrl IdP->>IdP: User authenticates IdP-->>Issuer: Redirect to
/openid4vci/external/oauth/callback?code=...&state=... activate Issuer Issuer->>IdP: Exchange code at accessTokenUrl
(handled transparently by the issuer's OAuth client) IdP-->>Issuer: id_token (+ access_token) Issuer->>Issuer: Look up session by external OAuth state,
apply idTokenClaimsMapping into credentialData Issuer-->>Wallet: 302 redirect back to wallet's redirect_uri
with issuer's own authorization code deactivate Issuer Wallet->>Issuer: POST /token
(authorization_code) Issuer-->>Wallet: access_token Wallet->>Issuer: POST /nonce activate Issuer Issuer->>Issuer: Generate fresh c_nonce Issuer-->>Wallet: {c_nonce} deactivate Issuer Wallet->>Wallet: Generate proof of possession
(incl. sign c_nonce with holder key) Wallet->>Issuer: POST /credential
(access_token, proof) activate Issuer Issuer->>Issuer: Sign credential using
mapped claims Issuer-->>Wallet: 200 OK
{credential} deactivate Issuer Wallet->>Wallet: Store credential ``` **Note:** This diagram is a simplified overview focused on how the Issuer2 API behaves. It omits some protocol details (e.g. metadata discovery of the Nonce Endpoint, PKCE, and the separate authorization-server metadata fetch). For the complete, normative sequence see the [OpenID for Verifiable Credential Issuance 1.0 specification](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html). ## Understanding `issuer_state` `issuer_state` is how the Issuer2 API correlates the authorization request it receives back at `/authorize` with the issuance session created when the offer was made. Set `"issuerStateMode": "INCLUDE"` when creating the offer to have it included. By default, `issuer_state` is **not** forwarded to the external IdP — it's only used internally by the issuer. Set `forwardIssuerStateToAuthorizationServer = true` in `authentication-service.conf` only if your IdP integration specifically needs to receive it as a query parameter on the outgoing authorize request. ## Related - [Authentication Service Configuration](https://docs.walt.id/community-stack/issuer2/configurations/config-files/authentication-service.md) — Register your OAuth/OIDC provider - [Issuer Profiles — ID Token Claim Mapping](https://docs.walt.id/community-stack/issuer2/configurations/config-files/issuer-profiles.md#id-token-claim-mapping) — Map ID token claims into credential fields - [Create an Offer](https://docs.walt.id/community-stack/issuer2/credential-offers/create-offer.md) — `issuerStateMode` and other offer parameters - [Pre-Authorized Code Flow](https://docs.walt.id/community-stack/issuer2/protocol-flows/pre-authorized-flow.md) — The simpler alternative, when user login isn't needed -------------------------------------------------------------------------------- PAGE: Feature Manager URL: https://docs.walt.id/community-stack/issuer2/configurations/feature-manager RAW: https://docs.walt.id/community-stack/issuer2/configurations/feature-manager.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Feature Manager Use the feature manager to control which Issuer2 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 always enabled and do not require additional configuration in most cases. They provide critical functionality expected to be available by default. ### Optional Features Optional features can be enabled or disabled based on your requirements. 1. **Enabled by default**: These features work without additional configuration (e.g. no extra config file needed). 2. **Disabled by default**: These features must be explicitly enabled in `_features.conf`. They may require their own configuration to function. **Note:** Each feature may have zero, one, or multiple configuration files associated with it. ## How the Feature Manager Works The Feature Manager ensures efficient and error-free service initialisation through the following steps: 1. **Initialisation with Feature Catalogs**: Services are passed one or more Feature Catalogs on startup. 2. **Determination of Active Features**: The initialiser identifies explicitly enabled or disabled features and resolves the default states of optional features. 3. **Loading Configurations**: Configurations for base features and explicitly enabled optional features are loaded. **This results in:** - Only necessary configurations being loaded, reducing complexity and avoiding unused features. - Minimal required configuration through sensible defaults. - Invalid configurations being caught during initialisation before they can reach production. ## Feature Configuration Configure features by updating the `_features.conf` file as explained [here](https://docs.walt.id/community-stack/issuer2/configurations/config-files/features.md). -------------------------------------------------------------------------------- PAGE: Features URL: https://docs.walt.id/community-stack/issuer2/configurations/config-files/features RAW: https://docs.walt.id/community-stack/issuer2/configurations/config-files/features.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Features With `_features.conf` you can control which Issuer2 API features are enabled. They are managed via the [feature manager](https://docs.walt.id/community-stack/issuer2/configurations/feature-manager.md). ## Available Features ### Base Features Base features are always enabled. They do not need to be listed in `_features.conf`. - `credential-types` — Credential types available in this issuer instance. Loads [credential-issuer-metadata.conf](https://docs.walt.id/community-stack/issuer2/configurations/config-files/credential-issuer-metadata.md) and [issuer2-profiles.conf](https://docs.walt.id/community-stack/issuer2/configurations/config-files/issuer-profiles.md). - `issuer-service` — Core issuer service implementation. Loads [issuer-service.conf](https://docs.walt.id/community-stack/issuer2/configurations/config-files/issuer-service.md). - `authentication-service` — External OAuth/OIDC provider used for authorization-code-flow issuance. Loads [authentication-service.conf](https://docs.walt.id/community-stack/issuer2/configurations/config-files/authentication-service.md). - `web` — Web server. Loads [web.conf](https://docs.walt.id/community-stack/issuer2/configurations/config-files/web.md). ### Optional Features - `persistence` — Redis-backed storage for issuance sessions, codes, and tokens. Disabled by default. Loads [persistence.conf](https://docs.walt.id/community-stack/issuer2/configurations/config-files/persistence.md). When disabled, state is kept in memory only. - `dev-mode` — Development mode. Disabled by default. When enabled, loads additional settings from [dev-mode.conf](https://docs.walt.id/community-stack/issuer2/configurations/config-files/dev-mode.md) (e.g. allows `did:web` resolution over plain HTTP). - `debug-endpoints` — Debug endpoints (heap dump, memory, CPU, etc.). Disabled by default. The remaining features (`httpclient`, `openapi`, `feature-flag-information-endpoint`, `healthchecks`) are enabled by default and provide standard infrastructure — the HTTP data fetcher, OpenAPI/Swagger, feature-state and health endpoints, and the OAuth client for authorization-code login. Most deployments leave these as-is. ## Example Setup ```editorconfig[_features.conf] enabledFeatures = [ persistence, dev-mode ] disabledFeatures = [ # ... ] ``` -------------------------------------------------------------------------------- PAGE: Issuer Service URL: https://docs.walt.id/community-stack/issuer2/configurations/config-files/issuer-service RAW: https://docs.walt.id/community-stack/issuer2/configurations/config-files/issuer-service.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Issuer Service Configuration The `issuer-service.conf` file configures core Issuer2 service settings: the service's public URL, the key used to sign OAuth2 tokens, and the Pushed Authorization Request (PAR) policy. ## File Location ``` waltid-services/waltid-issuer-api2/config/issuer-service.conf ``` ## Configuration Options | Property | Type | Required | Default | Description | |----------|------|----------|---------|-------------| | `baseUrl` | String | Yes | – | Public URL where the issuer service is reachable. | | `ciTokenKey` | String | No | Generated secp256r1 JWK | Serialized key used to sign/verify OAuth2 access & refresh tokens. | | `enforcePushedAuthorizationRequests` | Boolean | No | `false` | Require clients to use the Pushed Authorization Request (PAR) endpoint. | ### baseUrl The public URL where the issuer service is accessible. Every OpenID4VCI endpoint is derived from it: - The credential issuer base and metadata URLs become `"/openid4vci"`. This is what is written into credential offers and the `.well-known` metadata, so it **must** be the URL wallets can actually reach. - The external OAuth callback becomes `"/openid4vci/external/oauth/callback"`. Trailing slashes are trimmed automatically. ```hocon baseUrl = "http://localhost:7002" ``` When running via Docker Compose, environment interpolation is supported: ```hocon baseUrl = "http://${SERVICE_HOST}:${ISSUER_API2_PORT}" ``` ### ciTokenKey The key used to sign and verify the OAuth2 access tokens and refresh tokens issued by the service's token endpoint. The value is a **serialized walt.id key** — the same format used elsewhere in walt.id — provided as a single JSON string. The `type` field selects the key backend and **is required**; omitting it causes startup to fail. Because the value is a string (not a HOCON object), use a triple-quoted HOCON string so the inner quotes do not need escaping: ```hocon ciTokenKey = """{"type":"jwk","jwk":{"kty":"EC","d":"...","crv":"P-256","x":"...","y":"..."}}""" ``` Supported `type` values include `jwk` (local key), `tse` (HashiCorp Vault Transit), `aws-rest-api` (AWS KMS), `azure-rest-api` (Azure Key Vault) and `oci-rest-api` (Oracle Cloud KMS). #### Generating a key The simplest way to get a stable local JWK in exactly the format above is a one-line [Node.js](https://nodejs.org) command. It is identical on macOS, Linux, and Windows (PowerShell or cmd): ```bash node -e "const{generateKeyPairSync}=require('crypto');const{privateKey}=generateKeyPairSync('ec',{namedCurve:'P-256'});const q=String.fromCharCode(34).repeat(3);console.log('ciTokenKey = '+q+JSON.stringify({type:'jwk',jwk:privateKey.export({format:'jwk'})})+q)" ``` It prints the complete config line — triple-quoted and ready to paste straight into `issuer-service.conf`, for example: ```hocon ciTokenKey = """{"type":"jwk","jwk":{"kty":"EC","x":"...","y":"...","crv":"P-256","d":"..."}}""" ``` Generate your own — never reuse the example value from these docs. If you don't have Node installed, you can instead simply omit `ciTokenKey`: the service generates a key automatically on startup. Note the limitation described below. **Error:** If omitted, a new secp256r1 JWK is generated on **every** startup. This is fine for local development, but not for production: the signing key changes on each restart, so any tokens issued before a restart can no longer be verified. Always set `ciTokenKey` explicitly in real deployments. ### enforcePushedAuthorizationRequests Controls the OAuth2 Pushed Authorization Request (PAR, RFC 9126) policy for the authorization code flow. Defaults to `false`. When `true`, clients **must** submit authorization parameters to the PAR (`/par`) endpoint instead of passing them directly to `/authorize`. Leave this `false` unless you specifically need to mandate PAR. ```hocon enforcePushedAuthorizationRequests = false ``` ## Example Configuration ```hocon # issuer-service.conf baseUrl = "http://localhost:7002" # Optional: provide a stable token signing key (recommended for anything beyond local dev) ciTokenKey = """{"type":"jwk","jwk":{"kty":"EC","d":"...","crv":"P-256","x":"...","y":"..."}}""" # Optional: require Pushed Authorization Requests enforcePushedAuthorizationRequests = false ``` ## Production Configuration For production deployments, ensure: 1. `baseUrl` points to your public HTTPS URL. 2. `ciTokenKey` is set to a securely generated, persistent key (consider a KMS backend such as `aws-rest-api`, `azure-rest-api` or `tse` rather than an inline JWK). ```hocon # Production example baseUrl = "https://issuer.example.com" ciTokenKey = """{"type":"jwk","jwk":{"kty":"EC","d":"...","crv":"P-256","x":"...","y":"..."}}""" enforcePushedAuthorizationRequests = true ``` ## Related Configuration These concerns are configured in their own files, **not** in `issuer-service.conf`: - [Credential Issuer Metadata](https://docs.walt.id/community-stack/issuer2/configurations/config-files/credential-issuer-metadata.md) – OID4VCI metadata configuration (`credential-issuer-metadata.conf`) - [Issuer Profiles](https://docs.walt.id/community-stack/issuer2/configurations/config-files/issuer-profiles.md) – Credential profiles, including issuer signing keys and DIDs (`issuer2-profiles.conf`) -------------------------------------------------------------------------------- PAGE: Credential Issuer Metadata URL: https://docs.walt.id/community-stack/issuer2/configurations/config-files/credential-issuer-metadata RAW: https://docs.walt.id/community-stack/issuer2/configurations/config-files/credential-issuer-metadata.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Credential Issuer Metadata Configuration The `credential-issuer-metadata.conf` file defines the OpenID4VCI credential issuer metadata: how the issuer presents itself, and which credential types it supports. Its contents are published at the `/.well-known/openid-credential-issuer/openid4vci` endpoint. The file has exactly **two top-level keys**: `issuerDisplay` and `credentialConfigurations`. Each is parsed into strongly-typed OpenID4VCI metadata objects at startup, so the structure below is validated — an invalid entry stops the service from loading. ## File Location ``` waltid-services/waltid-issuer-api2/config/credential-issuer-metadata.conf ``` ## Configuration Options | Property | Type | Required | Description | |----------|------|----------|-------------| | `issuerDisplay` | Array | No | How the issuer (name/logo) appears in wallets. | | `credentialConfigurations` | Object (map) | No | Supported credential types, keyed by credential configuration ID. | ## issuerDisplay A list of display entries for the **issuer itself**, typically one per locale. Purely presentational. | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | String | No | Issuer display name. | | `locale` | String | No | BCP-47 locale, e.g. `"en-US"`. | | `logo` | Object | No | Issuer logo. | | `logo.uri` | String | Yes (within `logo`) | Logo URL. Must be non-blank. | | `logo.alt_text` | String | No | Alternative text for the logo. | ```hocon issuerDisplay = [ { name = "Example Issuer" locale = "en-US" logo = { uri = "https://example.com/logo.png" alt_text = "Example Issuer Logo" } } ] ``` ## credentialConfigurations A map of the credential types this issuer supports. **The map key is the credential configuration ID** (e.g. `"UniversityDegree_jwt_vc_json"`). This ID is the contract point with the rest of the system: - Profiles in [`issuer2-profiles.conf`](https://docs.walt.id/community-stack/issuer2/configurations/config-files/issuer-profiles.md) reference it via `credentialConfigurationId`. - It is matched against OAuth scopes during issuance. ### Fields | Field | Type | Required | Description | |-------|------|----------|-------------| | `format` | String (enum) | **Yes** | Credential format. One of `jwt_vc_json`, `dc+sd-jwt`, `mso_mdoc`. | | `scope` | String | No | OAuth scope used to request this credential. Conventionally equal to the configuration ID. Must be non-blank if present. | | `credential_definition` | Object | No | For W3C VC formats: `{ "@context": [...], type: [...] }`. | | `doctype` | String | No | Document type for `mso_mdoc`, e.g. `org.iso.18013.5.1.mDL`. | | `vct` | String | No | Verifiable Credential Type for `dc+sd-jwt`. See [VCT handling](#vct-handling). | | `credential_signing_alg_values_supported` | Array | No | Format-specific (see below). Must be non-empty if present. | | `cryptographic_binding_methods_supported` | Array | No | Key binding methods. See [co-dependency](#binding-methods-and-proof-types). | | `proof_types_supported` | Object | No | Wallet key-proof types. See [co-dependency](#binding-methods-and-proof-types). | | `credential_metadata` | Object | No | Per-credential display and claim descriptions. See [credential_metadata](#credential_metadata). | | *(any other key)* | — | No | Passed through verbatim into the published metadata as a custom parameter. Must not reuse a standard field name above. | **Error:** Only `format` is strictly required by the schema. However, a credential type that wallets can actually request normally also needs `scope`, `cryptographic_binding_methods_supported`, `proof_types_supported` and `credential_signing_alg_values_supported`. ### Binding methods and proof types `cryptographic_binding_methods_supported` and `proof_types_supported` are **co-dependent**: if you set one, you must set the other. Setting only one stops the service from loading. **`cryptographic_binding_methods_supported`** accepts: - `jwk` - `cose_key` - `did:` — e.g. `did:key`, `did:jwk`, `did:web`, `did:ebsi` **`proof_types_supported`** is a map keyed by proof type (typically `jwt`): ```hocon proof_types_supported = { jwt = { proof_signing_alg_values_supported = ["ES256"] # required, non-empty } } ``` ### Signing algorithms `credential_signing_alg_values_supported` is validated against the `format`: | Format | Expected values | Example | |--------|-----------------|---------| | `jwt_vc_json`, `dc+sd-jwt` | JOSE algorithm names (strings) | `["ES256"]` | | `mso_mdoc` | COSE algorithm identifiers (integers), e.g. `-7` = ES256 | `[-7, -9]` | ### VCT handling For `dc+sd-jwt` credentials, set `vct` to the special value `"vctBaseUrl"` to publish a self-hosted VCT: - It is replaced at runtime with `/openid4vci/`. - The issuer serves the corresponding VCT type metadata at `/.well-known/vct/`. Any other string is published unchanged, so you can also point to an externally hosted VCT, e.g. `vct = "https://example.com/credentials/identity"`. ### credential_metadata Per-credential display and claim metadata lives **under `credential_metadata`** — not at the top level of the credential configuration. **Error:** A `display` (or `claims`) key placed directly on the credential configuration is **not** interpreted as OpenID4VCI display metadata. It is treated as a custom parameter and republished without validation. Put display and claims under `credential_metadata`. **`credential_metadata.display`** — optional list of display entries (one per locale; locales must be unique): | Property | Type | Required | Description | |----------|------|----------|-------------| | `name` | String | Yes | Credential display name (non-blank). | | `locale` | String | No | Locale code, e.g. `"en-US"`. | | `logo` | Object | No | `{ uri, alt_text }` — `uri` required and must include a scheme. | | `description` | String | No | Description of the credential. | | `background_color` | String | No | Hex color. | | `background_image` | Object | No | `{ uri }` — must include a scheme. | | `text_color` | String | No | Hex color. | **`credential_metadata.claims`** — optional list of claim descriptions: | Property | Type | Required | Description | |----------|------|----------|-------------| | `path` | Array<String> | Yes | Claim path (non-empty, no blank segments). | | `mandatory` | Boolean | No | Whether the claim is mandatory. | | `display` | Array | No | `{ name, locale }` entries; locales must be unique. | ## Credential Configuration Examples ### SD-JWT VC (dc+sd-jwt) ```hocon "identity_credential" = { format = "dc+sd-jwt" scope = "identity_credential" vct = "vctBaseUrl" cryptographic_binding_methods_supported = ["jwk", "did:key", "did:web"] credential_signing_alg_values_supported = ["ES256"] proof_types_supported = { jwt = { proof_signing_alg_values_supported = ["ES256"] } } credential_metadata = { display = [ { name = "Identity Credential" locale = "en-US" background_color = "#12107c" text_color = "#FFFFFF" } ] } } ``` ### W3C JWT VC (jwt_vc_json) ```hocon "UniversityDegree_jwt_vc_json" = { format = "jwt_vc_json" scope = "UniversityDegree_jwt_vc_json" cryptographic_binding_methods_supported = ["jwk", "did:key", "did:web", "did:jwk"] credential_signing_alg_values_supported = ["ES256"] proof_types_supported = { jwt = { proof_signing_alg_values_supported = ["ES256"] } } credential_definition = { type = ["VerifiableCredential", "UniversityDegree"] } credential_metadata = { display = [ { name = "University Degree" locale = "en-US" logo = { uri = "https://example.edu/logo.png" alt_text = "University Logo" } background_color = "#1e3a8a" text_color = "#FFFFFF" } ] } } ``` ### mDoc (mso_mdoc) ```hocon "org.iso.18013.5.1.mDL" = { format = "mso_mdoc" scope = "org.iso.18013.5.1.mDL" doctype = "org.iso.18013.5.1.mDL" cryptographic_binding_methods_supported = ["cose_key"] credential_signing_alg_values_supported = [-7, -9] # COSE identifiers, e.g. -7 = ES256 proof_types_supported = { jwt = { proof_signing_alg_values_supported = ["ES256"] } } credential_metadata = { display = [ { name = "Mobile Driver's License" locale = "en-US" logo = { uri = "https://example.gov/dmv-logo.png" alt_text = "DMV Logo" } } ] } } ``` ## Example Configuration ```hocon # credential-issuer-metadata.conf issuerDisplay = [ { name = "Example University" locale = "en-US" logo = { uri = "https://example.edu/logo.png" alt_text = "Example University Logo" } } ] credentialConfigurations = { "identity_credential" = { format = "dc+sd-jwt" scope = "identity_credential" vct = "vctBaseUrl" cryptographic_binding_methods_supported = ["jwk", "did:key", "did:web"] credential_signing_alg_values_supported = ["ES256"] proof_types_supported = { jwt = { proof_signing_alg_values_supported = ["ES256"] } } credential_metadata = { display = [ { name = "Identity Credential" locale = "en-US" background_color = "#12107c" text_color = "#FFFFFF" } ] } } "UniversityDegree_jwt_vc_json" = { format = "jwt_vc_json" scope = "UniversityDegree_jwt_vc_json" cryptographic_binding_methods_supported = ["jwk", "did:key", "did:web"] credential_signing_alg_values_supported = ["ES256"] proof_types_supported = { jwt = { proof_signing_alg_values_supported = ["ES256"] } } credential_definition = { type = ["VerifiableCredential", "UniversityDegree"] } credential_metadata = { display = [ { name = "University Degree" locale = "en-US" background_color = "#1e3a8a" text_color = "#FFFFFF" } ] } } } ``` ## Related Configuration - [Issuer Profiles](https://docs.walt.id/community-stack/issuer2/configurations/config-files/issuer-profiles.md) – Binds a `credentialConfigurationId` from this file to an issuer key and credential data (`issuer2-profiles.conf`) - [Issuer Service](https://docs.walt.id/community-stack/issuer2/configurations/config-files/issuer-service.md) – Core service configuration, including the `baseUrl` used to build the published metadata -------------------------------------------------------------------------------- PAGE: Issuer Profiles URL: https://docs.walt.id/community-stack/issuer2/configurations/config-files/issuer-profiles RAW: https://docs.walt.id/community-stack/issuer2/configurations/config-files/issuer-profiles.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Issuer Profiles Configuration The `issuer2-profiles.conf` file defines **credential profiles** — templates that bind a credential type (declared in [`credential-issuer-metadata.conf`](https://docs.walt.id/community-stack/issuer2/configurations/config-files/credential-issuer-metadata.md)) to a concrete signing key, an issuer identifier, and the data that goes into the credential. All of the data defined in the profile can also be later overwritten using runtime overwrites when creating a credential offer. The file has two parts: 1. **Top-level shared material** — `defaultIssuerKey`, `defaultIssuerDid`, `defaultIssuerX5chain`. These are *not* applied automatically; profiles reuse them through HOCON substitution (see [Shared defaults](#shared-defaults-and-hocon-substitution)). 2. **`profiles`** — a map of named profiles. Each profile is parsed into a strongly-typed object at startup and validated, so an invalid entry stops the service from loading. ## File Location ``` waltid-services/waltid-issuer-api2/config/issuer2-profiles.conf ``` ## Configuration Options | Property | Type | Required | Description | |----------|------|----------|-------------| | `defaultIssuerKey` | Object | No | Shared issuer signing key, referenced by profiles via `${defaultIssuerKey}`. | | `defaultIssuerDid` | String | No | Shared issuer DID, referenced via `${defaultIssuerDid}`. | | `defaultIssuerX5chain` | Array<String> | No | Shared X.509 certificate chain (PEM) for mDoc / x5c signing, referenced via `${defaultIssuerX5chain}`. | | `profiles` | Object (map) | No | Credential profiles, keyed by profile ID. | ## Shared defaults The top-level defined `defaultIssuerKey`, `defaultIssuerDid` and `defaultIssuerX5chain` keys are global configuration values which can be used across multiple profiles via the `${...}` syntax: ```hocon defaultIssuerKey = { type = "jwk" jwk = { kty = "EC" d = "..." crv = "P-256" x = "..." y = "..." } } defaultIssuerDid = "did:jwk:eyJrdHk..." profiles = { identityCredential = { name = "IdentityCredential" credentialConfigurationId = "IdentityCredential_jwt_vc_json" issuerKey = ${defaultIssuerKey} # <- substitution issuerDid = ${defaultIssuerDid} # <- substitution credentialData = { ... } } } ``` **Info:** This pattern is just a convenience for sharing one key across many profiles. A profile may equally declare its own inline `issuerKey` / `issuerDid` instead of referencing the defaults. ## Profile properties | Property | Type | Required | Description | |----------|------|----------|-------------| | `name` | String | **Yes** | Human-readable profile name (non-blank). | | `credentialConfigurationId` | String | **Yes** | Must match a key in `credentialConfigurations` in `credential-issuer-metadata.conf`. | | `issuerKey` | Object | **Yes** | Serialized signing key. Must contain a `type` field. | | `credentialData` | Object | **Yes** | The credential body template (claims). | | `issuerDid` | String | No | Issuer DID. Used by `jwt_vc_json` and (optionally) `dc+sd-jwt`. | | `mapping` | Object | No | Dynamic [data functions](https://docs.walt.id/community-stack/issuer2/data-functions.md) for runtime fields. | | `selectiveDisclosure` | Object | No | SD-JWT selective-disclosure configuration. | | `idTokenClaimsMapping` | Object | No | Maps authorization-code-flow ID-token claims into the credential. | | `mDocNameSpacesDataMappingConfig` | Object | No | Per-namespace JSON→CBOR type conversions for `mso_mdoc`. | | `x5Chain` | Array<String> | No | X.509 certificate chain (PEM) for x5c-based signing. | | `notifications` | Object | No | Issuance webhook configuration. | | `credentialStatus` | Object | No | Credential status (revocation) configuration. | **Error:** `name`, `credentialConfigurationId`, `issuerKey` and `credentialData` are all **required** — a profile that omits any of them stops the service from loading. `issuerKey` must contain a `type` field, and `credentialConfigurationId` must reference a credential configuration that actually exists in the metadata file. ### Where each field sits Every property in the table above is a **direct child of a single profile** — they all sit at the same level, inside one entry of the `profiles` map. The skeleton below shows that layout (the sections further down zoom into each block): ```hocon profiles = { myProfile = { # profile ID (left of the "=") # ---- required ---- name = "..." # string credentialConfigurationId = "..." # string, must exist in credential-issuer-metadata.conf issuerKey = { type = "...", ... } # object credentialData = { ... } # object (the credential body template) # ---- optional ---- issuerDid = "did:..." # string mapping = { ... } # object (data functions) selectiveDisclosure = { # object (SD-JWT only) fields = { ... } decoyMode = "NONE" decoys = 0 } idTokenClaimsMapping = { # object: "" = "" "$.given_name" = "$.given_name" } mDocNameSpacesDataMappingConfig = { # object (mso_mdoc only), keyed by namespace "org.iso.18013.5.1" = { entriesConfigMap = { ... } } } x5Chain = [ "..." ] # array of PEM strings notifications = { # object webhook = { url = "..." } } credentialStatus = { ... } # object } } ``` ### Profile ID and uniqueness rules Inside the `profiles` block, each entry's **name on the left of the `=` is its profile ID**. In the example below, `identityCredential` is the profile ID: ```hocon profiles = { identityCredential = { # <- "identityCredential" is the profile ID name = "IdentityCredential" # ... } } ``` - A profile ID must be non-blank and must **not contain a `.` character**. - Only **one profile per `credentialConfigurationId`** is allowed — two profiles pointing at the same configuration ID will break issuance for that credential. ### Signing: what's needed for each credential type Which signing fields a profile needs depends on the credential format: | Format | Issuer mode | |--------|-------------| | `jwt_vc_json` (W3C JWT VC) | `issuerKey` + `issuerDid` (no `x5Chain`). | | `mso_mdoc` (mDoc) | `issuerKey` + `x5Chain` (no `issuerDid`). | | `dc+sd-jwt` (SD-JWT VC) | `issuerKey` + **exactly one** of `issuerDid` *or* `x5Chain`. | ## Issuer Key A **serialized walt.id key**. The `type` field selects the backend and is required. The same key backends apply as elsewhere in walt.id: | `type` | Backend | |--------|---------| | `jwk` | Local in-memory / file JWK. | | `tse` | HashiCorp Vault Transit Engine. | | `aws-rest-api` | AWS KMS. | | `azure-rest-api` | Azure Key Vault. | | `oci-rest-api` | Oracle Cloud KMS. | **Error:** The KMS backend identifiers are `aws-rest-api`, `azure-rest-api` and `oci-rest-api` — **not** `aws`, `azure` or `oci`. Using a short form is an unregistered type and fails at startup. **Option: Local JWK** The `jwk` field may be written as a nested HOCON object (the form used by the shipped config): ```hocon issuerKey = { type = "jwk" jwk = { kty = "EC" d = "..." crv = "P-256" x = "..." y = "..." } } ``` **Option: HashiCorp Vault** ```hocon issuerKey = { type = "tse" server = "http://vault:8200/v1/transit" id = "my-issuer-key" auth = { accessKey = "hvs.your-vault-token" } # Optional, for Vault Enterprise namespaces: # namespace = "my-namespace" } ``` **Option: AWS KMS** ```hocon issuerKey = { type = "aws-rest-api" id = "arn:aws:kms:eu-central-1:123456789012:key/abcd-..." config = { auth = { accessKeyId = "..." secretAccessKey = "..." region = "eu-central-1" } } } ``` **Option: Azure Key Vault** ```hocon issuerKey = { type = "azure-rest-api" id = "https://my-vault.vault.azure.net/keys/my-key/" auth = { clientId = "..." clientSecret = "..." tenantId = "..." keyVaultUrl = "https://my-vault.vault.azure.net/" } } ``` ## Credential Data `credentialData` is the static data template for the credential body. Fields that should be computed per issuance are filled in by [data functions](https://docs.walt.id/community-stack/issuer2/data-functions.md) defined in the `mapping` object such as ``, ``, ``, `` and ``: ```hocon credentialData = { "@context" = ["https://www.w3.org/2018/credentials/v1"] type = ["VerifiableCredential", "IdentityCredential"] credentialSubject = { id = "THIS WILL BE REPLACED WITH DYNAMIC DATA FUNCTION" given_name = "John" family_name = "Doe" } issuer = { id = "THIS WILL BE REPLACED WITH DYNAMIC DATA FUNCTION" } } mapping = { id = "" issuer = { id = "" } credentialSubject = { id = "" } issuanceDate = "" expirationDate = "" } ``` For SD-JWT VC, time claims are typically driven with the seconds variants: ```hocon mapping = { id = "" iat = "" nbf = "" exp = "" } ``` ## Selective Disclosure For `dc+sd-jwt` credentials, declare which claims are selectively disclosable. Each field carries `sd` (boolean), and nested objects can be described with `children`. The block may also configure decoy digests: ```hocon selectiveDisclosure = { fields = { birth_date = { sd = true } family_name = { sd = false } address = { sd = true children = { fields = { street = { sd = true } city = { sd = true } } } } } decoyMode = "NONE" # NONE | FIXED | RANDOM decoys = 0 } ``` ## ID Token Claim Mapping For the authorization-code flow, copy claims from the IdP's ID token into the credential. Each entry is **`"" = ""`**: - The **key** is a JSONPath into the **ID token** (the source of the value). - The **value** is a JSONPath into `credentialData` (where the value is written). ```hocon idTokenClaimsMapping = { # read $.family_name from the ID token, write it to $.registered_family_name in credentialData "$.family_name" = "$.registered_family_name" "$.given_name" = "$.registered_given_name" } ``` **Error:** **Both sides must be full JSONPath expressions** (starting with `$`), and **both must already resolve to a non-null value** — the source path must exist in the ID token, and the destination path must already exist in your `credentialData` template. The mapping *updates* an existing field; it does not create new ones. Bare keys like `given_name` or `address.street` are **not** valid here. ## mDocNameSpacesDataMappingConfig ### Why this is needed mDoc credentials (`mso_mdoc`) are **CBOR-encoded** as defined by [ISO/IEC 18013-5](https://www.iso.org/standard/69084.html), and the standard mandates a specific CBOR type for certain data elements: - **Dates** such as `birth_date`, `issue_date` and `expiry_date` must be a CBOR `full-date`; timestamps must be a `tdate`. - **Binary** values such as `portrait` must be a CBOR **byte string** (`bstr`). But you author `credentialData` as JSON, which has no native date or byte-string type — those values arrive as plain text strings. Without instructions, the issuer encodes them as CBOR **text strings**, which is **not standards-conformant**: the resulting credential does not match the CBOR types that ISO/IEC 18013-5 (and profiles built on it, such as the EUDI PID Rulebook) require for those data elements. `mDocNameSpacesDataMappingConfig` is what tells the issuer which JSON string fields to re-encode into the correct CBOR type. ### Is it required? The field itself is **optional** — but it is **effectively required for any data element the standard defines as a non-text type** (dates, binary). Plain text and numeric fields (e.g. `family_name`, `document_number`) need no entry; only list the fields that require a conversion. ### Syntax `credentialData` is organized by namespace, and each namespace maps to an `entriesConfigMap` keyed by claim name: ```hocon mDocNameSpacesDataMappingConfig = { "org.iso.18013.5.1" = { entriesConfigMap = { birth_date = { type = "string", conversionType = "stringToFullDate" } issue_date = { type = "string", conversionType = "stringToFullDate" } portrait = { type = "string", conversionType = "base64StringToByteString" } # Arrays and nested objects are described recursively: driving_privileges = { type = "array" arrayConfig = [ { type = "object" entriesConfigMap = { issue_date = { type = "string", conversionType = "stringToFullDate" } expiry_date = { type = "string", conversionType = "stringToFullDate" } } } ] } } } } ``` **Supported `conversionType` values:** | Value | Effect | |-------|--------| | `stringToFullDate` | Parses a date string into a CBOR full-date element. | | `stringToTDate` | Parses a date-time string into a CBOR `tdate` element. | | `base64StringToByteString` | Decodes standard Base64 into a CBOR byte string. | | `base64UrlStringToByteString` | Decodes Base64URL into a CBOR byte string. | ## Notifications Configure an issuance webhook, learn more [here](https://docs.walt.id/community-stack/issuer2/notifications.md): ```hocon notifications = { webhook = { url = "https://example.com/webhook/issuance" } } ``` ## Credential Status Embed a status-list reference into every credential issued from this profile (e.g. for revocation/suspension). The accepted shape is **format-specific**: - **W3C JWT VC** (`jwt_vc_json`) — the object is embedded verbatim as the `credentialStatus` claim: ```hocon credentialStatus = { type = "BitstringStatusListEntry" id = "https://issuer.example.com/status/1#94567" statusPurpose = "revocation" statusListIndex = "94567" statusListCredential = "https://issuer.example.com/status/1" } ``` - **SD-JWT VC** (`dc+sd-jwt`) and **mDoc** (`mso_mdoc`) — a Token Status List reference with `idx` and `uri`: ```hocon credentialStatus = { status_list = { idx = 94567 uri = "https://issuer.example.com/status/1" } } ``` **Info:** In the Community Stack you host and serve the status list yourself — the issuer only embeds this reference into the credential. The same value can also be supplied as a per-offer runtime override. See [Credential Status](https://docs.walt.id/community-stack/issuer2/credential-offers/credential-status.md) for the full reference across all formats. ## Example Configuration ```hocon # issuer2-profiles.conf defaultIssuerKey = { type = "jwk" jwk = { kty = "EC" d = "..." crv = "P-256" x = "..." y = "..." } } defaultIssuerDid = "did:jwk:eyJrdHk..." defaultIssuerX5chain = [ """-----BEGIN CERTIFICATE----- MIIBeTCCAR8CFHrWgrGl5KdefSvRQhR... -----END CERTIFICATE-----""" ] profiles = { # W3C JWT VC — uses issuerDid identityCredential = { name = "IdentityCredential" credentialConfigurationId = "IdentityCredential_jwt_vc_json" issuerKey = ${defaultIssuerKey} issuerDid = ${defaultIssuerDid} credentialData = { "@context" = ["https://www.w3.org/2018/credentials/v1"] type = ["VerifiableCredential", "IdentityCredential"] credentialSubject = { id = "THIS WILL BE REPLACED WITH DYNAMIC DATA FUNCTION" given_name = "John" family_name = "Doe" } issuer = { id = "THIS WILL BE REPLACED WITH DYNAMIC DATA FUNCTION" } } mapping = { id = "" issuer = { id = "" } credentialSubject = { id = "" } issuanceDate = "" expirationDate = "" } } # mDoc — uses x5Chain, no issuerDid mDL = { name = "ISO 18013-5 mDL" credentialConfigurationId = "org.iso.18013.5.1.mDL" issuerKey = ${defaultIssuerKey} credentialData = { "org.iso.18013.5.1" = { family_name = "Mustermann" given_name = "Erika" birth_date = "1971-09-01" } } mDocNameSpacesDataMappingConfig = { "org.iso.18013.5.1" = { entriesConfigMap = { birth_date = { type = "string", conversionType = "stringToFullDate" } } } } x5Chain = ${defaultIssuerX5chain} } } ``` ## Related Configuration - [Credential Issuer Metadata](https://docs.walt.id/community-stack/issuer2/configurations/config-files/credential-issuer-metadata.md) – Declares the credential configurations referenced by `credentialConfigurationId` - [Issuer Service](https://docs.walt.id/community-stack/issuer2/configurations/config-files/issuer-service.md) – Core service configuration - [Data Functions](https://docs.walt.id/community-stack/issuer2/data-functions.md) – Dynamic data population in `mapping` -------------------------------------------------------------------------------- PAGE: Authentication Service URL: https://docs.walt.id/community-stack/issuer2/configurations/config-files/authentication-service RAW: https://docs.walt.id/community-stack/issuer2/configurations/config-files/authentication-service.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Authentication Service Configuration The `authentication-service.conf` file configures the **external OAuth2 / OIDC provider** (such as Keycloak, Microsoft Entra ID or Auth0) used for authorization-code-flow credential issuance. During that flow the issuer sends the user to this provider to log in, and then receives the user's claims (e.g. email, name) in return. To use authorization code flow you configure your own OAuth/OIDC provider here — its endpoints, the client credentials the issuer uses to talk to it, and the scopes to request. **Note:** **Just want to try it out?** You don't need to set anything up. If you don't provide an `authentication-service.conf`, Issuer2 falls back to a hosted walt.id test identity provider with ready-made login accounts — see [Default Test Provider](#default-test-provider) below. Configure your own provider only when you move towards production. ## File Location ``` waltid-services/waltid-issuer-api2/config/authentication-service.conf ``` ## When This Is Used This configuration only matters when issuing credentials via **authorization code flow**. In that flow the issuer sends the user to the provider configured here to log in. The provider returns the user's claims (e.g. email, name) in an ID token, which can then be mapped into the credential. For **pre-authorized code flow** (PIN-based issuance), this configuration is not used. ## Default Test Provider To let you try the authorization code flow immediately — without registering your own Keycloak, Entra, or Auth0 tenant — Issuer2 ships with built-in defaults that point at a **hosted walt.id test identity provider** (a Keycloak realm at `keycloak.demo.walt.id`). These defaults are used automatically whenever a value isn't set in `authentication-service.conf`, so if you never create that file, the authorization code flow works out of the box against this test provider. You can then create an `AUTHORIZED` offer (see [Create an Offer](https://docs.walt.id/community-stack/issuer2/credential-offers/create-offer.md#authorization-code-flow)) and, when the wallet sends you to log in, sign in with one of the accounts below. ### Test Users | Username | Password | Claims returned in the ID token | |----------|----------|---------------------------------| | `jane@walt.id` | `jane` | `given_name`, `family_name` | | `max@walt.id` | `password` | `clearanceLevel`, `employeeId`, `position`, `department`, `startDate` | These claims are what the provider returns after login. To actually see them land in an issued credential, the credential profile needs an [`idTokenClaimsMapping`](https://docs.walt.id/community-stack/issuer2/configurations/config-files/issuer-profiles.md#id-token-claim-mapping) that maps them into credential fields. **Try it end to end with `jane`:** the ready-made `identityCredentialSdJwt` profile already maps `given_name` and `family_name` from the ID token. Create an `AUTHORIZED` offer for that profile, open it in a wallet, log in as `jane@walt.id` / `jane`, and the issued credential's `given_name` / `family_name` will be filled in from Jane's login instead of the profile's placeholder values. `max@walt.id` returns a richer set of workplace claims (`clearanceLevel`, `employeeId`, `position`, `department`, `startDate`). No default profile maps these, so use `max` when you want to practise writing your own `idTokenClaimsMapping` against real ID-token claims. **Warning:** The default test provider and these shared accounts are for local testing and demos only. They are public and unauthenticated for anyone running Issuer2, so never rely on them — or the built-in client secret — in production. Configure your own provider (see [Example Configurations](#example-configurations)) before going live. ## Configuration Options Every option has a built-in default that points at the [hosted test provider](#default-test-provider). You only need to set these when pointing Issuer2 at **your own** provider — at which point `authorizeUrl`, `accessTokenUrl`, `clientId`, and `clientSecret` are effectively required (the test-provider defaults won't match your setup). | Property | Type | Required for own provider | Default | Description | |----------|------|----------|---------|-------------| | `authorizeUrl` | String | **Yes** | Test provider | OAuth authorization endpoint. The user is sent here to authenticate. | | `accessTokenUrl` | String | **Yes** | Test provider | Token endpoint where the issuer exchanges the authorization code for an access token and ID token. | | `clientId` | String | **Yes** | Test provider | OAuth client ID registered with the provider. | | `clientSecret` | String | **Yes** | Test provider | OAuth client secret. | | `name` | String | No | `"keycloak"` | Provider name. Informational — passed through to the OAuth client settings. | | `defaultScopes` | Array<String> | No | `["openid", "profile"]` | OAuth scopes requested during authorization. | | `forwardIssuerStateToAuthorizationServer` | Boolean | No | `false` | Whether to forward the issuer's internal `issuer_state` to the authorization server. | ### name Provider name. Informational only — it is passed through as the OAuth client's `name` and does not change behaviour. ```hocon name = "keycloak" ``` ### authorizeUrl OAuth authorization endpoint URL. The user is redirected here to authenticate. ```hocon authorizeUrl = "https://keycloak.example.com/realms/myrealm/protocol/openid-connect/auth" ``` ### accessTokenUrl Token endpoint URL where the issuer exchanges the authorization code for an access token and ID token. ```hocon accessTokenUrl = "https://keycloak.example.com/realms/myrealm/protocol/openid-connect/token" ``` ### clientId OAuth client ID registered with the provider. ```hocon clientId = "issuer_api" ``` ### clientSecret OAuth client secret. ```hocon clientSecret = "secret-from-your-provider" ``` **Error:** Never commit secrets to version control. Use environment variables or a secret manager. ### defaultScopes OAuth scopes requested during authorization. Common scopes: - `openid` – required for OIDC - `profile` – access to name and similar profile claims - `email` – access to the email address ```hocon defaultScopes = ["openid", "profile"] ``` ### forwardIssuerStateToAuthorizationServer Controls whether the issuer's internal `issuer_state` value is forwarded to the **authorization server** as an extra `issuer_state` query parameter on the authorize request. Defaults to `false`. This is only needed for custom integrations where the external authorization server has to receive the issuer state. When `true`, the issuer extracts `issuer_state` from the internal login request and appends it to the outgoing authorize URL (it is skipped if the value is missing, blank, or already present). ```hocon forwardIssuerStateToAuthorizationServer = false ``` Leave this `false` unless your provider specifically requires the forwarded state. ## Redirect URI The issuer's OAuth callback is derived from `baseUrl` (set in [`issuer-service.conf`](https://docs.walt.id/community-stack/issuer2/configurations/config-files/issuer-service.md)): ``` /openid4vci/external/oauth/callback ``` Register this exact URL as an allowed redirect URI in your OAuth provider, otherwise the provider rejects the login. For example, with `baseUrl = "https://issuer.example.com"` the redirect URI is `https://issuer.example.com/openid4vci/external/oauth/callback`. ## Example Configurations ### Keycloak ```hocon # authentication-service.conf name = "keycloak" authorizeUrl = "https://keycloak.example.com/realms/myrealm/protocol/openid-connect/auth" accessTokenUrl = "https://keycloak.example.com/realms/myrealm/protocol/openid-connect/token" clientId = "issuer_api" clientSecret = "secret-from-keycloak" defaultScopes = ["openid", "profile", "email"] forwardIssuerStateToAuthorizationServer = false ``` ### Microsoft Entra ID (Azure AD) ```hocon # authentication-service.conf name = "entra" authorizeUrl = "https://login.microsoftonline.com/TENANT_ID/oauth2/v2.0/authorize" accessTokenUrl = "https://login.microsoftonline.com/TENANT_ID/oauth2/v2.0/token" clientId = "client-id-from-azure" clientSecret = "client-secret-from-azure" defaultScopes = ["openid", "profile", "email"] forwardIssuerStateToAuthorizationServer = false ``` ### Auth0 ```hocon # authentication-service.conf name = "auth0" authorizeUrl = "https://YOUR_DOMAIN.auth0.com/authorize" accessTokenUrl = "https://YOUR_DOMAIN.auth0.com/oauth/token" clientId = "client-id-from-auth0" clientSecret = "client-secret-from-auth0" defaultScopes = ["openid", "profile", "email"] forwardIssuerStateToAuthorizationServer = false ``` ## Mapping ID Token Claims to Credentials After successful authorization, the issuer receives an ID token with user claims. Use `idTokenClaimsMapping` in your credential profile to map those claims into credential fields. See [Issuer Profiles – ID Token Claim Mapping](https://docs.walt.id/community-stack/issuer2/configurations/config-files/issuer-profiles.md#id-token-claim-mapping). ## Related Configuration - [Issuer Service](https://docs.walt.id/community-stack/issuer2/configurations/config-files/issuer-service.md) – Sets the `baseUrl` the redirect URI is built from. - [Issuer Profiles](https://docs.walt.id/community-stack/issuer2/configurations/config-files/issuer-profiles.md) – Maps ID token claims to credential fields. - [Credential Offers](https://docs.walt.id/community-stack/issuer2/credential-offers/create-offer.md) – Create authorization-code-flow offers. -------------------------------------------------------------------------------- PAGE: Dev Mode URL: https://docs.walt.id/community-stack/issuer2/configurations/config-files/dev-mode RAW: https://docs.walt.id/community-stack/issuer2/configurations/config-files/dev-mode.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Dev Mode Configuration The `dev-mode.conf` file holds development-only settings. In the issuer it currently exposes a single option: whether `did:web` resolution may use plain HTTP instead of HTTPS only. ## File Location ``` waltid-services/waltid-issuer-api2/config/dev-mode.conf ``` ## Enabling It `dev-mode.conf` is only read when the `dev-mode` feature is enabled in `_features.conf`: ```hocon # _features.conf enabledFeatures = ["dev-mode"] ``` ```hocon # dev-mode.conf enableDidWebResolverHttps = false ``` If the feature is enabled but `dev-mode.conf` can't be loaded, the issuer logs a warning and keeps the secure default. ## `enableDidWebResolverHttps` | Value | Effect | |-------|--------| | `true` (default) | `did:web` documents are fetched over **HTTPS** only | | `false` | `did:web` documents may be fetched over **HTTP** | `did:web` resolves a DID by fetching a document over the network — e.g. `did:web:localhost:7002` → `http://localhost:7002/.well-known/did.json`. Local services usually run without TLS, so HTTP resolution must be allowed to use `did:web:localhost`. This is the only behaviour the `dev-mode` feature affects in the issuer; enabling the feature without setting this to `false` changes nothing. ## Related Configuration - [Features](https://docs.walt.id/community-stack/issuer2/configurations/config-files/features.md) – Enable/disable features - [Issuer Service](https://docs.walt.id/community-stack/issuer2/configurations/config-files/issuer-service.md) – Configure issuer DID -------------------------------------------------------------------------------- PAGE: Persistence URL: https://docs.walt.id/community-stack/issuer2/configurations/config-files/persistence RAW: https://docs.walt.id/community-stack/issuer2/configurations/config-files/persistence.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Persistence Configuration The `persistence.conf` file chooses where the issuer keeps short-lived OpenID4VCI state (issuance sessions, codes, tokens). The default is in-memory storage; switch to Redis when you run more than one issuer instance and they need to share that state. ## File Location ``` waltid-services/waltid-issuer-api2/config/persistence.conf ``` ## When This Is Used `persistence.conf` is only read when the `persistence` feature is enabled in `_features.conf`: ```hocon # _features.conf enabledFeatures = ["persistence"] ``` Without the feature, the issuer always uses in-memory storage, so all state is lost on restart and cannot be shared across instances. ## Configuration Options | Property | Type | Required | Default | Description | |----------|------|----------|---------|-------------| | `type` | String | No | `"memory"` | `memory`, `redis`, or `redis-cluster`. | | `nodes` | Array<{host, port}> | For Redis | – | Redis nodes. `redis` requires **exactly one** node; `redis-cluster` takes one or more. | | `user` | String | No | – | Redis username (Redis ACL / AUTH). | | `password` | String | No | – | Redis password. | A `redis` type with zero or multiple nodes fails at startup ("Redis persistence requires defining at least 1 node!" / "exactly 1 node is required"). **Error:** Don't commit `password` to version control — supply it via an environment variable or secret manager. ## What Is Stored When persistence is enabled, these stores move from memory into Redis: | Store | Holds | Fallback TTL | |-------|-------|--------------| | Issuance sessions | Credential offer state and subject data | 5 min | | Authorization codes | Authorization-code-flow codes | 5 min | | Pre-authorized codes | Pre-authorized-code-flow codes and tx codes | 5 min | | Pushed authorization requests (PAR) | PAR request state | 90 s | | Refresh tokens | OpenID4VCI refresh tokens | 1 day | Each entry is actually stored with a TTL until its own `expiresAt`; the values above are only the fallback used when no explicit expiry is set. Access tokens are self-contained and are **not** stored here. ## Example Configurations ```hocon # persistence.conf — in-memory (default) type = "memory" ``` ```hocon # persistence.conf — single Redis type = "redis" nodes = [{ host = "redis", port = 6379 }] # user = "issuer-service" # password = "secret-from-secrets-manager" ``` ```hocon # persistence.conf — Redis cluster type = "redis-cluster" nodes = [ { host = "redis-node-1", port = 6379 }, { host = "redis-node-2", port = 6379 }, { host = "redis-node-3", port = 6379 } ] ``` ## Docker Compose Connection details come from `nodes` in `persistence.conf` (mounted into the container), not from environment variables. Point `nodes` at the Redis service name: ```yaml services: issuer-api2: image: waltid/issuer-api2:latest ports: - "7002:7002" volumes: - ./config:/waltid-issuer-api2/config depends_on: - redis redis: image: redis:7-alpine command: redis-server --appendonly yes volumes: - redis-data:/data volumes: redis-data: ``` ## Related Configuration - [Features](https://docs.walt.id/community-stack/issuer2/configurations/config-files/features.md) – Enable the persistence feature - [Web](https://docs.walt.id/community-stack/issuer2/configurations/config-files/web.md) – Web server configuration -------------------------------------------------------------------------------- PAGE: Web URL: https://docs.walt.id/community-stack/issuer2/configurations/config-files/web RAW: https://docs.walt.id/community-stack/issuer2/configurations/config-files/web.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Web Server Configuration The `web.conf` file controls the HTTP server: which address and port it binds to, and how JSON responses are encoded. It is always read — there is no feature flag to enable. ## File Location ``` waltid-services/waltid-issuer-api2/config/web.conf ``` ## Configuration Options | Property | Type | Default | Description | |----------|------|---------|-------------| | `webHost` | String | `"0.0.0.0"` | Network interface to bind to. `0.0.0.0` = all interfaces; `127.0.0.1` = localhost only. | | `webPort` | Int | `3000` | TCP port to listen on. The issuer ships a `web.conf` that sets this to `7002`. | | `humanReadableResultEncoding` | Boolean | `false` | When `true`, JSON responses are pretty-printed. Useful for debugging; leave `false` in production. | **Info:** `3000` is the built-in default in code. The `web.conf` bundled with the issuer overrides it to `7002`, so a standalone issuer listens on `7002` unless you change it. ## Example ```hocon # web.conf webHost = "0.0.0.0" webPort = 7002 humanReadableResultEncoding = false ``` For local development you can restrict access to your machine and pretty-print responses: ```hocon # web.conf webHost = "127.0.0.1" webPort = 7002 humanReadableResultEncoding = true ``` ## Docker For the container to be reachable through a published port, the server must bind to all interfaces (`webHost = "0.0.0.0"`, the default) and you map the port on the host: ```bash docker run -p 7002:7002 \ -v $(pwd)/config:/waltid-issuer-api2/config \ waltid/issuer-api2:latest ``` In the bundled Docker Compose stack each service gets its own host port to avoid collisions. Issuer2 is published on `7005` so it doesn't clash with the v1 issuer on `7002`: | Service | Compose port | |---------|--------------| | Wallet API | 7001 | | Issuer API (v1) | 7002 | | Verifier API (v1) | 7003 | | Verifier API2 | 7004 | | Issuer API2 | 7005 | | Wallet API2 | 7006 | These ports come from `.env` / `docker-compose.yaml`, not from `web.conf`. ## Health Check When the `healthchecks` feature is enabled (it is by default), the server exposes a liveness endpoint that returns HTTP `200` once it is ready: ```bash curl -i http://localhost:7002/livez ``` ## Related Configuration - [Issuer Service](https://docs.walt.id/community-stack/issuer2/configurations/config-files/issuer-service.md) – Set `baseUrl` to match your public endpoint - [Features](https://docs.walt.id/community-stack/issuer2/configurations/config-files/features.md) – Enable/disable API features -------------------------------------------------------------------------------- PAGE: Data Functions URL: https://docs.walt.id/community-stack/issuer2/data-functions RAW: https://docs.walt.id/community-stack/issuer2/data-functions.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Data Functions The Issuer2 API offers the use of **data functions** when issuing credentials. These functions ensure that credentials always contain the most current and accurate information. **Note:** Data functions are supported across all credential types: **W3C JWT VC**, **IETF SD-JWT VC**, and **ISO mDoc**. ## How Data Functions Work Data functions are not executed at the moment you create a credential offer. Instead, they get executed at the point when the credential is actually claimed by the recipient. This means that the data within the credential 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 credential reflects any changes that occurred during that interval, maintaining the credibility of the credential. ## How to Use Data Functions When configuring a credential profile or creating an offer, you can provide a `mapping` object that defines which keys in the credential data structure should be replaced by the value returned from the provided data function. ### Example **Credential Data Template** ```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": "" }, "credentialSubject": { "id": "", "degree": "" }, "issuanceDate": "", "expirationDate": "" } ``` **Issued Credential** ```json { "@context": [ "https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1" ], "id": "urn:uuid:3732a5b8-...", "type": ["VerifiableCredential", "UniversityDegreeCredential"], "issuer": { "id": "did:key:z6Mk..." }, "credentialSubject": { "id": "did:key:z6Mk...", "degree": { "type": "BachelorDegree", "name": "Bachelor of Science and Arts" } }, "issuanceDate": "2024-01-15T10:30:00Z", "expirationDate": "2025-01-15T10:30:00Z" } ``` ## Available Functions ### UUID `` generates a unique identifier. ```json { "id": "" } ``` **Result:** `"urn:uuid:3732a5b8-1234-5678-9abc-def012345678"` ### Subject DID `` returns the recipient's DID once the credential is claimed. ```json { "credentialSubject": { "id": "" } } ``` ### Issuer DID `` returns the issuer's DID. ```json { "issuer": { "id": "" } } ``` ### Timestamp `` returns the current timestamp in ISO 8601 format `YYYY-MM-DDThh:mm:ss.sssZ`. ```json { "issuanceDate": "" } ``` **Result:** `"2024-01-15T10:30:00.000Z"` ### Timestamp Seconds `` returns the Unix timestamp (seconds since epoch). ```json { "iat": "" } ``` **Result:** `1705315800` ### Timestamp In `` returns a future date by providing the duration as an argument. ```json { "expirationDate": "" } ``` Supported duration formats: - `30d` - 30 days - `12h` - 12 hours - `30m` - 30 minutes - `1y` - 1 year **Result:** `"2025-01-15T10:30:00.000Z"` ### Timestamp In Seconds `` returns a future Unix timestamp. ```json { "exp": "" } ``` **Result:** `1736851800` ### Timestamp Before `` returns a date in the past. ```json { "validFrom": "" } ``` **Result:** `"2023-12-16T10:30:00.000Z"` ### Timestamp Before Seconds `` returns a past Unix timestamp. ```json { "nbf": "" } ``` ### Display `` returns the display metadata of the credential as defined in the issuer metadata. ```json { "display": "" } ``` **Result:** ```json { "name": "University Credential", "locale": "en-US", "logo": { "url": "https://university.example.edu/logo.png", "alt_text": "University Logo" }, "background_color": "#12107c", "text_color": "#FFFFFF" } ``` ### Webhook `` makes a request to a specific URL and inserts the response as plain text. ```json { "credentialSubject": { "studentId": "" } } ``` If the webhook returns `"STU-12345"`, the result is: ```json { "credentialSubject": { "studentId": "STU-12345" } } ``` ### Webhook JSON `` makes a request to a specific URL and inserts the response as a nested JSON object. ```json { "credentialSubject": { "degree": "" } } ``` If the webhook returns: ```json { "type": "BachelorDegree", "name": "Bachelor of Science" } ``` The result is: ```json { "credentialSubject": { "degree": { "type": "BachelorDegree", "name": "Bachelor of Science" } } } ``` ## 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. Use these functions for EBSI-compliant credentials: ### EBSI Timestamp `` returns the current timestamp in EBSI format `YYYY-MM-DDThh:mm:ssZ`. ```json { "issuanceDate": "" } ``` **Result:** `"2024-01-15T10:30:00Z"` ### EBSI Timestamp In `` returns a future date in EBSI format. ```json { "expirationDate": "" } ``` **Result:** `"2025-01-15T10:30:00Z"` ## Using Data Functions in Profiles Configure data functions in your credential profile: ```hocon # issuer2-profiles.conf profiles = { "university-degree" = { name = "University Degree" credentialConfigurationId = "UniversityDegree_jwt_vc_json" issuerKey = { ... } issuerDid = "did:key:z6Mk..." credentialData = { "@context" = [ "https://www.w3.org/2018/credentials/v1" ] type = ["VerifiableCredential", "UniversityDegree"] credentialSubject = { degree = { type = "BachelorDegree" name = "" } } } mapping = { id = "" "issuer.id" = "" "credentialSubject.id" = "" "credentialSubject.degree.name" = "" issuanceDate = "" expirationDate = "" } } } ``` ## Using Data Functions in Runtime Overrides Override data functions when creating an offer: ```bash curl -X POST 'http://localhost:7002/issuer2/credential-offers' \ -H 'Content-Type: application/json' \ -d '{ "profileId": "university-degree", "authMethod": "PRE_AUTHORIZED", "runtimeOverrides": { "mapping": { "credentialSubject.degree.name": "" } } }' ``` ## Next Steps - [Credential Profiles](https://docs.walt.id/community-stack/issuer2/credential-profiles/overview.md) – Configure profiles with data functions - [Create an Offer](https://docs.walt.id/community-stack/issuer2/credential-offers/create-offer.md) – Create offers with runtime overrides -------------------------------------------------------------------------------- PAGE: Notifications URL: https://docs.walt.id/community-stack/issuer2/notifications RAW: https://docs.walt.id/community-stack/issuer2/notifications.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Notifications The Issuer2 API supports webhook-based notifications for real-time updates on credential issuance events. Configure notifications at the profile level or override them per offer. ## Overview Notifications allow your backend systems to receive real-time updates when: - A credential offer is resolved by a wallet - An access token is requested - A credential is issued - The issuance session status changes This enables you to: - Track issuance progress - Update your database when credentials are claimed - Trigger downstream workflows - Audit credential issuance ## Configuration Notifications are configured in the `notifications` object of a credential profile or as a runtime override when creating an offer. ### Basic Configuration ```json { "notifications": { "webhook": { "url": "https://your-server.com/webhook/issuance" } } } ``` ## Configuration Properties | Property | Type | Required | Description | |----------|------|----------|-------------| | `webhook.url` | String | Yes | The URL to receive webhook notifications | ## Webhook Events Your webhook endpoint will receive POST requests with JSON payloads for the following events: ### Event Structure ```json { "target": "session-id-123", "event": "event_type", "session": { ... } } ``` | Field | Description | |-------|-------------| | `target` | The session/offer ID | | `event` | The event type | | `session` | Full session data as a JSON object | ### Event Types #### `resolved_credential_offer` Triggered when a wallet resolves the credential offer. ```json { "target": "session-id-123", "event": "resolved_credential_offer", "session": { "sessionId": "session-id-123", "credentialConfigurationId": "UniversityDegree_jwt_vc_json", "credentialOffer": { "credential_issuer": "http://localhost:7002", "credential_configuration_ids": ["UniversityDegree_jwt_vc_json"], "grants": { ... } } } } ``` #### `requested_token` Triggered when a wallet requests an access token. ```json { "target": "session-id-123", "event": "requested_token", "session": { "sessionId": "session-id-123", "credentialConfigurationId": "UniversityDegree_jwt_vc_json" } } ``` #### `sdjwt_issue` Triggered when an SD-JWT VC credential is issued. ```json { "target": "session-id-123", "event": "sdjwt_issue", "session": { "sessionId": "session-id-123", "credentialConfigurationId": "identity_credential_dc+sd-jwt" } } ``` #### `jwt_issue` Triggered when a W3C JWT credential is issued. ```json { "target": "session-id-123", "event": "jwt_issue", "session": { "sessionId": "session-id-123", "credentialConfigurationId": "UniversityDegree_jwt_vc_json" } } ``` #### `generated_mdoc` Triggered when an mDoc credential is generated. ```json { "target": "session-id-123", "event": "generated_mdoc", "session": { "sessionId": "session-id-123", "credentialConfigurationId": "org.iso.18013.5.1.mDL" } } ``` #### `issuance_status` Triggered when the issuance session status changes (e.g., when issuance completes). ```json { "target": "session-id-123", "event": "issuance_status", "session": { "sessionId": "session-id-123", "credentialConfigurationId": "UniversityDegree_jwt_vc_json", "status": "SUCCESSFUL", "isClosed": true } } ``` ## Profile-Level Notifications Configure notifications in your credential profile configuration file: ```hocon # issuer2-profiles.conf profiles = { "notified-credential" = { name = "Notified Credential" credentialConfigurationId = "identity_credential_dc+sd-jwt" issuerKey = { ... } credentialData = { ... } notifications = { webhook = { url = "https://your-server.com/webhook/issuance" } } } } ``` ## Per-Offer Notifications Override profile notifications for a specific offer using runtime overrides: ```bash curl -X POST 'http://localhost:7002/issuer2/credential-offers' \ -H 'Content-Type: application/json' \ -d '{ "profileId": "university-degree", "authMethod": "PRE_AUTHORIZED", "runtimeOverrides": { "notifications": { "webhook": { "url": "https://different-server.com/webhook/special-offer" } } } }' ``` ## Alternative: Server-Sent Events (SSE) For real-time monitoring in browser-based applications, the Issuer2 API also provides a Server-Sent Events (SSE) endpoint. This allows you to subscribe to session events directly from a browser without setting up a webhook server. **Endpoint:** `GET /issuer2/sessions/{sessionId}/events` ```javascript const sessionId = 'abc123-def456-ghi789'; const eventSource = new EventSource( `http://localhost:7002/issuer2/sessions/${sessionId}/events` ); eventSource.onmessage = (event) => { const data = JSON.parse(event.data); console.log('Session event:', data.event); if (data.event === 'issuance_status' && data.session.status === 'SUCCESSFUL') { console.log('Credential issued successfully!'); eventSource.close(); } }; ``` The SSE stream emits the same event types and payload structure as webhooks. **Note:** On connection, the SSE endpoint first sends an empty `data: {}` message as an initial handshake, before any session events. Guard against it in your handler (e.g. check `data.event` before acting), as shown in the example above where the `data.event === 'issuance_status'` check safely ignores it. | Feature | Webhooks | SSE | |---------|----------|-----| | Best for | Server-to-server | Browser/client apps | | Connection | Push to your server | Client pulls from issuer | | Reliability | More reliable | May disconnect | | Setup | Requires public endpoint | No server needed | ## Next Steps - [Issuer Profiles Configuration](https://docs.walt.id/community-stack/issuer2/configurations/config-files/issuer-profiles.md) – Add notifications to your profiles -------------------------------------------------------------------------------- PAGE: Full API Reference URL: https://docs.walt.id/community-stack/issuer2/full-api-reference RAW: https://docs.walt.id/community-stack/issuer2/full-api-reference.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Full API Reference Find it [here](https://issuer2.demo.walt.id/swagger/index.html) -------------------------------------------------------------------------------- PAGE: Getting Started URL: https://docs.walt.id/community-stack/wallet2/getting-started RAW: https://docs.walt.id/community-stack/wallet2/getting-started.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Getting Started **Wallet API v2** provides the capabilities to build end-to-end digital ID wallets. It manages wallets, keys, DIDs, and credentials, and implements the latest [OID4VCI 1.0](https://docs.walt.id/concepts/data-exchange-protocols/openid4vci.md) (receiving credentials from issuers) and [OID4VP 1.0](https://docs.walt.id/concepts/data-exchange-protocols.md) (presenting credentials to verifiers) specifications, using [DCQL](https://docs.walt.id/concepts/data-exchange-protocols.md) for credential matching. Supported formats include [SD-JWT VC (IETF)](https://docs.walt.id/concepts/digital-credentials/sd-jwt-vc.md), [W3C VC](https://docs.walt.id/concepts/digital-credentials/verifiable-credentials-w3c.md), and [ISO 18013-5 mDL](https://docs.walt.id/concepts/digital-credentials/mdoc-mdl-iso.md). **Note:** The original wallet service remains available for existing projects. For new projects, we recommend **Wallet API v2** with its OID4VCI/VP 1.0 support. Migrating from v1? See the [Migration guide](https://docs.walt.id/community-stack/wallet2/migration.md). ## 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 1.0, OID4VP 1.0 (DCQL) | | **Signing Algorithms:** | Ed25519, secp256k1, secp256r1, RSA | ## How It Differs from Wallet API v1 | Feature | Wallet API v1 | Wallet API v2 | |---------|---------------|---------------| | **OID4VP query language** | Presentation Exchange | DCQL | | **OID4VCI/VP version** | Draft 11–20 | 1.0 | | **Persistence** | Built-in | Optional, pluggable | | **Authentication** | Built-in | Optional, pluggable | ## Setup If you are new to the stack, the **Docker Compose quick start** is the fastest way to get running — it spins up the Issuer, Verifier, and Wallet together in one command, which lets you issue a credential, receive it into the wallet, and present it, end to end. Once running, the Wallet API v2 is available at `http://localhost:7006`. **Note:** When running via Docker Compose, Wallet API v2 runs on port `7006` (Issuer2 on `7005`, Verifier2 on `7004`). When running Wallet API v2 standalone with Docker, it uses port `7005`. - [Docker Compose quick start](https://docs.walt.id/community-stack/home.md#quick-start) — Spin up the wallet together with the issuer and verifier. _(Recommended for first-time users)_ - [Run standalone](https://docs.walt.id/community-stack/wallet2/setup.md) — Pull and run the wallet container in isolation using the [walt.id identity repo](https://github.com/walt-id/waltid-identity). - [Full API reference](https://docs.walt.id/community-stack/wallet2/full-api-reference.md) — Explore all endpoints via Swagger. ## Core Concepts - **Wallets** — A wallet is a container of keys, credentials and DIDs. Learn more on [Managing Wallets](https://docs.walt.id/community-stack/wallet2/wallets.md). - **Receiving Credentials** — Claim credentials from an issuer over [OID4VCI 1.0](https://docs.walt.id/community-stack/wallet2/credential-receiving/overview.md), via a one-call full flow or step-by-step [Custom Flow](https://docs.walt.id/community-stack/wallet2/credential-receiving/custom-flow.md). - **Presenting Credentials** — Share credentials with a verifier over [OID4VP 1.0](https://docs.walt.id/community-stack/wallet2/credential-presenting/overview.md) via a one-call full flow or step-by-step [Custom Flow](https://docs.walt.id/community-stack/wallet2/credential-presenting/custom-flow.md). - **Persistence and auth** — The default configuration provided via the waltid-identity repo enables **SQLite persistence** (data survives restarts); **authentication is off** (wallets are open, and the `auth` feature is not yet available). Manage these via [Configurations](https://docs.walt.id/community-stack/wallet2/configurations/feature-manager.md) — e.g. switch persistence to in-memory. ## Next Steps - [Setup](https://docs.walt.id/community-stack/wallet2/setup.md) — Run the wallet v2 service on your machine. - [Managing Wallets](https://docs.walt.id/community-stack/wallet2/wallets.md) — Create and manage wallets and stores. - [Key Management](https://docs.walt.id/community-stack/wallet2/key-management/overview.md) & [DID Management](https://docs.walt.id/community-stack/wallet2/did-management.md) — Manage the wallet's identity material. - [Receive a credential](https://docs.walt.id/community-stack/wallet2/credential-receiving/sd-jwt-vc.md) — Claim what an issuer offers. - [Present a credential](https://docs.walt.id/community-stack/wallet2/credential-presenting/sd-jwt-vc.md) — Share it with a verifier. - [Configurations](https://docs.walt.id/community-stack/wallet2/configurations/feature-manager.md) — Persistence, authentication, and more. -------------------------------------------------------------------------------- PAGE: Setup URL: https://docs.walt.id/community-stack/wallet2/setup RAW: https://docs.walt.id/community-stack/wallet2/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. **Note:** Want to see the whole flow — issue a credential, receive it into the wallet, and present it? The [Docker Compose quick start](https://docs.walt.id/community-stack/home.md#quick-start) spins up the Issuer, Verifier, and Wallet together in one command. This page covers running the Wallet API v2 on its own. ### Option 1: Using Docker Pull **1. Clone the Identity Repo** Clone the repository to your local machine. The repo contains the default configuration files that will be mounted into the container in the next step. ```bash git clone https://github.com/walt-id/waltid-identity.git && cd waltid-identity ``` **2. Pull the Docker Image** Pull the pre-built Docker image from Docker Hub: ```bash docker pull waltid/wallet-api2 ``` **3. Run the Docker Image** Run the container, mounting the config directory from the cloned repository. This gives the service its default configuration — see the note below for details. ```bash docker run -p 7005:7005 \ -itv $(pwd)/waltid-services/waltid-wallet-api2/config:/waltid-wallet-api2/config \ -t waltid/wallet-api2 ``` The `-v` flag mounts `waltid-services/waltid-wallet-api2/config` from the cloned repository into the container. This directory contains the service configuration files (e.g. server port, persistence, authentication). You can edit these files before starting the container to customise the service behaviour. **Note:** See the [configuration reference](https://docs.walt.id/community-stack/wallet2/configurations/feature-manager.md) for a full description of all available config files and their fields. The shipped config enables **SQLite persistence** and leaves **authentication off** (wallets are open; the `auth` feature is not yet available). Once the container starts, the API is available at `http://localhost:7005`. You can confirm it is running by opening `http://localhost:7005/swagger` in your browser. ### Option 2: Building the Image Yourself **Note:** This option requires **Java (JDK 17+)**, **Gradle**, and **Docker** to be installed on your machine. **1. Clone the Identity Repo** Clone the repository to your local machine: ```bash git clone https://github.com/walt-id/waltid-identity.git && cd waltid-identity ``` **2. Build 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-api2:publishImageToLocalRegistry # image: waltid/wallet-api2: ``` ```bash # Production (multi-arch push to your registry) export DOCKER_USERNAME= export DOCKER_PASSWORD= ./gradlew :waltid-services:waltid-wallet-api2:publishImage # image: docker.io//wallet-api2: ``` **3. Run the Docker Image** After building the image, run it using (adjust the image name if you pushed to your registry): ```bash docker run -p 7005:7005 \ -itv $(pwd)/waltid-services/waltid-wallet-api2/config:/waltid-wallet-api2/config \ -t waltid/wallet-api2 ``` The `-v` flag mounts `waltid-services/waltid-wallet-api2/config` from the cloned repository into the container. This directory contains the service configuration files (e.g. server port, persistence, authentication). You can edit these files before starting the container to customise the service behaviour. **Note:** See the [configuration reference](https://docs.walt.id/community-stack/wallet2/configurations/feature-manager.md) for a full description of all available config files and their fields. Once the container starts, the API is available at `http://localhost:7005`. You can confirm it is running by opening `http://localhost:7005/swagger` in your browser. **warning:** When running the Wallet API v2 **standalone** it listens on port `7005`. When running via [Docker Compose](https://docs.walt.id/community-stack/home.md#quick-start) alongside the other services, it is published on `7006` instead (Issuer2 on `7005`, Verifier2 on `7004`). Adjust the base URLs in the examples accordingly. ## Start Using the Wallet Now that the Wallet API v2 is running, create a wallet and put it to work: 1. [Create your first wallet](https://docs.walt.id/community-stack/wallet2/wallets.md) — Every operation is scoped to a wallet; start here to get a `walletId`. 2. [Manage keys](https://docs.walt.id/community-stack/wallet2/key-management/overview.md) & [DIDs](https://docs.walt.id/community-stack/wallet2/did-management.md) — The identity material the wallet signs with. 3. [Receive a credential](https://docs.walt.id/community-stack/wallet2/credential-receiving/sd-jwt-vc.md) — Claim what an issuer offers over OID4VCI. 4. [Present a credential](https://docs.walt.id/community-stack/wallet2/credential-presenting/sd-jwt-vc.md) — Share it with a verifier over OID4VP. 5. [Full API Reference](https://docs.walt.id/community-stack/wallet2/full-api-reference.md) — Explore all available endpoints via Swagger. -------------------------------------------------------------------------------- PAGE: Managing Wallets URL: https://docs.walt.id/community-stack/wallet2/wallets RAW: https://docs.walt.id/community-stack/wallet2/wallets.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Managing Wallets A **wallet** in the Wallet API v2 is a container for keys, credentials, and DIDs. This page covers creating wallets, inspecting and deleting them, and store-less wallets for isolated flows. **Note:** This page assumes the Wallet API v2 is already running. If it isn't, start with [Setup](https://docs.walt.id/community-stack/wallet2/setup.md) first. The examples use `http://localhost:7005` as the base URL. --- ## Create a Wallet The simplest wallet uses default, auto-created stores for data persitence — one key store, one credential store, and one DID store, created automatically. **Option: CURL** **Endpoint:** `POST /wallet` | [API Reference](https://docs.walt.id/community-stack/wallet2/full-api-reference.md) ##### Example Request ```bash curl -X POST http://localhost:7005/wallet \ -H 'Content-Type: application/json' \ -d '{}' ``` **Body Parameters** All fields are optional. An empty body creates a wallet with default, auto-created stores. - **noDidStore**: _Boolean_ (optional) - Create the wallet without a DID store. Only required if you want to receive, manage and share W3C credentials, for SD-JWT VC and mdoc based credentials this is not needed. Defaults to `false`. - **staticKey**: _Object_ (optional) - A serialized key for a [store-less wallet](#store-less-wallets), used instead of a key store. - **staticDid**: _String_ (optional) - A DID for a store-less wallet. --- ##### Example Response ```json { "walletId": "550e8400-e29b-41d4-a716-446655440000" } ``` **Response Fields** - **walletId**: _String_ - The new wallet's ID, used in every wallet-scoped call. **warning:** Stores, like key store or did store are attached at creation time only — **you cannot add or remove stores from a wallet after it is created.** Decide the store layout up front. --- ## Inspect and Delete Wallets ### List Wallets **Option: CURL** **Endpoint:** `GET /wallet` | [API Reference](https://docs.walt.id/community-stack/wallet2/full-api-reference.md) ##### Example Request ```bash curl http://localhost:7005/wallet ``` --- ##### Example Response ```json ["550e8400-e29b-41d4-a716-446655440000"] ``` When [authentication](https://docs.walt.id/community-stack/wallet2/configurations/config-files/auth.md) is enabled, this returns only the wallets owned by the authenticated account. ### Get Wallet Info **Option: CURL** **Endpoint:** `GET /wallet/{walletId}` | [API Reference](https://docs.walt.id/community-stack/wallet2/full-api-reference.md) ##### Example Request ```bash curl http://localhost:7005/wallet/{walletId} ``` **Path Parameters** - **walletId**: _String_ (required) - The wallet to inspect. --- ##### Example Response ```json { "walletId": "550e8400-e29b-41d4-a716-446655440000", "keyStoreCount": 1, "credentialStoreCount": 1, "hasDidStore": true, "hasStaticKey": false, "hasStaticDid": false } ``` **Response Fields** - **keyStoreCount** / **credentialStoreCount**: _Number_ - How many stores of each type are attached. - **hasDidStore**: _Boolean_ - Whether a DID store is attached. - **hasStaticKey** / **hasStaticDid**: _Boolean_ - Whether the wallet uses a static key/DID (see [store-less wallets](#store-less-wallets)). ### Delete a Wallet **Option: CURL** **Endpoint:** `DELETE /wallet/{walletId}` | [API Reference](https://docs.walt.id/community-stack/wallet2/full-api-reference.md) ##### Example Request ```bash curl -X DELETE http://localhost:7005/wallet/{walletId} ``` **Path Parameters** - **walletId**: _String_ (required) - The wallet to delete. --- ##### Example Response `204 No Content` on success. --- ## Store-less Wallets For isolated, one-off flows — signing a single presentation, or receiving a credential with externally managed key material — you can create a wallet with **no key or DID store**, backed by a static key and (optionally) a static DID: ```bash curl -X POST http://localhost:7005/wallet \ -H 'Content-Type: application/json' \ -d '{ "staticKey": { "type": "jwk", "jwk": { "kty": "OKP", "crv": "Ed25519", "x": "...", "d": "..." } }, "staticDid": "did:key:z6Mkfriq1MqLBoPWecGoDLjguo1sB9brj6wT3qZ5BxkKpuP6", "noDidStore": true }' ``` The `staticKey` becomes the wallet's default key — it appears in the wallet's key list and is used for signing — and `staticDid` becomes its default DID, so credential [receiving](https://docs.walt.id/community-stack/wallet2/credential-receiving/overview.md) and [presenting](https://docs.walt.id/community-stack/wallet2/credential-presenting/overview.md) work without a key or DID store. You can't add further keys or DIDs (as there are no key or DID stores to manage/persist them), but a **credential store is still created automatically**, so received credentials are stored and can be listed as usual. --- ## Next Steps - [Manage keys](https://docs.walt.id/community-stack/wallet2/key-management/overview.md) — generate, import, and list keys. - [Manage DIDs](https://docs.walt.id/community-stack/wallet2/did-management.md) — create and import DIDs. - [Receive a credential](https://docs.walt.id/community-stack/wallet2/credential-receiving/sd-jwt-vc.md) into the wallet. - [Enable persistence](https://docs.walt.id/community-stack/wallet2/configurations/config-files/persistence.md) so wallet data survives restarts. -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/community-stack/wallet2/key-management/overview RAW: https://docs.walt.id/community-stack/wallet2/key-management/overview.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Key Management Every credential a wallet holds is bound to a cryptographic **key**. The wallet signs proofs of possession (when receiving) and presentations (when presenting) with a key from its key store. This section covers generating, importing, and managing those keys. ## The Key Model - A wallet can have more then one key when using **key stores** (see [Managing Wallets](https://docs.walt.id/community-stack/wallet2/wallets.md)). - Each key has a **key ID** (derived from the key itself) and a **key type** (`Ed25519`, `secp256r1`, `secp256k1`, or `RSA`). - Keys are added by **generating** a new one or **importing** existing key material. ## Default Key Many endpoints — receiving, presenting, DID creation — accept an optional `keyId`. When you omit it, the wallet uses its **default key**. The default key is simply the **first key** across the wallet's key stores (or the `staticKey`, for a [store-less wallet](https://docs.walt.id/community-stack/wallet2/wallets.md#store-less-wallets)). There is no separate "set default" operation at the moment. To control which key is the default, either: - add your preferred key first (it becomes the default), or - pass `keyId` explicitly on each call, or - use a store-less wallet with a single `staticKey`. ## Generate vs Import - **[Generate](https://docs.walt.id/community-stack/wallet2/key-management/local.md)** — the wallet creates a new key of the type you choose. - **[Import](https://docs.walt.id/community-stack/wallet2/key-management/local.md)** — you supply existing key material (a JWK) and the wallet stores it. **info:** Keys are currently **local only** (JWK/Storend in memory or in the chosen DB if persistence is enabled). Support for external KMS backends (AWS KMS, Azure Key Vault, HashiCorp Vault, OCI Vault) is planned. ## Next Steps - [Local Keys](https://docs.walt.id/community-stack/wallet2/key-management/local.md) — generate, import, list, and delete keys. - [DID Management](https://docs.walt.id/community-stack/wallet2/did-management.md) — create DIDs from your keys. -------------------------------------------------------------------------------- PAGE: Local Keys URL: https://docs.walt.id/community-stack/wallet2/key-management/local RAW: https://docs.walt.id/community-stack/wallet2/key-management/local.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Local Keys Local keys are generated and stored by the wallet as JWKs in the configured DB if [persistence](https://docs.walt.id/community-stack/wallet2/configurations/config-files/persistence.md) is enabled. This page covers generating a key, importing existing key material, and listing or deleting keys. The examples use `http://localhost:7005` as the base URL. See [Key Management](https://docs.walt.id/community-stack/wallet2/key-management/overview.md) for how the wallet chooses a [default key](https://docs.walt.id/community-stack/wallet2/key-management/overview.md#default-key). --- ## Generate a Key Create a new key in the wallet's key store. **Option: CURL** **Endpoint:** `POST /wallet/{walletId}/keys/generate` | [API Reference](https://docs.walt.id/community-stack/wallet2/full-api-reference.md) ##### Example Request ```bash curl -X POST http://localhost:7005/wallet/{walletId}/keys/generate \ -H 'Content-Type: application/json' \ -d '{ "keyType": "Ed25519" }' ``` **Path Parameters** - **walletId**: _String_ (required) - The wallet to add the key to. See [Managing Wallets](https://docs.walt.id/community-stack/wallet2/wallets.md). **Body Parameters** - **keyType**: _String_ (optional) - One of `Ed25519`, `secp256r1`, `secp256k1`, `RSA`. Defaults to `Ed25519`. --- ##### Example Response ```json { "keyId": "UneoCeKrx7t0SBINBZQOaSCCzvhgpK8tRS3NEG4pCfU", "keyType": "Ed25519" } ``` **Response Fields** - **keyId**: _String_ - The new key's ID. Use it as `keyId` when receiving, presenting, or creating a DID. - **keyType**: _String_ - The key type. --- ## Import a Key Import existing key material as a JWK. **Option: CURL** **Endpoint:** `POST /wallet/{walletId}/keys/import` | [API Reference](https://docs.walt.id/community-stack/wallet2/full-api-reference.md) ##### Example Request ```bash curl -X POST http://localhost:7005/wallet/{walletId}/keys/import \ -H 'Content-Type: application/json' \ -d '{ "key": { "type": "jwk", "jwk": { "kty": "OKP", "crv": "Ed25519", "x": "...", "d": "..." } } }' ``` **Path Parameters** - **walletId**: _String_ (required) - The wallet to import the key into. **Body Parameters** - **key**: _Object_ (required) - The serialized key. `type` is `jwk`, and `jwk` carries the JWK (include the private `d` component for a signing key). --- ##### Example Response ```json { "keyId": "UneoCeKrx7t0SBINBZQOaSCCzvhgpK8tRS3NEG4pCfU", "keyType": "Ed25519" } ``` --- ## List Keys List the metadata of all keys across the wallet's key stores. **Option: CURL** **Endpoint:** `GET /wallet/{walletId}/keys` | [API Reference](https://docs.walt.id/community-stack/wallet2/full-api-reference.md) ##### Example Request ```bash curl http://localhost:7005/wallet/{walletId}/keys ``` **Path Parameters** - **walletId**: _String_ (required) - The wallet whose keys you want to list. --- ##### Example Response ```json [ { "keyId": "UneoCeKrx7t0SBINBZQOaSCCzvhgpK8tRS3NEG4pCfU", "keyType": "Ed25519" } ] ``` --- ## Get a Key Fetch a single key's metadata. **Option: CURL** **Endpoint:** `GET /wallet/{walletId}/keys/{keyId}` | [API Reference](https://docs.walt.id/community-stack/wallet2/full-api-reference.md) ##### Example Request ```bash curl http://localhost:7005/wallet/{walletId}/keys/{keyId} ``` **Path Parameters** - **walletId**: _String_ (required) - The wallet holding the key. - **keyId**: _String_ (required) - The key to fetch. Obtain it from [list keys](#list-keys) or the generate/import response. --- ##### Example Response ```json { "keyId": "UneoCeKrx7t0SBINBZQOaSCCzvhgpK8tRS3NEG4pCfU", "keyType": "Ed25519" } ``` --- ## Delete a Key **Option: CURL** **Endpoint:** `DELETE /wallet/{walletId}/keys/{keyId}` | [API Reference](https://docs.walt.id/community-stack/wallet2/full-api-reference.md) ##### Example Request ```bash curl -X DELETE http://localhost:7005/wallet/{walletId}/keys/{keyId} ``` **Path Parameters** - **walletId**: _String_ (required) - The wallet holding the key. - **keyId**: _String_ (required) - The key to delete. --- ##### Example Response `204 No Content` on success, or `404 Not Found` if the key doesn't exist. --- ## Next Steps - [Create a DID](https://docs.walt.id/community-stack/wallet2/did-management.md) from one of your keys. - [Receive a credential](https://docs.walt.id/community-stack/wallet2/credential-receiving/sd-jwt-vc.md) bound to a key. -------------------------------------------------------------------------------- PAGE: DID Management URL: https://docs.walt.id/community-stack/wallet2/did-management RAW: https://docs.walt.id/community-stack/wallet2/did-management.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # DID Management A [Decentralized Identifier (DID)](https://docs.walt.id/concepts/decentralized-identifiers) gives a wallet a portable, verifiable identity that credentials can be bound to. This page covers creating a DID from a wallet key, importing an existing DID, and listing or deleting DIDs. The examples use `http://localhost:7005` as the base URL. A wallet needs a **DID store** to manage DIDs — the default wallet setup includes one (see [Managing Wallets](https://docs.walt.id/community-stack/wallet2/wallets.md)). ## Default DID Receiving and presenting credential endpoints accept an optional `did`. When you omit it, the wallet uses its **default DID** — the **first DID** in the wallet's DID store (or the `staticDid`, for a [store-less wallet](https://docs.walt.id/community-stack/wallet2/wallets.md#store-less-wallets)). There is no separate "set default" operation at the moment; add your preferred DID first, or pass `did` explicitly. --- ## Create a DID Create a DID from a key in the wallet. If you omit `keyId`, the wallet's [default key](https://docs.walt.id/community-stack/wallet2/key-management/overview.md#default-key) is used. **Option: CURL** **Endpoint:** `POST /wallet/{walletId}/dids/create` | [API Reference](https://docs.walt.id/community-stack/wallet2/full-api-reference.md) ##### Example Request ```bash curl -X POST http://localhost:7005/wallet/{walletId}/dids/create \ -H 'Content-Type: application/json' \ -d '{ "method": "key" }' ``` **Path Parameters** - **walletId**: _String_ (required) - The wallet to create the DID in. See [Managing Wallets](https://docs.walt.id/community-stack/wallet2/wallets.md). **Body Parameters** - **method**: _String_ (required) - The DID method to create. Supported: `key`, `jwk`. - **keyId**: _String_ (optional) - The key to derive the DID from. Defaults to the wallet's default key. --- ##### Example Response ```json { "did": "did:key:z6Mkfriq1MqLBoPWecGoDLjguo1sB9brj6wT3qZ5BxkKpuP6", "document": { "@context": ["https://www.w3.org/ns/did/v1", "https://w3id.org/security/suites/jws-2020/v1"], "id": "did:key:z6Mkfriq1MqLBoPWecGoDLjguo1sB9brj6wT3qZ5BxkKpuP6", "verificationMethod": [ { "id": "did:key:z6Mkfri...#z6Mkfri...", "type": "JsonWebKey2020", "controller": "did:key:z6Mkfriq1MqLBoPWecGoDLjguo1sB9brj6wT3qZ5BxkKpuP6", "publicKeyJwk": { "kty": "OKP", "crv": "Ed25519", "x": "..." } } ], "assertionMethod": ["did:key:z6Mkfri...#z6Mkfri..."], "authentication": ["did:key:z6Mkfri...#z6Mkfri..."] } } ``` **Response Fields** - **did**: _String_ - The newly created DID. - **document**: _Object_ - The resolved DID document. **info:** Need a `did:web`? Creating it here isn't currently supported. Register it externally and bring it into the wallet with [Import a DID](#import-a-did) instead. --- ## Import a DID Import an existing DID and its document. **Option: CURL** **Endpoint:** `POST /wallet/{walletId}/dids/import` | [API Reference](https://docs.walt.id/community-stack/wallet2/full-api-reference.md) ##### Example Request ```bash curl -X POST http://localhost:7005/wallet/{walletId}/dids/import \ -H 'Content-Type: application/json' \ -d '{ "did": "did:web:example.org", "document": "{\"id\":\"did:web:example.org\",\"verificationMethod\":[]}" }' ``` **Path Parameters** - **walletId**: _String_ (required) - The wallet to import the DID into. **Body Parameters** - **did**: _String_ (required) - The DID to import. - **document**: _String_ (required) - The DID document, as a JSON string. --- ##### Example Response ```json { "did": "did:web:example.org", "document": { "id": "did:web:example.org", "verificationMethod": [] } } ``` --- ## List DIDs **Option: CURL** **Endpoint:** `GET /wallet/{walletId}/dids` | [API Reference](https://docs.walt.id/community-stack/wallet2/full-api-reference.md) ##### Example Request ```bash curl http://localhost:7005/wallet/{walletId}/dids ``` **Path Parameters** - **walletId**: _String_ (required) - The wallet whose DIDs you want to list. --- ##### Example Response ```json [ { "did": "did:key:z6Mkfriq1MqLBoPWecGoDLjguo1sB9brj6wT3qZ5BxkKpuP6", "document": { "id": "did:key:z6Mkfriq1MqLBoPWecGoDLjguo1sB9brj6wT3qZ5BxkKpuP6" } } ] ``` --- ## Get a DID **Option: CURL** **Endpoint:** `GET /wallet/{walletId}/dids/{did}` | [API Reference](https://docs.walt.id/community-stack/wallet2/full-api-reference.md) ##### Example Request ```bash curl http://localhost:7005/wallet/{walletId}/dids/did:key:z6Mkfriq1MqLBoPWecGoDLjguo1sB9brj6wT3qZ5BxkKpuP6 ``` **Path Parameters** - **walletId**: _String_ (required) - The wallet holding the DID. - **did**: _String_ (required) - The DID to fetch. Obtain it from [list DIDs](#list-dids) or the create/import response. --- ##### Example Response ```json { "did": "did:key:z6Mkfriq1MqLBoPWecGoDLjguo1sB9brj6wT3qZ5BxkKpuP6", "document": { "id": "did:key:z6Mkfriq1MqLBoPWecGoDLjguo1sB9brj6wT3qZ5BxkKpuP6" } } ``` --- ## Delete a DID **Option: CURL** **Endpoint:** `DELETE /wallet/{walletId}/dids/{did}` | [API Reference](https://docs.walt.id/community-stack/wallet2/full-api-reference.md) ##### Example Request ```bash curl -X DELETE http://localhost:7005/wallet/{walletId}/dids/did:key:z6Mkfriq1MqLBoPWecGoDLjguo1sB9brj6wT3qZ5BxkKpuP6 ``` **Path Parameters** - **walletId**: _String_ (required) - The wallet holding the DID. - **did**: _String_ (required) - The DID to delete. Obtain it from [list DIDs](#list-dids) or the create/import response. --- ##### Example Response `204 No Content` on success, or `404 Not Found` if the DID doesn't exist. --- ## Next Steps - [Receive a credential](https://docs.walt.id/community-stack/wallet2/credential-receiving/sd-jwt-vc.md) bound to your DID. - [Manage keys](https://docs.walt.id/community-stack/wallet2/key-management/overview.md) the DIDs are derived from. -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/community-stack/wallet2/credential-receiving/overview RAW: https://docs.walt.id/community-stack/wallet2/credential-receiving/overview.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Credential Receiving The Wallet API v2 receives credentials from an issuer using [OpenID4VCI 1.0](https://docs.walt.id/concepts/data-exchange-protocols/openid4vci.md). You hand the wallet a **credential offer** — typically an `openid-credential-offer://` URL from an issuer such as [Issuer2](https://docs.walt.id/community-stack/issuer2/getting-started.md) — and the wallet claims the credential and stores it. ## How It Works Receiving a credential runs through five steps: 1. **Resolve the offer** — parse the offer and fetch the issuer's metadata. 2. **Get an access token** — exchange a pre-authorized code (or an authorization code) for a token. 3. **Sign a proof of possession** — prove the wallet controls the holder key. 4. **Fetch the credential** — request it from the issuer's credential endpoint. 5. **Store it** — the credential is saved to the wallet's credential store. ## Two Ways to Receive - **Full flow** — a single call (`POST /wallet/{walletId}/credentials/receive`) runs all five steps for you. This is the recommended path, and what the credential guides below use. - **[Custom Flow](https://docs.walt.id/community-stack/wallet2/credential-receiving/custom-flow.md)** — drive each step yourself. Use it for custom wallet UIs, the authorization-code grant, deferred issuance, and advanced options like inline keys and client attestation. ## Supported Formats - [SD-JWT VC (IETF)](https://docs.walt.id/concepts/digital-credentials/sd-jwt-vc.md) - [W3C VC](https://docs.walt.id/concepts/digital-credentials/verifiable-credentials-w3c.md) (JWT-signed) - [ISO 18013-5 mDL](https://docs.walt.id/concepts/digital-credentials/mdoc-mdl-iso.md) ## Start Receiving Choose the credential type that matches your use case: - [SD-JWT VC via OID4VCI](https://docs.walt.id/community-stack/wallet2/credential-receiving/sd-jwt-vc.md) - [W3C VC via OID4VCI](https://docs.walt.id/community-stack/wallet2/credential-receiving/w3c-vc.md) - [mDL via OID4VCI](https://docs.walt.id/community-stack/wallet2/credential-receiving/mdl.md) Need fine-grained control? See [Custom Flow](https://docs.walt.id/community-stack/wallet2/credential-receiving/custom-flow.md). -------------------------------------------------------------------------------- PAGE: SD-JWT VC via OID4VCI URL: https://docs.walt.id/community-stack/wallet2/credential-receiving/sd-jwt-vc RAW: https://docs.walt.id/community-stack/wallet2/credential-receiving/sd-jwt-vc.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Receiving SD-JWT VC Credentials via OID4VCI This guide shows you how to receive an [IETF SD-JWT VC](https://docs.walt.id/concepts/digital-credentials/sd-jwt-vc.md) credential into a wallet using the walt.id Wallet API v2. You'll take a credential offer — for example one created with [Issuer2](https://docs.walt.id/community-stack/issuer2/credential-issuance/sd-jwt-vc.md) — and claim it into a wallet in a single call, then confirm it was stored. [**SD-JWT VC**](https://docs.walt.id/concepts/digital-credentials/sd-jwt-vc.md): An IETF credential format supporting selective disclosure, letting the holder reveal only specific claims when presenting. [**OID4VCI**](https://docs.walt.id/concepts/data-exchange-protocols/openid4vci.md): The protocol used to deliver the credential from the issuer to the wallet. **Note:** This page follows the **pre-authorized code flow**. For the authorization-code flow and for driving each step yourself (custom UIs, deferred issuance), see [Custom Flow](https://docs.walt.id/community-stack/wallet2/credential-receiving/custom-flow.md). ## Prerequisites Before you begin, ensure you have: - **Wallet API v2 running** — Follow the [Setup](https://docs.walt.id/community-stack/wallet2/setup.md) guide. The examples below use `http://localhost:7005` as the base URL. - **A wallet holding a key** — [Create a wallet](https://docs.walt.id/community-stack/wallet2/wallets.md), then [generate a key](https://docs.walt.id/community-stack/wallet2/key-management/local.md) into it (any type, e.g. `Ed25519`) — it becomes the wallet's [default key](https://docs.walt.id/community-stack/wallet2/key-management/overview.md#default-key). You'll need the wallet's `walletId`. - **An SD-JWT VC credential offer** — For a full end-to-end run, issue one with the [Issuer2 SD-JWT VC guide](https://docs.walt.id/community-stack/issuer2/credential-issuance/sd-jwt-vc.md). The offer is an `openid-credential-offer://` URL. **Note:** **Running via Docker Compose?** The quick-start stack exposes Wallet API v2 on `http://localhost:7006` (Issuer2 on `7005`, Verifier2 on `7004`). Use that base URL instead, so the wallet can reach the issuer running alongside it. --- ## Receive the Credential Hand the offer to the wallet. This single call resolves the offer, requests an access token, signs a proof of possession with the wallet's default key, fetches the credential, and stores it. **Option: CURL** **Endpoint:** `POST /wallet/{walletId}/credentials/receive` | [API Reference](https://docs.walt.id/community-stack/wallet2/full-api-reference.md) ##### Example Request ```bash curl -X POST http://localhost:7005/wallet/{walletId}/credentials/receive \ -H 'Content-Type: application/json' \ -d '{ "offerUrl": "openid-credential-offer://?credential_offer_uri=http%3A%2F%2Flocalhost%3A7005%2Fopenid4vci%2Fcredential-offer%3Fid%3D48f1dc54-134c-4e10-b889-17f3c0a595bf" }' ``` **Path Parameters** - **walletId**: _String_ (required) - The ID of the wallet that will receive the credential. Returned when you [create a wallet](https://docs.walt.id/community-stack/wallet2/wallets.md#create-a-wallet); to look up an existing one, list your wallets with `GET /wallet`. **Body Parameters** - **Credential offer** (required) — provide exactly one of: - **offerUrl**: _String_ - The OID4VCI credential offer URL, e.g. the `credentialOffer` value returned by Issuer2. - **offerJson**: _Object_ - The credential offer as an inline JSON object, instead of a URL. - **txCode**: _String_ (optional) - Transaction code (PIN) if the offer requires one. Check the offer, or [resolve it first](https://docs.walt.id/community-stack/wallet2/credential-receiving/custom-flow.md#resolve-the-offer) to see `txCodeRequired`. - **keyId**: _String_ (optional) - Key from the wallet's key store to bind the credential to. Defaults to the wallet's default key. - **did**: _String_ (optional) - DID for holder binding. Defaults to the wallet's default DID. - **clientId**: _String_ (optional) - The OAuth 2.0 `client_id` the wallet presents to the issuer's authorization server. Defaults to `wallet-client`, which works with issuers that don't require client registration. --- ##### Example Response ```json { "credentialIds": ["6ba7b810-9dad-11d1-80b4-00c04fd430c8"], "deferredTransactionIds": {} } ``` **Response Fields** - **credentialIds**: _String[]_ - Wallet-assigned IDs of the credentials that were received and stored. - **deferredTransactionIds**: _Object_ - Map of credential configuration ID → `transaction_id`, present only when the issuer defers issuance. See [deferred issuance](https://docs.walt.id/community-stack/wallet2/credential-receiving/custom-flow.md#deferred-credentials). 🎉 You've received an SD-JWT VC credential into the wallet. --- ## Confirm It Was Stored List the wallet's credentials to see the one you just received. This returns metadata only — no raw credential data. **Option: CURL** **Endpoint:** `GET /wallet/{walletId}/credentials` | [API Reference](https://docs.walt.id/community-stack/wallet2/full-api-reference.md) ##### Example Request ```bash curl http://localhost:7005/wallet/{walletId}/credentials ``` **Path Parameters** - **walletId**: _String_ (required) - The ID of the wallet whose credentials you want to list. Returned when you [create a wallet](https://docs.walt.id/community-stack/wallet2/wallets.md#create-a-wallet); to look up an existing one, list your wallets with `GET /wallet`. --- ##### Example Response ```json [ { "id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "format": "dc+sd-jwt", "issuer": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwi...", "addedAt": "2026-07-08T20:30:11.663155755Z" } ] ``` **Response Fields** - **id**: _String_ - The stored credential's ID. Use it with [Credential Management](https://docs.walt.id/community-stack/wallet2/credential-management.md) to view or delete it. - **format**: _String_ - Credential format. For an SD-JWT VC this is `dc+sd-jwt`. - **issuer**: _String_ (optional) - Issuer identifier when it can be determined — for the credential above, the issuer's `did:jwk`. - **subject**: _String_ (optional) - Holder/subject identifier, when the credential carries one. Often absent (as above). - **label**: _String_ (optional) - User-assigned label, when set. - **addedAt**: _String_ - ISO-8601 timestamp of when the credential was stored. Optional fields (`issuer`, `subject`, `label`) are omitted from the response when not present. --- ## Doing It Step by Step The full-flow call above is the fastest path. If you're building a wallet UI and need to show intermediate state — resolve the offer, preview what's being issued, prompt for a PIN, handle deferred issuance — drive the isolated endpoints yourself: `resolve-offer` → `request-token` → `sign-proof` → `fetch-credential` Each step, its request/response shape, and the authorization-code variant are documented in [Custom Flow](https://docs.walt.id/community-stack/wallet2/credential-receiving/custom-flow.md). --- ## Next Steps - **Present the credential you just received** — Try [Presenting SD-JWT VC Credentials](https://docs.walt.id/community-stack/wallet2/credential-presenting/sd-jwt-vc.md) to share it with [Verifier2](https://docs.walt.id/community-stack/verifier2/credential-verification/sd-jwt-vc-oid4vp.md). - **Receive other credential types** — [W3C VC](https://docs.walt.id/community-stack/wallet2/credential-receiving/w3c-vc.md) or [mDL](https://docs.walt.id/community-stack/wallet2/credential-receiving/mdl.md). - **Manage stored credentials** — [List, view, and delete](https://docs.walt.id/community-stack/wallet2/credential-management.md) credentials in the wallet. -------------------------------------------------------------------------------- PAGE: W3C VC via OID4VCI URL: https://docs.walt.id/community-stack/wallet2/credential-receiving/w3c-vc RAW: https://docs.walt.id/community-stack/wallet2/credential-receiving/w3c-vc.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Receiving W3C VC Credentials via OID4VCI This guide shows you how to receive a [W3C Verifiable Credential](https://docs.walt.id/concepts/digital-credentials/verifiable-credentials-w3c.md) (JWT-signed) into a wallet using the walt.id Wallet API v2. You'll take a credential offer — for example one created with [Issuer2](https://docs.walt.id/community-stack/issuer2/credential-issuance/w3c-vc.md) — and claim it into a wallet in a single call, then confirm it was stored. [**W3C VC**](https://docs.walt.id/concepts/digital-credentials/verifiable-credentials-w3c.md): The W3C Verifiable Credentials data model. Here the credential is delivered as a JWT (`jwt_vc_json`). [**OID4VCI**](https://docs.walt.id/concepts/data-exchange-protocols/openid4vci.md): The protocol used to deliver the credential from the issuer to the wallet. **Note:** This page follows the **pre-authorized code flow**. For the authorization-code flow and for driving each step yourself (custom UIs, deferred issuance), see [Custom Flow](https://docs.walt.id/community-stack/wallet2/credential-receiving/custom-flow.md). ## Prerequisites - **Wallet API v2 running** — Follow the [Setup](https://docs.walt.id/community-stack/wallet2/setup.md) guide. The examples use `http://localhost:7005` as the base URL. - **A wallet holding a key and a DID** — [Create a wallet](https://docs.walt.id/community-stack/wallet2/wallets.md), then [generate a key](https://docs.walt.id/community-stack/wallet2/key-management/local.md) and [create a `did:key` or `did:jwk`](https://docs.walt.id/community-stack/wallet2/did-management.md) from it; both become the wallet's defaults. You'll need the wallet's `walletId`. - **A W3C VC credential offer** — For a full end-to-end run, issue one with the [Issuer2 W3C VC guide](https://docs.walt.id/community-stack/issuer2/credential-issuance/w3c-vc.md). The offer is an `openid-credential-offer://` URL. **warning:** A W3C VC is bound to the holder's **DID**, and the wallet signs the OID4VCI proof with a **key** — so it needs both. Without a DID the issuer can't bind the credential subject and rejects the request with `Cannot find in context: subjectDid`. (SD-JWT VC and mDL need only a key.) **Note:** **Running via Docker Compose?** The quick-start stack exposes Wallet API v2 on `http://localhost:7006` (Issuer2 on `7005`, Verifier2 on `7004`). Use that base URL instead. --- ## Receive the Credential Hand the offer to the wallet. This single call resolves the offer, requests a token, signs a proof of possession with the wallet's default key, fetches the credential, and stores it. **Option: CURL** **Endpoint:** `POST /wallet/{walletId}/credentials/receive` | [API Reference](https://docs.walt.id/community-stack/wallet2/full-api-reference.md) ##### Example Request ```bash curl -X POST http://localhost:7005/wallet/{walletId}/credentials/receive \ -H 'Content-Type: application/json' \ -d '{ "offerUrl": "openid-credential-offer://?credential_offer_uri=http%3A%2F%2Flocalhost%3A7005%2Fopenid4vci%2Fcredential-offer%3Fid%3D48f1dc54-134c-4e10-b889-17f3c0a595bf" }' ``` **Path Parameters** - **walletId**: _String_ (required) - The ID of the wallet that will receive the credential. Returned when you [create a wallet](https://docs.walt.id/community-stack/wallet2/wallets.md#create-a-wallet); to look up an existing one, list your wallets with `GET /wallet`. **Body Parameters** - **Credential offer** (required) — provide exactly one of: - **offerUrl**: _String_ - The OID4VCI credential offer URL, e.g. the `credentialOffer` value returned by Issuer2. - **offerJson**: _Object_ - The credential offer as an inline JSON object, instead of a URL. - **txCode**: _String_ (optional) - Transaction code (PIN) if the offer requires one. Check the offer, or [resolve it first](https://docs.walt.id/community-stack/wallet2/credential-receiving/custom-flow.md#resolve-the-offer) to see `txCodeRequired`. - **keyId**: _String_ (optional) - Key from the wallet's key store to bind the credential to. Defaults to the wallet's default key. - **did**: _String_ (optional) - DID for holder binding. Defaults to the wallet's default DID. - **clientId**: _String_ (optional) - The OAuth 2.0 `client_id` the wallet presents to the issuer's authorization server. Defaults to `wallet-client`, which works with issuers that don't require client registration. --- ##### Example Response ```json { "credentialIds": ["6ba7b810-9dad-11d1-80b4-00c04fd430c8"], "deferredTransactionIds": {} } ``` **Response Fields** - **credentialIds**: _String[]_ - Wallet-assigned IDs of the credentials that were received and stored. - **deferredTransactionIds**: _Object_ - Map of credential configuration ID → `transaction_id`, present only when the issuer defers issuance. See [deferred issuance](https://docs.walt.id/community-stack/wallet2/credential-receiving/custom-flow.md#deferred-credentials). 🎉 You've received a W3C VC credential into the wallet. --- ## Confirm It Was Stored List the wallet's credentials to see the one you just received. This returns metadata only — no raw credential data. **Option: CURL** **Endpoint:** `GET /wallet/{walletId}/credentials` | [API Reference](https://docs.walt.id/community-stack/wallet2/full-api-reference.md) ##### Example Request ```bash curl http://localhost:7005/wallet/{walletId}/credentials ``` **Path Parameters** - **walletId**: _String_ (required) - The ID of the wallet whose credentials you want to list. Returned when you [create a wallet](https://docs.walt.id/community-stack/wallet2/wallets.md#create-a-wallet); to look up an existing one, list your wallets with `GET /wallet`. --- ##### Example Response ```json [ { "id": "5ea45ef4-19aa-40bc-9746-96bb9d3893ec", "format": "jwt_vc_json", "issuer": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Ii...", "subject": "did:key:zDnaeyvVjxHZwnWBb8gLxnHhJLM6TcRZfyCzs35DHMGk39V4f", "addedAt": "2026-07-08T20:35:24.653545720Z" } ] ``` **Response Fields** - **id**: _String_ - The stored credential's ID. Use it with [Credential Management](https://docs.walt.id/community-stack/wallet2/credential-management.md) to view or delete it. - **format**: _String_ - Credential format, `jwt_vc_json` for a JWT-signed W3C VC. - **issuer**: _String_ (optional) - Issuer identifier — here the issuer's `did:jwk`. - **subject**: _String_ (optional) - Holder DID the credential is bound to. For a W3C VC this is your wallet's DID from the prerequisites above. - **label**: _String_ (optional) - User-assigned label, when set. - **addedAt**: _String_ - ISO-8601 timestamp of when the credential was stored. Optional fields (`issuer`, `subject`, `label`) are omitted when not present. --- ## Doing It Step by Step The full-flow call above is the fastest path. If you're building a wallet UI and need to show intermediate state — resolve the offer, preview what's being issued, prompt for a PIN, handle the authorization-code flow — drive the isolated endpoints yourself. Each step and its request/response shape is documented in [Custom Flow](https://docs.walt.id/community-stack/wallet2/credential-receiving/custom-flow.md). --- ## Next Steps - **Present the credential you just received** — Try [Presenting W3C VC Credentials](https://docs.walt.id/community-stack/wallet2/credential-presenting/w3c-vc.md) to share it with [Verifier2](https://docs.walt.id/community-stack/verifier2/credential-verification/vc-oid4vp.md). - **Receive other credential types** — [SD-JWT VC](https://docs.walt.id/community-stack/wallet2/credential-receiving/sd-jwt-vc.md) or [mDL](https://docs.walt.id/community-stack/wallet2/credential-receiving/mdl.md). - **Manage stored credentials** — [List, view, and delete](https://docs.walt.id/community-stack/wallet2/credential-management.md) credentials in the wallet. -------------------------------------------------------------------------------- PAGE: mDL via OID4VCI URL: https://docs.walt.id/community-stack/wallet2/credential-receiving/mdl RAW: https://docs.walt.id/community-stack/wallet2/credential-receiving/mdl.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Receiving mDL Credentials via OID4VCI This guide shows you how to receive an [ISO 18013-5 mobile driving licence (mDL)](https://docs.walt.id/concepts/digital-credentials/mdoc-mdl-iso.md) into a wallet using the walt.id Wallet API v2. You'll take a credential offer — for example one created with [Issuer2](https://docs.walt.id/community-stack/issuer2/credential-issuance/mdl.md) — and claim it into a wallet in a single call, then confirm it was stored. [**mDL / mdoc**](https://docs.walt.id/concepts/digital-credentials/mdoc-mdl-iso.md): The ISO/IEC 18013-5 mobile document format (`mso_mdoc`), used for mobile driving licences and other ISO-standard credentials. [**OID4VCI**](https://docs.walt.id/concepts/data-exchange-protocols/openid4vci.md): The protocol used to deliver the credential from the issuer to the wallet. **Note:** This page follows the **pre-authorized code flow**. For the authorization-code flow and for driving each step yourself (custom UIs, deferred issuance), see [Custom Flow](https://docs.walt.id/community-stack/wallet2/credential-receiving/custom-flow.md). ## Prerequisites - **Wallet API v2 running** — Follow the [Setup](https://docs.walt.id/community-stack/wallet2/setup.md) guide. The examples use `http://localhost:7005` as the base URL. - **A wallet holding a `secp256r1` key** — [Create a wallet](https://docs.walt.id/community-stack/wallet2/wallets.md), then [generate a `secp256r1` key](https://docs.walt.id/community-stack/wallet2/key-management/local.md) into it (ES256, the curve mdoc/ISO 18013-5 expects) — it becomes the wallet's default key. You'll need the wallet's `walletId`. - **An mDL credential offer** — For a full end-to-end run, issue one with the [Issuer2 mDL guide](https://docs.walt.id/community-stack/issuer2/credential-issuance/mdl.md). The offer is an `openid-credential-offer://` URL. **Note:** **Running via Docker Compose?** The quick-start stack exposes Wallet API v2 on `http://localhost:7006` (Issuer2 on `7005`, Verifier2 on `7004`). Use that base URL instead. --- ## Receive the Credential Hand the offer to the wallet. This single call resolves the offer, requests a token, signs a proof of possession with the wallet's default key, fetches the credential, and stores it. **Option: CURL** **Endpoint:** `POST /wallet/{walletId}/credentials/receive` | [API Reference](https://docs.walt.id/community-stack/wallet2/full-api-reference.md) ##### Example Request ```bash curl -X POST http://localhost:7005/wallet/{walletId}/credentials/receive \ -H 'Content-Type: application/json' \ -d '{ "offerUrl": "openid-credential-offer://?credential_offer_uri=http%3A%2F%2Flocalhost%3A7005%2Fopenid4vci%2Fcredential-offer%3Fid%3D48f1dc54-134c-4e10-b889-17f3c0a595bf" }' ``` **Path Parameters** - **walletId**: _String_ (required) - The ID of the wallet that will receive the credential. Returned when you [create a wallet](https://docs.walt.id/community-stack/wallet2/wallets.md#create-a-wallet); to look up an existing one, list your wallets with `GET /wallet`. **Body Parameters** - **Credential offer** (required) — provide exactly one of: - **offerUrl**: _String_ - The OID4VCI credential offer URL, e.g. the `credentialOffer` value returned by Issuer2. - **offerJson**: _Object_ - The credential offer as an inline JSON object, instead of a URL. - **txCode**: _String_ (optional) - Transaction code (PIN) if the offer requires one. Check the offer, or [resolve it first](https://docs.walt.id/community-stack/wallet2/credential-receiving/custom-flow.md#resolve-the-offer) to see `txCodeRequired`. - **keyId**: _String_ (optional) - Key from the wallet's key store to bind the credential to. Defaults to the wallet's default key. - **did**: _String_ (optional) - DID for holder binding. Defaults to the wallet's default DID. Not needed for mdoc credentials. - **clientId**: _String_ (optional) - The OAuth 2.0 `client_id` the wallet presents to the issuer's authorization server. Defaults to `wallet-client`, which works with issuers that don't require client registration. --- ##### Example Response ```json { "credentialIds": ["6ba7b810-9dad-11d1-80b4-00c04fd430c8"], "deferredTransactionIds": {} } ``` **Response Fields** - **credentialIds**: _String[]_ - Wallet-assigned IDs of the credentials that were received and stored. - **deferredTransactionIds**: _Object_ - Map of credential configuration ID → `transaction_id`, present only when the issuer defers issuance. See [deferred issuance](https://docs.walt.id/community-stack/wallet2/credential-receiving/custom-flow.md#deferred-credentials). 🎉 You've received an mDL credential into the wallet. --- ## Confirm It Was Stored List the wallet's credentials to see the one you just received. This returns metadata only — no raw credential data. **Option: CURL** **Endpoint:** `GET /wallet/{walletId}/credentials` | [API Reference](https://docs.walt.id/community-stack/wallet2/full-api-reference.md) ##### Example Request ```bash curl http://localhost:7005/wallet/{walletId}/credentials ``` **Path Parameters** - **walletId**: _String_ (required) - The ID of the wallet whose credentials you want to list. Returned when you [create a wallet](https://docs.walt.id/community-stack/wallet2/wallets.md#create-a-wallet); to look up an existing one, list your wallets with `GET /wallet`. --- ##### Example Response ```json [ { "id": "afbfe84f-839b-40ef-bf0c-8a579e433ba8", "format": "mso_mdoc", "addedAt": "2026-07-08T20:35:47.770818300Z" } ] ``` **Response Fields** - **id**: _String_ - The stored credential's ID. Use it with [Credential Management](https://docs.walt.id/community-stack/wallet2/credential-management.md) to view or delete it. - **format**: _String_ - Credential format, `mso_mdoc` for an ISO 18013-5 mdoc. - **addedAt**: _String_ - ISO-8601 timestamp of when the credential was stored. For an mdoc the optional `issuer`, `subject`, and `label` fields are not populated (mdoc binds to a device key, not a subject DID), so they're omitted from the response. --- ## Doing It Step by Step The full-flow call above is the fastest path. If you're building a wallet UI and need to show intermediate state — resolve the offer, preview what's being issued, prompt for a PIN, handle the authorization-code flow — drive the isolated endpoints yourself. Each step and its request/response shape is documented in [Custom Flow](https://docs.walt.id/community-stack/wallet2/credential-receiving/custom-flow.md). --- ## Next Steps - **Present the credential you just received** — Try [Presenting mDL Credentials](https://docs.walt.id/community-stack/wallet2/credential-presenting/mdl.md) to share it with [Verifier2](https://docs.walt.id/community-stack/verifier2/credential-verification/mdl-oid4vp.md). - **Receive other credential types** — [SD-JWT VC](https://docs.walt.id/community-stack/wallet2/credential-receiving/sd-jwt-vc.md) or [W3C VC](https://docs.walt.id/community-stack/wallet2/credential-receiving/w3c-vc.md). - **Manage stored credentials** — [List, view, and delete](https://docs.walt.id/community-stack/wallet2/credential-management.md) credentials in the wallet. -------------------------------------------------------------------------------- PAGE: Custom Flow URL: https://docs.walt.id/community-stack/wallet2/credential-receiving/custom-flow RAW: https://docs.walt.id/community-stack/wallet2/credential-receiving/custom-flow.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Custom Flow The [full-flow endpoint](https://docs.walt.id/community-stack/wallet2/credential-receiving/overview.md) runs the whole OID4VCI exchange in a single call. When you're building a wallet UI and need to show intermediate state, prompt for a PIN, support the authorization-code grant, or handle deferred issuance, drive the **isolated endpoints** explained on this page yourself. All endpoints on this page are rooted at `/wallet/{walletId}/credentials/receive`. The examples use `http://localhost:7005` as the base URL. ## When to Use the Isolated Flow | You want to… | Use | |---|---| | Claim a credential in one call | [Full flow](https://docs.walt.id/community-stack/wallet2/credential-receiving/overview.md) | | Preview the offer before claiming | [Resolve the offer](#resolve-the-offer) first | | Issuer requires user auth before claiming | [Authorization-code flow](#authorization-code-flow) | | Poll for a credential issued later | [Deferred credentials](#deferred-credentials) | | Manage key material outside the wallet | [Inline keys](#inline-keys) | --- ## Pre-Authorized Code Flow The isolated pre-authorized flow is: ```text 1. Resolve Offer → 2. Request Token → 2b. Get Nonce → 3. Sign Proof → 4. Fetch Credential ``` ### Resolve the Offer Parse a credential offer and fetch the issuer metadata needed for the later steps. **Option: CURL** **Endpoint:** `POST /wallet/{walletId}/credentials/receive/resolve-offer` | [API Reference](https://docs.walt.id/community-stack/wallet2/full-api-reference.md) ##### Example Request ```bash curl -X POST http://localhost:7005/wallet/{walletId}/credentials/receive/resolve-offer \ -H 'Content-Type: application/json' \ -d '{ "offerUrl": "openid-credential-offer://?credential_offer_uri=http%3A%2F%2Flocalhost%3A7005%2Fopenid4vci%2Fcredential-offer%3Fid%3D48f1dc54-134c-4e10-b889-17f3c0a595bf" }' ``` **Path Parameters** - **walletId**: _String_ (required) - The wallet handling the exchange. See [how to obtain it](https://docs.walt.id/community-stack/wallet2/wallets.md#create-a-wallet). **Body Parameters** - **Credential offer** (required) — provide exactly one of: - **offerUrl**: _String_ - The OID4VCI credential offer URL. - **offerJson**: _Object_ - The credential offer as an inline JSON object. --- ##### Example Response ```json { "credentialIssuer": "http://localhost:7005/openid4vci", "credentialConfigurationIds": ["identity_credential"], "grantType": "pre-authorized_code", "txCodeRequired": true, "credentialEndpoint": "http://localhost:7005/openid4vci/credential", "offeredCredentials": ["identity_credential"] } ``` **Response Fields** - **credentialIssuer**: _String_ - Issuer identifier from the offer. - **credentialConfigurationIds**: _String[]_ - Credential configuration IDs listed in the offer. - **grantType**: _String_ - `pre-authorized_code` or `authorization_code`. - **txCodeRequired**: _Boolean_ - Whether the pre-authorized flow requires a transaction code (PIN). - **credentialEndpoint**: _String_ - The issuer's credential endpoint, from its metadata. - **offeredCredentials**: _String[]_ - Resolved offered credential configuration IDs. ### Request Token Exchange the pre-authorized code for an access token. **warning:** `resolve-offer` does not return the `preAuthorizedCode` or `tokenEndpoint` this step needs — source them yourself. The **pre-authorized code** is carried inside the credential offer at `grants."urn:ietf:params:oauth:grant-type:pre-authorized_code"."pre-authorized_code"`. The **token endpoint** is the issuer's `token_endpoint` from its authorization-server metadata — for Issuer2 that is `{credentialIssuer}/token` (the `credentialIssuer` returned by `resolve-offer`). **Option: CURL** **Endpoint:** `POST /wallet/{walletId}/credentials/receive/request-token` | [API Reference](https://docs.walt.id/community-stack/wallet2/full-api-reference.md) ##### Example Request ```bash curl -X POST http://localhost:7005/wallet/{walletId}/credentials/receive/request-token \ -H 'Content-Type: application/json' \ -d '{ "tokenEndpoint": "http://localhost:7005/openid4vci/token", "preAuthorizedCode": "SplxlOBeZQQYbYS6WxSbIA", "txCode": "123456" }' ``` **Body Parameters** - **tokenEndpoint**: _String_ (required) - The issuer's token endpoint. - **preAuthorizedCode**: _String_ (required) - The pre-authorized code from the credential offer. - **txCode**: _String_ (optional) - Transaction code (PIN), if the offer requires one. - **clientId**: _String_ (optional) - See [OAuth client identity](#oauth-client-identity). Defaults to `wallet-client`. - **redirectUri**: _String_ (optional) - Defaults to `openid://`. --- ##### Example Response ```json { "accessToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", "expiresIn": 300 } ``` **Response Fields** - **accessToken**: _String_ - Use it in the fetch-credential request. - **cNonce**: _String_ (optional) - Proof nonce, **only if** the issuer returns one in the token response. Under OID4VCI 1.0 the issuer (including Issuer2) serves the nonce from a dedicated **Nonce Endpoint** instead, so this is usually absent — fetch it in [Get a Nonce](#get-a-nonce) below. - **expiresIn**: _Number_ - Token lifetime in seconds. **warning:** Under OID4VCI 1.0 the proof nonce (`c_nonce`) comes from the issuer's **Nonce Endpoint**, not the token response — so `request-token` typically returns no `cNonce`. Get it via [Get a Nonce](#get-a-nonce) before signing the proof. ### Get a Nonce Under OID4VCI 1.0 the proof `c_nonce` comes from the issuer's dedicated **Nonce Endpoint**, not the token response. The wallet does not proxy it — call the **issuer's** nonce endpoint directly. Its URL is the `nonce_endpoint` published in the issuer's credential-issuer metadata; for [Issuer2](https://docs.walt.id/community-stack/issuer2/getting-started.md) it is `{credentialIssuer}/nonce` (the `credentialIssuer` returned by [resolve-offer](#resolve-the-offer)). **Option: CURL** **Endpoint:** `POST {credentialIssuer}/nonce` — the **issuer's** endpoint, not a wallet endpoint. ##### Example Request ```bash curl -X POST http://localhost:7005/openid4vci/nonce ``` --- ##### Example Response ```json { "c_nonce": "wKI4LT5Sadf6a0dyih5b0Ws" } ``` Use the returned `c_nonce` as the `nonce` in [Sign Proof](#sign-proof). ### Sign Proof Sign a proof-of-possession JWT that proves the wallet controls the holder key. **Option: CURL** **Endpoint:** `POST /wallet/{walletId}/credentials/receive/sign-proof` | [API Reference](https://docs.walt.id/community-stack/wallet2/full-api-reference.md) ##### Example Request ```bash curl -X POST http://localhost:7005/wallet/{walletId}/credentials/receive/sign-proof \ -H 'Content-Type: application/json' \ -d '{ "issuerUrl": "http://localhost:7005/openid4vci", "nonce": "wKI4LT5Sadf6a0dyih5b0Ws" }' ``` **Body Parameters** - **issuerUrl**: _String_ (required) - The credential issuer URL, used as the proof audience. - **nonce**: _String_ (required) - The `c_nonce` from [Get a Nonce](#get-a-nonce) (or from the token response, if the issuer returned one). - **keyId**: _String_ (optional) - Key from the wallet's key store. Defaults to the wallet's default key. - **did**: _String_ (optional) - DID to use as the proof key ID. If omitted, the proof carries the JWK directly. - **key**: _Object_ (optional) - An [inline key](#inline-keys). Takes precedence over `keyId`. --- ##### Example Response ```json { "proofJwt": "eyJhbGciOiJFUzI1NiIsInR5cCI6Im9wZW5pZDR2Y2ktcHJvb2Yrand0In0..." } ``` ### Fetch Credential Request the credential from the issuer's credential endpoint. **Option: CURL** **Endpoint:** `POST /wallet/{walletId}/credentials/receive/fetch-credential` | [API Reference](https://docs.walt.id/community-stack/wallet2/full-api-reference.md) ##### Example Request ```bash curl -X POST http://localhost:7005/wallet/{walletId}/credentials/receive/fetch-credential \ -H 'Content-Type: application/json' \ -d '{ "credentialEndpoint": "http://localhost:7005/openid4vci/credential", "accessToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", "credentialConfigurationId": "identity_credential", "proofJwt": "eyJhbGciOiJFUzI1NiIsInR5cCI6Im9wZW5pZDR2Y2ktcHJvb2Yrand0In0..." }' ``` **Body Parameters** - **credentialEndpoint**: _String_ (required) - The issuer's credential endpoint. - **accessToken**: _String_ (required) - The access token from the token response. - **credentialConfigurationId**: _String_ (required) - The credential configuration ID to request. - **proofJwt**: _String_ (optional) - The proof JWT from the sign-proof step. Required if the issuer requires proof. - **clientId**: _String_ (optional) - See [OAuth client identity](#oauth-client-identity). Defaults to `wallet-client`. --- ##### Example Response ```json { "rawCredentials": [ "eyJraWQiOiJkaWQ6a2V5Ono2TWtmcmlxMU1xTEJvUFdlY0dvRExqZ3VvMXNCOWJyajZ3VDNxWjVCeGtLcHVQNiJ9..." ] } ``` **info:** The isolated fetch endpoint returns **raw credential strings — it does not store them.** To keep a credential, [import it](https://docs.walt.id/community-stack/wallet2/credential-management.md) with `POST /wallet/{walletId}/credentials/import`, or use the [full flow](https://docs.walt.id/community-stack/wallet2/credential-receiving/overview.md), which stores automatically. --- ## Authorization-Code Flow When the issuer requires the holder to log in first, use the authorization-code grant. It replaces the "request token" step with a redirect through the issuer's authorization server. ```text 1. Resolve Offer → 2. Generate Authorization URL → (user logs in, issuer redirects back) → 3. Exchange Code → 4. Sign Proof → 5. Fetch Credential ``` Steps 1, 4 and 5 are identical to the pre-authorized flow above. ### Generate Authorization URL Build the authorization URL to redirect the user to. **Option: CURL** **Endpoint:** `POST /wallet/{walletId}/credentials/receive/authorization-url` | [API Reference](https://docs.walt.id/community-stack/wallet2/full-api-reference.md) ##### Example Request ```bash curl -X POST http://localhost:7005/wallet/{walletId}/credentials/receive/authorization-url \ -H 'Content-Type: application/json' \ -d '{ "offerUrl": "openid-credential-offer://?credential_offer_uri=...", "redirectUri": "https://wallet.example.org/callback", "usePkce": true }' ``` **Body Parameters** - **Credential offer** (required) — provide exactly one of: - **offerUrl**: _String_ - The OID4VCI credential offer URL. - **offerJson**: _Object_ - The credential offer as an inline JSON object. - **redirectUri**: _String_ (optional) - Redirect URI registered with the issuer. Defaults to `openid://`. - **usePkce**: _Boolean_ (optional) - Whether to use PKCE. Defaults to `true`. - **clientId**: _String_ (optional) - See [OAuth client identity](#oauth-client-identity). Defaults to `wallet-client`. --- ##### Example Response ```json { "authorizationUrl": "http://localhost:7005/openid4vci/authorize?response_type=code&client_id=wallet-client&state=abc123&code_challenge=...", "state": "abc123", "codeVerifier": "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM", "credentialConfigurationId": "identity_credential" } ``` **Response Fields** - **authorizationUrl**: _String_ - Redirect the user here. - **state**: _String_ - Returned to your redirect URI; use it to correlate the response. - **codeVerifier**: _String_ - Store it — you'll need it to exchange the code (PKCE). - **credentialConfigurationId**: _String_ - The credential being requested. After the user authenticates, the issuer redirects to your `redirectUri` with `code` and `state` query parameters. ### Exchange Code Exchange the authorization code for an access token. **Option: CURL** **Endpoint:** `POST /wallet/{walletId}/credentials/receive/exchange-code` | [API Reference](https://docs.walt.id/community-stack/wallet2/full-api-reference.md) ##### Example Request ```bash curl -X POST http://localhost:7005/wallet/{walletId}/credentials/receive/exchange-code \ -H 'Content-Type: application/json' \ -d '{ "tokenEndpoint": "http://localhost:7005/openid4vci/token", "code": "SplxlOBeZQQYbYS6WxSbIA", "codeVerifier": "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM", "redirectUri": "https://wallet.example.org/callback" }' ``` **Body Parameters** - **tokenEndpoint**: _String_ (required) - The issuer's token endpoint. - **code**: _String_ (required) - The authorization code returned to your redirect URI. - **codeVerifier**: _String_ (optional) - The PKCE code verifier from the authorization-url step. - **redirectUri**: _String_ (optional) - The redirect URI used in the authorization request. Defaults to `openid://`. - **clientId**: _String_ (optional) - See [OAuth client identity](#oauth-client-identity). Defaults to `wallet-client`. --- ##### Example Response Returns the same shape as [Request Token](#request-token) — an `accessToken`, optional `cNonce`, and `expiresIn`. Continue with [Sign Proof](#sign-proof) and [Fetch Credential](#fetch-credential). --- ## Deferred Credentials If the issuer isn't ready to issue immediately, the receive result returns a `transaction_id` in `deferredTransactionIds` instead of a credential. Poll the deferred endpoint with that ID until the credential is ready. **Option: CURL** **Endpoint:** `POST /wallet/{walletId}/credentials/receive/deferred` | [API Reference](https://docs.walt.id/community-stack/wallet2/full-api-reference.md) ##### Example Request ```bash curl -X POST http://localhost:7005/wallet/{walletId}/credentials/receive/deferred \ -H 'Content-Type: application/json' \ -d '{ "deferredCredentialEndpoint": "http://localhost:7005/openid4vci/credential_deferred", "transactionId": "8xFQ5j9Q8L-3PUdnl4RWxXgB7oNc3mK2pL9vR6tY1zA", "accessToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." }' ``` **Body Parameters** - **deferredCredentialEndpoint**: _String_ (required) - The deferred credential endpoint from the issuer metadata. - **transactionId**: _String_ (required) - The transaction ID returned when issuance was deferred. - **accessToken**: _String_ (required) - The access token from the original token response. --- ##### Example Response ```json { "credentialIds": ["550e8400-e29b-41d4-a716-446655440000"], "deferredTransactionIds": {} } ``` If the issuer is still not ready, `credentialIds` comes back empty — retry later. --- ## Advanced Options ### OAuth Client Identity Several endpoints accept a `clientId`. It is the OAuth 2.0 `client_id` the wallet presents to the **issuer's authorization server** in token and authorization requests (and it feeds client-attestation headers when those are used). The default, `wallet-client`, works with issuers that don't require client registration — including the walt.id [Issuer2](https://docs.walt.id/community-stack/issuer2/getting-started.md) in its default configuration. Set it explicitly when the issuer's authorization server requires a registered or attested client identity. ### Inline Keys The `sign-proof` step (and the [full-flow receive](https://docs.walt.id/community-stack/wallet2/credential-receiving/overview.md)) accept an inline `key` object instead of a `keyId`. An inline key takes precedence over `keyId` and the wallet's default key. Use it only when the caller manages key material outside the wallet store — most deployments should prefer `keyId` or the default key. ```json { "key": { "type": "jwk", "jwk": { "kty": "OKP", "crv": "Ed25519", "x": "...", "d": "..." } } } ``` The `key` uses the same serialized shape as the [key import endpoint](https://docs.walt.id/community-stack/wallet2/key-management/local.md). ### Token Request Headers `ReceiveCredentialRequest` includes a `tokenRequestHeaders` map, intended for headers such as `OAuth-Client-Attestation` and `OAuth-Client-Attestation-PoP`. **warning:** The current token request builder does **not** forward these per-request headers. If `tokenRequestHeaders` are supplied, the service logs a warning and continues without sending them. Do not rely on this field for attestation-based client authentication until the underlying token builder supports additional headers. --- ## Endpoint Reference | Endpoint | Method | Description | |----------|--------|-------------| | `/wallet/{walletId}/credentials/receive` | POST | Full pre-authorized code flow | | `/wallet/{walletId}/credentials/receive/resolve-offer` | POST | Resolve a credential offer | | `/wallet/{walletId}/credentials/receive/request-token` | POST | Exchange a pre-authorized code for a token | | `/wallet/{walletId}/credentials/receive/authorization-url` | POST | Generate an authorization-code redirect URL | | `/wallet/{walletId}/credentials/receive/exchange-code` | POST | Exchange an authorization code for a token | | `/wallet/{walletId}/credentials/receive/sign-proof` | POST | Sign a proof-of-possession JWT | | `/wallet/{walletId}/credentials/receive/fetch-credential` | POST | Fetch credential(s) from the issuer | | `/wallet/{walletId}/credentials/receive/deferred` | POST | Poll deferred credential issuance | -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/community-stack/wallet2/credential-presenting/overview RAW: https://docs.walt.id/community-stack/wallet2/credential-presenting/overview.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Credential Presenting The Wallet API v2 presents credentials to a verifier using [OpenID4VP 1.0](https://docs.walt.id/concepts/data-exchange-protocols.md) with **DCQL** (Digital Credential Query Language) for credential matching. You hand the wallet a **presentation request** — typically an `openid4vp://` URL from a verifier such as [Verifier2](https://docs.walt.id/community-stack/verifier2/getting-started.md) — and the wallet finds matching credentials, builds a presentation, and submits it. ## How It Works Presenting a credential runs through four steps: 1. **Resolve the request** — parse the verifier's authorization request and read its DCQL query. 2. **Match credentials** — find stored credentials that satisfy the query. 3. **Build the presentation** — sign the selected credential(s) with the holder key. 4. **Submit** — send the presentation to the verifier's response endpoint. ## Two Ways to Present - **Full flow** — a single call (`POST /wallet/{walletId}/credentials/present`) auto-matches credentials from the wallet's credential stores, signs, and submits. This is what the credential guides below use. - **[Custom Flow](https://docs.walt.id/community-stack/wallet2/credential-presenting/custom-flow.md)** — drive each step yourself. Use it to preview what will be shared (consent UIs), let the user choose between matches, or present credentials that aren't stored in the wallet (managed externally). ## Selective Disclosure For SD-JWT VC and mDL, the wallet reveals **only the claims the verifier asks for** — you don't tell the wallet which claims to disclose, the verifier does, through the `claims` array of its DCQL query. The wallet resolves the request and discloses accordingly, automatically, in the full `POST /credentials/present` call (and identically in the [Custom Flow](https://docs.walt.id/community-stack/wallet2/credential-presenting/custom-flow.md)). What "only what's asked" means differs by format: - **SD-JWT VC** — the verifier's requested paths select which **selectively-disclosable** claims are revealed. Claims marked selectively-disclosable by the issuer that the verifier does **not** request stay hidden; a claim that isn't selectively-disclosable is always present and can't be withheld. If the verifier sends no `claims` at all, only the always-present (non-SD) claims are shared. - **mDL (mso_mdoc)** — every data element is individually disclosable, so the wallet discloses **exactly** the elements the verifier lists in `claims` (by `[namespace, element]` path) and nothing else. ```json // A verifier asking (via DCQL) for just two claims of an SD-JWT VC "claims": [ { "path": ["given_name"] }, { "path": ["birthdate"] } ] ``` ## Supported Formats - [SD-JWT VC (IETF)](https://docs.walt.id/concepts/digital-credentials/sd-jwt-vc.md) - [W3C VC](https://docs.walt.id/concepts/digital-credentials/verifiable-credentials-w3c.md) (JWT-signed) - [ISO 18013-5 mDL](https://docs.walt.id/concepts/digital-credentials/mdoc-mdl-iso.md) ## Start Presenting Choose the credential type that matches your use case: - [SD-JWT VC via OID4VP](https://docs.walt.id/community-stack/wallet2/credential-presenting/sd-jwt-vc.md) - [W3C VC via OID4VP](https://docs.walt.id/community-stack/wallet2/credential-presenting/w3c-vc.md) - [mDL via OID4VP](https://docs.walt.id/community-stack/wallet2/credential-presenting/mdl.md) Need fine-grained control? See [Custom Flow](https://docs.walt.id/community-stack/wallet2/credential-presenting/custom-flow.md). -------------------------------------------------------------------------------- PAGE: SD-JWT VC via OID4VP URL: https://docs.walt.id/community-stack/wallet2/credential-presenting/sd-jwt-vc RAW: https://docs.walt.id/community-stack/wallet2/credential-presenting/sd-jwt-vc.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Presenting SD-JWT VC Credentials via OID4VP This guide shows you how to present an [IETF SD-JWT VC](https://docs.walt.id/concepts/digital-credentials/sd-jwt-vc.md) credential from a wallet using the walt.id Wallet API v2. You'll take a presentation request — for example one created with [Verifier2](https://docs.walt.id/community-stack/verifier2/credential-verification/sd-jwt-vc-oid4vp.md) — and satisfy it in a single call. [**SD-JWT VC**](https://docs.walt.id/concepts/digital-credentials/sd-jwt-vc.md): An IETF credential format supporting selective disclosure — the wallet reveals only the claims the verifier asks for. [**OID4VP**](https://docs.walt.id/concepts/data-exchange-protocols.md): The protocol used to deliver the presentation from the wallet to the verifier. Credential matching uses DCQL. **Note:** This page uses the **full presentation flow** — one call that matches, signs, and submits. To preview what will be shared, let the user choose between matches, or present a credential that isn't stored in the wallet, see [Custom Flow](https://docs.walt.id/community-stack/wallet2/credential-presenting/custom-flow.md). ## Prerequisites - **Wallet API v2 running** — Follow the [Setup](https://docs.walt.id/community-stack/wallet2/setup.md) guide. The examples use `http://localhost:7005` as the base URL. - **A wallet holding an SD-JWT VC** — [Receive one first](https://docs.walt.id/community-stack/wallet2/credential-receiving/sd-jwt-vc.md); you'll need the wallet's `walletId`. - **A presentation request** — For a full end-to-end run, create one with the [Verifier2 SD-JWT VC guide](https://docs.walt.id/community-stack/verifier2/credential-verification/sd-jwt-vc-oid4vp.md). The request is an `openid4vp://` URL. **Note:** **Running via Docker Compose?** The quick-start stack exposes Wallet API v2 on `http://localhost:7006` (Verifier2 on `7004`). Use that base URL instead. --- ## Present the Credential Hand the verifier's request to the wallet. This single call resolves the request, matches a stored SD-JWT VC against the verifier's DCQL query, signs the presentation with the wallet's default key, and submits it. **Option: CURL** **Endpoint:** `POST /wallet/{walletId}/credentials/present` | [API Reference](https://docs.walt.id/community-stack/wallet2/full-api-reference.md) ##### Example Request ```bash curl -X POST http://localhost:7005/wallet/{walletId}/credentials/present \ -H 'Content-Type: application/json' \ -d '{ "requestUrl": "openid4vp://authorize?client_id=x509_san_dns%3Averifier.example.org&request_uri=http%3A%2F%2Flocalhost%3A7004%2Fopenid4vc%2Frequest%2Fabc123" }' ``` **Path Parameters** - **walletId**: _String_ (required) - The wallet holding the credential to present. Returned when you [create a wallet](https://docs.walt.id/community-stack/wallet2/wallets.md); to look up an existing one, list your wallets with `GET /wallet`. **Body Parameters** - **Presentation request** (required) — provide exactly one of: - **requestUrl**: _String_ - The OID4VP authorization request URL from the verifier. - **requestObject**: _Object_ - A pre-fetched authorization request as an inline JSON object. - **keyId**: _String_ (optional) - Key used to sign the presentation. Defaults to the wallet's default key. - **did**: _String_ (optional) - DID for the presenter identity. Defaults to the wallet's default DID. --- ##### Example Response ```json { "transmission_success": true, "verifier_response": { "status": "received", "message": "Presentation received and is being processed." } } ``` **Response Fields** - **transmission_success**: _Boolean_ - Whether the wallet successfully submitted the presentation to the verifier. - **verifier_response**: _Object_ - The raw response body returned by the verifier (for a `direct_post` submission, a status/message acknowledgement as above). - **redirect_to**: _String_ (optional) - Where to send the user next, if the verifier returned a redirect. Omitted otherwise. - **get_url** / **form_post_html**: _String_ (optional) - Present instead for `direct_post.jwt` / form-post response modes. Omitted for a plain `direct_post`. **Note:** `transmission_success: true` means the wallet delivered the presentation — it doesn't by itself mean the verifier's policies passed. Confirm the outcome on the verifier side (e.g. `GET /verification-session/{sessionId}/info` → `status: SUCCESSFUL`). 🎉 You've presented an SD-JWT VC credential to the verifier. Only the selectively-disclosable claims the verifier requested were disclosed — see [Selective Disclosure](https://docs.walt.id/community-stack/wallet2/credential-presenting/overview.md#selective-disclosure) for how the verifier's DCQL `claims` drive what's shared. --- ## Doing It Step by Step The full-flow call above matches and submits in one shot. If you're building a wallet UI and need to show the user what will be shared before they consent — or present a credential that isn't stored in the wallet — drive the isolated endpoints yourself. Resolving the request, DCQL matching, and inline presentation are documented in [Custom Flow](https://docs.walt.id/community-stack/wallet2/credential-presenting/custom-flow.md). --- ## Next Steps - **Present other credential types** — [W3C VC](https://docs.walt.id/community-stack/wallet2/credential-presenting/w3c-vc.md) or [mDL](https://docs.walt.id/community-stack/wallet2/credential-presenting/mdl.md). - **Preview and consent** — Use [Custom Flow](https://docs.walt.id/community-stack/wallet2/credential-presenting/custom-flow.md#match-from-the-wallet-stores) to show the user which credentials match before sharing. - **Receive more credentials** — [Credential Receiving](https://docs.walt.id/community-stack/wallet2/credential-receiving/overview.md). -------------------------------------------------------------------------------- PAGE: W3C VC via OID4VP URL: https://docs.walt.id/community-stack/wallet2/credential-presenting/w3c-vc RAW: https://docs.walt.id/community-stack/wallet2/credential-presenting/w3c-vc.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Presenting W3C VC Credentials via OID4VP This guide shows you how to present a [W3C Verifiable Credential](https://docs.walt.id/concepts/digital-credentials/verifiable-credentials-w3c.md) from a wallet using the walt.id Wallet API v2. You'll take a presentation request — for example one created with [Verifier2](https://docs.walt.id/community-stack/verifier2/credential-verification/vc-oid4vp.md) — and satisfy it in a single call. [**W3C VC**](https://docs.walt.id/concepts/digital-credentials/verifiable-credentials-w3c.md): The W3C Verifiable Credentials data model, here presented as a JWT (`jwt_vc_json`). [**OID4VP**](https://docs.walt.id/concepts/data-exchange-protocols.md): The protocol used to deliver the presentation from the wallet to the verifier. Credential matching uses DCQL. **Note:** This page uses the **full presentation flow** — one call that matches, signs, and submits. To preview what will be shared, let the user choose between matches, or present a credential that isn't stored in the wallet, see [Custom Flow](https://docs.walt.id/community-stack/wallet2/credential-presenting/custom-flow.md). ## Prerequisites - **Wallet API v2 running** — Follow the [Setup](https://docs.walt.id/community-stack/wallet2/setup.md) guide. The examples use `http://localhost:7005` as the base URL. - **A wallet holding a W3C VC** — [Receive one first](https://docs.walt.id/community-stack/wallet2/credential-receiving/w3c-vc.md); you'll need the wallet's `walletId`. - **A presentation request** — For a full end-to-end run, create one with the [Verifier2 W3C VC guide](https://docs.walt.id/community-stack/verifier2/credential-verification/vc-oid4vp.md). The request is an `openid4vp://` URL. **Note:** **Running via Docker Compose?** The quick-start stack exposes Wallet API v2 on `http://localhost:7006` (Verifier2 on `7004`). Use that base URL instead. --- ## Present the Credential Hand the verifier's request to the wallet. This single call resolves the request, matches a stored W3C VC against the verifier's DCQL query, signs the presentation with the wallet's default key, and submits it. **Option: CURL** **Endpoint:** `POST /wallet/{walletId}/credentials/present` | [API Reference](https://docs.walt.id/community-stack/wallet2/full-api-reference.md) ##### Example Request ```bash curl -X POST http://localhost:7005/wallet/{walletId}/credentials/present \ -H 'Content-Type: application/json' \ -d '{ "requestUrl": "openid4vp://authorize?client_id=x509_san_dns%3Averifier.example.org&request_uri=http%3A%2F%2Flocalhost%3A7004%2Fopenid4vc%2Frequest%2Fabc123" }' ``` **Path Parameters** - **walletId**: _String_ (required) - The wallet holding the credential to present. Returned when you [create a wallet](https://docs.walt.id/community-stack/wallet2/wallets.md); to look up an existing one, list your wallets with `GET /wallet`. **Body Parameters** - **Presentation request** (required) — provide exactly one of: - **requestUrl**: _String_ - The OID4VP authorization request URL from the verifier. - **requestObject**: _Object_ - A pre-fetched authorization request as an inline JSON object. - **keyId**: _String_ (optional) - Key used to sign the presentation. Defaults to the wallet's default key. - **did**: _String_ (optional) - DID for the presenter identity. Defaults to the wallet's default DID. --- ##### Example Response ```json { "transmission_success": true, "verifier_response": { "status": "received", "message": "Presentation received and is being processed." } } ``` **Response Fields** - **transmission_success**: _Boolean_ - Whether the wallet successfully submitted the presentation to the verifier. - **verifier_response**: _Object_ - The raw response body returned by the verifier (for a `direct_post` submission, a status/message acknowledgement as above). - **redirect_to**: _String_ (optional) - Where to send the user next, if the verifier returned a redirect. Omitted otherwise. - **get_url** / **form_post_html**: _String_ (optional) - Present instead for `direct_post.jwt` / form-post response modes. **Note:** `transmission_success: true` means the wallet delivered the presentation — confirm the verification outcome on the verifier side (`GET /verification-session/{sessionId}/info` → `status: SUCCESSFUL`). 🎉 You've presented a W3C VC credential to the verifier. --- ## Doing It Step by Step The full-flow call above matches and submits in one shot. If you're building a wallet UI and need to show the user what will be shared before they consent — or present a credential that isn't stored in the wallet — drive the isolated endpoints yourself. Resolving the request, DCQL matching, and inline presentation are documented in [Custom Flow](https://docs.walt.id/community-stack/wallet2/credential-presenting/custom-flow.md). --- ## Next Steps - **Present other credential types** — [SD-JWT VC](https://docs.walt.id/community-stack/wallet2/credential-presenting/sd-jwt-vc.md) or [mDL](https://docs.walt.id/community-stack/wallet2/credential-presenting/mdl.md). - **Preview and consent** — Use [Custom Flow](https://docs.walt.id/community-stack/wallet2/credential-presenting/custom-flow.md#match-from-the-wallet-stores) to show the user which credentials match before sharing. - **Receive more credentials** — [Credential Receiving](https://docs.walt.id/community-stack/wallet2/credential-receiving/overview.md). -------------------------------------------------------------------------------- PAGE: mDL via OID4VP URL: https://docs.walt.id/community-stack/wallet2/credential-presenting/mdl RAW: https://docs.walt.id/community-stack/wallet2/credential-presenting/mdl.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Presenting mDL Credentials via OID4VP This guide shows you how to present an [ISO 18013-5 mobile driving licence (mDL)](https://docs.walt.id/concepts/digital-credentials/mdoc-mdl-iso.md) from a wallet using the walt.id Wallet API v2. You'll take a presentation request — for example one created with [Verifier2](https://docs.walt.id/community-stack/verifier2/credential-verification/mdl-oid4vp.md) — and satisfy it in a single call. [**mDL / mdoc**](https://docs.walt.id/concepts/digital-credentials/mdoc-mdl-iso.md): The ISO/IEC 18013-5 mobile document format (`mso_mdoc`). [**OID4VP**](https://docs.walt.id/concepts/data-exchange-protocols.md): The protocol used to deliver the presentation from the wallet to the verifier. Credential matching uses DCQL. **Note:** This page uses the **full presentation flow** — one call that matches, signs, and submits. To preview what will be shared, let the user choose between matches, or present a credential that isn't stored in the wallet, see [Custom Flow](https://docs.walt.id/community-stack/wallet2/credential-presenting/custom-flow.md). ## Prerequisites - **Wallet API v2 running** — Follow the [Setup](https://docs.walt.id/community-stack/wallet2/setup.md) guide. The examples use `http://localhost:7005` as the base URL. - **A wallet holding an mDL** — [Receive one first](https://docs.walt.id/community-stack/wallet2/credential-receiving/mdl.md); you'll need the wallet's `walletId`. - **A presentation request** — For a full end-to-end run, create one with the [Verifier2 mDL guide](https://docs.walt.id/community-stack/verifier2/credential-verification/mdl-oid4vp.md). The request is an `openid4vp://` URL. **Note:** **Running via Docker Compose?** The quick-start stack exposes Wallet API v2 on `http://localhost:7006` (Verifier2 on `7004`). Use that base URL instead. --- ## Present the Credential Hand the verifier's request to the wallet. This single call resolves the request, matches a stored mDL against the verifier's DCQL query, signs the presentation with a key, and submits it. **Option: CURL** **Endpoint:** `POST /wallet/{walletId}/credentials/present` | [API Reference](https://docs.walt.id/community-stack/wallet2/full-api-reference.md) ##### Example Request ```bash curl -X POST http://localhost:7005/wallet/{walletId}/credentials/present \ -H 'Content-Type: application/json' \ -d '{ "requestUrl": "openid4vp://authorize?client_id=x509_san_dns%3Averifier.example.org&request_uri=http%3A%2F%2Flocalhost%3A7004%2Fopenid4vc%2Frequest%2Fabc123" }' ``` **Path Parameters** - **walletId**: _String_ (required) - The wallet holding the credential to present. Returned when you [create a wallet](https://docs.walt.id/community-stack/wallet2/wallets.md); to look up an existing one, list your wallets with `GET /wallet`. **Body Parameters** - **Presentation request** (required) — provide exactly one of: - **requestUrl**: _String_ - The OID4VP authorization request URL from the verifier. - **requestObject**: _Object_ - A pre-fetched authorization request as an inline JSON object. - **keyId**: _String_ (optional) - Key used to sign the presentation. Defaults to the wallet's default key. An mdoc is bound to a device key, so no DID is involved. --- ##### Example Response ```json { "transmission_success": true, "verifier_response": { "status": "received", "message": "Presentation received and is being processed." } } ``` **Response Fields** - **transmission_success**: _Boolean_ - Whether the wallet successfully submitted the presentation to the verifier. - **verifier_response**: _Object_ - The raw response body returned by the verifier (for a `direct_post` submission, a status/message acknowledgement as above). - **redirect_to**: _String_ (optional) - Where to send the user next, if the verifier returned a redirect. Omitted otherwise. - **get_url** / **form_post_html**: _String_ (optional) - Present instead for `direct_post.jwt` / form-post response modes. **Note:** `transmission_success: true` means the wallet delivered the presentation — confirm the verification outcome on the verifier side (`GET /verification-session/{sessionId}/info` → `status: SUCCESSFUL`). 🎉 You've presented an mDL credential to the verifier. Only the data elements the verifier requested were disclosed — see [Selective Disclosure](https://docs.walt.id/community-stack/wallet2/credential-presenting/overview.md#selective-disclosure) for how the verifier's DCQL `claims` drive what's shared. --- ## Doing It Step by Step The full-flow call above matches and submits in one shot. If you're building a wallet UI and need to show the user what will be shared before they consent — or present a credential that isn't stored in the wallet — drive the isolated endpoints yourself. Resolving the request, DCQL matching, and inline presentation are documented in [Custom Flow](https://docs.walt.id/community-stack/wallet2/credential-presenting/custom-flow.md). --- ## Next Steps - **Present other credential types** — [SD-JWT VC](https://docs.walt.id/community-stack/wallet2/credential-presenting/sd-jwt-vc.md) or [W3C VC](https://docs.walt.id/community-stack/wallet2/credential-presenting/w3c-vc.md). - **Preview and consent** — Use [Custom Flow](https://docs.walt.id/community-stack/wallet2/credential-presenting/custom-flow.md#match-from-the-wallet-stores) to show the user which credentials match before sharing. - **Receive more credentials** — [Credential Receiving](https://docs.walt.id/community-stack/wallet2/credential-receiving/overview.md). -------------------------------------------------------------------------------- PAGE: Custom Flow URL: https://docs.walt.id/community-stack/wallet2/credential-presenting/custom-flow RAW: https://docs.walt.id/community-stack/wallet2/credential-presenting/custom-flow.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Custom Flow The [full-flow endpoint](https://docs.walt.id/community-stack/wallet2/credential-presenting/overview.md) matches and submits a presentation in a single call. When you're building a wallet UI and need to show the user what will be shared, let them choose between matching credentials, or present a credential that isn't stored in the wallet, drive the **isolated endpoints** shown on this page yourself. All endpoints on this page are rooted at `/wallet/{walletId}/credentials/present`. The examples use `http://localhost:7005` as the base URL. ## When to Use the Isolated Flow | You want to… | Use | |---|---| | Present in one call | [Full flow](https://docs.walt.id/community-stack/wallet2/credential-presenting/overview.md) | | Read what the verifier is asking for | [Resolve the request](#resolve-the-request) | | Get the verifier's DCQL query | [Where the DCQL query comes from](#where-the-dcql-query-comes-from) | | Show a consent preview of what matches | [Match from the wallet stores](#match-from-the-wallet-stores) | | Present a credential not held by the wallet | [Present inline credentials](#present-inline-credentials) | --- ## Resolve the Request Parse the verifier's authorization request to read who is asking and what they want. **Option: CURL** **Endpoint:** `POST /wallet/{walletId}/credentials/present/resolve-request` | [API Reference](https://docs.walt.id/community-stack/wallet2/full-api-reference.md) ##### Example Request ```bash curl -X POST http://localhost:7005/wallet/{walletId}/credentials/present/resolve-request \ -H 'Content-Type: application/json' \ -d '{ "requestUrl": "openid4vp://authorize?client_id=x509_san_dns%3Averifier.example.org&request_uri=http%3A%2F%2Flocalhost%3A7004%2Fopenid4vc%2Frequest%2Fabc123" }' ``` **Path Parameters** - **walletId**: _String_ (required) - The wallet handling the presentation. See [how to obtain it](https://docs.walt.id/community-stack/wallet2/wallets.md). **Body Parameters** - **Presentation request** (required) — provide exactly one of: - **requestUrl**: _String_ - The OID4VP authorization request URL. - **requestObject**: _Object_ - A pre-fetched authorization request as an inline JSON object. --- ##### Example Response ```json { "nonce": "n-0S6_WzA2Mj", "clientId": "x509_san_dns:verifier.example.org", "responseUri": "http://localhost:7004/openid4vc/response", "hasRequestUri": true } ``` **Response Fields** - **nonce**: _String_ - The verifier's nonce, which the presentation must be bound to. - **clientId**: _String_ - The verifier's client identifier. - **responseUri**: _String_ - Where the presentation will be submitted. - **hasRequestUri**: _Boolean_ - Whether the request was fetched from a `request_uri` (vs supplied inline). **warning:** `resolve-request` returns the request's metadata but **not** the DCQL query itself. To match credentials in the next step you need the query — see [Where the DCQL Query Comes From](#where-the-dcql-query-comes-from) below. --- ## Where the DCQL Query Comes From The next step matches credentials against a **DCQL query** — the machine-readable statement of what the verifier wants. The DCQL query travels inside the verifier's authorization request as the `dcql_query` parameter, and the wallet reads it out of that request. Given only the `openid4vp://` request URL, extract the query from the request object: - **Inline request** — the query is the `dcql_query` parameter in the URL itself. Read it directly from the query string. - **`request_uri` request** (the common case) — the URL carries a `request_uri` pointing to the request object rather than the query. Fetch that URL and read the `dcql_query` claim out of what it returns. - By default, Verifier2 returns a **plain JSON object** (`Content-Type: application/json`) — read `dcql_query` directly from it. This is what you get unless the verification session was created with `signed_request: true`. - If the session set `signed_request: true`, the request object is instead a **signed JWT** (`Content-Type: application/oauth-authz-req+jwt`, fetched via `POST`) — `dcql_query` is in its payload, so decode the JWT payload to read it. **Note:** The wallet has no endpoint that hands you the query directly — `resolve-request` deliberately returns only the request metadata (nonce, client, response URI), so you extract `dcql_query` from the request object yourself as above. --- ## Match from the Wallet Stores Run the verifier's DCQL query (obtained [above](#where-the-dcql-query-comes-from)) against the wallet's stored credentials to see what would satisfy it — without submitting anything. This is the call to enable a **consent preview**: show the user which credentials match before they approve. **Option: CURL** **Endpoint:** `POST /wallet/{walletId}/credentials/present/match-credentials-from-store` | [API Reference](https://docs.walt.id/community-stack/wallet2/full-api-reference.md) ##### Example Request ```bash curl -X POST http://localhost:7005/wallet/{walletId}/credentials/present/match-credentials-from-store \ -H 'Content-Type: application/json' \ -d '{ "dcqlQuery": { "credentials": [ { "id": "identity_credential", "format": "dc+sd-jwt", "meta": { "vct_values": ["https://issuer.example.com/identity_credential"] }, "claims": [ { "path": ["given_name"] }, { "path": ["family_name"] } ] } ] } }' ``` **Path Parameters** - **walletId**: _String_ (required) - The wallet whose stored credentials are matched. **Body Parameters** - **dcqlQuery**: _Object_ (required) - The verifier's DCQL query — see [Where the DCQL Query Comes From](#where-the-dcql-query-comes-from) for how to obtain it, and [DCQL query format](#dcql-query-format) for its structure. --- ##### Example Response ```json { "matchedQueryIds": ["credential_1"], "matchCount": 2, "matchedCredentialIds": { "credential_1": ["0", "1"] } } ``` **Response Fields** - **matchedQueryIds**: _String[]_ - The DCQL credential-query IDs that were satisfied. - **matchCount**: _Number_ - Total number of matching credentials. - **matchedCredentialIds**: _Object_ - Map of query ID → identifiers of the matching credentials. **warning:** The values in `matchedCredentialIds` are currently the credential's **positional index** within the wallet's credential stores (`"0"`, `"1"`, …), **not** the stored credential ID returned by `GET /wallet/{walletId}/credentials`. They can't yet be correlated back to a specific stored credential. (The inline [Match Inline Credentials](#match-inline-credentials) endpoint does echo back the `id` you supply.) After the user consents, complete the presentation with the [full-flow endpoint](https://docs.walt.id/community-stack/wallet2/credential-presenting/overview.md). ## Match Inline Credentials Run a DCQL query against credentials you supply in the request, rather than the wallet's credential stores. Useful for testing a query, or matching credentials held outside the wallet. **Option: CURL** **Endpoint:** `POST /wallet/{walletId}/credentials/present/match-credentials` | [API Reference](https://docs.walt.id/community-stack/wallet2/full-api-reference.md) ##### Example Request ```bash curl -X POST http://localhost:7005/wallet/{walletId}/credentials/present/match-credentials \ -H 'Content-Type: application/json' \ -d '{ "dcqlQuery": { "credentials": [ { "id": "credential_1", "format": "dc+sd-jwt", "meta": { "vct_values": ["https://issuer.example.com/identity_credential"] }, "claims": [ { "path": ["given_name"] } ] } ] }, "credentials": [ { "id": "inline-1", "credential": { "type": "vc-sd_jwt", "format": "dc+sd-jwt", "signed": "eyJ...~WyJ...~", "credentialData": { "vct": "https://issuer.example.com/identity_credential", "given_name": "John" } } } ] }' ``` **Body Parameters** - **dcqlQuery**: _Object_ (required) - The DCQL query to match against. - **credentials**: _Array_ (required) - The credentials to match. Each entry has an `id` (your own label, echoed back in the result) and a `credential`: the credential in the wallet's own JSON representation — **not** a raw credential string. Its `type` field marks the format (`vc-sd_jwt`, `vc-w3c_2`, or `mso_mdoc`) and the rest of the object holds the credential data (as in the example above). The simplest way to get one in the right shape is to copy the `credential` object from a [`GET /wallet/{walletId}/credentials/{id}`](https://docs.walt.id/community-stack/wallet2/credential-management.md) response. --- ##### Example Response Returns the same `MatchCredentialsResult` shape as [Match from the wallet stores](#match-from-the-wallet-stores). --- ## Present Inline Credentials Present a credential that isn't stored in the wallet — you supply it directly in the request. The wallet resolves the request, builds and signs the presentation from the inline credential, and submits it. **Option: CURL** **Endpoint:** `POST /wallet/{walletId}/credentials/present/isolated` | [API Reference](https://docs.walt.id/community-stack/wallet2/full-api-reference.md) ##### Example Request ```bash curl -X POST http://localhost:7005/wallet/{walletId}/credentials/present/isolated \ -H 'Content-Type: application/json' \ -d '{ "requestUrl": "openid4vp://authorize?client_id=verifier2&request_uri=http%3A%2F%2Flocalhost%3A7003%2Fverification-session%2Fabc123%2Frequest", "credentials": [ { "id": "inline-1", "credential": { "type": "vc-sd_jwt", "format": "dc+sd-jwt", "signed": "eyJ...~WyJ...~", "credentialData": { "vct": "https://issuer.example.com/identity_credential", "given_name": "John" } } } ], "keyId": "my-holder-key" }' ``` **Body Parameters** - **Presentation request** (required) — provide exactly one of: - **requestUrl**: _String_ - The OID4VP authorization request URL. - **requestObject**: _Object_ - A pre-fetched authorization request as an inline JSON object. - **credentials**: _Array_ (required) - The credentials to present. Each entry has an `id` and a `credential`: the credential in the wallet's own JSON representation (the same shape as in [Match Inline Credentials](#match-inline-credentials) above), **not** a raw credential string. The simplest way to get one is to copy the `credential` object from a [`GET /wallet/{walletId}/credentials/{id}`](https://docs.walt.id/community-stack/wallet2/credential-management.md) response. - **keyId**: _String_ (optional) - Key used to sign the presentation. Must be the holder key the inline credential is bound to. Defaults to the wallet's default key. - **did**: _String_ (optional) - DID for the presenter identity. Defaults to the wallet's default DID. --- ##### Example Response Returns the same shape as the [full presentation flow](https://docs.walt.id/community-stack/wallet2/credential-presenting/overview.md) — `transmission_success`, `verifier_response`, and optionally `redirect_to`. --- ## Endpoint Reference | Endpoint | Method | Description | |----------|--------|-------------| | `/wallet/{walletId}/credentials/present` | POST | Full presentation flow (match from stores, sign, submit) | | `/wallet/{walletId}/credentials/present/isolated` | POST | Present inline credentials | | `/wallet/{walletId}/credentials/present/resolve-request` | POST | Parse a VP authorization request | | `/wallet/{walletId}/credentials/present/match-credentials-from-store` | POST | DCQL-match the wallet's stored credentials | | `/wallet/{walletId}/credentials/present/match-credentials` | POST | DCQL-match inline credentials | -------------------------------------------------------------------------------- PAGE: Credential Management URL: https://docs.walt.id/community-stack/wallet2/credential-management RAW: https://docs.walt.id/community-stack/wallet2/credential-management.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Credential Management Once a wallet has [received](https://docs.walt.id/community-stack/wallet2/credential-receiving/overview.md) credentials, you can list them, inspect a single credential in full, import a raw credential directly, and delete credentials. The examples use `http://localhost:7005` as the base URL. --- ## List Credentials List the metadata of every credential in the wallet. This is lightweight — it does **not** include the raw credential data. **Option: CURL** **Endpoint:** `GET /wallet/{walletId}/credentials` | [API Reference](https://docs.walt.id/community-stack/wallet2/full-api-reference.md) ##### Example Request ```bash curl http://localhost:7005/wallet/{walletId}/credentials ``` **Path Parameters** - **walletId**: _String_ (required) - The wallet whose credentials you want to list. See [Managing Wallets](https://docs.walt.id/community-stack/wallet2/wallets.md). --- ##### Example Response ```json [ { "id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "format": "dc+sd-jwt", "issuer": "http://localhost:7005/openid4vci", "subject": "did:key:z6Mkfriq1MqLBoPWecGoDLjguo1sB9brj6wT3qZ5BxkKpuP6", "label": null, "addedAt": "2026-07-08T10:15:30Z" } ] ``` **Response Fields** - **id**: _String_ - The credential's ID, used to fetch or delete it. - **format**: _String_ - Credential format (`dc+sd-jwt`, `jwt_vc_json`, `mso_mdoc`). - **issuer** / **subject**: _String_ - Issuer and holder identifiers, when available. - **label**: _String_ - Optional label set on import. - **addedAt**: _String_ - ISO-8601 timestamp of when the credential was stored. --- ## Get a Credential Fetch a single credential, including its parsed contents and raw data. **Option: CURL** **Endpoint:** `GET /wallet/{walletId}/credentials/{credentialId}` | [API Reference](https://docs.walt.id/community-stack/wallet2/full-api-reference.md) ##### Example Request ```bash curl http://localhost:7005/wallet/{walletId}/credentials/{credentialId} ``` **Path Parameters** - **walletId**: _String_ (required) - The wallet holding the credential. - **credentialId**: _String_ (required) - The credential to fetch. Obtain it from [list credentials](#list-credentials). --- ##### Example Response ```json { "id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "credential": { "format": "dc+sd-jwt", "...": "parsed credential contents" }, "label": null, "addedAt": "2026-07-08T10:15:30Z" } ``` **Response Fields** - **id**: _String_ - The credential's ID. - **credential**: _Object_ - The parsed credential, including its raw representation. - **label** / **addedAt**: The optional label and storage timestamp. --- ## Import a Credential Store a raw credential directly — for example one obtained outside the wallet, or fetched via the [Custom Flow](https://docs.walt.id/community-stack/wallet2/credential-receiving/custom-flow.md#fetch-credential) fetch step (which returns raw credentials without storing them). **Option: CURL** **Endpoint:** `POST /wallet/{walletId}/credentials/import` | [API Reference](https://docs.walt.id/community-stack/wallet2/full-api-reference.md) ##### Example Request ```bash curl -X POST http://localhost:7005/wallet/{walletId}/credentials/import \ -H 'Content-Type: application/json' \ -d '{ "rawCredential": "eyJhbGciOiJFUzI1NiIsInR5cCI6InZjK3NkLWp3dCJ9...", "label": "My identity credential" }' ``` **Path Parameters** - **walletId**: _String_ (required) - The wallet to import into. **Body Parameters** - **rawCredential**: _String_ (required) - The raw credential (JWT, SD-JWT, or base64url mdoc). The wallet detects the format automatically. - **label**: _String_ (optional) - A human-readable label to store alongside the credential. --- ##### Example Response ```json { "id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "credential": { "format": "dc+sd-jwt", "...": "parsed credential contents" }, "label": "My identity credential", "addedAt": "2026-07-08T10:15:30Z" } ``` --- ## Delete a Credential **Option: CURL** **Endpoint:** `DELETE /wallet/{walletId}/credentials/{credentialId}` | [API Reference](https://docs.walt.id/community-stack/wallet2/full-api-reference.md) ##### Example Request ```bash curl -X DELETE http://localhost:7005/wallet/{walletId}/credentials/{credentialId} ``` **Path Parameters** - **walletId**: _String_ (required) - The wallet holding the credential. - **credentialId**: _String_ (required) - The credential to delete. --- ##### Example Response `204 No Content` on success, or `404 Not Found` if the credential doesn't exist. --- ## Next Steps - [Present a credential](https://docs.walt.id/community-stack/wallet2/credential-presenting/sd-jwt-vc.md) to a verifier. - [Receive more credentials](https://docs.walt.id/community-stack/wallet2/credential-receiving/overview.md). -------------------------------------------------------------------------------- PAGE: Feature Manager URL: https://docs.walt.id/community-stack/wallet2/configurations/feature-manager RAW: https://docs.walt.id/community-stack/wallet2/configurations/feature-manager.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Feature Manager Use the feature manager to control which Wallet API v2 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 always enabled and provide critical functionality expected to be available by default. For the Wallet API v2, the core `wallet-service` is a base feature. ### Optional Features Optional features can be enabled or disabled based on your requirements. 1. **Enabled by default**: work without additional configuration. 2. **Disabled by default**: must be explicitly enabled in `_features.conf`, and may require their own configuration file. **Note:** Each feature may have zero, one, or multiple configuration files associated with it. ## How the Feature Manager Works 1. **Initialisation with Feature Catalogs**: the service is passed its feature catalog on startup. 2. **Determination of Active Features**: the initialiser resolves which features are explicitly enabled or disabled, and the default state of the rest. 3. **Loading Configurations**: only the configurations for base features and explicitly enabled optional features are loaded. **warning:** The service must be **restarted** for feature-flag changes to take effect — there is no hot reload. ## Feature Configuration Configure features by editing `_features.conf`, as explained in [Features](https://docs.walt.id/community-stack/wallet2/configurations/config-files/features.md). -------------------------------------------------------------------------------- PAGE: Features URL: https://docs.walt.id/community-stack/wallet2/configurations/config-files/features RAW: https://docs.walt.id/community-stack/wallet2/configurations/config-files/features.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Features With `_features.conf` you can control which Wallet API v2 features are enabled. They are managed via the [feature manager](https://docs.walt.id/community-stack/wallet2/configurations/feature-manager.md). ## Available Features ### Base Features Base features are always enabled and do not need to be listed in `_features.conf`. - `wallet-service` — Core wallet service (wallet, key, DID, and credential APIs). Loads [wallet-service.conf](https://docs.walt.id/community-stack/wallet2/configurations/config-files/wallet-service.md). ### Optional Features Optional features are opt-in — each is active only when listed in `enabledFeatures`. The **shipped `_features.conf` enables `wallet2-persistence`** (SQLite); `auth` and `dev-mode` are left disabled. - `wallet2-persistence` — SQL persistence (SQLite or PostgreSQL) for wallets, keys, DIDs, and credentials. Loads [wallet2-persistence.conf](https://docs.walt.id/community-stack/wallet2/configurations/config-files/persistence.md). **Enabled in the shipped config.** Remove it to keep all data in memory (lost on restart). - `auth` — Email/password authentication and account-to-wallet ownership. Loads [auth.conf](https://docs.walt.id/community-stack/wallet2/configurations/config-files/auth.md). When disabled (the default), all wallets are accessible without a token. **Not functional in the current release — enabling it prevents the service from starting;** see [Authentication](https://docs.walt.id/community-stack/wallet2/configurations/config-files/auth.md). - `dev-mode` — Development mode. Loads [dev-mode.conf](https://docs.walt.id/community-stack/wallet2/configurations/config-files/dev-mode.md). **Currently a no-op — nothing in the service reads it yet** (it does not disable authentication); see [Dev Mode](https://docs.walt.id/community-stack/wallet2/configurations/config-files/dev-mode.md). The standard infrastructure features (web server, OpenAPI/Swagger, health checks) are provided by the framework and enabled by default; the web server is configured via [web.conf](https://docs.walt.id/community-stack/wallet2/configurations/config-files/web.md). ## Example Setup ```editorconfig[_features.conf] enabledFeatures = [ wallet2-persistence # auth # dev-mode ] disabledFeatures = [ # ... ] ``` **warning:** Restart the service after editing `_features.conf` — feature changes are applied at startup only. -------------------------------------------------------------------------------- PAGE: Wallet Service URL: https://docs.walt.id/community-stack/wallet2/configurations/config-files/wallet-service RAW: https://docs.walt.id/community-stack/wallet2/configurations/config-files/wallet-service.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Wallet Service Configuration The `wallet-service.conf` file configures the core wallet service. It is loaded by the `wallet-service` base feature. ## File Location ``` waltid-services/waltid-wallet-api2/config/wallet-service.conf ``` ## Configuration Options | Property | Type | Required | Default | Description | |----------|------|----------|---------|-------------| | `publicBaseUrl` | String (URL) | Yes | – | Public-facing base URL of this wallet service. | ### publicBaseUrl The public-facing base URL of the wallet, used when building response and redirect URLs (e.g. OpenID4VCI/VP redirect URIs). It is the URL end users reach the wallet at, which is **not necessarily the same as the API's bind port** ([`webPort`](https://docs.walt.id/community-stack/wallet2/configurations/config-files/web.md)) — typically it points at the user-facing wallet frontend. ```hocon publicBaseUrl = "http://localhost:4000" ``` The value can be overridden with the `WALLET_PUBLIC_BASE_URL` environment variable. **Note:** The shipped config sets this to `http://localhost:4000`. A dedicated Wallet API v2 frontend is planned; until then this value is largely a placeholder, as the current API does not yet build user-facing redirect flows from it. Set it to the URL your wallet is publicly reachable at. ## Example Configuration ```hocon # wallet-service.conf publicBaseUrl = "http://localhost:4000" ``` For production, set it to your public HTTPS URL: ```hocon publicBaseUrl = "https://wallet.example.com" ``` -------------------------------------------------------------------------------- PAGE: Persistence URL: https://docs.walt.id/community-stack/wallet2/configurations/config-files/persistence RAW: https://docs.walt.id/community-stack/wallet2/configurations/config-files/persistence.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Persistence Configuration The shipped configuration **enables `wallet2-persistence`**, so wallets, keys, DIDs, and credentials are persisted to a SQL database (SQLite by default) and survive restarts. This page covers the `wallet2-persistence.conf` settings. If you remove `wallet2-persistence` from [`_features.conf`](https://docs.walt.id/community-stack/wallet2/configurations/config-files/features.md), the Wallet API v2 falls back to keeping all data **in memory**, which is lost on restart. ## File Location ``` waltid-services/waltid-wallet-api2/config/wallet2-persistence.conf ``` ## Configuration Options | Property | Type | Default | Description | |----------|------|---------|-------------| | `jdbcUrl` | String | `jdbc:sqlite:wallet2.db` | JDBC connection URL. | | `driverClassName` | String | `org.sqlite.JDBC` | JDBC driver class. | | `username` | String | `""` | Database username (PostgreSQL). | | `password` | String | `""` | Database password (PostgreSQL). | | `maximumPoolSize` | Int | `5` | Maximum connection pool size. | | `minimumIdle` | Int | `1` | Minimum idle connections. | ## SQLite ```hocon # wallet2-persistence.conf jdbcUrl = "jdbc:sqlite:data/wallet2.db" driverClassName = "org.sqlite.JDBC" ``` ## PostgreSQL **Error:** PostgreSQL is not yet supported. ```hocon # wallet2-persistence.conf jdbcUrl = "jdbc:postgresql://localhost:5432/wallet2" driverClassName = "org.postgresql.Driver" username = "wallet" password = "secret" maximumPoolSize = 10 minimumIdle = 1 ``` **Note:** Enabling persistence requires listing `wallet2-persistence` in [`_features.conf`](https://docs.walt.id/community-stack/wallet2/configurations/config-files/features.md), then restarting the service. Set `jdbcUrl` (and the other options) directly in `wallet2-persistence.conf`. When running via Docker, mount your edited config directory into the container so these values take effect. -------------------------------------------------------------------------------- PAGE: Authentication URL: https://docs.walt.id/community-stack/wallet2/configurations/config-files/auth RAW: https://docs.walt.id/community-stack/wallet2/configurations/config-files/auth.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Authentication Configuration By default the Wallet API v2 runs without authentication — every wallet is accessible without a token. The `auth` [optional feature](https://docs.walt.id/community-stack/wallet2/configurations/config-files/features.md) is intended to require email/password login and enforce that an account only accesses its own wallets, configured in `auth.conf`. **Error:** **The `auth` feature is not functional in the current release.** Enabling it prevents the service from starting. The configuration and endpoints below are documented for reference and for when the feature is completed. Leave it out of `_features.conf` (the default). ## File Location ``` waltid-services/waltid-wallet-api2/config/auth.conf ``` ## Configuration Options | Property | Type | Required | Default | Description | |----------|------|----------|---------|-------------| | `jwtSecret` | String | Yes | – | The secret for signing session tokens. | | `tokenExpirySeconds` | Long | No | `86400` | Session token lifetime in seconds. | ```hocon # auth.conf jwtSecret = "change-me-in-production" tokenExpirySeconds = 86400 ``` **Note:** Always use a strong, randomly generated `jwtSecret` in production, and never commit it to version control. --- ## Authentication Endpoints When `auth` is enabled, the following endpoints are available. After logging in, pass the returned token as `Authorization: Bearer {token}` on every wallet request. | Endpoint | Method | Description | |----------|--------|-------------| | `/auth/register` | POST | Register a new account | | `/auth/emailpass` | POST | Log in with email + password | | `/auth/logout` | POST | Invalidate the current token | | `/auth/account` | GET | Get the authenticated account (id, email, wallet IDs) | | `/auth/account/wallets` | GET | List wallets owned by the account | | `/auth/account/wallets/{walletId}` | POST | Link a wallet to the account | ### Register **Option: CURL** **Endpoint:** `POST /auth/register` ##### Example Request ```bash curl -X POST http://localhost:7005/auth/register \ -H 'Content-Type: application/json' \ -d '{ "email": "user@example.com", "password": "secret123" }' ``` **Body Parameters** - **email**: _String_ (required) - The account email. - **password**: _String_ (required) - The account password. --- ##### Example Response ```json { "accountId": "b2c3d4e5-..." } ``` ### Log In **Option: CURL** **Endpoint:** `POST /auth/emailpass` ##### Example Request ```bash curl -X POST http://localhost:7005/auth/emailpass \ -H 'Content-Type: application/json' \ -d '{ "email": "user@example.com", "password": "secret123" }' ``` **Body Parameters** - **email**: _String_ (required) - The account email. - **password**: _String_ (required) - The account password. --- ##### Example Response The response is a session object containing the bearer token. Use the token on subsequent requests: ```bash curl http://localhost:7005/wallet \ -H 'Authorization: Bearer {token}' ``` **Note:** New wallets created while authenticated are automatically linked to your account, so `GET /wallet` and `GET /auth/account/wallets` return only your own wallets. -------------------------------------------------------------------------------- PAGE: Dev Mode URL: https://docs.walt.id/community-stack/wallet2/configurations/config-files/dev-mode RAW: https://docs.walt.id/community-stack/wallet2/configurations/config-files/dev-mode.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Dev Mode Configuration Dev mode is an [optional feature](https://docs.walt.id/community-stack/wallet2/configurations/config-files/features.md) intended for local development. Enable it by listing `dev-mode` in `_features.conf`; configure it in `dev-mode.conf`. **Error:** **Dev mode currently has no effect in the Wallet API v2.** ## File Location ``` waltid-services/waltid-wallet-api2/config/dev-mode.conf ``` ## Configuration Options | Property | Type | Default | Description | |----------|------|---------|-------------| | `devMode` | Boolean | `false` | Reserved for development conveniences. Currently unused — see the note above. | ```hocon # dev-mode.conf devMode = true ``` **Note:** Dev mode is intended for local development only. Do not enable it in production. -------------------------------------------------------------------------------- PAGE: Web URL: https://docs.walt.id/community-stack/wallet2/configurations/config-files/web RAW: https://docs.walt.id/community-stack/wallet2/configurations/config-files/web.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Web Configuration The `web.conf` file controls the host and port the Wallet API v2 web server binds to. ## File Location ``` waltid-services/waltid-wallet-api2/config/web.conf ``` ## Configuration Options | Property | Type | Default | Description | |----------|------|---------|-------------| | `webHost` | String | `0.0.0.0` | Network interface to bind to. | | `webPort` | Int | – | Port the service listens on. | ```hocon # web.conf webHost = "0.0.0.0" webPort = 7005 ``` `webPort` is the port the **API** itself listens on — the one you send requests to and open Swagger on. The shipped `web.conf` binds to `7005`, so run the standalone container with `-p 7005:7005`. (Under [Docker Compose](https://docs.walt.id/community-stack/home.md#quick-start) the port is set via the `WALLET_API2_PORT` environment variable, which is why the wallet is reachable on `7006` there.) **Note:** This is separate from [`publicBaseUrl`](https://docs.walt.id/community-stack/wallet2/configurations/config-files/wallet-service.md) in `wallet-service.conf`, which is the wallet's public/front-end-facing base URL and need not use the same port. -------------------------------------------------------------------------------- PAGE: Migration from Wallet API v1 URL: https://docs.walt.id/community-stack/wallet2/migration RAW: https://docs.walt.id/community-stack/wallet2/migration.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Migration from Wallet API v1 The `waltid-wallet-migration` tool migrates data from the old `waltid-wallet-api` (v1) database schema to the new `waltid-wallet-api2` schema. This guide covers the migration process, what gets migrated, and how to run it. **warning:** **Important:** Always back up your v1 database before running the migration. The migration tool is designed to be idempotent (running it twice won't duplicate data), but having a backup is essential. --- ## What Gets Migrated The migration tool transfers the following data from v1 to v2: | v1 Table | v2 Table(s) | Description | |----------|-------------|-------------| | `wallets` | `wallet2_wallets` + store registration tables | Wallet records with auto-created stores | | `wallet_keys` | `wallet2_keys` | Keys (re-serialized with type inference) | | `credentials` | `wallet2_credentials` | Credentials (re-parsed into the v2 credential format) | | `wallet_dids` | `wallet2_dids` | DIDs with documents | | `account_wallet_mapping` | `wallet2_account_wallets` | Account-to-wallet ownership | ### Store Creation For each migrated wallet, the tool automatically creates: - One key store: `wallet-{id}-keys` - One credential store: `wallet-{id}-creds` - One DID store: `wallet-{id}-dids` These deterministic IDs ensure the migration is idempotent. --- ## Prerequisites 1. **Source database**: Your existing v1 wallet database (SQLite or PostgreSQL) 2. **Target database**: A new or empty v2 database (SQLite or PostgreSQL) 3. **Java 17+**: Required to run the migration tool --- ## Running the Migration ### Using Gradle From the `waltid-identity` repository root: **SQLite to SQLite:** ```bash ./gradlew :waltid-services:waltid-wallet-migration:run \ --args="--source jdbc:sqlite:/path/to/old/wallet.db --target jdbc:sqlite:/path/to/new/wallet2.db" ``` **PostgreSQL to PostgreSQL:** ```bash ./gradlew :waltid-services:waltid-wallet-migration:run \ --args="--source 'jdbc:postgresql://localhost:5432/walletv1?user=wallet&password=secret' --target 'jdbc:postgresql://localhost:5432/walletv2?user=wallet&password=secret'" ``` **Cross-database (SQLite to PostgreSQL):** ```bash ./gradlew :waltid-services:waltid-wallet-migration:run \ --args="--source jdbc:sqlite:/path/to/wallet.db --target 'jdbc:postgresql://localhost:5432/walletv2?user=wallet&password=secret'" ``` ### Dry Run Mode Add `--dry-run` to preview what would be migrated without writing anything: ```bash ./gradlew :waltid-services:waltid-wallet-migration:run \ --args="--source jdbc:sqlite:/path/to/wallet.db --target jdbc:sqlite:/path/to/wallet2.db --dry-run" ``` --- ## Command Line Arguments | Argument | Required | Description | |----------|----------|-------------| | `--source` | Yes | JDBC URL for the v1 source database | | `--target` | Yes | JDBC URL for the v2 target database | | `--dry-run` | No | Preview migration without writing data | --- ## Migration Output The migration tool logs progress and provides a summary at the end: ``` INFO - Source: jdbc:sqlite:/path/to/wallet.db INFO - Target: jdbc:sqlite:/path/to/wallet2.db INFO - Found 5 wallets to migrate INFO - INFO - ═══════════════════════════════════════════ INFO - Migration complete INFO - Wallets: 5 INFO - Keys: 12 INFO - Credentials: 28 INFO - DIDs: 5 INFO - Account mappings: 5 INFO - ═══════════════════════════════════════════ ``` If any items fail to migrate, warnings are logged with details: ``` WARN - Failed to migrate credential abc123 for wallet xyz789: Invalid credential format ``` --- ## Post-Migration Steps ### 1. Verify the Migration After migration, verify the data was transferred correctly: Point `wallet2-persistence.conf` at the migrated database (e.g. `jdbcUrl = "jdbc:sqlite:/data/wallet2.db"`), then start wallet-api2 with your config directory and the database mounted: ```bash docker run \ -p 7005:7005 \ -v /path/to/config:/waltid-wallet-api2/config \ -v /path/to/data:/data \ waltid/wallet-api2 # List wallets curl http://localhost:7005/wallet ``` ### 2. Update Your Configuration Update your deployment to use the new wallet-api2 service and database: **wallet2-persistence.conf:** ```hocon # wallet2-persistence.conf jdbcUrl = "jdbc:sqlite:/data/wallet2.db" driverClassName = "org.sqlite.JDBC" ``` Or for PostgreSQL: ```hocon # wallet2-persistence.conf jdbcUrl = "jdbc:postgresql://localhost:5432/walletv2" driverClassName = "org.postgresql.Driver" username = "wallet" password = "secret" maximumPoolSize = 10 ``` See the [Persistence configuration reference](https://docs.walt.id/community-stack/wallet2/configurations/config-files/persistence.md) for all options. ### 3. Update API Calls The v2 API has some differences from v1. Key changes: | v1 Endpoint | v2 Endpoint | Notes | |-------------|-------------|-------| | `/api/wallet/{walletId}/...` | `/wallet/{walletId}/...` | Simplified path | | Presentation Exchange | DCQL | Different query format for OID4VP | | `/api/wallet/{walletId}/credentials/usePresentationRequest` | `/wallet/{walletId}/credentials/present` | New presentation endpoint | --- ## Troubleshooting ### "Key store not found" errors The migration creates stores with deterministic IDs. If you see this error, ensure the target database is empty or the migration completed successfully. ### Credential parsing failures Some v1 credentials may use formats not fully supported in v2. Check the warning logs for specific credential IDs and manually verify/re-import if needed. ### Database connection issues Ensure the JDBC URLs are correct and the databases are accessible: - SQLite: File path must exist and be writable - PostgreSQL: User must have CREATE TABLE permissions --- ## Rollback If you need to rollback: 1. Stop the wallet-api2 service 2. Restore your v1 database from backup 3. Start the wallet-api (v1) service pointing to the restored database The migration does not modify the source database, so your v1 data remains intact. -------------------------------------------------------------------------------- PAGE: Full API Reference URL: https://docs.walt.id/community-stack/wallet2/full-api-reference RAW: https://docs.walt.id/community-stack/wallet2/full-api-reference.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Full API Reference The Wallet API v2 serves an interactive Swagger UI covering every endpoint — wallet, key, DID, and credential management, plus receiving and presenting. When the service is running, open it at: ``` http://localhost:7005/swagger ``` The raw OpenAPI document is available at `http://localhost:7005/api.json`.