# walt.id Documentation — Full Text > Complete documentation of walt.id's digital identity infrastructure in one file: Part 1 covers stack-agnostic concepts, Part 2 the open-source Community Stack, Part 3 the commercial Enterprise Stack. The two stacks are distinct products with different APIs — always note which part a page belongs to. Generated: 2026-07-20 · Community Stack version: 0.21.0 · Enterprise Stack version: 0.21.0 Page index: https://docs.walt.id/llms.txt · Stack comparison: https://docs.walt.id/community-vs-enterprise.md OpenAPI 3.1 specifications (machine-readable): - Issuer API — Community Stack: https://issuer.demo.walt.id/api.json - Verifier API — Community Stack: https://verifier.demo.walt.id/api.json - Wallet API — Community Stack: https://wallet.demo.walt.id/api.json - Enterprise API — Enterprise Stack: https://enterprise.sandbox.walt.id/api.json ################################################################################ # PART 1 OF 3: CONCEPTS — STACK-AGNOSTIC (20 pages) # Stack-agnostic background: DIDs, credential formats (W3C VC, SD-JWT VC, mDL/mdoc), # selective disclosure, OID4VCI/OID4VP protocols, trust systems. # These concepts apply to BOTH the Community Stack and the Enterprise Stack. ################################################################################ -------------------------------------------------------------------------------- PAGE: Concepts URL: https://docs.walt.id/concepts RAW: https://docs.walt.id/concepts.md STACK: Concepts (stack-agnostic, applies to both Community and Enterprise Stack) -------------------------------------------------------------------------------- # Concepts Learn more about the general building blocks of decentralized ID solutions. - [Decentralised Identity](https://docs.walt.id/concepts/decentralized-identity.md) - An introduction to decentralized identity. - [ID Ecosystems](https://docs.walt.id/concepts/id-ecosystems.md) - The role of ecosystems in decentralized ID solutions. - [Decentralised Identifiers (DIDs)](https://docs.walt.id/concepts/decentralised-identifiers.md) - The role of DIDs and how they work. ### Digital Credentials Learn more about the different types of digital credentials - [Verifiable Credentials (W3C)](https://docs.walt.id/concepts/digital-credentials/verifiable-credentials-w3c.md) - The W3C data model for tamper-evident digital credentials. - [mDL / mdoc credentials](https://docs.walt.id/concepts/digital-credentials/mdoc-mdl-iso.md) - ISO/IEC 18013 mobile driver's license and mdoc credential format. - [SD-JWT VC credentials](https://docs.walt.id/concepts/digital-credentials/sd-jwt-vc.md) - JWT-based credentials with selective disclosure for claim-level privacy. - [Mono claim credentials](https://docs.walt.id/concepts/selective-disclosure/mono-claim-credentials) - Single-attribute credentials for minimal disclosure and simple verification. ### Credential Exchange Protocols - [OpenID4VCI (Issuance)](https://docs.walt.id/concepts/data-exchange-protocols/openid4vci.md) - Protocol for issuing verifiable credentials to wallets. - [OpenID4VP (Presentation)](https://docs.walt.id/concepts/data-exchange-protocols/openid4vp.md) - Protocol for presenting credentials from a wallet to a verifier. - [Digital Credentials API (Browser-Based)](https://docs.walt.id/concepts/data-exchange-protocols/dc-api.md) - W3C browser API that mediates credential presentation and issuance natively in the browser also known as DC API. - [HAIP (High Assurance Profile)](https://docs.walt.id/concepts/data-exchange-protocols/haip.md) - Security-hardened profile of OpenID4VCI and OpenID4VP for high-assurance interoperability. Required by eIDAS 2. ### Trust Systems - [EU Trust Lists](https://docs.walt.id/concepts/trust-systems/eu-trust-lists.md) - Machine-readable registries of trusted entities for the EU digital identity ecosystem (eIDAS 2.0). - [ISO mDL Trust / VICALs](https://docs.walt.id/concepts/trust-systems/iso-trust-vicals.md) - Certificate authority hierarchies for verifying ISO mobile document issuers. - [Wallet Attestations](https://docs.walt.id/concepts/trust-systems/wallet-attestations.md) - Cryptographic proofs that establish the authenticity and integrity of digital wallets. ### Credential Lifecycle - [Credential lifecycle](https://docs.walt.id/concepts/credential-lifecycle/credential-status-and-revocation.md) - How to manage the status (e.g. revocation) of credentials. -------------------------------------------------------------------------------- PAGE: Decentralized Identity URL: https://docs.walt.id/concepts/decentralized-identity RAW: https://docs.walt.id/concepts/decentralized-identity.md STACK: Concepts (stack-agnostic, applies to both Community and Enterprise Stack) -------------------------------------------------------------------------------- # Decentralized Identity ## Definitions Digital identity is the sum of all the (digital) information that exists about you. For example: - core identity attributes (name, address or birthday), - education and work history (diplomas, work records, certificates), - health or insurance data (medical reports, prescriptions, vaccination passes), - financial information (bank account information, transaction histories). As a result, your digital identity describes who you are in every aspect of your (digital) life. Decentralized identity is simply one (of many) approaches for making digital identity possible. ## **How decentralized identity works** Decentralized identity enables governments and businesses to issue identity credentials to their citizens, users or other stakeholders. These credentials are stored in ID wallets and can be easily shared with others. It is like a 3-sided marketplace: 1. **Issuers** issue digital identity credentials (e.g. governments issue passports, universities issue diplomas). 2. **Holders** receive digital credentials (from Issuers) and store them in ID wallets. They control and share their digital credentials with third parties (Verifiers). 3. **Verifiers** rely on identity data to provide products and services, they verify and process credentials provided by Holders. The so-called “**Trust Triangle**” illustrates these roles: ![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 3: COMMUNITY STACK — OPEN SOURCE (200 pages) # The Community Stack is walt.id's FREE, OPEN-SOURCE product line: standalone # Issuer API, Verifier API, and Wallet API services plus Kotlin multi-platform # libraries. Single-tenant, self-hosted (Docker), no license required. # Do NOT confuse with the commercial Enterprise Stack. ################################################################################ -------------------------------------------------------------------------------- PAGE: Getting Started URL: https://docs.walt.id/community-stack/home RAW: https://docs.walt.id/community-stack/home.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Getting Started Explore our issuer, verifier and wallet product below or learn more about the walt.id Community Stack and its architecture [here](https://docs.walt.id/community-stack/home/the-community-stack.md). Video: https://youtu.be/KINBsck7Jzg #### [Issuer](https://docs.walt.id/community-stack/issuer) - [API](https://docs.walt.id/community-stack/issuer/getting-started.md) - Issue credentials via OID4VC using the issuer API - [Apps](https://docs.walt.id/community-stack/issuer/apps/portal/overview.md) - White-label issuer portal to create and configure different types of credential offers. #### [Verifier](https://docs.walt.id/community-stack/verifier) - [API](https://docs.walt.id/community-stack/verifier/getting-started.md) - Request and verify credentials from holders via OID4VP using the verifier API. - [Apps](https://docs.walt.id/community-stack/verifier/apps/portal/overview.md) - White-label verifier portal to request, configure and verify various types of credentials. #### [Wallet](https://docs.walt.id/community-stack/wallet) - [API](https://docs.walt.id/community-stack/wallet/getting-started.md) - Create and manage digital ID wallets for your users via the wallet API (custodial). - [Apps](https://docs.walt.id/community-stack/wallet/apps/overview.md) - White-label web wallet and PWA (Progressive Web App) solution. --- ### Quick Start If you want to try out all the walt.id services quickly, you can use our [docker compose](https://github.com/walt-id/waltid-identity/tree/main/docker-compose) shown below. This launches locally an instance of the [issuer API](https://github.com/walt-id/waltid-identity/tree/main/waltid-services/waltid-issuer-api), [verifier API](https://github.com/walt-id/waltid-identity/tree/main/waltid-services/waltid-verifier-api) and [wallet API](https://github.com/walt-id/waltid-identity/tree/main/waltid-services/waltid-wallet-api) and our [Web Wallet](https://github.com/walt-id/waltid-identity/tree/main/waltid-applications/waltid-web-wallet), [Web Portal](https://github.com/walt-id/waltid-identity/tree/main/waltid-applications/waltid-web-portal) and [Verifiable Credentials Repo](https://github.com/walt-id/waltid-credentials). We also provide links to all services deployed in our test environment below. **Clone [walt.id identity](https://github.com/walt-id/waltid-identity)**
```bash git clone https://github.com/walt-id/waltid-identity.git && cd waltid-identity ``` **Launch the services** ```bash cd docker-compose && docker compose up ``` #### Access Services Click the link(s) below to open the related services. Local environment link only works if you executed the command above.
**APIs** - **Issuer API** - [Deployed](https://issuer.demo.walt.id/swagger/index.html) | [Local](http://localhost:7002/) - **Verifier API** - [Deployed](https://verifier.demo.walt.id/swagger/index.html) | [Local](http://localhost:7003/) - **Verifier2 API** - [Deployed](https://verifier2.demo.walt.id/swagger/index.html) | [Local](http://localhost:7004/) - **Wallet API** - [Deployed](https://wallet.walt.id/swagger/index.html#/) | [Local](http://localhost:7001/) **Apps** - **Web Wallet** - [Deployed](https://wallet.demo.walt.id/login) | [Local](http://localhost:7101/) - **Web Portal** - [Deployed](https://portal.walt.id/) | [Local](http://localhost:7102/) - **Verifiable Credential Repo** - [Deployed](https://credentials.walt.id/) | [Local](http://localhost:7103/) ### Guides - [Quickstart (5 min)](https://docs.walt.id/community-stack/home/quickstart-5-min.md) - Get started quickly by creating a wallet, issuing a credential and presenting it. - [Tutorial (30 min)](https://docs.walt.id/community-stack/home/tutorial-30-min.md) - End-to-end ID use-case using issuer, verifier and wallet API. - [Issuer Protocol Flows](https://docs.walt.id/community-stack/issuer/protocol-flows) - Full issuance flow from portal to wallet, including KMS signing. - [Verifier Protocol Flows](https://docs.walt.id/community-stack/verifier/protocol-flows.md) - Full verification flow from portal to wallet presentation. - [FAQ](https://docs.walt.id/community-stack/home/faq.md) - Frequently asked questions about the Community Stack. -------------------------------------------------------------------------------- PAGE: Quickstart (5 min) URL: https://docs.walt.id/community-stack/home/quickstart-5-min RAW: https://docs.walt.id/community-stack/home/quickstart-5-min.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Quickstart (5 min) Video: https://youtu.be/fufhtqb_yjQ?si=zKkQSr20BpgX4Brt In this quickstart guide, we will get ourselves a digital ID wallet, issue a credential into the wallet, and then present that credential to a verifier. ### The Components - [**The Wallet**](https://wallet.demo.walt.id/){:target="_blank"}: This will utilize a demo version of the walt.id web wallet. - [**The Issuer**](https://portal.demo.walt.id/){:target="_blank"}: This will utilize a demo version of the walt.id web portal. - [**The Verifier**](https://portal.demo.walt.id/){:target="_blank"}: This will also utilize a demo version of the walt.id web portal. ## 1. Create Wallet Visit the walt.id web-wallet [here](https://wallet.demo.walt.id){:target="_blank"} and [register a new account](https://wallet.demo.walt.id/signup){:target="_blank"} using your email and password. ## 2. Issue Credential To issue a credential using the [walt.id web portal](https://portal.demo.walt.id/){:target="_blank"}, follow these steps: 1. Access the Portal: Visit the walt.id web portal [here](https://portal.demo.walt.id/){:target="_blank"} and select the credential you wish to issue, such as `BankID`. Click "Start" located at the top right corner. 2. (optional) Configure Credential Issuance: On the next page, you can configure the credential issuance settings. You have the option to: - Choose the credential format, such as [W3C (JWT/SD-JWT)](https://docs.walt.id/concepts/digital-credentials/verifiable-credentials-w3c.md){:target="_blank"}, [IETF SD-JWT VC](https://docs.walt.id/concepts/digital-credentials/sd-jwt-vc.md){:target="_blank"}, or others. - Specify the [DID](https://docs.walt.id/concepts/decentralised-identifiers.md){:target="_blank"} method to be used as the issuer DID. - Under "Security Settings," select the authentication method. By default, it is set to "pre-authorized," meaning the user is already authenticated when receiving the credential offer. 3. Issue the Credential: Use the configured settings or leave the default credential issuance settings and click "Issue." This will generate an OID4VC credential offer using the issuer API in the background. 4. Receive the Credential Offer: The OID4VC offer will be displayed on the next screen as a QR code and also provided as a direct link behind the "Open Web Wallet" button. - The QR code is ideal if the wallet is on a mobile device, as the device can easily scan the code. - The direct link is useful if the receiving device also has the wallet installed. 5. Open the Credential offer in [Web Wallet](https://wallet.demo.walt.id/){:target="_blank"}: Click "Open Web Wallet" to view the offer in the web wallet. ## 3. Receive Credential Accept the credential offer displayed in the wallet. You should now have the credential in your wallet. ## 4. Verify Credential To verify the credential in your wallet using the [walt.id web portal](https://portal.demo.walt.id/){:target="_blank"}, follow these steps: 1. Access the Portal: Visit the walt.id web portal [here](https://portal.demo.walt.id/){:target="_blank"} and select the credential you issued in the previous step to verify, such as `BankID`. Click "Start" located at the top right corner. 2. Switch to the 'Verify' tab at the top below the smiley. 3. (optional) Configure Credential Verification with the following options: - Choose the credential format, such as [W3C (JWT/SD-JWT)](https://docs.walt.id/concepts/digital-credentials/verifiable-credentials-w3c.md){:target="_blank"}, [IETF SD-JWT VC](https://docs.walt.id/concepts/digital-credentials/sd-jwt-vc.md){:target="_blank"}, or others. - Specify the [DID](https://docs.walt.id/concepts/decentralised-identifiers.md){:target="_blank"} method - Under "Credential Policies", select the [policies](https://docs.walt.id/community-stack/verifier/credential-verification/policies/overview.md){:target="_blank"} which should be applied against the credential. 4. Verify the Credential: Use the configured settings or leave the default credential verification settings and click "Verify." This will generate an OID4VP authorization request using the verifier API in the background. 5. Receive Credential Authorization Request: The OID4VP authorization URL will be displayed on the next screen as a QR code and also provided as a direct link behind the "Open Web Wallet" button. - The QR code is ideal if the wallet is on a mobile device, as the device can easily scan the code. - The direct link is useful if the receiving device also has the wallet installed. 6. Open the Credential authorization request in [Web Wallet](https://wallet.demo.walt.id/){:target="_blank"}: Click "Open Web Wallet" to view the verification request in the web wallet. ## 5. Present Credential Accept the verification and inspect the verification results on the page you get redirected to. ## Next Steps **Note:** If you enjoy our tools, please leave us a star ⭐ [on GitHub](https://github.com/walt-id/waltid-identity).
[Checkout our tutorial](https://docs.walt.id/community-stack/home/tutorial-30-min.md) for an end-to-end use-case using our APIs (Issuer, Verifier and Wallet). -------------------------------------------------------------------------------- PAGE: Tutorial (30 min) URL: https://docs.walt.id/community-stack/home/tutorial-30-min RAW: https://docs.walt.id/community-stack/home/tutorial-30-min.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # Tutorial (30 min) Video: https://youtu.be/1Qbj2kVMee8 In this 30 min tutorial, we will take a look at the issuer, verifier, and wallet API of the walt.id Community Stack. If you want to try it out via a UI first, please go [here](https://docs.walt.id/community-stack/home/quickstart-5-min.md). The tutorial will be split into the following sections: 1. Create a wallet 2. Issue a credential into the wallet 3. Request & verify the credential from the wallet With that, we are covering a regular digital ID use-case end-to-end using the walt.id API components: - [Issuer API](https://docs.walt.id/community-stack/issuer/getting-started.md) - [Verifier API](https://docs.walt.id/community-stack/verifier/getting-started.md) - [Wallet API](https://docs.walt.id/community-stack/wallet/getting-started.md) ### The Credential For this tutorial, we will be issuing ourselves a University Degree Credential in the [W3C credential format](https://docs.walt.id/concepts/digital-credentials/verifiable-credentials-w3c.md) using a JWT signature. **Note:** If you are curious to learn more about other types of credential formats, please go [here](https://docs.walt.id/concepts/digital-credentials.md). #### Example University Degree Credential ```json { // The contextual information required for data integrity and 1.api "@context": [ "https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1" ], "id": "http://example.gov/credentials/3732", // The credential's unique identifier "type": [ "VerifiableCredential", "UniversityDegree" // Specifies the kind of credential being issued ], "issuer": { "id": "did:web:vc.transmute.world" // The issuer's unique identifier (DID) }, "issuanceDate": "2020-03-10T04:24:12.164Z", // When the credential was issued "credentialSubject": { // Information about the credential's recipient and the degree earned "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", "degree": { "type": "BachelorDegree", "name": "Bachelor of Science and Arts" } } } ``` ## Setup To bring up the [issuer](https://docs.walt.id/community-stack/issuer/getting-started.md), [wallet](https://docs.walt.id/community-stack/wallet/getting-started.md) and [verifier API](https://docs.walt.id/community-stack/verifier/getting-started.md) locally, we will be using our [docker compose](https://github.com/walt-id/waltid-identity/tree/main/docker-compose). **Clone [walt.id identity](https://github.com/walt-id/waltid-identity)**
```bash git clone https://github.com/walt-id/waltid-identity.git && cd waltid-identity ``` **Launch the services** ```bash cd docker-compose && docker compose up ``` #### Access APIs - **Issuer API** - [http://localhost:7002/](http://localhost:7002/) - **Verifier API** - [http://localhost:7003/](http://localhost:7003/) - **Wallet API** - [http://localhost:7001/](http://localhost:7001/) ## Create a Wallet To create our wallet, we will use the [walt.id wallet API](https://docs.walt.id/community-stack/wallet/getting-started.md). This API will be managed by the operator, which means that the wallet we create will be a custodial wallet. In a custodial wallet, the keys are controlled by the operator of the software, unlike in a non-custodial wallet where the user has full control over their keys. ### Create Wallet Account **Option: CURL** [API Reference](https://wallet.demo.walt.id/swagger/index.html#/Authentication/post_wallet_api_auth_create) Each created account receives a default "did:key" and private key (Ed25519). ```bash curl -X 'POST' \ 'http://0.0.0.0:7001/wallet-api/auth/register' \ -H 'accept: */*' \ -H 'Content-Type: application/json' \ -d '{ "type": "email", "name": "Test", "email": "test@email.com", "password": "test" }' ``` **Body Parameters** ```json { "name": "string", "email": "string", "password": "string", "type": "{type}" } ``` - `name`: _[string]_ - The name of the user. - `email`: _[string]_ - The email of the user. - `password`: _[string]_ - The password of the user. - `type`: _[string]_ - The type of the user account. `email` or `address` (for web3 login). ### Login to Wallet Account **Option: CURL** [API Reference](https://wallet.demo.walt.id/swagger/index.html#/Authentication/post_wallet_api_auth_login) ```bash curl -X 'POST' \ 'http://0.0.0.0:7001/wallet-api/auth/login' \ -H 'accept: */*' \ -H 'Content-Type: application/json' \ -d '{ "type": "email", "email": "test@email.com", "password": "test" }' ``` **Body Parameters** ```json { "type": "{type}", "email": "string", "password": "string" } ``` - `type`: _[string]_ - The type of the user account. `email` or `address` (for web3 login). - `email`: _[string]_ - The email of the user. - `password`: _[string]_ - The password of the user. **Example Response**
Now a session is automatically created for cookie-based authentication. For Bearer Token Authentication, the `token` returned must be provided in the header for each request that needs authentication. Refer to the [overview section](https://docs.walt.id/community-stack/wallet/authentication/overview.md#cookie-based-authentication) for more details. ```json { "token": "KL-a_dk1qO8moCX4gxaGfb7_TS8RK-JWVKZk9BBP0-s", "id": "018045e5-942c-4362-b535-658c4dd581ef", "username": "user@email.com" } ``` ### Retrieve Wallet ID As there could be support for multiple wallets per account in the future. We will need to retrieve our wallet ID for the one wallet that got created with the following call. We should save this ID for future reference. **Option: CURL** [API Reference](https://wallet.demo.walt.id/swagger/index.html#/Accounts/get_wallet_api_wallet_accounts_wallets) ```bash curl -X 'GET' \ 'http://0.0.0.0:7001/wallet-api/wallet/accounts/wallets' \ -H 'accept: application/json' \ -H 'authorization: Bearer {token}' ``` **Header Parameters** - `authorization`: _String_ - Bearer token obtained from the [login step](#login-to-wallet-account). Format: `Bearer {token}`. **Example Response** ```json { "account": "108753e3-5ab4-4096-8699-ad0653c79a09", "wallets": [ { // ⬇️ Save for later ⬇️ "id": "4d7ef7c6-a76b-409b-b042-68d15a484082", // ⬆️ Save for later ⬆️ "name": "Wallet of Test", "createdOn": "2025-04-16T09:19:11.585Z", "addedOn": "2025-04-16T09:19:11.597Z", "permission": "ADMINISTRATE" } ] } ``` **Note:** Make sure to save the ID for future reference. Now that we have created the wallet account and have the wallet ID, we can proceed to the issuer to create a credential offer. This offer can then be received in the wallet we just set up. Additionally, it's important to note that each wallet is automatically assigned a set of keys and a did:jwk. This feature can be disabled if desired through the [wallet configuration settings](/community-stack/wallet/configurations/config-files/features). ## Issue Credential ### Create Credential Offer **Option: CURL** **Endpoint: ** `/openid4vc/jwt/issue` | [API Reference](https://issuer.demo.walt.id/swagger/index.html#/Credential%20Issuance/post_openid4vc_jwt_issue) **Example Request** ```bash curl -X 'POST' \ 'http://localhost:7002/openid4vc/jwt/issue' \ -H 'accept: text/plain' \ -H 'Content-Type: application/json' \ -d '{ "issuerKey": { "type": "jwk", "jwk": { "kty": "OKP", "d": "JvJIpga2GD8LJeRu4Sv-mL4thE31DuFlr9PA04CIoZY", "crv": "Ed25519", "kid": "iJMS5bkZVIlncfq_Lf_SuxJ2JtQ5Hvaz7tWPnAjUUds", "x": "FZdvwC8aGhRwqzWptej0NZgtwYAI1SyFg1mKDETOfqE" } }, "issuerDid": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiaUpNUzVia1pWSWxuY2ZxX0xmX1N1eEoySnRRNUh2YXo3dFdQbkFqVVVkcyIsIngiOiJGWmR2d0M4YUdoUndxeldwdGVqME5aZ3R3WUFJMVN5RmcxbUtERVRPZnFFIn0", "credentialConfigurationId": "UniversityDegree_jwt_vc_json", "credentialData": { "@context": [ "https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1" ], "id": "http://example.gov/credentials/3732", "type": [ "VerifiableCredential", "UniversityDegree" ], "issuer": { "id": "did:web:vc.transmute.world" }, "issuanceDate": "2020-03-10T04:24:12.164Z", "credentialSubject": { "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", "degree": { "type": "BachelorDegree", "name": "Bachelor of Science and Arts" } } }, "mapping": { "id": "", "issuer": { "id": "" }, "credentialSubject": { "id": "" }, "issuanceDate": "", "expirationDate": "" }, "authenticationMethod": "PRE_AUTHORIZED", "standardVersion": "DRAFT13" }' ``` **Header Parameters** - `statusCallbackUri`: _URL_ - Receive updates on the created issuance process, e.g. when a credential was successfully claimed. The parameter expects a URL which can accept a JSON POST request. The URL can also hold a `$id`, which will be replaced by the issuance session id. For example: `https://myurl.com/$id`, `https://myurl.com` or `https://myurl.com/test/$id`

Expand To Learn More
**Body** The data send to the provided URL will contain a JSON body: - `id` : _String_ - the issuance session id - `type`: _String_ - the event type - `data`: _JsonObject_ - the data for the event **Event Types** Possible events (event types) and their data are: - `resolved_credential_offer` with the credential offer as JSON (in our Web Wallet: called when the issuance offer is entered into the wallet, but not processing / accepted yet) - `requested_token` with the issuance request for the token as json object (called for the token required to receive the credentials) Credential issuance (called for every credential that's issued (= requested from wallet)) - `jwt_issue` with `jwt` being the issued jwt - `sdjwt_issue` with `sdjwt` being the issued sdjwt - `batch_jwt_issue` with `jwt` being the issued jwt - `batch_sdjwt_issue` with `sdjwt` being the issued sdjwt - `generated_mdoc` with `mdoc` being the CBOR (HEX) of the signed mdoc To allow for secure business logic flows, if a callback URL is set, and it cannot be reached, the issuance will not commence further (after that point). If no callback URL is set, the issuance logic does not change in any way.
**Body** ```json { "issuerKey": { "type": "jwk", "jwk": { "kty": "OKP", "d": "JvJIpga2GD8LJeRu4Sv-mL4thE31DuFlr9PA04CIoZY", "crv": "Ed25519", "kid": "iJMS5bkZVIlncfq_Lf_SuxJ2JtQ5Hvaz7tWPnAjUUds", "x": "FZdvwC8aGhRwqzWptej0NZgtwYAI1SyFg1mKDETOfqE" } }, "issuerDid": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiaUpNUzVia1pWSWxuY2ZxX0xmX1N1eEoySnRRNUh2YXo3dFdQbkFqVVVkcyIsIngiOiJGWmR2d0M4YUdoUndxeldwdGVqME5aZ3R3WUFJMVN5RmcxbUtERVRPZnFFIn0", "credentialConfigurationId": "UniversityDegree_jwt_vc_json", "credentialData": { "@context": [ "https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1" ], "id": "http://example.gov/credentials/3732", "type": [ "VerifiableCredential", "UniversityDegree" ], "issuer": { "id": "did:web:vc.transmute.world" }, "issuanceDate": "2020-03-10T04:24:12.164Z", "credentialSubject": { "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", "degree": { "type": "BachelorDegree", "name": "Bachelor of Science and Arts" } } }, "mapping": { "id": "", "issuer": { "id": "" }, "credentialSubject": { "id": "" }, "issuanceDate": "", "expirationDate": "" }, "authenticationMethod": "PRE_AUTHORIZED", "standardVersion": "DRAFT13" } ``` **Body Parameters** - `issuerKey`: _JSON_ - A JWK or reference object to a key stored in an external KMS to sign the credential with. Supported algorithms: ed25519, secp256k1, secp256r1, or RSA. **JWK Format:** `{"type": "jwk", "jwk": Here JSON Web Key Object}`. Must be provided as JWK object to "issuerKey". **KMS Key**: Please refer to the [Key Management Section](https://docs.walt.id/community-stack/issuer/key-management/overview.md) and the KMS you want to use for more details on the structure of the reference object.
- `issuerDid`: _String_ - The DID related to the provided key. - `credentialConfigurationId`: _String_ - Reference to a specific credential configuration the issuer supports. As our issuer currently supports W3C JWT & SD-JWT credentials and SD-JWT VCs (IETF). The structure of the credentialConfigurationId is the following: " credentialType_jwt_vc_json" for W3C JWT & SD-JWT credentials and "credentialType_vc+sd-jwt" for SD-JWT VCs (IETF). E.g. "UniversityDegree_jwt_vc_json" or " UniversityDegree_vc+sd-jwt". You can also view the credentialConfigurationIds by visiting the `/.well-known/openid-credential-issuer` endpoint of your issuer. The metadata of our deployed issuer for testing can be seen [here](https://issuer.demo.walt.id/swagger/index.html#/oidc/get__standardVersion___well_known_openid_credential_issuer). - `credentialData`: _JSON_ - A credential data structure to sign (W3C compliant). A list of predefined valid structures can be found [here](https://credentials.walt.id/). - `mapping` (optional): _JSON_ - The mapping object that allows for **dynamic value insertion via data functions**, executed at the time when the credentials is claimed. This feature enables personalized credentials based on real-time data. Learn more about it and see a list of supported data functions [here](https://docs.walt.id/community-stack/issuer/data-functions). - `standardVersion`: (optional) _String_ - Defines which OIDC4VCI standard version is used. The supported standard versions are: `DRAFT13` and `DRAFT11`. If no value is provided, it will default to `DRAFT13`. - `authenticationMethod`: (optional) _String_ - Defines which OIDC4VC exchange flow is used (pre-auth or full-auth). If no value is provided, it will default to pre-auth flow indicated as `PRE_AUTHORIZED`. The parameter options are:
Expand To Learn More About The Options - `PWD` - used for authorization code flow with username/password authentication with external auth server - `ID_TOKEN` - used for authorization code flow with id_token authentication. When this method is used an authorization request is sent to the wallet, which generates and issues the ID_TOKEN using its DID ( Decentralized Identifier). The issuer then verifies the ID_TOKEN to confirm the holder's identity and DID. - `VP_TOKEN` - used for authorization code flow with vp_token(OIDC4VP). With this method, the receiver of the credential must be authenticated by presenting the requested credential. The credential which must be presented can be configured using the `vpRequestedValue` as explained below. The policies which are applied to the presented credential are 'signature,' 'expired,' and 'not-before.' Learn more about policies [here](https://docs.walt.id/community-stack/verifier/credential-verification/policies/overview.md). - `NONE` - used for authorization code flow with none authentication method - `PRE_AUTHORIZED` - used for pre-authorizated code flow **Additional parameters required for selected auth method:** **For "ID_TOKEN" and "VP_TOKEN":** - `useJar`: (Optional) _Boolean_ - used for using JAR OAuth specification in the id/vp_token requests. If omitted, the default value is true. **For VP_TOKEN:** - `vpRequestedValue`: _String_ - Specifies the requested credential type value for the VP token. E.g. "VerifiableId" - `vpProfile`: (Optional) _String_ - Specifies the profile of the VP request. Available Profiles: DEFAULT: For W3C OpenID4VP, ISO_18013_7_MDOC: For MDOC OpenID4VP, EBSIV3: For EBSI V3 Compliant VP. If omitted, the default value is DEFAULT
--- **Example Response** The issuer endpoint will respond with Credential Offer URL. **Plain Response** ``` openid-credential-offer://issuer.portal.walt.id/?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Fissuer.portal.walt.id%22%2C%22credentials%22%3A%5B%7B%22format%22%3A%22jwt_vc_json%22%2C%22types%22%3A%5B%22VerifiableCredential%22%2C%22UniversityDegree%22%5D%2C%22credential_definition%22%3A%7B%22%40context%22%3A%5B%22https%3A%2F%2Fwww.w3.org%2F2018%2Fcredentials%2Fv1%22%2C%22https%3A%2F%2Fwww.w3.org%2F2018%2Fcredentials%2Fexamples%2Fv1%22%5D%2C%22types%22%3A%5B%22VerifiableCredential%22%2C%22UniversityDegree%22%5D%7D%7D%5D%2C%22grants%22%3A%7B%22authorization_code%22%3A%7B%22issuer_state%22%3A%220431b78c-cd94-4f50-bfdf-e24d436c0cf6%22%7D%2C%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%7B%22pre-authorized_code%22%3A%22eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiIwNDMxYjc4Yy1jZDk0LTRmNTAtYmZkZi1lMjRkNDM2YzBjZjYiLCJpc3MiOiJodHRwczovL2lzc3Vlci5wb3J0YWwud2FsdC5pZCIsImF1ZCI6IlRPS0VOIn0.NorG7GtjmA-HXMJfUzU9vfnshcIgFY0oYQb8qJjDfORPoNxuurgySSOIDKFi7Z4XJmC-oJZnM0Nbb0NUd57cDA%22%2C%22user_pin_required%22%3Afalse%7D%7D%7D ``` **Decoded** ``` openid-credential-offer://issuer.portal.walt.id/? credential_offer= { "credential_issuer": "https://issuer.demo.walt.id", "credentials": [ { "format": "jwt_vc_json", "types": [ "VerifiableCredential", "UniversityDegree" ], "credential_definition": { "@context": [ "https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1" ], "types": [ "VerifiableCredential", "UniversityDegree" ] } } ], "grants": { "authorization_code": { "issuer_state": "0431b78c-cd94-4f50-bfdf-e24d436c0cf6" }, "urn:ietf:params:oauth:grant-type:pre-authorized_code": { "pre-authorized_code": "eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiIwNDMxYjc4Yy1jZDk0LTRmNTAtYmZkZi1lMjRkNDM2YzBjZjYiLCJpc3MiOiJodHRwczovL2lzc3Vlci5wb3J0YWwud2FsdC5pZCIsImF1ZCI6IlRPS0VOIn0.NorG7GtjmA-HXMJfUzU9vfnshcIgFY0oYQb8qJjDfORPoNxuurgySSOIDKFi7Z4XJmC-oJZnM0Nbb0NUd57cDA", "user_pin_required": false } } } ``` The response will get from the issuer API will be an OIDCVI offer URL. This URL can then be passed to the wallet to claim the credentials. ### Receive Credential in Wallet **Option: CURL** **Example Call ** | [Api Reference](https://wallet.demo.walt.id/swagger/index.html#/Credential%20exchange/post_wallet_api_wallet__wallet__exchange_useOfferRequest) ```bash curl -X 'POST' \ 'http://0.0.0.0:7001/wallet-api/wallet/{walletID}/exchange/useOfferRequest' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -H 'authorization: Bearer {loginToken}' \ -d '{credentialOfferFromIssuer}' ``` **Path Parameters** - `walletID`: _String_ - the ID of our wallet, retrieved [previously](#retrieve-wallet-id). Provided as part of the URL path. **Header Parameters** - `authorization`: _String_ - Bearer token obtained from the [login step](#login-to-wallet-account). Format: `Bearer {loginToken}`.
**Body** ```json credentialOfferFromIssuerAPI ``` **Note:** When you are using the terminal the credentialOffer return from the issuer might have a `%` at the end of it. Make sure to remove this before passing it to the wallet.
**Example Response** ```json [ { "wallet": "8abd7a5f-ae86-46e5-9693-1bb8a98f7c5d", "id": "urn:uuid:d730bc77-8a9b-4f9b-8d56-50689aefe3d7", "document": "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaWFVcE5VelZpYTFwV1NXeHVZMlp4WDB4bVgxTjFlRW95U25SUk5VaDJZWG8zZEZkUWJrRnFWVlZrY3lJc0luZ2lPaUpHV21SMmQwTTRZVWRvVW5keGVsZHdkR1ZxTUU1YVozUjNXVUZKTVZONVJtY3hiVXRFUlZSUFpuRkZJbjAjaUpNUzVia1pWSWxuY2ZxX0xmX1N1eEoySnRRNUh2YXo3dFdQbkFqVVVkcyIsInR5cCI6IkpXVCIsImFsZyI6IkVkRFNBIn0.eyJpc3MiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaWFVcE5VelZpYTFwV1NXeHVZMlp4WDB4bVgxTjFlRW95U25SUk5VaDJZWG8zZEZkUWJrRnFWVlZrY3lJc0luZ2lPaUpHV21SMmQwTTRZVWRvVW5keGVsZHdkR1ZxTUU1YVozUjNXVUZKTVZONVJtY3hiVXRFUlZSUFpuRkZJbjAiLCJzdWIiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaVNtTXlWWHByY1RWRU5uaHJRbXAyWlRGVk5tRlJibU4yUzJkdFVrNTFTVU50Y1ZGTmIzVlFXVFJ5WXlJc0luZ2lPaUpFZEVoalgyTmZVSGgyY3pkb2NYRkRXRVZJTjIxWlRYSkZjWGgwY25jeVZWZGlRemhRYjA1WVRVMVJJbjAiLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL2V4YW1wbGVzL3YxIl0sImlkIjoidXJuOnV1aWQ6ZDczMGJjNzctOGE5Yi00ZjliLThkNTYtNTA2ODlhZWZlM2Q3IiwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIlVuaXZlcnNpdHlEZWdyZWUiXSwiaXNzdWVyIjp7ImlkIjoiZGlkOmp3azpleUpyZEhraU9pSlBTMUFpTENKamNuWWlPaUpGWkRJMU5URTVJaXdpYTJsa0lqb2lhVXBOVXpWaWExcFdTV3h1WTJaeFgweG1YMU4xZUVveVNuUlJOVWgyWVhvM2RGZFFia0ZxVlZWa2N5SXNJbmdpT2lKR1dtUjJkME00WVVkb1VuZHhlbGR3ZEdWcU1FNWFaM1IzV1VGSk1WTjVSbWN4YlV0RVJWUlBabkZGSW4wIn0sImlzc3VhbmNlRGF0ZSI6IjIwMjUtMDQtMTZUMTI6MDg6MzEuOTMwOTQ0MDg1WiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmp3azpleUpyZEhraU9pSlBTMUFpTENKamNuWWlPaUpGWkRJMU5URTVJaXdpYTJsa0lqb2lTbU15VlhwcmNUVkVObmhyUW1wMlpURlZObUZSYm1OMlMyZHRVazUxU1VOdGNWRk5iM1ZRV1RSeVl5SXNJbmdpT2lKRWRFaGpYMk5mVUhoMmN6ZG9jWEZEV0VWSU4yMVpUWEpGY1hoMGNuY3lWVmRpUXpoUWIwNVlUVTFSSW4wIiwiZGVncmVlIjp7InR5cGUiOiJCYWNoZWxvckRlZ3JlZSIsIm5hbWUiOiJCYWNoZWxvciBvZiBTY2llbmNlIGFuZCBBcnRzIn19LCJleHBpcmF0aW9uRGF0ZSI6IjIwMjYtMDQtMTZUMTI6MDg6MzEuOTMxMjU1NjY4WiJ9LCJqdGkiOiJ1cm46dXVpZDpkNzMwYmM3Ny04YTliLTRmOWItOGQ1Ni01MDY4OWFlZmUzZDciLCJleHAiOjE3NzYzNDEzMTEsImlhdCI6MTc0NDgwNTMxMSwibmJmIjoxNzQ0ODA1MzExfQ.tjhXghX6WtMYm0R31cULd0aKVrqRRFkINI-KEjYsDeyoALxl833dEdESosLKUm197Yh8WK5QmX0jMc-gyvnnCw", "disclosures": "", "addedOn": "2025-04-16T12:08:32.036587543Z", "pending": false, "format": "jwt_vc_json", "parsedDocument": { "@context": [ "https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1" ], "id": "urn:uuid:d730bc77-8a9b-4f9b-8d56-50689aefe3d7", "type": [ "VerifiableCredential", "UniversityDegree" ], "issuer": { "id": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiaUpNUzVia1pWSWxuY2ZxX0xmX1N1eEoySnRRNUh2YXo3dFdQbkFqVVVkcyIsIngiOiJGWmR2d0M4YUdoUndxeldwdGVqME5aZ3R3WUFJMVN5RmcxbUtERVRPZnFFIn0" }, "issuanceDate": "2025-04-16T12:08:31.930944085Z", "credentialSubject": { "id": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiSmMyVXprcTVENnhrQmp2ZTFVNmFRbmN2S2dtUk51SUNtcVFNb3VQWTRyYyIsIngiOiJEdEhjX2NfUHh2czdocXFDWEVIN21ZTXJFcXh0cncyVVdiQzhQb05YTU1RIn0", "degree": { "type": "BachelorDegree", "name": "Bachelor of Science and Arts" } }, "expirationDate": "2026-04-16T12:08:31.931255668Z" } } ] ``` ## Request & Verify Credential In the next section, we will be using the walt.id verifier API to generate an OID4VP authorization request. This request we will then pass to the wallet to present the credential we received and stored in the previous step. ### Create Authorization Request Below, we will outline the specific credentials and the format we wish to request from a wallet. In our example, we will be requesting a `UniversityDegree` in the W3C Verifiable Credential (VC) format (`jwt_vc_json`). The Verifier API will take the incoming request and convert it into a Presentation Definition which becomes part of the returned Authorization request. The Authorization request with the presentation definition allows the wallet to understand which credentials it needs to present to the verifier. **Option: CURL** ```bash curl -X 'POST' \ 'http://0.0.0.0:7003/openid4vc/verify' \ -H 'accept: */*' \ -H 'authorizeBaseUrl: openid4vp://authorize' \ -H 'responseMode: direct_post' \ -H 'Content-Type: application/json' \ -d '{ "request_credentials": [ { "type": "UniversityDegree", "format": "jwt_vc_json" } ] }' ``` **Header Parameters** - **authorizeBaseUrl** - is used to modify the start of the OID4VC request URL. If you are using the cross-device flow, where you will display the URL as a QR code, you can leave the value as `openid4vp://authorize` or if you don't know the wallet the user will be using to claim the credential. If you are using the same device flow, where you already know the user's wallet and want the user to be able to go directly to it, you can use the wallet URL path that is able to receive an OIDC request as a query parameter. Our wallet for example can receive OID4VC requests here `https://wallet.demo.walt.id/wallet-api/wallet/{wallet}/exchange/useOfferRequest`. - **responseMode** - should be `direct_post` as the other options are not yet supported. - **successRedirectUri** (optional) - is used to redirect the user if verification is successful. You can use the `$id` placeholder to get access to the id of verification session in your application in order to retrieve the verification results. E.g. `/success?id=$id` will be replaced with `/success?id=1234567890` - **errorRedirectUri** (optional) - is used to redirect the user if verification is unsuccessful. You can use the `$id` placeholder to get access to the id of verification session in your application in order to retrieve the verification results. E.g. `/error?id=$id` will be replaced with `/error?id=1234567890` - **statusCallbackUri** (optional) - URL that should be called when the presentation request has been fulfilled by a wallet. The request sent will be a `POST` including the whole presentation result. [See Verification Status Policies Response](#verification-status-policies-response) - **statusCallbackApiKey** (optional) - If the endpoint you provide via `statusCallbackUri` is protected, you can use the `statusCallbackApiKey` to authenticate. The provided key will be appended as Authorization Header to the request. - **stateId** (optional) - overwrite the unique `state` value which gets created for each verification request with your own. - **openId4VPProfile** (optional) - Define profile for VP (Verifiable Presentation) request. The default is W3C OpenID4VP, which can optionally provided as `DEFAULT`. Apart from that, you can choose from the following options: - `EBSIV3`: For EBSI V3 compliant VP. Learn more [here](https://docs.walt.id/community-stack/verifier/ecosystems/ebsi/overview.md). - `ISO_18013_7_MDOC`: For mdoc Openid4VP. **Body** ```json { "request_credentials": [ { "type": "UniversityDegree", "format": "jwt_vc_json" } ] } ``` **Body Parameters** - **request_credentials** - An array of objects detailing the credentials to be requested from the user. Each object varies based on the type of credential being requested.
Expand To Learn More Below are the possible credential types and their respective object structures: 1. **W3C JWT Credential** - **type** Specifies the type of credential (e.g., `VerifiableID`, `VerifiableDiploma`). This maps to the `type` attribute in W3C credentials. - **format**: Describes the format of the credential. For W3C JWT credentials, this would be `jwt_vc_json`. ```json { "type": "ProofOfResidence", "format": "jwt_vc_json" } ``` 2. **SD-JWT VC Credential (IETF Standard)** - **vct**: Specifies the type of credential (e.g., https://issuer.com/identity_credential). This maps to the `vct` attribute in the SD-JWT VC credential. - **format**: Describes the format of the credential. For SD-JWT VC credentials, this would be vc+sd-jwt.: Describes the format of the credential. For SD-JWT VC credentials, this would be `vc+sd-jwt`. ```json { "vct": "test.com/identity_credential", "format": "vc+sd-jwt" } ``` 3. **mDL Credential (ISO/IEC 18013-5)** - **doc_type**: Specifies the type of credential (e.g., org.iso.18013.5.1.mDL). This maps to the doc_type attribute of the mdoc document. - **format**: Describes the format of the credential. For mDL credentials, this would be `mso_mdoc`. ```json { "doc_type": "org.iso.18013.5.1.mDL", "format": "mso_mdoc" } ``` **Optional Parameters**
Next to describing the type and format of the credential, the objects also take an optional `policies` and `id` attribute - **policies**: An array of policies to apply to the specified credential. A list of all policies can be found [here](https://docs.walt.id/community-stack/verifier/credential-verification/policies/overview.md). - **id**: Used to set a specific id for the generated [Presentation Definition](https://identity.foundation/presentation-exchange/spec/v1.0.0/#presentation-definition). If not set, the verifier API auto-assigns a generated one. **Full Examples** ```json { "type": "ProofOfResidence", "format": "jwt_vc_json", "policies": [ "schema", { "policy": "webhook", "args": "https://example.org/abc/xyz" } ], "id": "test123" } ```
**Example Response** **Raw Response** ```text openid4vp://authorize?response_type=vp_token&client_id=http%3A%2F%2Fhost.docker.internal%3A7003%2Fopenid4vc%2Fverify&response_mode=direct_post&state=5smqTtxA1L0F&presentation_definition_uri=http%3A%2F%2Fhost.docker.internal%3A7003%2Fopenid4vc%2Fpd%2F5smqTtxA1L0F&client_id_scheme=redirect_uri&client_metadata=%7B%22authorization_encrypted_response_alg%22%3A%22ECDH-ES%22%2C%22authorization_encrypted_response_enc%22%3A%22A256GCM%22%7D&nonce=2afa2fdc-b8ab-4d4c-a4b6-bd299949fb4b&response_uri=http%3A%2F%2Fhost.docker.internal%3A7003%2Fopenid4vc%2Fverify%2F5smqTtxA1L0F ``` **Decoded Response** ```text openid4vp://authorize? response_type=vp_token &client_id=http://host.docker.internal:7003/openid4vc/verify &response_mode=direct_post &state=5smqTtxA1L0F &presentation_definition_uri=http://host.docker.internal:7003/openid4vc/pd/5smqTtxA1L0F &client_id_scheme=redirect_uri &client_metadata={ "authorization_encrypted_response_alg":"ECDH-ES", "authorization_encrypted_response_enc":"A256GCM" } &nonce=2afa2fdc-b8ab-4d4c-a4b6-bd299949fb4b &response_uri=http://host.docker.internal:7003/openid4vc/verify/5smqTtxA1L0F ``` **Presentation Definition URI result** Below we see the [presentation definition](https://identity.foundation/presentation-exchange/spec/v2.0.0/) the verifier API has generated based on our request. This helps the wallet find matching credentials to present. ```json { "id": "EjRyJNiWqKcc", "input_descriptors": [ { "id": "UniversityDegree", "format": { "jwt_vc_json": { "alg": [ "EdDSA" ] } }, "constraints": { "fields": [ { "path": [ "$.vc.type" ], "filter": { "type": "string", "pattern": "UniversityDegree" } } ] } } ] } ``` ## Present Credential ### 1. Extract Presentation Definition First, we will need to decode the Authorization Request we got from the verifier. **Example Authorization Request** ```text openid4vp://authorize?response_type=vp_token&client_id=http%3A%2F%2Fhost.docker.internal%3A7003%2Fopenid4vc%2Fverify&response_mode=direct_post&state=5smqTtxA1L0F&presentation_definition_uri=http%3A%2F%2Fhost.docker.internal%3A7003%2Fopenid4vc%2Fpd%2F5smqTtxA1L0F&client_id_scheme=redirect_uri&client_metadata=%7B%22authorization_encrypted_response_alg%22%3A%22ECDH-ES%22%2C%22authorization_encrypted_response_enc%22%3A%22A256GCM%22%7D&nonce=2afa2fdc-b8ab-4d4c-a4b6-bd299949fb4b&response_uri=http%3A%2F%2Fhost.docker.internal%3A7003%2Fopenid4vc%2Fverify%2F5smqTtxA1L0F ``` For the decoding we can use an online tool like [this](https://www.url-encode-decode.com/){:target="_blank"}. **Example of Decoded Authorization Request** ```text openid4vp://authorize? response_type=vp_token &client_id=http://host.docker.internal:7003/openid4vc/verify &response_mode=direct_post &state=5smqTtxA1L0F &presentation_definition_uri=http://host.docker.internal:7003/openid4vc/pd/5smqTtxA1L0F &client_id_scheme=redirect_uri &client_metadata={ "authorization_encrypted_response_alg":"ECDH-ES", "authorization_encrypted_response_enc":"A256GCM" } &nonce=2afa2fdc-b8ab-4d4c-a4b6-bd299949fb4b &response_uri=http://host.docker.internal:7003/openid4vc/verify/5smqTtxA1L0F ``` Now we need to visit the `presentation_definition_uri` provided and extract the presentation definition. **Example Presentation Definition** ```json { "id": "EjRyJNiWqKcc", "input_descriptors": [ { "id": "UniversityDegree", "format": { "jwt_vc_json": { "alg": [ "EdDSA" ] } }, "constraints": { "fields": [ { "path": [ "$.vc.type" ], "filter": { "type": "string", "pattern": "UniversityDegree" } } ] } } ] } ``` ### 2. Find Credential(s) Matching Presentation Definition Use the `matchCredentialsForPresentationDefinition` endpoint in the wallet API to find all user credentials matching the presentation request constraints. **Option: CURL** **Endpoint:** `/wallet-api/wallet/{wallet}/exchange/matchCredentialsForPresentationDefinition`| [API Reference](https://wallet.demo.walt.id/swagger/index.html#/Credential%20exchange/post_wallet_api_wallet__wallet__exchange_matchCredentialsForPresentationDefinition) **Example Request** ```bash curl -X 'POST' \ 'http://0.0.0.0:7001/wallet-api/wallet/{wallet_id}/exchange/matchCredentialsForPresentationDefinition' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -H 'authorization: Bearer {loginToken}' \ -d '{presentationDefinitionExtractedFromAuthorizationRequest}' ``` **Path Parameters** - **wallet_id** - Use the saved wallet ID from earlier or extract it via the `/wallet-api/wallet/accounts/wallets` endpoint as explained [here](#retrieve-wallet-id). Provided as part of the URL path. **Header Parameters** - `authorization`: _String_ - Bearer token obtained from the [login step](#login-to-wallet-account). Format: `Bearer {loginToken}`. **Body** The extracted presentation definition as JSON, as explained above. ```json { "id": "EjRyJNiWqKcc", "input_descriptors": [ { "id": "UniversityDegree", "format": { "jwt_vc_json": { "alg": [ "EdDSA" ] } }, "constraints": { "fields": [ { "path": [ "$.vc.type" ], "filter": { "type": "string", "pattern": "UniversityDegree" } } ] } } ] } ``` --- **Example Response** If credentials matching the presentation definition are found, a list of these credentials will be returned. If no match is found, an empty list will be returned. Take a note of the credential id(s) returned as we will be needing them in a later step. ```json [ { "wallet": "5f2eb7d7-7d04-461c-b93d-28d95efbf15b", // ⬇️ Note for later ⬇️ "id": "urn:uuid:fb09ba3d-b73d-49ad-9d88-3d018878ea83", // ⬆️ Note for later ⬆️ "document": "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pTTFsT1pEbEdibmc1U214NVVGWlpkMmRYUmtVek4wVXpSM2RKTUdWSGJFTkxPSGRHYkZkNFIyWndUU0lzSW5naU9pSkdiM1paTWpGTVFVRlBWR3huTFcwdFRtVkxWMmhhUlV3MVlVWnlibEl3ZFdOS2FrUTFWRXR3UjNWbklpd2llU0k2SWtOeVJrcG1SMVJrVURJNVNrcGpZM0JSV0hWNVRVOHpiMmgwZW5KVWNWQjZRbEJDU1ZSWmFqQnZaMEVpZlEiLCJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJkaWQ6andrOmV5SnJkSGtpT2lKRlF5SXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pTTFsT1pEbEdibmc1U214NVVGWlpkMmRYUmtVek4wVXpSM2RKTUdWSGJFTkxPSGRHYkZkNFIyWndUU0lzSW5naU9pSkdiM1paTWpGTVFVRlBWR3huTFcwdFRtVkxWMmhhUlV3MVlVWnlibEl3ZFdOS2FrUTFWRXR3UjNWbklpd2llU0k2SWtOeVJrcG1SMVJrVURJNVNrcGpZM0JSV0hWNVRVOHpiMmgwZW5KVWNWQjZRbEJDU1ZSWmFqQnZaMEVpZlEiLCJzdWIiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaVEyVTVNbGczUVhVMVRuQlRWV0ZoWlU5VFl6TkpSMjlDTFVacFNUTmtaMjFYT1Y5c2NGTldja3hrYXlJc0luZ2lPaUpIY1VabWRqbFNjemh1TlRrMk4wSXlVR3g0TW1wbFduRnlOWFZxVkRoM2RYWk5XbEpIYlZoeGVFSkZJbjAiLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVmVyaWZpYWJsZUF0dGVzdGF0aW9uIiwiVmVyaWZpYWJsZUlkIl0sImNyZWRlbnRpYWxTY2hlbWEiOnsiaWQiOiJodHRwczovL2FwaS5wcmVwcm9kLmVic2kuZXUvdHJ1c3RlZC1zY2hlbWFzLXJlZ2lzdHJ5L3YxL3NjaGVtYXMvMHhiNzdmODUxNmE5NjU2MzFiNGYxOTdhZDU0YzY1YTllMmY5OTM2ZWJmYjc2YmFlNDkwNmQzMzc0NGRiY2M2MGJhIiwidHlwZSI6IkZ1bGxKc29uU2NoZW1hVmFsaWRhdG9yMjAyMSJ9LCJjcmVkZW50aWFsU3ViamVjdCI6eyJjdXJyZW50QWRkcmVzcyI6WyIxIEJvdWxldmFyZCBkZSBsYSBMaWJlcnTDqSwgNTk4MDAgTGlsbGUiXSwiZGF0ZU9mQmlydGgiOiIxOTkzLTA0LTA4IiwiZmFtaWx5TmFtZSI6IkRPRSIsImZpcnN0TmFtZSI6IkphbmUiLCJnZW5kZXIiOiJGRU1BTEUiLCJpZCI6ImRpZDpqd2s6ZXlKcmRIa2lPaUpQUzFBaUxDSmpjbllpT2lKRlpESTFOVEU1SWl3aWEybGtJam9pUTJVNU1sZzNRWFUxVG5CVFZXRmhaVTlUWXpOSlIyOUNMVVpwU1ROa1oyMVhPVjlzY0ZOV2NreGtheUlzSW5naU9pSkhjVVptZGpsU2N6aHVOVGsyTjBJeVVHeDRNbXBsV25GeU5YVnFWRGgzZFhaTldsSkhiVmh4ZUVKRkluMCIsIm5hbWVBbmRGYW1pbHlOYW1lQXRCaXJ0aCI6IkphbmUgRE9FIiwicGVyc29uYWxJZGVudGlmaWVyIjoiMDkwNDAwODA4NEgiLCJwbGFjZU9mQmlydGgiOiJMSUxMRSwgRlJBTkNFIn0sImV2aWRlbmNlIjpbeyJkb2N1bWVudFByZXNlbmNlIjpbIlBoeXNpY2FsIl0sImV2aWRlbmNlRG9jdW1lbnQiOlsiUGFzc3BvcnQiXSwic3ViamVjdFByZXNlbmNlIjoiUGh5c2ljYWwiLCJ0eXBlIjpbIkRvY3VtZW50VmVyaWZpY2F0aW9uIl0sInZlcmlmaWVyIjoiZGlkOmVic2k6MkE5Qlo5U1VlNkJhdGFjU3B2czFWNUNkakh2THBRN2JFc2kySmI2TGRIS25ReGFOIn1dLCJpZCI6InVybjp1dWlkOmZiMDliYTNkLWI3M2QtNDlhZC05ZDg4LTNkMDE4ODc4ZWE4MyIsImlzc3VlZCI6IjIwMjEtMDgtMzFUMDA6MDA6MDBaIiwiaXNzdWVyIjoiZGlkOmp3azpleUpyZEhraU9pSkZReUlzSW1OeWRpSTZJbEF0TWpVMklpd2lhMmxrSWpvaU0xbE9aRGxHYm5nNVNteDVVRlpaZDJkWFJrVXpOMFV6UjNkSk1HVkhiRU5MT0hkR2JGZDRSMlp3VFNJc0luZ2lPaUpHYjNaWk1qRk1RVUZQVkd4bkxXMHRUbVZMVjJoYVJVdzFZVVp5YmxJd2RXTktha1ExVkV0d1IzVm5JaXdpZVNJNklrTnlSa3BtUjFSa1VESTVTa3BqWTNCUldIVjVUVTh6YjJoMGVuSlVjVkI2UWxCQ1NWUlphakJ2WjBFaWZRIiwidmFsaWRGcm9tIjoiMjAyMS0wOC0zMVQwMDowMDowMFoiLCJpc3N1YW5jZURhdGUiOiIyMDI0LTA4LTIxVDA5OjExOjMzLjI0NjM4OTA0MVoifSwianRpIjoidXJuOnV1aWQ6ZmIwOWJhM2QtYjczZC00OWFkLTlkODgtM2QwMTg4NzhlYTgzIiwiaWF0IjoxNzI0MjMxNDkzLCJuYmYiOjE3MjQyMzE0OTN9.OsPTKQC6tvD6TtxeoCd8FtaIThYrYeYRjCHzDXkmgTUjRw78faT9R2bljT03ncrb0YstK0EmjM9D5lwqMZ9ZIA", "addedOn": "2024-08-21T09:11:33.284Z", "format": "jwt_vc" } ] ``` ### 3. Get User Consent (not needed for this tutorial) Normally, you should ask for the owner's consent before presenting these credentials. Only if the user agrees should you proceed with sharing them. As we are the user, we don't need this step. ### 4. Share Credential(s) With Verifier Now that we have identified the credentials to share and received confirmation from the wallet owner, we are ready to reply to the verifier's presentation request. However, before we can do so we need to resolve the original presentation request using the following endpoint `/exchange/resolvePresentationRequest` #### 4.1. Resolve Presentation Request **Option: CURL** **Endpoint:** `/wallet-api/wallet/{wallet}/exchange/resolvePresentationRequest`| [API Reference](https://wallet.demo.walt.id/swagger/index.html#/Credential%20exchange/post_wallet_api_wallet__wallet__exchange_resolvePresentationRequest) **Example Request** ```bash curl -X 'POST' \ 'http://0.0.0.0:7001/wallet-api/wallet/{wallet_id}/exchange/resolvePresentationRequest' \ -H 'accept: text/plain' \ -H 'Content-Type: text/plain' \ -H 'authorization: Bearer {loginToken}' \ -d '{authorizationRequestFromVerifier}' ``` **Path Parameters** - **wallet_id** - Extract needed wallet id parameter via the `/wallet-api/wallet/accounts/wallets` endpoint as explained [here](#retrieve-wallet-id). Provided as part of the URL path. **Header Parameters** - `authorization`: _String_ - Bearer token obtained from the [login step](#login-to-wallet-account). Format: `Bearer {loginToken}`. **Body** The original presentation request received from the verifier. ``` openid4vp://authorize? response_type=vp_token& client_id=https%3A%2F%2Fverifier.demo.walt.id%2Fopenid4vc%2Fverify& response_mode=direct_post& state=V3pp6GFoSro6& presentation_definition_uri=https%3A%2F%2Fverifier.demo.walt.id%2Fopenid4vc%2Fpd%2FV3pp6GFoSro6& client_id_scheme=redirect_uri& client_metadata=%7B%22authorization_encrypted_response_alg%22%3A%22ECDH-ES%22%2C%22authorization_encrypted_response_enc%22%3A%22A256GCM%22%7D& nonce=0c4e8d3c-1017-4a38-b6d2-468e4a2c13f8& response_uri=https%3A%2F%2Fverifier.demo.walt.id%2Fopenid4vc%2Fverify%2FV3pp6GFoSro6 ``` **Body Parameters** The original presentation request received from the verifier. --- **Example Response** ``` openid4vp://authorize?response_type=vp_token&client_id=https%3A%2F%2Fverifier.demo.walt.id%2Fopenid4vc%2Fverify&response_mode=direct_post&state=V3pp6GFoSro6&presentation_definition=%7B%22id%22%3A%22GgLtLahfRjuK%22%2C%22input_descriptors%22%3A%5B%7B%22id%22%3A%22VerifiableId%22%2C%22format%22%3A%7B%22jwt_vc_json%22%3A%7B%22alg%22%3A%5B%22EdDSA%22%5D%7D%7D%2C%22constraints%22%3A%7B%22fields%22%3A%5B%7B%22path%22%3A%5B%22%24.type%22%5D%2C%22filter%22%3A%7B%22type%22%3A%22string%22%2C%22pattern%22%3A%22VerifiableId%22%7D%7D%5D%7D%7D%5D%7D&presentation_definition_uri=https%3A%2F%2Fverifier.demo.walt.id%2Fopenid4vc%2Fpd%2FV3pp6GFoSro6&client_id_scheme=redirect_uri&client_metadata=%7B%22authorization_encrypted_response_alg%22%3A%22ECDH-ES%22%2C%22authorization_encrypted_response_enc%22%3A%22A256GCM%22%7D&nonce=0c4e8d3c-1017-4a38-b6d2-468e4a2c13f8&response_uri=https%3A%2F%2Fverifier.demo.walt.id%2Fopenid4vc%2Fverify%2FV3pp6GFoSro6 ``` #### 4.2. Fulfill Presentation Request **Option: CURL** **Endpoint:** `/wallet-api/wallet/{wallet}/exchange/usePresentationRequest`| [API Reference](https://wallet.demo.walt.id/swagger/index.html#/Credential%20exchange/post_wallet_api_wallet__wallet__exchange_usePresentationRequest) **Example Request** ```bash curl -X 'POST' \ 'http://0.0.0.0:7001/wallet-api/wallet/{wallet_id}/exchange/usePresentationRequest' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -H 'authorization: Bearer {loginToken}' \ -d '{ "presentationRequest": "{resolvedPresentationRequestFromLastStep}", "selectedCredentials": [ "{credentialIDMatchFromStepBeforeLastStep}" ] }' ``` **Path Parameters** - **wallet_id** - Extract needed wallet id parameter via the `/wallet-api/wallet/accounts/wallets` endpoint as explained [here](#retrieve-wallet-id). Provided as part of the URL path. **Header Parameters** - `authorization`: _String_ - Bearer token obtained from the [login step](#login-to-wallet-account). Format: `Bearer {loginToken}`. **Body** ```json { "presentationRequest": "openid4vp://authorize?response_type=vp_token&client_id=https%3A%2F%2Fverifier.demo.walt.id%2Fopenid4vc%2Fverify&response_mode=direct_post&state=pXZz119vn5SG&presentation_definition=%7B%22id%22%3A%222k6OTMoo2wbU%22%2C%22input_descriptors%22%3A%5B%7B%22id%22%3A%22VerifiableId%22%2C%22format%22%3A%7B%22jwt_vc_json%22%3A%7B%22alg%22%3A%5B%22EdDSA%22%5D%7D%7D%2C%22constraints%22%3A%7B%22fields%22%3A%5B%7B%22path%22%3A%5B%22%24.type%22%5D%2C%22filter%22%3A%7B%22type%22%3A%22string%22%2C%22pattern%22%3A%22VerifiableId%22%7D%7D%5D%7D%7D%5D%7D&presentation_definition_uri=https%3A%2F%2Fverifier.demo.walt.id%2Fopenid4vc%2Fpd%2FpXZz119vn5SG&client_id_scheme=redirect_uri&client_metadata=%7B%22authorization_encrypted_response_alg%22%3A%22ECDH-ES%22%2C%22authorization_encrypted_response_enc%22%3A%22A256GCM%22%7D&nonce=d319f72a-cadf-4b15-827f-8ed0e9f755c0&response_uri=https%3A%2F%2Fverifier.demo.walt.id%2Fopenid4vc%2Fverify%2FpXZz119vn5SG", "selectedCredentials": [ "urn:uuid:fb09ba3d-b73d-49ad-9d88-3d018878ea83" ] } ``` **Body Parameters** - `presentationRequest`: _String_ - response from previous call to [resolvePresentationRequest](https://docs.walt.id/community-stack/wallet/credential-exchange/guides/present-vc-oid4vp#_41-resolve-presentation-request) - `selectedCredentials`: _Array_ - list of credential id's to share which we received from the call to [find matching credentials](https://docs.walt.id/community-stack/wallet/credential-exchange/guides/present-vc-oid4vp#_2-find-credentials-matching-presentation-definition) - `disclosures`: (optional) _Object_ - This object specifies which selectively disclosable fields of a credential should be shared with the verifier. It is applicable only if the credential to be shared contains selective disclosure attributes. The object uses the credential ID as the key(s) and an array of the disclosures of the fields to be disclosed by credential as values. **Example:** ```json "disclosures": { "urn:uuid:fb09ba3d-b73d-49ad-9d88-3d018878ea83": [ "WyJiZ1I5OTdnRUVRNEU5bFpXNEhwVjRRPT0iLCJuYW1lIiwiSkZGIHggdmMtZWR1IFBsdWdGZXN0IDMgSW50ZXJvcGVyYWJpbGl0eSJd" ], "urn:uuid:fb0234-b7234d-55ad-25d88-3d018878ea83": [ "WyJiZ1I5OTdnRUVRNEU5bFpXNEhwVjRRPT0iLCJuYW1lIiwiSkZGIHggdmMtZWR1IFBsdWdGZXN0IDMgSW50ZXJvcGVyYWJpbGl0eSJd" ] } ``` **Explanation:** - **Key**: The unique identifier (ID) of the credential to be shared. - **Value**: An array containing the disclosures of credential fields that should be disclosed. This structure allows you to specify which parts of a credential are shared on a per-credential basis, enhancing privacy and control over the information disclosed. --- **Example Response** If the verifier provided a redirect URI we will receive it as a response. ```json { "redirectUri": null } ``` ## Verify Credential Presentation Now we return to the Verifier to confirm whether the information provided by the wallet is valid and has successfully passed all necessary checks. **Option: CURL** **Endpoint:** `/openid4vc/session/{id}`| [API Reference](https://verifier.demo.walt.id/swagger/index.html#/Credential%20Verification/get_openid4vc_session__id_) First, we need to return to the original Authorization Request that [we created](#create-authorization-request) and retrieve the `state` value from it. This state value serves as a unique identifier for this specific verification request. **Example** ``` openid4vp://authorize?...state=a07bdb17-7d87-4965-9296-1adefcaaddd9... ``` Making the call to receive the verification result **Example Call** ```bash curl -X 'GET' \ 'http://0.0.0.0:7003/openid4vc/session/{state}' \ -H 'accept: */*' ``` **Path Parameters** - `state` _String_ - state value from [authorization request created](#create-authorization-request) via the `openid4vc/verify` endpoint. **Example Response** ```json { "id": "Ag9gyQkYknmI", "presentationDefinition": { "id": "8cu3ixcb4cm", "input_descriptors": [ { "id": "UniversityDegree", "format": { "jwt_vc_json": { "alg": [ "EdDSA" ] } }, "constraints": { "fields": [ { "path": [ "$.vc.type" ], "filter": { "type": "string", "pattern": "UniversityDegree" } } ] } } ], "customParameters": {} }, "tokenResponse": { "vp_token": "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaWVWUk9XRXBFVUZKRU0wUnFVSGRYYXpBMFNHeEhVbGhzVkVod09WTk5OVE4xY0hnelRGcEpMV1JrVlNJc0luZ2lPaUpMWVUwelYxazRObWR4ZHpGdFdsUnRORk00VmtNMGJHeG5RV1ZKVjI1Q2FUZHliMEZ2WVMwNWNVWnZJbjAjMCIsInR5cCI6IkpXVCIsImFsZyI6IkVkRFNBIn0.eyJzdWIiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaWVWUk9XRXBFVUZKRU0wUnFVSGRYYXpBMFNHeEhVbGhzVkVod09WTk5OVE4xY0hnelRGcEpMV1JrVlNJc0luZ2lPaUpMWVUwelYxazRObWR4ZHpGdFdsUnRORk00VmtNMGJHeG5RV1ZKVjI1Q2FUZHliMEZ2WVMwNWNVWnZJbjAiLCJuYmYiOjE3NDQ4NzczNjAsImlhdCI6MTc0NDg3NzQyMCwianRpIjoiOGN1M2l4Y2I0Y20iLCJpc3MiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaWVWUk9XRXBFVUZKRU0wUnFVSGRYYXpBMFNHeEhVbGhzVkVod09WTk5OVE4xY0hnelRGcEpMV1JrVlNJc0luZ2lPaUpMWVUwelYxazRObWR4ZHpGdFdsUnRORk00VmtNMGJHeG5RV1ZKVjI1Q2FUZHliMEZ2WVMwNWNVWnZJbjAiLCJub25jZSI6ImIxNmZkNDY2LTM4NDktNDc2Mi1hZGE4LTJjZmU3MzdkYWJhNSIsImF1ZCI6Imh0dHA6Ly9ob3N0LmRvY2tlci5pbnRlcm5hbDo3MDAzL29wZW5pZDR2Yy92ZXJpZnkiLCJ2cCI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVQcmVzZW50YXRpb24iXSwiaWQiOiI4Y3UzaXhjYjRjbSIsImhvbGRlciI6ImRpZDpqd2s6ZXlKcmRIa2lPaUpQUzFBaUxDSmpjbllpT2lKRlpESTFOVEU1SWl3aWEybGtJam9pZVZST1dFcEVVRkpFTTBScVVIZFhhekEwU0d4SFVsaHNWRWh3T1ZOTk5UTjFjSGd6VEZwSkxXUmtWU0lzSW5naU9pSkxZVTB6VjFrNE5tZHhkekZ0V2xSdE5GTTRWa00wYkd4blFXVkpWMjVDYVRkeWIwRnZZUzA1Y1VadkluMCIsInZlcmlmaWFibGVDcmVkZW50aWFsIjpbImV5SnJhV1FpT2lKa2FXUTZhbmRyT21WNVNuSmtTR3RwVDJsS1VGTXhRV2xNUTBwcVkyNVphVTlwU2taYVJFa3hUbFJGTlVscGQybGhNbXhyU1dwdmFXRlZjRTVWZWxacFlURndWMU5YZUhWWk1scDRXREI0YlZneFRqRmxSVzk1VTI1U1VrNVZhREpaV0c4elpFWmtVV0pyUm5GV1ZsWnJZM2xKYzBsdVoybFBhVXBIVjIxU01tUXdUVFJaVldSdlZXNWtlR1ZzWkhka1IxWnhUVVUxWVZvelVqTlhWVVpLVFZaT05WSnRZM2hpVlhSRlVsWlNVRnB1UmtaSmJqQWphVXBOVXpWaWExcFdTV3h1WTJaeFgweG1YMU4xZUVveVNuUlJOVWgyWVhvM2RGZFFia0ZxVlZWa2N5SXNJblI1Y0NJNklrcFhWQ0lzSW1Gc1p5STZJa1ZrUkZOQkluMC5leUpwYzNNaU9pSmthV1E2YW5kck9tVjVTbkprU0d0cFQybEtVRk14UVdsTVEwcHFZMjVaYVU5cFNrWmFSRWt4VGxSRk5VbHBkMmxoTW14clNXcHZhV0ZWY0U1VmVsWnBZVEZ3VjFOWGVIVlpNbHA0V0RCNGJWZ3hUakZsUlc5NVUyNVNVazVWYURKWldHOHpaRVprVVdKclJuRldWbFpyWTNsSmMwbHVaMmxQYVVwSFYyMVNNbVF3VFRSWlZXUnZWVzVrZUdWc1pIZGtSMVp4VFVVMVlWb3pVak5YVlVaS1RWWk9OVkp0WTNoaVZYUkZVbFpTVUZwdVJrWkpiakFpTENKemRXSWlPaUprYVdRNmFuZHJPbVY1U25Ka1NHdHBUMmxLVUZNeFFXbE1RMHBxWTI1WmFVOXBTa1phUkVreFRsUkZOVWxwZDJsaE1teHJTV3B2YVdWV1VrOVhSWEJGVlVaS1JVMHdVbkZWU0dSWVlYcEJNRk5IZUVoVmJHaHpWa1ZvZDA5V1RrNU9WRTR4WTBobmVsUkdjRXBNVjFKclZsTkpjMGx1WjJsUGFVcE1XVlV3ZWxZeGF6Uk9iV1I0WkhwR2RGZHNVblJPUmswMFZtdE5NR0pIZUc1UlYxWktWakkxUTJGVVpIbGlNRVoyV1ZNd05XTlZXblpKYmpBaUxDSjJZeUk2ZXlKQVkyOXVkR1Y0ZENJNld5Sm9kSFJ3Y3pvdkwzZDNkeTUzTXk1dmNtY3ZNakF4T0M5amNtVmtaVzUwYVdGc2N5OTJNU0lzSW1oMGRIQnpPaTh2ZDNkM0xuY3pMbTl5Wnk4eU1ERTRMMk55WldSbGJuUnBZV3h6TDJWNFlXMXdiR1Z6TDNZeElsMHNJbWxrSWpvaWRYSnVPblYxYVdRNk9EQmhPREUzTnpFdE56azRNUzAwWkdFMkxUZ3laV1F0TWpCaE9USTJOekppWm1GbUlpd2lkSGx3WlNJNld5SldaWEpwWm1saFlteGxRM0psWkdWdWRHbGhiQ0lzSWxWdWFYWmxjbk5wZEhsRVpXZHlaV1VpWFN3aWFYTnpkV1Z5SWpwN0ltbGtJam9pWkdsa09tcDNhenBsZVVweVpFaHJhVTlwU2xCVE1VRnBURU5LYW1OdVdXbFBhVXBHV2tSSk1VNVVSVFZKYVhkcFlUSnNhMGxxYjJsaFZYQk9WWHBXYVdFeGNGZFRWM2gxV1RKYWVGZ3dlRzFZTVU0eFpVVnZlVk51VWxKT1ZXZ3lXVmh2TTJSR1pGRmlhMFp4VmxaV2EyTjVTWE5KYm1kcFQybEtSMWR0VWpKa01FMDBXVlZrYjFWdVpIaGxiR1IzWkVkV2NVMUZOV0ZhTTFJelYxVkdTazFXVGpWU2JXTjRZbFYwUlZKV1VsQmFia1pHU1c0d0luMHNJbWx6YzNWaGJtTmxSR0YwWlNJNklqSXdNalV0TURRdE1UZFVNRGM2TkRnNk5EWXVNelkxTkRrM01EZzJXaUlzSW1OeVpXUmxiblJwWVd4VGRXSnFaV04wSWpwN0ltbGtJam9pWkdsa09tcDNhenBsZVVweVpFaHJhVTlwU2xCVE1VRnBURU5LYW1OdVdXbFBhVXBHV2tSSk1VNVVSVFZKYVhkcFlUSnNhMGxxYjJsbFZsSlBWMFZ3UlZWR1NrVk5NRkp4VlVoa1dHRjZRVEJUUjNoSVZXeG9jMVpGYUhkUFZrNU9UbFJPTVdOSVozcFVSbkJLVEZkU2ExWlRTWE5KYm1kcFQybEtURmxWTUhwV01XczBUbTFrZUdSNlJuUlhiRkowVGtaTk5GWnJUVEJpUjNodVVWZFdTbFl5TlVOaFZHUjVZakJHZGxsVE1EVmpWVnAyU1c0d0lpd2laR1ZuY21WbElqcDdJblI1Y0dVaU9pSkNZV05vWld4dmNrUmxaM0psWlNJc0ltNWhiV1VpT2lKQ1lXTm9aV3h2Y2lCdlppQlRZMmxsYm1ObElHRnVaQ0JCY25SekluMTlMQ0psZUhCcGNtRjBhVzl1UkdGMFpTSTZJakl3TWpZdE1EUXRNVGRVTURjNk5EZzZORFl1TXpZMk1EQTJNemMzV2lKOUxDSnFkR2tpT2lKMWNtNDZkWFZwWkRvNE1HRTRNVGMzTVMwM09UZ3hMVFJrWVRZdE9ESmxaQzB5TUdFNU1qWTNNbUptWVdZaUxDSmxlSEFpT2pFM056WTBNVEl4TWpZc0ltbGhkQ0k2TVRjME5EZzNOakV5Tml3aWJtSm1Jam94TnpRME9EYzJNVEkyZlEuSVNPNmhxRmVNdnpBOE4tRXRKcEFYd2h2dWZTaWIxYk9FYTFWdGhhMWlpSGJHN1plTVFGVll6ZnJmbnBpRmJXM2ExaV9ySVRud3RCS2VLNkVVUGdUQkEiXX19.AUqf8cy4UgaEbL4-2dsJ1fkEf_xPRJkbBhOJJOJHw8XsV6xDAXFEaakw6IMyr47cpUr9C-O1nji4-tHjbocyCg", "presentation_submission": { "id": "8cu3ixcb4cm", "definition_id": "8cu3ixcb4cm", "descriptor_map": [ { "id": "UniversityDegree", "format": "jwt_vp", "path": "$", "path_nested": { "id": "UniversityDegree", "format": "jwt_vc_json", "path": "$.verifiableCredential[0]", "customParameters": {} } } ] }, "state": "Ag9gyQkYknmI", "customParameters": {} }, "verificationResult": true, "policyResults": { "results": [ { "credential": "VerifiablePresentation", "policyResults": [ { "policy": "signature", "description": "Checks a JWT credential by verifying its cryptographic signature using the key referenced by the DID in `iss`.", "is_success": true, "result": { "sub": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoieVROWEpEUFJEM0RqUHdXazA0SGxHUlhsVEhwOVNNNTN1cHgzTFpJLWRkVSIsIngiOiJLYU0zV1k4NmdxdzFtWlRtNFM4VkM0bGxnQWVJV25CaTdyb0FvYS05cUZvIn0", "nbf": 1744877360, "iat": 1744877420, "jti": "8cu3ixcb4cm", "iss": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoieVROWEpEUFJEM0RqUHdXazA0SGxHUlhsVEhwOVNNNTN1cHgzTFpJLWRkVSIsIngiOiJLYU0zV1k4NmdxdzFtWlRtNFM4VkM0bGxnQWVJV25CaTdyb0FvYS05cUZvIn0", "nonce": "b16fd466-3849-4762-ada8-2cfe737daba5", "aud": "http://host.docker.internal:7003/openid4vc/verify", "vp": { "@context": [ "https://www.w3.org/2018/credentials/v1" ], "type": [ "VerifiablePresentation" ], "id": "8cu3ixcb4cm", "holder": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoieVROWEpEUFJEM0RqUHdXazA0SGxHUlhsVEhwOVNNNTN1cHgzTFpJLWRkVSIsIngiOiJLYU0zV1k4NmdxdzFtWlRtNFM4VkM0bGxnQWVJV25CaTdyb0FvYS05cUZvIn0", "verifiableCredential": [ "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaWFVcE5VelZpYTFwV1NXeHVZMlp4WDB4bVgxTjFlRW95U25SUk5VaDJZWG8zZEZkUWJrRnFWVlZrY3lJc0luZ2lPaUpHV21SMmQwTTRZVWRvVW5keGVsZHdkR1ZxTUU1YVozUjNXVUZKTVZONVJtY3hiVXRFUlZSUFpuRkZJbjAjaUpNUzVia1pWSWxuY2ZxX0xmX1N1eEoySnRRNUh2YXo3dFdQbkFqVVVkcyIsInR5cCI6IkpXVCIsImFsZyI6IkVkRFNBIn0.eyJpc3MiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaWFVcE5VelZpYTFwV1NXeHVZMlp4WDB4bVgxTjFlRW95U25SUk5VaDJZWG8zZEZkUWJrRnFWVlZrY3lJc0luZ2lPaUpHV21SMmQwTTRZVWRvVW5keGVsZHdkR1ZxTUU1YVozUjNXVUZKTVZONVJtY3hiVXRFUlZSUFpuRkZJbjAiLCJzdWIiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaWVWUk9XRXBFVUZKRU0wUnFVSGRYYXpBMFNHeEhVbGhzVkVod09WTk5OVE4xY0hnelRGcEpMV1JrVlNJc0luZ2lPaUpMWVUwelYxazRObWR4ZHpGdFdsUnRORk00VmtNMGJHeG5RV1ZKVjI1Q2FUZHliMEZ2WVMwNWNVWnZJbjAiLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL2V4YW1wbGVzL3YxIl0sImlkIjoidXJuOnV1aWQ6ODBhODE3NzEtNzk4MS00ZGE2LTgyZWQtMjBhOTI2NzJiZmFmIiwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIlVuaXZlcnNpdHlEZWdyZWUiXSwiaXNzdWVyIjp7ImlkIjoiZGlkOmp3azpleUpyZEhraU9pSlBTMUFpTENKamNuWWlPaUpGWkRJMU5URTVJaXdpYTJsa0lqb2lhVXBOVXpWaWExcFdTV3h1WTJaeFgweG1YMU4xZUVveVNuUlJOVWgyWVhvM2RGZFFia0ZxVlZWa2N5SXNJbmdpT2lKR1dtUjJkME00WVVkb1VuZHhlbGR3ZEdWcU1FNWFaM1IzV1VGSk1WTjVSbWN4YlV0RVJWUlBabkZGSW4wIn0sImlzc3VhbmNlRGF0ZSI6IjIwMjUtMDQtMTdUMDc6NDg6NDYuMzY1NDk3MDg2WiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmp3azpleUpyZEhraU9pSlBTMUFpTENKamNuWWlPaUpGWkRJMU5URTVJaXdpYTJsa0lqb2llVlJPV0VwRVVGSkVNMFJxVUhkWGF6QTBTR3hIVWxoc1ZFaHdPVk5OTlROMWNIZ3pURnBKTFdSa1ZTSXNJbmdpT2lKTFlVMHpWMWs0Tm1keGR6RnRXbFJ0TkZNNFZrTTBiR3huUVdWSlYyNUNhVGR5YjBGdllTMDVjVVp2SW4wIiwiZGVncmVlIjp7InR5cGUiOiJCYWNoZWxvckRlZ3JlZSIsIm5hbWUiOiJCYWNoZWxvciBvZiBTY2llbmNlIGFuZCBBcnRzIn19LCJleHBpcmF0aW9uRGF0ZSI6IjIwMjYtMDQtMTdUMDc6NDg6NDYuMzY2MDA2Mzc3WiJ9LCJqdGkiOiJ1cm46dXVpZDo4MGE4MTc3MS03OTgxLTRkYTYtODJlZC0yMGE5MjY3MmJmYWYiLCJleHAiOjE3NzY0MTIxMjYsImlhdCI6MTc0NDg3NjEyNiwibmJmIjoxNzQ0ODc2MTI2fQ.ISO6hqFeMvzA8N-EtJpAXwhvufSib1bOEa1Vtha1iiHbG7ZeMQFVYzfrfnpiFbW3a1i_rITnwtBKeK6EUPgTBA" ] } } } ] }, { "credential": "UniversityDegree", "policyResults": [ { "policy": "signature", "description": "Checks a JWT credential by verifying its cryptographic signature using the key referenced by the DID in `iss`.", "is_success": true, "result": { "iss": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiaUpNUzVia1pWSWxuY2ZxX0xmX1N1eEoySnRRNUh2YXo3dFdQbkFqVVVkcyIsIngiOiJGWmR2d0M4YUdoUndxeldwdGVqME5aZ3R3WUFJMVN5RmcxbUtERVRPZnFFIn0", "sub": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoieVROWEpEUFJEM0RqUHdXazA0SGxHUlhsVEhwOVNNNTN1cHgzTFpJLWRkVSIsIngiOiJLYU0zV1k4NmdxdzFtWlRtNFM4VkM0bGxnQWVJV25CaTdyb0FvYS05cUZvIn0", "vc": { "@context": [ "https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1" ], "id": "urn:uuid:80a81771-7981-4da6-82ed-20a92672bfaf", "type": [ "VerifiableCredential", "UniversityDegree" ], "issuer": { "id": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiaUpNUzVia1pWSWxuY2ZxX0xmX1N1eEoySnRRNUh2YXo3dFdQbkFqVVVkcyIsIngiOiJGWmR2d0M4YUdoUndxeldwdGVqME5aZ3R3WUFJMVN5RmcxbUtERVRPZnFFIn0" }, "issuanceDate": "2025-04-17T07:48:46.365497086Z", "credentialSubject": { "id": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoieVROWEpEUFJEM0RqUHdXazA0SGxHUlhsVEhwOVNNNTN1cHgzTFpJLWRkVSIsIngiOiJLYU0zV1k4NmdxdzFtWlRtNFM4VkM0bGxnQWVJV25CaTdyb0FvYS05cUZvIn0", "degree": { "type": "BachelorDegree", "name": "Bachelor of Science and Arts" } }, "expirationDate": "2026-04-17T07:48:46.366006377Z" }, "jti": "urn:uuid:80a81771-7981-4da6-82ed-20a92672bfaf", "exp": 1776412126, "iat": 1744876126, "nbf": 1744876126 } } ] } ], "time": "PT2.675756835S", "policiesRun": 2 }, "customParameters": {} } ``` **Verification Status Policies Response** The response of the verification status call will contain the status of the verification policies applied to the credential(s) presented by the user. The policy results will be in the following format: ```json { "verificationResult": true, "policyResults": { "results": [ { "credential": "VerifiableDiploma", "policies": [ { "policy": "signature", "is_success": true } ] } ] } } ``` The `verificationResult` field will be `true` if all policies were successful, otherwise it will be `false`. The `policyResults` field will contain the results of the policies applied to each credential. The `credential` field will contain the name of the credential, and the `policies` field will contain the results of the policies applied to the credential. The `policy` field will contain the name of the policy, and the `is_success` field will contain the result of the policy. ## Next Steps **Note:** If you enjoy our tools, please leave us a star ⭐ [on GitHub](https://github.com/walt-id/waltid-identity).
You can learn more about the the different APIs in detail below: - [Wallet](https://docs.walt.id/community-stack/wallet){:target="_blank"} - [Issuer](https://docs.walt.id/community-stack/issuer){:target="_blank"} - [Verifier](https://docs.walt.id/community-stack/verifier){:target="_blank"} -------------------------------------------------------------------------------- PAGE: What is the Community Stack URL: https://docs.walt.id/community-stack/home/the-community-stack RAW: https://docs.walt.id/community-stack/home/the-community-stack.md STACK: Community Stack (open source) — version 0.21.0 -------------------------------------------------------------------------------- # What is the Community Stack The walt.id Community Stack is **a set of open-source tools designed for developers and organizations to build robust end-to-end identity and wallet solutions** across industries and ecosystems. It is designed to facilitate the development and deployment of holistic identity solutions, ensuring modularity, interoperability, and ease of use. ## Architecture and Structure The walt.id Community Stack provides all the necessary functionalities to launch identity solutions seamlessly. **Its architecture can be broadly categorized into multi-platform libraries, services and APIs, Applications and external services and integrations** as shown below in the architecture image. ![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`. ################################################################################ # PART 3 OF 3: ENTERPRISE STACK — COMMERCIAL (239 pages) # The Enterprise Stack is walt.id's COMMERCIAL product line: one multi-tenant # platform organized into organizations, tenants, and services (KMS, DID, Issuer, # Verifier, Wallet, Credential Status, ...), with admin APIs, role-based access, # and external KMS/IAM integrations. Its API paths (/v1/{target}/...) differ from # the Community Stack APIs. Do NOT confuse with the open-source Community Stack. ################################################################################ -------------------------------------------------------------------------------- PAGE: Getting Started URL: https://docs.walt.id/enterprise-stack/home RAW: https://docs.walt.id/enterprise-stack/home.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Getting Started Explore the different Enterprise features below or learn more about the walt.id Enterprise Stack in general [here](https://docs.walt.id/enterprise-stack/home/the-enterprise-stack.md). Video: https://www.youtube.com/watch?v=FKzoD9F23VE ### Foundations Learn the core building blocks of the Enterprise Stack: Organisations, Tenants, Services, and Permissions & Access. [Checkout Enterprise Foundations](https://docs.walt.id/enterprise-stack/foundations.md) ### Administration Explore how to use Organizations, Tenants, and allow Access & Permissions in detail. - [Organizations](https://docs.walt.id/enterprise-stack/administration/organizations/overview.md) - The starting point. - [Tenants](https://docs.walt.id/enterprise-stack/administration/tenants/overview.md) - Container for services. - [Access & Permissions](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/overview.md) - Secure your enterprise infrastructure. ### Services - [KMS Service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) - Manage cryptographic keys. - [DID Service](https://docs.walt.id/enterprise-stack/services/did-service/overview.md) - Create decentralized identifiers. - [Issuer Service](https://docs.walt.id/enterprise-stack/services/issuer-service/overview.md) - Sign & issue various digital credentials (W3C, SD-JWT VC). - [Verifier Service](https://docs.walt.id/enterprise-stack/services/verifier-service/overview.md) - Request & verify digital credentials. - [Wallet Service](https://docs.walt.id/enterprise-stack/services/wallet-service/overview.md) - Run identity wallets for individuals or organizations. - [Credential Status Service](https://docs.walt.id/enterprise-stack/services/credential-status-service/overview.md) - Manage credential status lists. - [DID Store Service](https://docs.walt.id/enterprise-stack/services/did-store-service/overview.md) - Store and manage DIDs. - [Credential Store Service](https://docs.walt.id/enterprise-stack/services/credential-store-service/overview.md) - Store and query credentials. - [DID Registry](https://docs.walt.id/enterprise-stack/services/did-registry/overview.md) - Host did:web documents. Checkout full list [here](https://docs.walt.id/enterprise-stack/services.md) ---- ## Quickstart
**Note:** You need to be a walt.id Enterprise customer to use this quickstart.
1. **Clone the repository** ```bash git clone https://github.com/walt-id/waltid-enterprise-quickstart.git ``` 2. **Login to Docker** ```bash docker login -u waltid ``` When prompted, use your personal access token as the password. 3. **Start docker-compose** ```bash docker-compose up ``` Once docker-compose is running, you can visit [enterprise.localhost:3000/swagger](http://enterprise.localhost:3000/swagger/index.html) to access the Enterprise APIs. ### Next Steps - [Enterprise Foundations](https://docs.walt.id/enterprise-stack/foundations.md) - Learn more about the Enterprise Stack core building blocks - [Quickstart (5 min)](https://docs.walt.id/enterprise-stack/home/quickstart-5-min.md) - Guided CLI setup - [Tutorial (30 min)](https://docs.walt.id/enterprise-stack/home/tutorial-30-min.md) - Step-by-step walkthrough - [Administration](https://docs.walt.id/enterprise-stack/administration.md) - Learn about organizations, tenants, and the details of authentication and access management. - [Services](https://docs.walt.id/enterprise-stack/services.md) - Explore various services (e.g., issuer, verifier) and their detailed usage. - [FAQ](https://docs.walt.id/enterprise-stack/home/faq.md) - Frequently asked questions about the Enterprise Stack. -------------------------------------------------------------------------------- PAGE: Quickstart (5 min) URL: https://docs.walt.id/enterprise-stack/home/quickstart-5-min RAW: https://docs.walt.id/enterprise-stack/home/quickstart-5-min.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Quickstart (5 min) Video: https://youtu.be/47KN0km-ONc?si=xbJM7pz-SeGzlqdU ## 1. Clone the Quickstart Guide we will be using the [walt.id enterprise quickstart repository](https://github.com/walt-id/waltid-enterprise-quickstart). It can be used to explore enterprise features via a CLI tool and/or to bring up the whole stack using Docker Compose. We will CLI tool for this guide. **Clone the repo** ```bash git clone https://github.com/walt-id/waltid-enterprise-quickstart.git ``` **Change Working Directory** ```bash cd waltid-enterprise-quickstart ``` **Add Docker Token** Save the docker-access token provided by walt.id to get access to the private enterprise stack img. ```bash echo "DOCKER_TOKEN_PROVIDED_BY_WALT_ID_HERE" > .docker-token ``` ## 2. Start the Enterprise API ```bash ./waltid-enterprise run ``` ## 3. Run The Wizard **Note:** Make sure to open a second window in your terminal and keep the Enterprise Stack running in the other.
**Run CLI wizard** ```bash ./waltid-enterprise wizard ``` ## Next Steps If you want to learn about the API calls behind the wizard command, [check out our 30-minute tutorial](https://docs.walt.id/enterprise-stack/home/tutorial-30-min.md) that guides you through issuing and verifying a W3C credential with a revocation status property. -------------------------------------------------------------------------------- PAGE: Tutorial (30 min) URL: https://docs.walt.id/enterprise-stack/home/tutorial-30-min RAW: https://docs.walt.id/enterprise-stack/home/tutorial-30-min.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Tutorial (30 min) **Note:** Please note: You need to be an Enterprise Stack customer & have access to the private enterprise stack images, to go through this tutorial.
In this tutorial, we will provide a comprehensive guide on **setting up the enterprise API on your local machine**. We will demonstrate the **complete process of issuing and verifying a W3C credential** that includes a revocation status property. Additionally, you can watch the accompanying video guide below. Video: https://youtu.be/_oNwxT1-7ns ## 1. Setup For the setup, we will be using the [walt.id enterprise quickstart repository](https://github.com/walt-id/waltid-enterprise-quickstart). It can be used to explore enterprise features via a CLI tool and/or to bring up the whole stack using Docker Compose. We will be using the latter for this tutorial. **Clone the repo** ```bash git clone https://github.com/walt-id/waltid-enterprise-quickstart.git ``` **Change Working Directory** ```bash cd waltid-enterprise-quickstart ``` **Add Docker Token** Save the docker-access token provided by walt.id to get access to the private enterprise stack img. ```bash echo "DOCKER_TOKEN_PROVIDED_BY_WALT_ID_HERE" > .docker-token ``` **Run the stack via docker-compose** ```bash ./waltid-enterprise run ``` When running the docker-compose a mongoDB database is created (used to store data for the Enterprise Stack) and the Enterprise API is started. **Visit the API** Visit [enterprise.localhost:3000/swagger](http://enterprise.localhost:3000/swagger) to view the API. **Note:** Learn more about the base domain (enterprise.localhost) configuration [here](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/enterprise.md) and in general about the configurations files available for the Enterprise Stack [here](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/overview.md). ## 2. Super Admin - Auth The first step, after the Enterprise Stack API is up and running is to register a super-admin user. One or multiple super admin accounts can be specified via the [`superadmin-registration.conf`](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/superadmin-registration.md) file. This file can be found in the `config` folder of the quickstart-repo in your local setup. (`waltid-enterprise-quickstart/config`) ### Activate Super Admin **Option: CURL** Endpoint:`/v1/superadmin/create-by-token` | [API Reference](http://waltid.enterprise.localhost:3000/swagger/index.html#/Administration%20%2F%20Superadmin/post_v1_superadmin_create_by_token) **Example Request** ```bash curl -X 'POST' \ 'http://waltid.enterprise.localhost:3000/v1/superadmin/create-by-token' \ -H 'accept: */*' \ -H 'Content-Type: text/plain' \ -d '' ``` **Body** ```text ``` **Body Content**
Please provide the super-admin-token in the body without any quotation marks. Use the token map key from `superadmin-registration.conf` as value (eg. `super-admin-token`). --- **Response Codes** - `200` - Super admin account activated successfully Now that we have activated the super-admin user, we can login with the super-admin credentials also specified in the [`superadmin-registration.conf`](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/superadmin-registration.md) using the regular account/user login endpoint. ### Login as Super Admin **Option: CURL** Endpoint:`/auth/account/emailpass` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Authentication/post_auth_account_emailpass) **Example Request** ```bash curl -X 'POST' \ 'http://waltid.enterprise.localhost:3000/auth/account/emailpass' \ -H 'accept: */*' \ -H 'Content-Type: application/json' \ -d '{ "email": "superadmin@walt.id", "password": "pw-according-super-admin-conf" }' ``` **Body** ```json { "email": "superadmin@walt.id", "password": "pw-according-super-admin-conf" } ``` **Body Parameters** - `email`: _String_ - Email of the super admin account. Defined in `superadmin-registration.conf`. - `password`: _String_ - Password of the super admin account. Defined in `superadmin-registration.conf` --- **Response** ```json { "session_id": "39ed1ecd-aa61-4401-bf35-ca0a14a39c8e", "status": "OK", "token": "eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiJmZmZmZmZmZi1mZmZmLWZmZmYtZmZmZi1mZmZmZmZmZmZmZmYiLCJzZXNzaW9uIjoiMzllZDFlY2QtYWE2MS00NDAxLWJmMzUtY2EwYTE0YTM5YzhlIn0.NEKj737AqG7T7SAfQ2RovhWSjBsXCWCLPmkLtr_O5i8q_42BFX7pSGZbIslRy1PEQ3HKrnXib6D06D1kZugvCw" } ``` The token we receive can be used in subsequent requests as a Bearer token. ### Authenticate as Super Admin via API To access all protected endpoints of the Enterprise API, we will use the token obtained from the response of the previous request. This token should be included in the header of each subsequent request. **Example Request** ```bash curl -X 'POST' \ 'http://waltid.enterprise.localhost:3000/v1/...' \ -H 'Authorization: Bearer {tokenReceived}' -d '{ ... }' ``` **Note:** To learn more about the Super Admin Account in general, click [here](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/super-admin/overview.md). ## 3. Organization With the super-admin account active and the auth session token in our Authorization header, we can create an Organization. The organization is at the top of the enterprise stack resource hierarchy, as shown in the diagram below. Under the Organization you can group customers as tenants, sub-tenants, or create a tenant of your own. This way services and their data can be kept virtually separate, enabling you to easily manage different customers or services of your own under one roof. Also, this structure allows you to build and sell B2B2C products (e.g., you can resell to business clients who offer consumer products). ![image of enterprise resource diagram](https://docs.walt.id/img/enterprise-stack/concepts/enterprise-resource-tree.png) **Note:** While the Enterprise Stack allows for multiple Organizations, **on-prem Enterprise deployments should always have only one Organization.** ### Create a new Organization **Option: CURL** **Endpoint: ** `/v1/admin/organizations` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Administration%20(Global)%20%2F%20Organizations/post_v1_admin_organizations) **Example Request** ```bash curl -X 'POST' \ 'http://enterprise.localhost:3000/v1/admin/organizations' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "_id": "waltid", "profile": { "name": "Test GmbH" }, "billing": { "billingCountry": "AT", "billingAddress": "test 111/115, 1090 Vienna", "vatNr": "test" } }' ``` **Body** ```json { "_id": "string", "profile": { "name": "string" }, "billing": { "billingCountry": "", "billingAddress": "", "vatNr": "", "iban": "", "swift": "" } } ``` **Body Parameters** - `_id` - `type`: _String_ - Unique identifier for the organization, e.g. `waltid` - `profile` - `name`: _String_ - Human readable name for the organization. - `billing`: Billing information --- **Response Codes** - `201` - Organization created successfully. When we create a new organization, an admin role is automatically generated in the background. This role is named using the format `[orgID].admin`. For example, if our organization ID is `waltid`, the admin role will be called `waltid.admin`. In the next step, we will use this role to create an API Key that grants admin rights exclusively for a specific organization. This is a common pratice to not always use the super-admin to do regular day-to-day operations. If you want to learn more about how permissions and roles work, please go [here](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/overview.md). Also, each organization is given a unique subdomain, which is utilized for carrying out operations specific to that organization. Operations that are not tied to any particular organization mainly involve the global `admin` endpoints, as well as endpoints that do not require the `target` path parameter. The structure of the subdomain for each new organization is as follows: {orgID}.{baseUrl}. For example, in our case, this would be represented as http://waltid.enterprise.localhost:3000. **Note:** Learn more about the Organization in general [here](https://docs.walt.id/enterprise-stack/administration/organizations/overview.md). ## 4. API Keys - Auth In this section, we will create an API for the organization and grant it admin rights by assigning the `waltid.admin` role to the API key, which was automatically created along with the `waltid` organization in the last step. **Error:** Every operation from now on will be conducted within the scope of an organization, specifically `waltid`. Therefore, we need to update the base URL of our API to include `waltid`. The new URL will be `http://waltid.enterprise.localhost:3000`, in contrast to the previous URL, which was `http://enterprise.localhost:3000`. ## Create API-Key
**Option: CURL** Endpoint:`/v1/{target}/apikeys-api/api-keys/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%2F%20API%20Keys/post_v1__target__apikeys_api_api_keys_create) **Example Request** ```bash curl -X 'POST' \ 'http://waltid.enterprise.localhost:3000/v1/waltid.myApiKey/apikeys-api/api-keys/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "name": "My API Key", "expiration": "30d" }' ``` **Body** ```json { "name": "My API Key", "expiration": "30d" } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization in which to create the new API Key and the API-Key's ID (`{organizationID}.[YourID]`), e.g. `waltid.myApiKey` **Body Parameters** - `name`: _String_ - Human readable name for API-Key. - `expiration`: (optional) _String_ - Expiration time of the API-Key. - Set expiration times using the kotlinx.datetime Duration Syntax. Valid units are days (d), hours (h), minutes (m), and seconds (s), with finer granularity using milliseconds (ms), microseconds ( us), and nanoseconds (ns). Examples include `5h`, `1d 12h`, and `1h 0m 30.340s`. - Alternatively, you can use a simplified ISO-8601 duration format (PThHmMs.fS), such as P1DT2H3M4.058S. In this format: - The largest non-time designator is days (D). Years (Y), weeks (W), and months (M) are not parsed. Instead, use 365d for years, 7d for weeks, and 30d for months. - A day is always assumed to be 24 hours (24-hour clock time scale). - There is currently no alternative week-based representation like ["P"][number]["W"]. --- **Response** ```json { "_id": "waltid.myApiKey", "name": "My API Key", "token": "eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiJhYjNhZjc0Zi0wNmNhLTRkY2EtYWJlZi1iNjA3MTJjOTM5YzEiLCJzZXNzaW9uIjoiYXBpLWFiM2FmNzRmLTA2Y2EtNGRjYS1hYmVmLWI2MDcxMmM5MzljMSJ9.FHEmayjhrfZMnOnEecNnOa6ADDwBm1jL_tue64PhxCTk1DZJeIdz-KPvuwWdSlLgw538fqAIoW8ulv6-B91ZAw" } ``` The returned token can be used as a Bearer token in API requests. **Note:** If you want to check the expiration time of the API-Key you can use the `/v1/{target}/apikeys-api/api-keys/view` endpoint. By default, the API key we just created doesn't have any permissions. Therefore, we will assign the API Key the admin role `waltid.admin` in the next step. ### Assigning Admin Role to API-Key **Option: CURL** Endpoint:`/v1/{target}/roles-api/roles/apikey/assign` **Example Request** ```bash curl -X 'POST' \ 'http://waltid.enterprise.localhost:3000/v1/waltid.admin/roles-api/roles/apikey/assign' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d ' { "apikey": "waltid.myApiKey" }' ``` **Body** ```json { "apikey": "waltid.myApiKey" } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the ID of the role to assign to an API-Key. e.g. `waltid.admin` **Body Parameters** - `apikey`: _resourceIdentifier_ - Specifies the ID of the API-Key to assign the role to. --- **Response Codes** - `200` - Role assigned successfully. ## 3. Tenant In this section, we will move further down the Enterprise Stack hirachy (shown below) and create our first tenant to then be able to launch different types of services like Issuer, Verifier and others. You can also create sub-tenants inside of tenants and sub-tenants. At this point, there is no limit to the level of nested sub-tenants. Services and their data inside tenants and sub-tenants are kept separate. ![image of enterprise resource diagram](https://docs.walt.id/img/enterprise-stack/concepts/enterprise-resource-tree.png) ### Create a New Tenant **Option: CURL** Endpoint:`/v1/{target}/resource-api/tenants/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%2F%20Resource/post_v1__target__resource_api_tenants_create) **Example Request** ```bash curl -X 'POST' \ 'http://waltid.enterprise.localhost:3000/v1/waltid.tenant1/resource-api/tenants/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "name": "My Tenant" }' ``` **Body** ```json { "name": "My Tenant" } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization in which to create the new tenant and the tenant's ID (`{organizationID}.[YourID]`), e.g. `waltid.tenant1` **Body Parameters** - `name`: _String_ - Human readable name for the tenant. --- **Response Codes** - `201` - Tenant created successfully. ## 4. Services With the new tenant created, we will now establish various types of services within this tenant, progressing further down the hierarchy illustrated in the diagram below. ![image of enterprise resource diagram](https://docs.walt.id/img/enterprise-stack/concepts/enterprise-resource-tree.png) ### Services To Be Created - [KMS Service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) - Create and manage keys based on different algorithms. - [DID Service](https://docs.walt.id/enterprise-stack/services/did-service/overview.md) - Create DID based on various methods (did:key, did: web, ...). - [DID Store Service]() - Store and query DIDs & DID Documents. - [Issuer Service](https://docs.walt.id/enterprise-stack/services/issuer-service/overview.md) - Sign & Issue various credentials (W3C, SD-JWT VC) via OID4VC. - [Credential Status Service](https://docs.walt.id/enterprise-stack/services/credential-status-service/overview.md) - Create, update, and manage status credentials. - [Verifier Service](https://docs.walt.id/enterprise-stack/services/verifier-service/overview.md) - Request & verify various types of digital credentials via OID4VP. - [Wallet Service](https://docs.walt.id/enterprise-stack/services/wallet-service/overview.md) - Run identity wallets for individuals or organizations. - [Credential Store Service]() - Store and query different types of credentials (e.g. W3C, SD-JWT VC, mDL). ### Create A KMS Service First, we will set up a Key Management Service (KMS) to create and store the keys necessary for signing the verifiable credential and the status credential. In this tutorial, we will use the Enterprise Stack database to store these keys. However, for production scenarios, it is recommended to store the keys in an external KMS that the Enterprise Stack can access through integrations. For more information about the supported KMS providers, please go [here](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md). ### Setup Key Service **Option: CURL** Endpoint: `/v1/{target}/resource-api/services/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%2F%20Resource/post_v1__target__resource_api_services_create) **Example Request** ```bash curl -X 'POST' \ 'http://waltid.enterprise.localhost:3000/v1/waltid.tenant1.kms1/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "kms" }' ``` **Body** ```json { "type": "kms" } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant in which to create the new KMS service and the service's ID (`{organizationID}.{tenantID}.[NewKmsServiceID]`), e.g. `waltid.tenant1.kms1` **Body Parameters** - `type`: _serviceType_ - Specifies the type of service to create. In our case `kms` --- **Response Codes** - `201` - Service created successfully. ### Create Key **Option: CURL** Endpoint: `/v1/{target}/kms-service-api/keys/generate` | [API Reference](http://waltid.enterprise.localhost:8000/swagger/index.html#/Service%20%2F%20Key%20Management/post_v1__target__kms_service_api_keys_generate) **Example Request** ```bash curl -X 'POST' \ 'http://waltid.enterprise.localhost:3000/v1/waltid.tenant1.kms1.key1/kms-service-api/keys/generate' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "backend": "jwk", "keyType": "Ed25519" }' ``` **Body** ```json { "backend": "jwk", "keyType": "Ed25519" } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + kmsService in which to create the new key and the key's ID (`{organizationID}.{tenantID}.{kmsServiceID}.[newKeyID]`), e.g. `waltid.tenant1.kms1.key1` **Body Parameters** - `backend`: _String_ - Specifies the storage type of key. `jwk` means the local storage option is used and the key is stored in the Enterprise Stack backend. - `keyType`: _String_ - the algorithm used to generate the key. For local, it can be ed25519, secp256k1, secp256r1, or RSA. --- **Response** `201` - Key created successfully. ```json { "_id": "waltid.tenant1.kms1.key1", "key": { "type": "jwk", "jwk": { "kty": "OKP", "d": "ywmoRVTD9fexMtGW0lKE3o9_0ulfzGXr9xHGL0lPhhA", "crv": "Ed25519", "kid": "IQO7DILxtagpTLXkuHkRkJURb2GqcUIwXYZAcGwW1AU", "x": "JOsiIE7ME9UZ8y2H-P5RSuYAUiIfs1ywtdBjMGN7I5s" } }, "parent": "waltid.tenant1.kms1" } ``` Now, we created one key that can be used for both the issuer and the credential status service in this tutorial. In practice, however, different keys should be created for different purposes. You can also use the example above to create another key if desired. In the next step, we will create a DID service and create a did:key for our key. ### Create DID Service **Option: CURL** Endpoint: `/v1/{target}/resource-api/services/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%2F%20Resource/post_v1__target__resource_api_services_create) **Example Request** ```bash curl -X 'POST' \ 'http://waltid.enterprise.localhost:3000/v1/waltid.tenant1.did-service-1/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "did" }' ``` **Body** ```json { "type": "did" } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant in which to create the new DID service and the service's ID (`{organizationID}.{tenantID}.[NewDIDServiceID]`), e.g. `waltid.tenant1.did1` **Body Parameters** - `type`: _serviceType_ - Specifies the type of service to create. In our case `did` --- **Response Codes** - `201` - Service created successfully. ### Create DID Store (for Issuer DID) We will create a DID Store for persisting the issuer's DID and DID Document and link it to the DID Service so that any newly created DID is stored automatically. **Option: CURL** Endpoint: `/v1/{target}/resource-api/services/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%2F%20Resource/post_v1__target__resource_api_services_create) **Example Request** ```bash curl -X 'POST' \ 'http://waltid.enterprise.localhost:3000/v1/waltid.tenant1.did-store-issuer-1/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "did-store" }' ``` **Body** ```json { "type": "did-store" } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant in which to create the new DID store and the service's ID (`{organizationID}.{tenantID}.[NewDidStoreServiceID]`), e.g. `waltid.tenant1.did-store-issuer-1` --- **Response Codes** - `201` - Service created successfully. ### Link DID Store to DID Service Now link the DID Store to the DID Service so that newly created DIDs are stored automatically. **Option: CURL** Endpoint: `/v1/{target}/did-service-api/dids/dependencies/add` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/DID%20Service/post_v1__target__did_service_api_dids_dependencies_add) **Example Request** ```bash curl -X 'POST' \ 'http://waltid.enterprise.localhost:3000/v1/waltid.tenant1.did-service-1/did-service-api/dids/dependencies/add' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d 'waltid.tenant1.did-store-issuer-1' ``` **Body** ``` waltid.tenant1.did-store-issuer-1 ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + DID service to which to add an existing DID Store (`{organizationID}.{tenantID}.{didServiceID}`), e.g. `waltid.tenant1.did-service-1` --- **Response Codes** - `201` - Unit attached. Before creating a DID from a key, run the same dependency call again with the KMS ID: ```bash curl -X 'POST' \ 'http://waltid.enterprise.localhost:3000/v1/waltid.tenant1.did-service-1/did-service-api/dids/dependencies/add' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d 'waltid.tenant1.kms1' ``` ### Create did:key **Option: CURL** Endpoint: `/v1/{target}/did-service-api/dids/create/key` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/DID%20Service/post_v1__target__did_service_api_dids_create_key) **Example Request** ```bash curl -X 'POST' \ 'http://waltid.enterprise.localhost:3000/v1/waltid.tenant1.did-service-1/did-service-api/dids/create/key' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "keyId": "waltid.tenant1.kms1.key1", "useJwkJcsPub": false }' ``` **Body** ```json { "keyId": "waltid.tenant1.kms1.key1", "useJwkJcsPub": false } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + DID service in which to execute the DID creation (`{organizationID}.{tenantID}.{didServiceID}`), e.g. `waltid.tenant1.did1` **Body Parameters** - `keyId`: _resourceIdentifier_ - Specifies the key for which to generate a DID + DID Document. Please make sure that the key is stored in a KMS service under the same tenant as the DID service. - `useJwkJcsPub`: _boolean_ - If set to `true` it generates an EBSI compliant DID:key + DID Document. This is only important if you are operating within the EBSI ecosystem. **Note:** Because we linked a DID Store to the DID Service, the new DID and DID Document will be stored automatically in `waltid.tenant1.did-store-issuer-1`. --- **Response** - `201` - DID created successfully. ```json { "did": "did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt", "document": { "@context": [ "https://www.w3.org/ns/did/v1", "https://w3id.org/security/suites/jws-2020/v1" ], "id": "did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt", "verificationMethod": [ { "id": "did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt#z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt", "type": "JsonWebKey2020", "controller": "did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt", "publicKeyJwk": { "kty": "EC", "crv": "P-256", "kid": "zY9AzWFTMKveeXIdHWSsvX16Cso1JXMEVKGFUuw_LkQ", "x": "PMzrrJU6Gh5JZK_6aIpiWWHcGoWymGFkYptM10a1sec", "y": "6-hp5Y7Hpb9nFfovyeycXSPubEJ7LWdaGNoCcKZCIyA" } } ], "assertionMethod": [ "did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt#z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt" ], "authentication": [ "did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt#z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt" ], "capabilityInvocation": [ "did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt#z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt" ], "capabilityDelegation": [ "did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt#z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt" ], "keyAgreement": [ "did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt#z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt" ] } } ``` **Note:** Please make sure to save the generated DID (Decentralized Identifier) along with the key ID for future reference. This information will be necessary, when issuing credentials through the Issuer Service. We are working on a solution to have this more easily referenced from the issuer service. ### Create Wallet Service We’ll create a Wallet Service for the holder, along with its own KMS and DID Service, plus a DID Store and Credential Store. The DID Service will generate a did:key for this wallet. We’ll use a helper endpoint to set up the wallet and all its dependencies in one step, so there’s no need to manually link the KMS, DID Service, or the DID and Credential Stores to the wallet service. The dependency services will play the following roles: - [KMS Service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) - Will store and manage the wallet's keys. - [DID Service](https://docs.walt.id/enterprise-stack/services/did-service/overview.md) - Will help with the creation of DIDs based on keys managed via the KMS service. - [DID Store](https://docs.walt.id/enterprise-stack/services/did-store-service/overview.md) - Will store the DIDs & DID Documents created by the DID service. - [Credential Store](https://docs.walt.id/enterprise-stack/services/credential-store-service/overview.md) - Will store the credentials received via the wallet service. **Note:** To dive deeper into the Wallet Service and its API, see the [Wallet Service docs](https://docs.walt.id/enterprise-stack/services/wallet-service/setup.md).
**Option: CURL** Endpoint: `/v1/{target}/wallet-service-api/init-wallet` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Wallet%20Service/post_v1__target__wallet_service_api_init_wallet) **Example Request** ```bash curl -X 'POST' \ 'http://waltid.enterprise.localhost:3000/v1/waltid.tenant1.wallet1/wallet-service-api/init-wallet' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "createKms": true, "kmsName": "wallet-kms-1", "createKeyInKms": { "backend": "jwk", "keyType": "Ed25519" }, "createDidService": true, "didServiceName": "wallet-did-service-1", "createDidWithDidService": "key", "createDidStore": true, "didStoreName": "wallet-did-store-1", "createCredentialStore": true, "credentialStoreName": "wallet-credential-store-1" }' ``` **Body** ```json { "createKms": true, "kmsName": "wallet-kms-1", "createKeyInKms": { "backend": "jwk", "keyType": "Ed25519" }, "createDidService": true, "didServiceName": "wallet-did-service-1", "createDidWithDidService": "key", "createDidStore": true, "didStoreName": "wallet-did-store-1", "createCredentialStore": true, "credentialStoreName": "wallet-credential-store-1" } ``` **Notes** - This creates a new dedicated KMS (`waltid.tenant1.wallet-kms-1`) and DID Service (`waltid.tenant1.wallet-did-service-1`) for the wallet and generates a key for it. - Set `createDidWithDidService` to "key" to provision a did:key for the wallet. - This also creates and links a DID Store (`waltid.tenant1.wallet-did-store-1`) and a Credential Store (`waltid.tenant1.wallet-credential-store-1`). The generated DID is stored in the created DID Store. #### (Optional) Find the Wallet DID reference in the DID Store Use the DID Store to list stored DID IDs and compose the DID reference as `waltid.tenant1.did-store-1.{didId}`. **Option: CURL** Endpoint: `/v1/{target}/did-store-service-api/dids/list-did-ids` ```bash curl -X 'GET' \ 'http://waltid.enterprise.localhost:3000/v1/waltid.tenant1.wallet-did-store-1/did-store-service-api/dids/list-did-ids' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' ``` This returns an array like `["wallet_did"]`. Combine it as `waltid.tenant1.wallet-did-store-1.wallet_did` when a DID reference is needed below. ### Create Credential Status Service (optional) In this section, we will set up a credential status service and create a configuration for it. To proceed with this section, you need to have an account with one of the following cloud providers: AWS, Azure, or Google Cloud. This is necessary because the credential status must be hosted publicly, and currently, we only support these three providers for hosting. **Note:** If you prefer not to create a cloud account (AWS, Azure, or Google Cloud) which is required for this step or wish to skip the credential status setup altogether, you can move directly to the example for creating an issuer. #### Credential Status Service Setup The Credential Status service enables users to create, update, and manage status credentials. Status credentials can have different purposes depending on the use-case. The most common purpose is revocation. Currently, status credentials can be stored and made available via three types of external services: 1. [AWS S3 Bucket](https://aws.amazon.com/s3/) 2. [Azure Blob Storage](https://azure.microsoft.com/en-us/products/storage/blobs) 3. [Google Cloud Storage](https://cloud.google.com/storage) **Option: AWS** **Option: CURL** Endpoint: `/v1/{target}/resource-api/services/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%2F%20Resource/post_v1__target__resource_api_services_create) **Example Request** ```bash curl -X 'POST' \ 'http://waltid.enterprise.localhost:3000/v1/waltid.tenant1.credential-status-service-1/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "credential-status", "config": { "registry": { "type": "aws", "bucketName": "bucket-name", "region": "region", "accessKeyId": "s3-access-key-id", "secretKey": "s3-secret-key" }, "bucketUrl": "https://bucket-name.s3.amazonaws.com" } }' ``` **Body** ```json { "type": "credential-status", "config": { "registry": { "type": "aws", "bucketName": "bucket-name", "region": "region", "accessKeyId": "s3-access-key-id", "secretKey": "s3-secret-key" }, "bucketUrl": "https://bucket-name.s3.amazonaws.com" } } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant in which to create the new credential status service and the service's ID (`{organizationID}.{tenantID}.[NewCredentialStatusServiceID]`), e.g. `waltid.tenant1.credential-status-service-id` **Body Parameters** - `type`: _credential-status_ - Specifies the type of service to create. In this case, it is `credential-status`. - `config`: _object_ - Storage configurations for status credential service. - `registry`: _object_ - Defines registry & access credentials for chosen registry. - `type`: _aws_ - Indicates the type of registry, which is `aws` in this context. - `bucketName`: _string_ - The name of the AWS S3 bucket. - `region`: _string_ - The AWS region where the bucket is located. - `accessKeyId`: _string_ - The access key ID for accessing the AWS S3 bucket. - `secretKey`: _string_ - The secret access key for accessing the AWS S3 bucket. - `bucketUrl`: _string_ - The URL of the chosen registry bucket, e.g. "https://bucket-name.s3.amazonaws.com" for AWS. --- **Response Codes** - `201` - Service created successfully. - `401` - Invalid authentication. **Option: Azure** **Option: CURL** Endpoint: `/v1/{target}/resource-api/services/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%2F%20Resource/post_v1__target__resource_api_services_create) **Example Request** ```bash curl -X 'POST' \ 'http://waltid.enterprise.localhost:3000/v1/waltid.tenant1.credential-status-service-1/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "credential-status", "config": { "registry": { "type": "azure", "bucketName": "bucket-name", "connectionString": "connection-string" }, "bucketUrl": "https://storage-name.blob.core.windows.net/bucket-name" } }' ``` **Body** ```json { "type": "credential-status", "config": { "registry": { "type": "azure", "bucketName": "bucket-name", "connectionString": "connection-string" }, "bucketUrl": "https://storage-name.blob.core.windows.net/bucket-name" } } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant in which to create the new credential status service and the service's ID (`{organizationID}.{tenantID}.[NewCredentialStatusServiceID]`), e.g. `waltid.tenant1.credential-status-service-id` **Body Parameters** - `type`: _credential-status_ - Specifies the type of service to create. In this case, it is `credential-status`. - `config`: _object_ - Storage configurations for status credential service. - `registry`: _object_ - Holds the details for the Azure registry configuration. - `type`: _azure_ - Indicates the type of registry, which is `azure` in this context. - `bucketName`: _string_ - The name of the Azure storage bucket. - `connectionString`: _string_ - The connection string used to access the Azure storage account. - `bucketUrl`: _string_ - The URL of the Azure storage bucket, specified as `https://storage-name.blob.core.windows.net/bucket-name`. --- **Response Codes** - `201` - Service created successfully. - `401` - Invalid authentication. **Option: Google** **Option: CURL** Endpoint: `/v1/{target}/resource-api/services/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%2F%20Resource/post_v1__target__resource_api_services_create) **Example Request** ```bash curl -X 'POST' \ 'http://waltid.enterprise.localhost:3000/v1/waltid.tenant1.credential-status-service-1/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "credential-status", "config": { "registry": { "type": "gcp", "bucketName": "bucket-name", "projectId": "project-id", "serviceAccountKeyJson": { "type": "service_account", "project_id": "YOUR_PROJECT_ID", "private_key_id": "YOUR_PRIVATE_KEY_ID", "private_key": "-----BEGIN PRIVATE KEY----- YOUR_PRIVATE_KEY -----END PRIVATE KEY----- ", "client_email": "YOUR_SERVICE_ACCOUNT_EMAIL", "client_id": "YOUR_CLIENT_ID", "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://oauth2.googleapis.com/token", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/YOUR_SERVICE_ACCOUNT_EMAIL", "universe_domain": "googleapis.com" } }, "bucketUrl": "https://storage.cloud.google.com/bucket-name" } }' ``` **Body** ```json { "type": "credential-status", "config": { "registry": { "type": "gcp", "bucketName": "bucket-name", "projectId": "project-id", "serviceAccountKeyJson": { "type": "service_account", "project_id": "YOUR_PROJECT_ID", "private_key_id": "YOUR_PRIVATE_KEY_ID", "private_key": "-----BEGIN PRIVATE KEY----- YOUR_PRIVATE_KEY -----END PRIVATE KEY----- ", "client_email": "YOUR_SERVICE_ACCOUNT_EMAIL", "client_id": "YOUR_CLIENT_ID", "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://oauth2.googleapis.com/token", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/YOUR_SERVICE_ACCOUNT_EMAIL", "universe_domain": "googleapis.com" } }, "bucketUrl": "https://storage.cloud.google.com/bucket-name" } } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant in which to create the new credential status service and the service's ID (`{organizationID}.{tenantID}.[NewCredentialStatusServiceID]`), e.g. `waltid.tenant1.credential-status-service-id` **Body Parameters** - `type`: _credential-status_ - Specifies the type of service to create. In this case, it is `credential-status`. - `config`: _object_ - Storage configurations for status credential service. - `registry`: _object_ - Holds the details for the GCP registry configuration. - `type`: _gcp_ - Indicates the type of registry, which is `gcp` in this context. - `bucketName`: _string_ - The name of the GCP storage bucket. - `projectId`: _string_ - The ID of the GCP project. - `serviceAccountKeyJson`: _object_ - Contains the service account key details for accessing GCP. - `type`: _service_account_ - Specifies the type of account - `project_id`: _string_ - The project ID for the service account. - `private_key_id`: _string_ - The ID of the private key. - `private_key`: _string_ - The private key for the service account, specified in a PEM format. - `client_email`: _string_ - The email associated with the service account. - `client_id`: _string_ - The client ID for the service account. - `auth_uri`: _string_ - The URI for authentication. - `token_uri`: _string_ - The URI for token issuance. - `auth_provider_x509_cert_url`: _string_ - The URL for the authentication provider's certificate. - `client_x509_cert_url`: _string_ - The URL for the client certificate. - `universe_domain`: _string_ - The domain for the universe of the API. - `bucketUrl`: _string_ - The URL of the GCP storage bucket, specified as `https://storage.cloud.google.com/bucket-name`. --- **Response Codes** - `201` - Service created successfully. - `401` - Invalid authentication. #### Create A Status List Credential Configuration Before we can create a status for a specific credential, we first need to establish a status credential configuration. This configuration serves as the blueprint for creating a status credential. The status credential itself is responsible for holding the status entries of one or more credentials. Each time we issue a new status for a credential that we want to provide to a holder, the status credential get updated, re-issued, and made publicly available through the configured registry. When defining the configuration, we specify which key will sign the status credential, the Decentralized Identifier ( DID) of the issuer, and the configuration details of the status itself. This configuration includes the `purpose` of the status (for example, revocation), the `statusSize`, and additional parameters. In the example below, we will create a configuration specifically for a revocation status credential. **Note:** One StatusListCredential can host 32.000 status list entries.
**Option: CURL** Endpoint: `/v1/{target}/credential-status-service-api/status-credential/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Credential%20Status%20Service/post_v1__target__credential_status_service_api_status_credential_create) **Example Request** ```bash curl -X 'POST' \ 'http://waltid.enterprise.localhost:3000/v1/waltid.tenant1.credential-status-service-1.config1/credential-status-service-api/status-credential/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "kid": "waltid.tenant1.kms1.key1", "did": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiMW1rQnBEYWZqVk9ONm9XVmlIZGREZXNEYXpKaXk1R0NTYko3VDU4QkJ3cyIsIngiOiJzZ1BiZWRPQjl1WEMtVG54LUVhV1IxRmg1Y25JLUxRLU43NUV1UE8wV2VFIn0", "config": { "purpose": "revocation", "type": "StatusList2021", "statusSize": 1, "statuses": [ { "status": "0x0", "message": "unset" }, { "status": "0x1", "message": "set" } ] } }' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + credential status service in which to execute the status credential creation (`{organizationID}.{tenantID}.{credentialStatusServiceID}`), e.g. `waltid.tenant1.status-service1` **Body** ```json { "kid": "waltid.tenant1.kms1.key1", "did": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiMW1rQnBEYWZqVk9ONm9XVmlIZGREZXNEYXpKaXk1R0NTYko3VDU4QkJ3cyIsIngiOiJzZ1BiZWRPQjl1WEMtVG54LUVhV1IxRmg1Y25JLUxRLU43NUV1UE8wV2VFIn0", "config": { "purpose": "revocation", "type": "StatusList2021", "statusSize": 1, "statuses": [ { "status": "0x0", "message": "unset" }, { "status": "0x1", "message": "set" } ] } } ``` **Body Parameters** - `kid`: _resourceIdentifier_ - Specifies a key used for singing the status credential. The key must come from a KMS service that lives under the same tenant as the credential status service. - `did`: _String_ - Specifies that DID related to the `kid`. - `config`: _Object_ - Status credential configuration. - `purpose`: _String_ - Describes the type of status e.g. `revocation`, `suspension` or any other custom type. - `type`: _String_ - Describes the status credential standard. It should be `StatusList2021`. - `statusSize`: _String_ - Describes the size of each status entry in bits. The default value is 1, meaning each status is represented by a single bit. If a value greater than 1 is used, it allows for more complex status messages, and the number of possible status messages must equal the number of possible values (e.g., 2^statusSize). - `statuses`: _Array_ - An array of objects, each representing a possible status value and its associated message. Each object contains: - `status`: A string representing the hexadecimal value of the status, prefixed with 0x. - `message`: A descriptive string used by developers for debugging purposes, indicating what the status value represents. It is not intended for end-user display. --- **Response** - `201` - Credential Status config created successfully. ### Create Issuer Service In this section, we will create an issuer service to issue credentials via OID4VCI. We will configure the issuer service to support the issuance of W3C `UniversityDegree` type credentials using JWT signatures. You can learn more about how to configure other and custom credentials [here](https://docs.walt.id/enterprise-stack/services/issuer-service/configurations/overview.md). **Option: CURL** Endpoint: `/v1/{target}/resource-api/services/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%2F%20Resource/post_v1__target__resource_api_services_create) **Example Request** ```bash curl -X 'POST' \ 'http://waltid.enterprise.localhost:3000/v1/waltid.tenant1.issuer1/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "issuer", "baseUrl": "http://waltid.enterprise.localhost:3000", "kms": "waltid.tenant1.kms1", "tokenKeyId": "waltid.tenant1.kms1.key1", "supportedCredentialTypes": { "UniversityDegree_jwt_vc_json": { "format": "jwt_vc_json", "cryptographic_binding_methods_supported": [ "did" ], "credential_signing_alg_values_supported": [ "ES256" ], "credential_definition": { "type": [ "VerifiableCredential", "UniversityDegree" ] } } } }' ``` **Body** ```json { "type": "issuer", "baseUrl": "http://waltid.enterprise.localhost:3000", "kms": "waltid.tenant1.kms1", "tokenKeyId": "waltid.tenant1.kms1.key1", "supportedCredentialTypes": { "UniversityDegree_jwt_vc_json": { "format": "jwt_vc_json", "cryptographic_binding_methods_supported": [ "did" ], "credential_signing_alg_values_supported": [ "ES256" ], "credential_definition": { "type": [ "VerifiableCredential", "UniversityDegree" ] } } } } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant in which to create the new Issuer service and the service's ID (`{organizationID}.{tenantID}.[NewIssuerServiceID]`), e.g. `waltid.tenant1.issuer1` **Body Parameters** - `type`: _serviceType_ - Specifies the type of service to create. In our case `issuer` - `baseUrl`: _String_ - This URL will be included in the generated OIDC4VC offer, allowing the wallet to know how to reach the issuer. It should reflect your organization's base URL. The general format for this URL is `https://{orgID}.yourEnterpriseStackUrl.com`. For example, if your organization is named **myorg** and the Enterprise Stack is hosted at the domain **enterprise-stack.com**, your base URL would be: https://myorg.enterprise-stack.com. - `kms`: - _resourceIdentifier_ - A [KMS service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) you want to connect with the to be created Issuer service. It should be setup under the same tenant. - `tokenKeyId`: - _resourceIdentifier_ - A key in the connected [KMS service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) used to sign the access token, which is used to get the credential from the credential endpoint - `supportedCredentialTypes`: _Object_ - A map of credentials the issuer supports. This list will be used to generate the [issuer metadata](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-credential-issuer-metadata). This list of supported credentials can be updated also after the initial setup.

Expand For W3C JWT & SD-JWT Credentials
To specify the support of a W3C credential the object will look as follows:

```json "OpenBadgeCredential_jwt_vc_json": { "format": "jwt_vc_json", "cryptographic_binding_methods_supported": [ "did" ], "credential_signing_alg_values_supported": [ "ES256" ], "credential_definition": { "type": [ "VerifiableCredential", "OpenBadgeCredential" ] } } ``` The key will have the following structure: `[CustomCredentialType]_jwt_vc_json`, e.g. `OpenBadgeCredential_jwt_vc_json` Inside the object: - `format` - will be `jwt_vc_json` for W3C JWT & SD-JWT credentials - `cryptographic_binding_methods_supported` - will be `did` - `credential_signing_alg_values_supported` - will be `ES256` - `credential_definition` - `type` - specifies a list of credential types. E.g. `[VerifiableCredential, MyCustomCredential]` 1. First Entry: Your list must always start with `VerifiableCredential`. 2. Subsequent Entries: After `VerifiableCredential`, you have two options: - You can add your custom type, such as `CustomCredential`. - If your credential is based on another credential (for example, `VerifiableAttestation`), first list all the credentials it builds upon, and then add your custom type at the end.


Expand For SD-JWT VC Credentials
To specify the support of a SD-JWT VC credential the object will look as follows:

```json "identity_credential_vc+sd-jwt": { "format": "vc+sd-jwt", "vct": "{vctBaseURL}/identity_credential", "cryptographic_binding_methods_supported": [ "jwk" ], "credential_signing_alg_values_supported": [ "ES256" ], "sdJwtVcTypeMetadata": { "name": "Identity Credential", "description": "The Identity Verifiable Credential", "vct": "{vctBaseURL}/identity_credential" } } ``` The key will have the following structure: `[custom_credential_type]_vc+sd-jwt`, e.g. `identity_credential_vc+sd-jwt` Inside the object: - `format` - will be `vc+sd-jwt` for SD-JWT VC credentials. - `vct` - will follow the structure `{vctBaseURL}/[custom_credential_type]`, e.g. `{vctBaseURL}/custom_credential_type`. The vctBaseURL will be replaced during issuance by the issuer API. - `cryptographic_binding_methods_supported` - will be `jwk` - `credential_signing_alg_values_supported` - will be `ES256` - `sdJwtVcTypeMetadata` - `name` - Name of the credential - `description` - Description of the credential - `vct` - Holding the same value as the `vct` above.
- `displayConfigurations` (optional) - an optional list of objects, where each object contains specific display information for different languages. Including the local option is not mandatory; you can simply provide one object if localization is not required. To learn more about the display config in general go [here](./3.configurations/2.openid-metadata/0.display.md), for object property details expand below.

Expand to learn more about the display config object properties **Display Config Object** ```json { "name": "walt.id Enterprise Issuer Service", "locale": "en-US", "logo": { "uri": "http://cdn.walt.id/issuer/logo.png", "alt_text": "Logo of walt.id Enterprise Issuer Service" } } ``` - `name` (optional) _String_: String value of a display name for the Credential Issuer. - `locale` (optional) _String_: String value that identifies the language of this object represented as a language tag taken from values defined in BCP47 [RFC5646](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#RFC5646). There MUST be only one object for each language identifier. Example values are: `en-US`, `de-DE`, or `fr-FR`. - `logo` (optional) _Object_: Object with information about the logo of the Credential Issuer. - `uri` _String_: String value that contains a URI where the Wallet can obtain the logo of the Credential Issuer. - `alt_text` (optional) _String_: String value of the alternative text for the logo image.
--- **Response Codes** - `201` - Service created successfully. ### Create Verifier Service In this section, we will create a verifier service to verify credentials via OID4VCP. **Option: CURL** Endpoint: `/v1/{target}/resource-api/services/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%2F%20Resource/post_v1__target__resource_api_services_create) **Example Request** ```bash curl -X 'POST' \ 'http://waltid.enterprise.localhost:3000/v1/waltid.tenant1.verifier1/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "verifier", "baseUrl": "http://waltid.enterprise.localhost:3000" }' ``` **Body** ```json { "type": "verifier", "baseUrl": "http://waltid.enterprise.localhost:3000", "metadataConfig": { "name": "walt.id Enterprise Verifier", "logoUri": "https://cdn.walt.id/verifier/logo.png" } } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant in which to create the new Verifier service and the service's ID (`{organizationID}.{tenantID}.[NewVerifierServiceID]`), e.g. `waltid.tenant1.verifier1` **Body Parameters** - `type`: _serviceType_ - Specifies the type of service to create. In our case `verifier` - `baseUrl`: _String_ - This URL will be included in the generated OIDC4VC offer, allowing the wallet to know how to reach the verifier. It should reflect your organization's base URL. The general format for this URL is `https://{orgID}.yourEnterpriseStackUrl.com`. For example, if your organization is named **myorg** and the Enterprise Stack is hosted at the domain **enterprise-stack.com**, your base URL would be: https://myorg.enterprise-stack.com. - `metadataConfig`: _Object_ - Verifier metadata config. Can be used to edit e.g. externally exposed human-readable verifier name and logo. Learn more in general [here](4.configurations/1.openid-metadata.md) or expand below to see object property options.

Expand to learn more about metadata config object properties **Metadata Config Object** ```json "metadataConfig": { "name": "walt.id Enterprise Verifier", "logoUri": "https://cdn.walt.id/verifier/logo.png" } ``` - `name` (optional) _String_: Human-readable string name of the verifier. - `logoUri` (optional) _String_: URL string that references a logo for the verifier.
--- **Response Codes** - `201` - Service created successfully. ### Issue Credential Now, we'll issue a verifiable credential using the key and DID created earlier. You can choose to either issue the W3C credential without or with a status (revocation). To issue the credential with a status, you must have completed the [optional setup of a Credential Status Service](#create-credential-status-service-optional). 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.
After calling the endpoint, an OID4VCI offer URL is generated. Initially, this URL does not include the actual signed credentials; instead, it contains a list of credentials that we are offering for the wallet to claim. Once the wallet processes this offer and chooses to accept the credential, it will request those credentials through a series of exchanges between the issuer and the wallet. Only at this point, when the wallet requests the credential, will the issuer sign it. Additionally, just before the signing occurs, the provided [data functions](https://docs.walt.id/enterprise-stack/services/issuer-service/data-functions.md) are executed. **Option: Without Revocation Status** [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%2F%20Issuer%20Service/post_v1__target__issuer_service_api_credentials_issue) **Option: CURL** Endpoint: `/v1/{target}/issuer-service-api/credentials/issue` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%2F%20Issuer%20Service/post_v1__target__issuer_service_api_credentials_issue) **Example Request** ```bash curl -X 'POST' \ 'http://waltid.enterprise.localhost:3000/v1/waltid.tenant1.issuer1/issuer-service-api/credentials/issue' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'statusCallbackUri: https://example.com/$id' \ -H 'Content-Type: application/json' \ -d '{ "issuerKeyId": "waltid.tenant1.kms1.key1", "issuerDid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp", "authenticationMethod": "PRE_AUTHORIZED", "credentialConfigurationId": "UniversityDegree_jwt_vc_json", "credentialData": { "@context": [ "https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1" ], "id": "http://example.gov/credentials/3732", "type": [ "VerifiableCredential", "UniversityDegree" ], "issuer": { "id": "did:web:vc.transmute.world" }, "issuanceDate": "2020-03-10T04:24:12.164Z", "credentialSubject": { "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", "degree": { "type": "BachelorDegree", "name": "Bachelor of Science and Arts" } } }, "mapping": { "id": "", "issuer": { "id": "" }, "credentialSubject": { "id": "" }, "issuanceDate": "", "expirationDate": "" } }' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + issuer service with which to execute the credential issuance (`{organizationID}.{tenantID}.{issuerServiceID}`), e.g. `waltid.tenant1.issuer1` **Header Parameters** - `statusCallbackUri`: _URL_ - Receive updates on the created issuance process, e.g. when a credential was successfully claimed. The parameter expects a URL which can accept a JSON POST request. The URL can also hold a `$id`, which will be replaced by the issuance session id. For example: `https://myurl.com/$id`, `https://myurl.com` 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 { "issuerKeyId": "waltid.tenant1.kms1.key1", "issuerDid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp", "authenticationMethod": "PRE_AUTHORIZED", "credentialConfigurationId": "UniversityDegree_jwt_vc_json", "credentialData": { "@context": [ "https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1" ], "id": "http://example.gov/credentials/3732", "type": [ "VerifiableCredential", "UniversityDegree" ], "issuer": { "id": "did:web:vc.transmute.world" }, "issuanceDate": "2020-03-10T04:24:12.164Z", "credentialSubject": { "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", "degree": { "type": "BachelorDegree", "name": "Bachelor of Science and Arts" } } }, "mapping": { "id": "", "issuer": { "id": "" }, "credentialSubject": { "id": "" }, "issuanceDate": "", "expirationDate": "" } } ``` **Body Parameters** - `issuerKeyId`: _resourceIdentifier_ - The keyID of a key stored in a [KMS service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) under the same tenant. E.g. `waltid.tenant1.kms1.key1` - `issuerDid`: _String_ - The DID related to the provided key. - `credentialConfigurationId`: _String_ - Reference to a specific credential configuration the issuer supports. Configured during [issuer service setup](https://docs.walt.id/enterprise-stack/setup.md). - `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](../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/enterprise-stack/services/verifier-service/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: With Revocation Status** [API Reference](https://issuer.demo.walt.id/swagger/index.html#/Credential%20Issuance/post_openid4vc_sdjwt_issue) **Option: CURL** Endpoint: `/v1/{target}/issuer-service-api/credentials/issue` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%2F%20Issuer%20Service/post_v1__target__issuer_service_api_credentials_issue) **Example Request** ```bash curl -X 'POST' \ 'http://waltid.enterprise.localhost:3000/v1/waltid.tenant1.issuer1/issuer-service-api/credentials/issue' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'statusCallbackUri: https://example.com/$id' \ -H 'Content-Type: application/json' \ -d '{ "issuerKeyId": "waltid.tenant1.kms1.key1", "issuerDid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp", "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" } } }, "status": { "statusCredentialConfig": "waltid.tenant1.credential-status-service-1.config1", "initialStatus": "0x0" }, "mapping": { "id": "", "issuer": { "id": "" }, "credentialSubject": { "id": "" }, "issuanceDate": "", "expirationDate": "" }, "authenticationMethod": "PRE_AUTHORIZED" }' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + issuer service with which to execute the credential issuance (`{organizationID}.{tenantID}.{issuerServiceID}`), e.g. `waltid.tenant1.issuer1` **Header Parameters** - `statusCallbackUri`: _URL_ - Receive updates on the created issuance process, e.g. when a credential was successfully claimed. The parameter expects a URL which can accept a JSON POST request. The URL can also hold a `$id`, which will be replaced by the issuance session id. For example: `https://myurl.com/$id`, `https://myurl.com` 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 { "issuerKeyId": "waltid.tenant1.kms1.key1", "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" } } }, "status": { "statusCredentialConfig": "waltid.tenant1.credential-status-service-1.config1", "initialStatus": "0x0" }, "mapping": { "id": "", "issuer": { "id": "" }, "credentialSubject": { "id": "" }, "issuanceDate": "", "expirationDate": "" }, "authenticationMethod": "PRE_AUTHORIZED" } ``` **Body Parameters** - `issuerKeyId`: _resourceIdentifier_ - The keyID of a key stored in a [KMS service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) under the same tenant. E.g. `waltid.tenant1.kms1.key1` - `issuerDid`: _String_ - The DID related to the provided key. - `credentialConfigurationId`: _String_ - Reference to a specific credential configuration the issuer supports. 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__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](../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/enterprise-stack/services/verifier-service/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%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. **Note:** If you want to dive deeper into the issuer service, you can go [here](https://docs.walt.id/enterprise-stack/services/issuer-service/overview.md). #### Receive the Credential Offer Use the Enterprise Wallet Service to receive and store the credential: **Option: CURL** Endpoint: `/v1/{target}/wallet-service-api/credentials/receive` ```bash curl -X 'POST' \ 'http://waltid.enterprise.localhost:3000/v1/waltid.tenant1.wallet1/wallet-service-api/credentials/receive' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "offerUrl": "", "keyReference": "waltid.tenant1.wallet-kms-1.wallet_key", "didReference": "waltid.tenant1.wallet-did-store-1.wallet_did" }' ``` This runs the full OID4VCI flow from the wallet-side and stores the received credential in the linked Credential Store. Optional: You can also render the credential offer as a QR code for any OID4VCI-compliant wallet to scan. ### Verify A Credential To request and verify the credential issued previously we will leverage the verifier service and make the following request. **Option: Without Credential Status** **Option: CURL** ```bash curl -X 'POST' \ 'http://waltid.enterprise.localhost:3000/v1/waltid.tenant1.verifier1/verifier-service-api/credentials/verify' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'authorizeBaseUrl: openid4vp://authorize' \ -H 'responseMode: direct_post' \ -H 'successRedirectUri: https://example.com/success/{id}' \ -H 'errorRedirectUri: https://example.com/error/{id}' \ -H 'statusCallbackUri: https://example.com/status_callback/{id}' \ -H 'statusCallbackApiKey: myAPIKey' \ -H 'stateId: myUniqueStateValue' \ -H 'Content-Type: application/json' \ -d '{ "request_credentials": [ { "type": "UniversityDegree", "format": "jwt_vc_json" } ] }' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + verifier service with which to execute the credential verification (`{organizationID}.{tenantID}.{verifierServiceID}`), e.g. `waltid.tenant1.verifier1` **Header Parameters** - **authorizeBaseUrl** - is used to modify the start of the OID4VC request URL. If you are using the cross-device flow, where you will display the URL as a QR code, you can leave the value as `openid4vp://authorize` or if you don't know the wallet the user will be using to claim the credential. If you are using the same device flow, where you already know the user's wallet and want the user to be able to go directly to it, you can use the wallet URL path that is able to receive an OIDC request as a query parameter. Our wallet for example can receive OID4VC requests 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/enterprise-stack/services/verifier-service/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.
**Option: With Credential Status** **Option: CURL** ```bash curl -X 'POST' \ 'http://waltid.enterprise.localhost:3000/v1/waltid.tenant1.verifier1/verifier-service-api/credentials/verify' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'authorizeBaseUrl: openid4vp://authorize' \ -H 'responseMode: direct_post' \ -H 'successRedirectUri: https://example.com/success/{id}' \ -H 'errorRedirectUri: https://example.com/error/{id}' \ -H 'statusCallbackUri: https://example.com/status_callback/{id}' \ -H 'statusCallbackApiKey: myAPIKey' \ -H 'stateId: myUniqueStateValue' \ -H 'Content-Type: application/json' \ -d '{ "request_credentials": [ { "type": "UniversityDegree", "format": "jwt_vc_json", "policies": [ { "policy": "credential-status", "args": { "discriminator": "w3c", "value": 0, "purpose": "revocation", "type": "StatusList2021" } } ] } ] }' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + verifier service with which to execute the credential verification (`{organizationID}.{tenantID}.{verifierServiceID}`), e.g. `waltid.tenant1.verifier1` **Header Parameters** - **authorizeBaseUrl** - is used to modify the start of the OID4VC request URL. If you are using the cross-device flow, where you will display the URL as a QR code, you can leave the value as `openid4vp://authorize` or if you don't know the wallet the user will be using to claim the credential. If you are using the same device flow, where you already know the user's wallet and want the user to be able to go directly to it, you can use the wallet URL path that is able to receive an OIDC request as a query parameter. Our wallet for example can receive OID4VC requests 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", "policies": [ { "policy": "credential-status", "args": { "discriminator": "w3c", "value": 0, "purpose": "revocation", "type": "StatusList2021" } } ] } ] } ``` **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/enterprise-stack/services/verifier-service/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.
#### Present the Credential Use the Enterprise Wallet Service to present the stored credential in response to the verifier’s request URL. #### Find Matching Credentials via Presentation Definition Before presenting, use the wallet's linked credential store to find credentials matching the verifier's presentation definition. From the response, take the `_id` (e.g., `waltid.tenant1.wallet-credential-store-1.3d629d4b-...`) and use it in the next step. **Option: CURL** Endpoint: `/v1/{target}/credential-store-service-api/credentials/query/document/schema` **Example Request** ```bash curl -X 'POST' \ 'http://waltid.enterprise.localhost:3000/v1/waltid.tenant1.wallet-credential-store-1/credential-store-service-api/credentials/query/document/schema' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '[ { "path": ["$.vc.type"], "filter": { "type": "string", "pattern": "UniversityDegree" } } ]' ``` **Path Parameters** - **`orgID`**: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - **`target`**: resourceIdentifier - The target indicates the organization + tenant + credential store which should be used to query for credentials (`{organizationID}.{tenantID}.[credentialStoreID]`), e.g. `waltid.tenant1.credential-store1` **Body** ```json [ { "path": ["$.vc.type"], "filter": { "type": "string", "pattern": "UniversityDegree" } } ] ``` **Body Parameters** - **`Array of contraints.fields`** - In the body of the request, we provide an array containing the values for each `constraints.fields` entry in all `input_descriptors` objects of the presentation definition provided in the Authorization Request. For example, the presentation definition for the body provided above looks as follows: ```json { "id": "ANREjIRDBnb", "input_descriptors": [ { "id": "BankId", "format": { "jwt_vc_json": { "alg": [ "EdDSA" ] } }, "constraints": { "fields": [ // ⬇️ what we provide in the body ⬇️ { "path": [ "$.vc.type" ], "filter": { "type": "string", "pattern": "BankId" } } // ⬆️ what we provide in the body ⬆️ ] } } ], "format": { "jwt_vp_json": { "alg": [ "ES256" ] } } } ``` --- **Reponse Codes** - `200` - Matching credentials **Body** ```json [ { "_id": "waltid.tenant1.wallet-credential-store-1.b5db5ab5-03cb-4103-a0ff-0299edad15ee", "credential": { "type": "vc-w3c_1_1", "disclosables": {}, "credentialData": { "@context": [ "https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1" ], "id": "urn:uuid:4b776a85-dbc3-4c14-9c0f-2428a3e98b7b", "type": [ "VerifiableCredential", "UniversityDegree" ], "issuer": { "id": "did:key:z6Mkkbug58H7h3fts2W8mqQUPGTeXX3wEa32fY3Jk15nJNYf" }, "issuanceDate": "2025-08-18T12:35:53.055156800Z", "credentialSubject": { "id": "did:key:z6MkegzJFcs5txZwv4NrAx2JAkv38MYq9ydwjsFXsV3Sn1Ed", "degree": { "type": "BachelorDegree", "name": "Bachelor of Science and Arts" } }, "credentialStatus": { "id": "https://83abcuyiuweiuoyiy8943ir732yjhghj23gdnshuy7623rjh23jhrg23g78hkkk.s3.amazonaws.com/waltid.tenant1.credential-status-service-1.config1#10561", "type": "StatusList2021Entry", "statusPurpose": "revocation", "statusListIndex": "10561", "statusSize": 1, "statusListCredential": "https://83abcuyiuweiuoyiy8943ir732yjhghj23gdnshuy7623rjh23jhrg23g78hkkk.s3.amazonaws.com/waltid.tenant1.credential-status-service-1.config1", "statusMessage": [ { "status": "0x0", "message": "unset" }, { "status": "0x1", "message": "set" } ] }, "expirationDate": "2026-08-18T12:35:53.055397550Z" }, "signature": { "type": "signature-jwt", "signature": "hsHnzRzN_-Tcq0TPiJYKxJnCIliIq63LcQpC7ctKSegc6Rb40dSx6puhgX9qIOFo1mOAGSsEcjMk8pTc8MwYBQ" }, "signed": "eyJraWQiOiJkaWQ6a2V5Ono2TWtrYnVnNThIN2gzZnRzMlc4bXFRVVBHVGVYWDN3RWEzMmZZM0prMTVuSk5ZZiN6Nk1ra2J1ZzU4SDdoM2Z0czJXOG1xUVVQR1RlWFgzd0VhMzJmWTNKazE1bkpOWWYiLCJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtrYnVnNThIN2gzZnRzMlc4bXFRVVBHVGVYWDN3RWEzMmZZM0prMTVuSk5ZZiIsInN1YiI6ImRpZDprZXk6ejZNa2VnekpGY3M1dHhad3Y0TnJBeDJKQWt2MzhNWXE5eWR3anNGWHNWM1NuMUVkIiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy9leGFtcGxlcy92MSJdLCJpZCI6InVybjp1dWlkOjRiNzc2YTg1LWRiYzMtNGMxNC05YzBmLTI0MjhhM2U5OGI3YiIsInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJVbml2ZXJzaXR5RGVncmVlIl0sImlzc3VlciI6eyJpZCI6ImRpZDprZXk6ejZNa2tidWc1OEg3aDNmdHMyVzhtcVFVUEdUZVhYM3dFYTMyZlkzSmsxNW5KTllmIn0sImlzc3VhbmNlRGF0ZSI6IjIwMjUtMDgtMThUMTI6MzU6NTMuMDU1MTU2ODAwWiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmtleTp6Nk1rZWd6SkZjczV0eFp3djROckF4MkpBa3YzOE1ZcTl5ZHdqc0ZYc1YzU24xRWQiLCJkZWdyZWUiOnsidHlwZSI6IkJhY2hlbG9yRGVncmVlIiwibmFtZSI6IkJhY2hlbG9yIG9mIFNjaWVuY2UgYW5kIEFydHMifX0sImNyZWRlbnRpYWxTdGF0dXMiOnsiaWQiOiJodHRwczovLzgzYWJjdXlpdXdlaXVveWl5ODk0M2lyNzMyeWpoZ2hqMjNnZG5zaHV5NzYyM3JqaDIzamhyZzIzZzc4aGtray5zMy5hbWF6b25hd3MuY29tL3dhbHRpZC50ZW5hbnQxLmNyZWRlbnRpYWwtc3RhdHVzLXNlcnZpY2UtMS5jb25maWcxIzEwNTYxIiwidHlwZSI6IlN0YXR1c0xpc3QyMDIxRW50cnkiLCJzdGF0dXNQdXJwb3NlIjoicmV2b2NhdGlvbiIsInN0YXR1c0xpc3RJbmRleCI6IjEwNTYxIiwic3RhdHVzU2l6ZSI6MSwic3RhdHVzTGlzdENyZWRlbnRpYWwiOiJodHRwczovLzgzYWJjdXlpdXdlaXVveWl5ODk0M2lyNzMyeWpoZ2hqMjNnZG5zaHV5NzYyM3JqaDIzamhyZzIzZzc4aGtray5zMy5hbWF6b25hd3MuY29tL3dhbHRpZC50ZW5hbnQxLmNyZWRlbnRpYWwtc3RhdHVzLXNlcnZpY2UtMS5jb25maWcxIiwic3RhdHVzTWVzc2FnZSI6W3sic3RhdHVzIjoiMHgwIiwibWVzc2FnZSI6InVuc2V0In0seyJzdGF0dXMiOiIweDEiLCJtZXNzYWdlIjoic2V0In1dfSwiZXhwaXJhdGlvbkRhdGUiOiIyMDI2LTA4LTE4VDEyOjM1OjUzLjA1NTM5NzU1MFoifSwianRpIjoidXJuOnV1aWQ6NGI3NzZhODUtZGJjMy00YzE0LTljMGYtMjQyOGEzZTk4YjdiIiwiZXhwIjoxNzg3MDU2NTUzLCJpYXQiOjE3NTU1MjA1NTMsIm5iZiI6MTc1NTUyMDU1M30.hsHnzRzN_-Tcq0TPiJYKxJnCIliIq63LcQpC7ctKSegc6Rb40dSx6puhgX9qIOFo1mOAGSsEcjMk8pTc8MwYBQ" }, "parent": "waltid.tenant1.wallet-credential-store-1" } ] ``` The returned array will include `_id` values like `waltid.tenant1.wallet-credential-store-1.` that you can pass directly in the `credentials` array when presenting. **Present Credential via wallet** **Option: CURL** Endpoint: `/v1/{target}/wallet-service-api/credentials/present` **Example Request** ```bash curl -X 'POST' \ 'http://waltid.enterprise.localhost:3000/v1/waltid.tenant1.wallet1/wallet-service-api/credentials/present' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "requestUrl": "", "keyReference": "waltid.tenant1.wallet-kms-1.wallet_key", "didReference": "waltid.tenant1.wallet-did-store-1.wallet_did", "credentials": [ { "credential": "waltid.tenant1.wallet-credential-store-1." } ] }' ``` You must provide at least one credential to present via the `credentials` array. Use the presentation-definition query step above to obtain a valid credential reference from the wallet's Credential Store. #### (Optional) Selective disclosure If your credential supports selectively disclosable fields, you can specify exactly which fields to share: ```json { "credentials": [ { "credential": "waltid.tenant1.wallet-credential-store-1.", "disclosures": [ "$.name", "$.credentialSubject.achievement.description" ] } ] } ``` **Note:** Under the hood, the wallet wraps the credential(s) in a Verifiable Presentation and signs it with the holder’s key referenced by `keyReference`, proving possession to the verifier. #### Inspect Verification Result After the we've presented the credential(s), we can now verify the status by performing the following call with the `state` value obtained from the URL query params we received from the verifier service previously. **Option: CURL** **Example** ``` openid4vp://authorize?...state=waltid.tenant1.verifier1.fb94470b-bcee-4ed7-93f5-906a4f5f585d... ``` Making the call to receive the verification result ```bash curl -X 'GET' \ 'http://waltid.enterprise.localhost:3000/v1/{state}/verifier-service-api/credentials/sessions/view' \ -H 'accept: */*' -H 'Authorization: Bearer {yourToken}' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + verifier service + sessionID to request (`{organizationID}.{tenantID}.{verifierServiceID}.{sessionID}`), e.g. `waltid.tenant1.verifier1.fb94470b-bcee-4ed7-93f5-906a4f5f585d` In this flow, the `state` value from the OID4VP request URL already contains this full target, so it can be used as is. ##### 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. **Note:** Want to learn more about the verifier service in detail? Please go [here](https://docs.walt.id/enterprise-stack/services/verifier-service/overview.md). ## 5. Bonus With this we went through a whole credential issuance and verification flow using different services of the walt.id Enterprise Stack. If you want, you can now (when you issued a credential with status), revoke the issued credential and run through the verification another time. This time it should fail. For guidence on how to revoke the credential, please go [here](https://docs.walt.id/enterprise-stack/services/credential-status-service/manage.md#update-a-credential-status). ## 6. Next Steps 1. Explore administrative features (e.g. Access & Permissions, Events & Metrics) [here](https://docs.walt.id/enterprise-stack/administration.md). 2. Explore the different services (e.g KMS, issuer, verifier, wallet) [here](https://docs.walt.id/enterprise-stack/services.md). -------------------------------------------------------------------------------- PAGE: What is the Enterprise Stack URL: https://docs.walt.id/enterprise-stack/home/the-enterprise-stack RAW: https://docs.walt.id/enterprise-stack/home/the-enterprise-stack.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # What is the Enterprise Stack We are proud to introduce The Enterprise Stack by walt.id. Enabling businesses and governments to offer highly scalable, reliable and secure digital identity and wallet solutions across industries and ecosystems. At walt.id, our goal has always been to make digital identity simple and to enable the mass adoption of decentralized identity and identity wallets. For this, organizations need tools that easily scale along market demands, deliver high up-time, ensure security and compliance with digital identity standards and regulations as well as allow for customization to promote flexibility and openness. This is what the Enterprise Stack is about. It can be hosted on-premise, comes with enterprise-grade integrations and makes large-scale digital identity solutions possible in days, instead of months. Let’s dive in. Video: https://www.youtube.com/watch?v=FKzoD9F23VE ## The Developers’ Dilemma (Why The Enterprise Stack) Over the last years we have worked with public and private sector clients around the globe. We built countless pilots, went to production with customers and got feedback from thousands of developers. All of these efforts culminated in[ The Community Stack](https://walt.id/blog/p/community-stack), our leading open source digital identity and wallet infrastructure. ![image of community stack benefits](https://docs.walt.id/img/enterprise-stack/getting-started/community-stack-benefits.png) The Community Stack addresses a lot of the challenges developers face when building digital identity and wallet solutions or end-to-end use cases. It supports all major identity standards, multiple identity ecosystems and complies with open standards to maximize interoperability. But it has its limits. It still takes effort to build and maintain solutions that can scale. Large organizations as well as businesses who are building identity platforms or SaaS products need the ability to offer secure and robust services with high up-time, tools for process automation, monitoring and logging, integrations with third party systems and compliance with regulations. To address these challenges, we built The Enterprise Stack. It enables governments and businesses of any size to build digital identity and wallet products or platforms at scale, fast and with ease. ![image of enterprise stack benefits](https://docs.walt.id/img/enterprise-stack/getting-started/enterprise-stack-benefits.png) **Architecture & Features** ![image of enterprise stack architecture](https://docs.oss.walt.id/img/enterprise-stack/getting-started/enterprise-architecture.png) **The Enterprise Stack offers:** - **Scalability & Persistence** - Multi-tenancy and the option to run and sync as many enterprise instances in parallel as required for load distribution. A built-in data persistence layer keeps digital identity data synced and secure across deployments. - **Process Automation** — Store issuance and verification configurations as re-usable flows providing a one-click issue and verify experience after first setup. Use webhooks to integrate external business logic into workflows and enable pre-built issuance and verification pages to make the delivery of credential offers and requests as easy as sending out an email. - **Logging & Analytics** — View, track and export events and metrics like the number of issuance and verification requests, claimed credentials, active wallets and more. Filter data for specific credential formats, exchange protocols or ecosystems. Access audit logs and configure logging events to be streamed to various systems for more fine grained control. - **User Interfaces** — Configure, manage and use digital identity services via a UI. Execute simple credential requests and offers or build more complex multi-step issuance and verification flows. View and export usage data via customisable analytics dashboards. - **Pre-built integrations** — Use pre-built integrations to leverage and build on existing infrastructure and cut down integration overhead. Connect an external KMS for signature creation, IAM for user authentication, or external ID verifications for traditional onboarding among others. - **Security & Compliance** — The Enterprise Stack was built to enable a robust and secure infrastructure. Fine-grained role-based authentication and permission system for user accounts and API keys ensures controlled usage of features. Continued integration tests and audits with other digital identity solution providers ensure compliance with regulations such as eIDAS2. ![image of community stack as open core](https://docs.walt.id/img/enterprise-stack/getting-started/open-core.png) ## Community Stack vs. Enterprise Stack The Community Stack is our open-source tooling for building digital identity solutions across industries and ecosystems. Our multi-platform-based approach offers libs and APIs for mobile, browser and server environments with a great developer experience, tested interoperability and example apps for various use-cases. It is used by thousands of developers, governments and businesses across industries and is a great way to get started, build your own use cases or apps for free. The Enterprise Stack builds on top of The Community Stack. It adds enterprise capabilities like multi-tenancy support, logs, audits, external certifications, pre-built integrations, UIs, automations and more. As a result, the Enterprise Stack is a great option for large organizations - like governments and enterprises - and/or for businesses who want to offer new digital identity platforms or SaaS solutions that scale. ![image of community vs enterprise](https://docs.walt.id/img/enterprise-stack/getting-started/community-stack-vs-enterprise-stack.png) Finally, we made it easy to switch between The Community Stack and The Enterprise Stackt to make sure that everyone has the flexibility of moving between our product editions as required. While the Enterprise Stack offers more features ( i.e. more APIs) the core interfaces for our products (Issuer, Verifier, and Wallet) are similar or the same. Of course, we are also happy to help with migrations. ## Get Started - [Enterprise Foundations](https://docs.walt.id/enterprise-stack/foundations.md) - Learn more about the Enterprise Stack core building blocks - [Administration](https://docs.walt.id/enterprise-stack/administration.md) - Learn about organizations, tenants, and the details of authentication and access management. - [Services](https://docs.walt.id/enterprise-stack/services.md) - Explore various services (e.g., issuer, verifier) and their detailed usage. -------------------------------------------------------------------------------- PAGE: Building with Walt.id URL: https://docs.walt.id/enterprise-stack/home/how-to-build RAW: https://docs.walt.id/enterprise-stack/home/how-to-build.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # How to Build an Application using the Walt Enterprise Stack The Walt Enterprise Stack is a collection of services that are used to build enterprise-ready applications. The APIs are very flexible and can be used to build a variety of applications. Customers of Walt.id generally build platforms around the Enterprise Stack which simplifies the functionality of the platform to exactly what is required by the end customer. They also generally house additional business logic and integrations with other systems. Below we will discuss a step by step guide on how to build an application using the Walt Enterprise Stack. # Table of contents - [Gathering Requirements](#gathering-requirements) - [Deciding on an Integration Pattern](#deciding-on-an-integration-pattern) - [Deploying the Application](#deploying-the-application) - [Monitoring the Application](#monitoring-the-application) - [Support](#support) ## Gathering Requirements The digital identity space is a complex and ever-evolving landscape. There are many different standards, protocols, and frameworks to choose from. It is important to gather requirements from the customer to understand their specific needs and goals. In terms of credential types, you have the choice of W3C Verifiable Credentials (VC), IETF SD-JWT VC, or ISO mdoc. Different regions and use cases may require different credential types. While they all more or less support the same features, the decision will be mainly driven by the regulatory requirements of the region and the use case. In terms of exchange protocols, we only support the OpenID family of protocols. This is because we believe it is the most comprehensive and flexible protocol for the job. We currently support the following OpenID protocols: - OpenID4VP (Draft 14, 20, and v1.0) - OpenID4VCI (Draft 11, 13, and soon v1.0) We are currently looking also at supporting the following protocols: - Digital Credentials API (DCA) - 18013-7 (ISO mdoc, for proximity flows) For wallets, we only provide custodial online wallets at the moment. We are looking at adding support for edge wallets in the future. ## Deciding on an Integration Pattern We generally see most customers building their platform around the Walt.id Enterprise Stack. This allows them to have a single platform that can be used to issue, store, and verify credentials for a variety of use cases. We do not vendor lock any part of our stack, so you can decide to use only the parts of the stack that you need, and then source other parts from other vendors if you need to. Generally, we see customers build the following features: - Trust Registry Integrations: The walt stack does not come with a trust registry out of the box, or any integrations with existing trust registries. We do however support API based and certificate based trust registry integrations (The main two types available in the market). When trying to build an ecosystem, you may need to create your own trust registry to whitelist your end-issuers and verifiers. If you are integrating with an existing ecosystem, you will likely need to integrate with their trust registry, which can be simply done using our verification policies which add extra checks to the verification process. - KYB/KYC Integrations: Our APIs provide a simple way to onboard a new entity into the ecosystem by provisioning them a key, did and wallet. However, many real world use cases require more than just a simple onboarding process. For example, many use cases require the entity to be KYC'd and approved before they can issue credentials. As this is specific to the use case, we do not provide a built in solution for this. Our API should be able to be used as a building block in the onboarding process of your choice. - Service Provider Integrations: Many customers provide specific portals, SDKs or other services to their end-users. Or in exsiting ecosystems, maybe they need to extend existing offerings to integrate with our APIs. Our APIs are designed to be very flexible and can be used to build these services. - Schema Registries: When trying to build for an ecosystem, you may need to register schemas for the different credential types to ensure reusability and interoperability across the space. If you are integrating with an existing ecosystem, you are unlikely to need to build a full trust registry, but you may decide to offer simple services to your end-issuers to allow them to quickly map their data to the ecosystem's schemas. - KMS Integrations: Our APIs support a variety of KMS providers. We support both hardware and software based KMS providers. We have APIs for generating keys within the KMS, but also support BYOK (Bring Your Own Key) scenarios in case integration is required against an existing PKI infrastructure. We also support a variety of key types and algorithms. If you need any other integration, we can build it during the project. - Wallet Integrations: We provide a simple way to integrate with our wallet API to allow your end-users to store and manage their credentials. This can be then integrated into a new web-based wallet or a mobile wallet app, or used in an existing mobile app or even have no interface at all. The diagram below shows a typical integration pattern for a customer using the Walt.id Enterprise Stack, mirroring architecture diagrams created by our customers in the past ![Integration Pattern](https://docs.walt.id/img/enterprise-stack/home/architecture.png) ## Deploying the Application We provide a docker image for our enterprise stack which includes all the services and dependencies needed to run the stack. This means it is very easy to deploy the stack in your own infrastructure. We have customers who have deployed our stack in OCI, AWS and Azure, and support the major managed services from those providers. If you need any other integration, we can build it during the project. We are in the process of extending our offering to also provide the necessary infrastructure as code files (via Terraform) to help with the deployment of the stack in your own infrastructure. ## Using the platform We provide documentation through our official documentation site (where you are right now). All the APIs also come with a working swagger interface which can be used to test the APIs directly from the browser. We also provide an Enteprise UI which contains some of the basic functionality to allow you to test and demo the APIs of the platform. ## Monitoring the Application We have various ways for you to ingest the logs and metrics from our stack into your centralised observability tools. This means you can use your existing monitoring system to consume the logs and metrics from the stack through our integrations with OpenTelemetry and Prometheus. If any additional requirements are needed, we can implement them as needed. ## Support Alongside the enterprise license, we provide yearly support contracts for the stack. Generally we are Level 2 or beyond support for incident resolution and feature requests. -------------------------------------------------------------------------------- PAGE: FAQs URL: https://docs.walt.id/enterprise-stack/home/faq RAW: https://docs.walt.id/enterprise-stack/home/faq.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Frequently Asked Questions This page addresses common questions about the walt.id Enterprise Stack. ## General Questions **Q: What is the walt.id Enterprise Stack?** A: The walt.id Enterprise Stack enables businesses and governments to offer highly scalable, reliable, and secure digital identity solutions (issuer, verifier, wallet) across industries and ecosystems. It builds on top of the Community Stack and adds enterprise capabilities like multi-tenancy support, built-in GUI management tools, lifecycle management, credential revocation mechanisms, logs, audits, pre-built integrations, and more. Learn more [here](https://docs.walt.id/enterprise-stack/home/the-enterprise-stack.md) **Q: What's the difference between the Community Stack and Enterprise Stack?** A: The Community Stack is open-source and designed for simpler use cases where you manage your own infrastructure. It's stateless and typically deployed as single instances via Docker. 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 more about the [Community Stack](https://docs.walt.id/community-stack/home.md). **Q: Who should use the Enterprise Stack?** A: The Enterprise Stack is ideal for: - **Large organizations and/or governments** that need scalable digital identity solutions - **Businesses building identity platforms** or SaaS solutions that need to scale - **Organizations requiring enterprise-grade features** like multi-tenancy, audit logs, and compliance tools - **Organizations requiring high uptime** and robust security features **Q: What are the main components of the Enterprise Stack?** A: The Enterprise Stack consists of: - **Organizations**: Top-level accounts that can contain multiple tenants - **Tenants**: Containers for services that can be nested (sub-tenants) - **Services**: Functional capabilities including [KMS Service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md), [DID Service](https://docs.walt.id/enterprise-stack/services/did-service/overview.md), [Issuer Service](https://docs.walt.id/enterprise-stack/services/issuer-service/overview.md), [Verifier Service](https://docs.walt.id/enterprise-stack/services/verifier-service/overview.md), [Wallet Service](https://docs.walt.id/enterprise-stack/services/wallet-service/overview.md), [Credential Status Service](https://docs.walt.id/enterprise-stack/services/credential-status-service/overview.md), and more - **Administration**: Role-based access control with accounts, API keys, and permissions - **User Interfaces**: Built-in GUI for managing services and viewing analytics **Q: What credential formats does the Enterprise Stack support?** A: The Enterprise 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 JWT and SD-JWT 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 Enterprise Stack supports: - **OID4VCI**: Draft 11 and Draft 13 for credential issuance - **OID4VP**: Draft 14, 20 for credential verification - **ISO/IEC 18013-7**: For mobile driver's license exchange - **Multiple DID methods**: did:key, did:jwk, did:web, did:cheqd, and more - **Credential Status**: StatusList2021, Bitstring Status List, Token Status List, RevocationList2020 ## Architecture & Multi-Tenancy **Q: How does the Enterprise Stack architecture work?** A: The Enterprise Stack uses a hierarchical resource structure: 1. **Organizations**: Top-level accounts (e.g., `waltid`) 2. **Tenants**: Containers for services within organizations (e.g., `waltid.tenant1`) 3. **Sub-tenants**: Nested tenants with unlimited nesting levels 4. **Services**: Functional capabilities within tenants (e.g., `waltid.tenant1.issuer1`) 5. **Service Data**: Data specific to each service **Q: What is multi-tenancy and how does it work?** A: Multi-tenancy allows you to: - **Separate data**: Services and data inside tenants are kept separate - **Manage customers**: Group different customers as tenants or sub-tenants - **B2B2C products**: Build and sell products to business clients who offer consumer products - **Unlimited nesting**: Create sub-tenants inside tenants with no limit to nesting levels - **Virtual separation**: Keep services and data virtually separate while managing them under one roof **Q: How do I identify resources in the Enterprise Stack?** A: Resources are identified using hierarchical identifiers: - **Organization**: `waltid` - **Tenant**: `waltid.tenant1` - **Service**: `waltid.tenant1.issuer1` - **Sub-tenant**: `waltid.tenant1.subtenant1` **Q: What services are available in the Enterprise Stack?** A: The Enterprise Stack offers: - **[KMS Service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md)**: Manage cryptographic keys - **[DID Service](https://docs.walt.id/enterprise-stack/services/did-service/overview.md)**: Create decentralized identifiers - **[Issuer Service](https://docs.walt.id/enterprise-stack/services/issuer-service/overview.md)**: Sign & issue various digital credentials - **[Verifier Service](https://docs.walt.id/enterprise-stack/services/verifier-service/overview.md)**: Request & verify digital credentials - **[Wallet Service](https://docs.walt.id/enterprise-stack/services/wallet-service/overview.md)**: Run identity wallets for individuals or organizations - **[Credential Status Service](https://docs.walt.id/enterprise-stack/services/credential-status-service/overview.md)**: Manage credential status lists - **[DID Store Service](https://docs.walt.id/enterprise-stack/services/did-store-service/overview.md)**: Store and manage DIDs - **[Credential Store Service](https://docs.walt.id/enterprise-stack/services/credential-store-service/overview.md)**: Store and query credentials - **[DID Registry](https://docs.walt.id/enterprise-stack/services/did-registry/overview.md)**: Host did:web documents ## Administration & Access Control **Q: How does access control work in the Enterprise Stack?** A: The Enterprise Stack uses a role-based access control system: - **Super Admin**: Highest level of access, can perform all operations without roles - **Accounts**: Used primarily for UI access, require assigned roles for permissions - **API Keys**: Used for machine access, require assigned roles for permissions - **Roles**: Grouped permissions (e.g., create key, delete service) that can be assigned to accounts or API keys **Q: What types of access credentials are available?** A: Three types of access credentials: - **Super Admin**: Can perform all operations on the enterprise API and UI out of the box - **Accounts**: Primarily used for UI access, must be assigned specific roles for permissions - **API Keys**: Programmatic access for interacting with enterprise API, must be assigned specific roles for permissions **Q: How do I create an organization?** A: Organizations can be created by: - **Super Admin**: Using the `/v1/admin/organizations` endpoint - **Any Account**: When the `create-organization` feature is enabled via the Feature Manager - **Automatic Role Assignment**: When a new organization is created, an admin role is automatically generated (e.g., `waltid.admin`) **Q: How do I manage tenants and services?** A: Tenants and services are managed through: - **API endpoints**: Programmatic creation and management - **Admin UI**: Graphical interface for managing resources - **Role-based permissions**: Control who can create and manage resources - **Hierarchical structure**: Organize resources in a logical hierarchy ## Setup & Deployment **Q: How do I get started with the Enterprise Stack?** A: Assuming you have an Enterprise Stack License, you can get started in several ways: 1. **Quickstart**: Use the [enterprise quickstart repository](https://github.com/walt-id/waltid-enterprise-quickstart) with Docker Compose 2. **Local Development**: Set up locally with Docker and MongoDB 3. **Cloud Deployment**: Deploy on AWS, Azure, or other cloud platforms 4. **On-premise**: Deploy in your own infrastructure If you do not have a Enterprise Stack License yet, please [contact us](https://walt.id/contact) for more information. **Q: What are the system requirements for running the Enterprise Stack?** A: The Enterprise Stack requires: - **Docker**: For containerized deployment - **MongoDB**: For data persistence (v20.10.14+) **Q: How do I access the Enterprise Stack once it's running?** A: When running locally, you can access: - **Enterprise API**: [Local](http://enterprise.localhost:3000/swagger/index.html) - **Admin UI**: Available through the enterprise API endpoints **Q: What configuration files are required?** A: The Enterprise Stack requires several configuration files: - **`_features.conf`**: Control which features are enabled - **`auth.conf`**: Authentication settings and token configuration - **`database.conf`**: MongoDB connection and configuration - **`enterprise.conf`**: Enterprise-specific settings - **`superadmin-registration.conf`**: Super admin account configuration - **`web.conf`**: Host and port settings ## Enterprise Features **Q: What enterprise features does the Enterprise Stack offer?** A: The Enterprise Stack provides: - **Scalability & Persistence**: Multi-tenancy and parallel instances for load distribution - **Process Automation**: Reusable flows and webhook integrations - **Logging & Analytics**: Events, metrics, and audit logs with filtering capabilities - **User Interfaces**: Built-in UI for managing services and viewing analytics - **Pre-built Integrations**: Connect with existing infrastructure (KMS, IAM, etc.) - **Security & Compliance**: Fine-grained role-based authentication and compliance tools **Q: What logging and analytics capabilities are available?** A: The Enterprise Stack provides: - **Events and metrics**: Track issuance/verification requests, claimed credentials, active wallets - **Filtering**: Filter by credential formats, exchange protocols, or ecosystems - **Audit logs**: Access comprehensive audit trails - **Streaming**: Configure logging events to stream to various systems ## Services & Dependencies **Q: How do service dependencies work?** A: Services can depend on other services to provide functionality: - **Issuer Service**: Requires KMS Service for signing credentials - **Wallet Service**: May require DID Service and Credential Store Service - **Service configuration**: Each service has its own configuration - **Tenant scope**: Services must be in the same tenant or sub-tenant to depend on each other **Q: How do I create and manage services?** A: Services are created and managed through: - **API endpoints**: Programmatic service creation - **Admin UI**: Graphical interface for service management - **Configuration**: Each service has its own configuration - **Dependencies**: Services can depend on other services within the same tenant **Q: What is the DID Registry Service?** A: The DID Registry Service: - **Lists registered DIDs**: Shows all registered DID:web identifiers - **Resolves DIDs**: Provides resolution for specific DID:web documents - **Two levels**: Organization-level and tenant-level DIDs - **Lightweight**: Acts as a registry and resolution layer - **Delegates storage**: Uses DID Store Service for persistence ## Security & Compliance **Q: What security features does the Enterprise Stack provide?** A: The Enterprise Stack includes: - **Fine-grained permissions**: Role-based access control for all operations - **API key management**: Secure programmatic access - **Audit logging**: Comprehensive audit trails for compliance - **Multi-tenancy isolation**: Secure separation of data and services **Q: How does the Enterprise Stack support compliance?** A: Compliance features include: - **eIDAS2 alignment**: Continuous alignment with EU digital ID ecosystem - **Audit logs**: Comprehensive logging for compliance requirements - **Role-based access**: Fine-grained control over who can access what ## Migration & Integration **Q: Can I migrate from Community Stack to Enterprise Stack?** A: Yes, migration is supported: - **Similar interfaces**: Core APIs (Issuer, Verifier, Wallet) have similar interfaces - **Gradual migration**: Move services one at a time - **Support available**: walt.id can provide migration assistance - **Backward compatibility**: Enterprise Stack builds on Community Stack **Q: How do I integrate the Enterprise Stack with my existing systems?** A: Integration options include: - **REST APIs**: Use the comprehensive API suite - **Pre-built integrations**: Leverage existing connectors - **Webhook support**: Connect external business logic **Q: What cloud platforms are supported?** A: The Enterprise Stack supports deployment on any Docker-compatible environment. ## Support & Licensing **Q: Where can I get help with the Enterprise Stack?** A: Several resources are available: - **Documentation**: Comprehensive guides and API references - **Enterprise Support**: Available for Enterprise Stack customers - **Community**: [Join our community channel](https://walt.id/discord) for peer support - **Direct Contact**: [Contact](https://walt.id/contact) walt.id for enterprise-specific inquiries **Q: How do I become an Enterprise Stack customer?** A: To become an Enterprise Stack customer: - **Contact walt.id**: [Reach out](https://walt.id/contact) for licensing and pricing information --- ### Still Have Questions? If you couldn't find the answer to your question here, please: - Check our [comprehensive documentation](https://docs.walt.id/enterprise-stack/home.md) - [Contact walt.id](https://walt.id/contact) for enterprise-specific inquiries -------------------------------------------------------------------------------- PAGE: Foundations URL: https://docs.walt.id/enterprise-stack/foundations RAW: https://docs.walt.id/enterprise-stack/foundations.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Enterprise Foundations The Enterprise Stack is built up of a set of resources, like Organizations, Tenants/Sub-Tenants, Services (e.g. Issuer, Verifier, KMS), and the data of the individual services (e.g. Issuance Requests, Private/Public Keys, Configurations). Together the resources form a tree-like structure as shown below. ![image of did](https://docs.walt.id/img/enterprise-stack/concepts/enterprise-resource-tree.png) ## The Organization The Organization is your account. Under the Organization you can group customers as tenants, sub-tenants, or create a tenant of your own. This way services and their data can be kept virtually separate, enabling you to easily manage different customers or services of your own under one roof. Also, this structure allows you to build and sell B2B2C products (e.g., you can resell to business clients who offer consumer products). While the Enterprise Stack allows for multiple Organizations, on-prem Enterprise deployments should always have one Organization. An organization identifier might be `waltid` ## Tenants Tenants enable you and your customers to launch enterprise services like issuer, verifier, KMS, Credential Web Registry, and more. You can also create sub-tenants inside of tenants and sub-tenants. At this point, there is no limit to the level of nested sub-tenants. Services and their data inside tenants and sub-tenants are kept separate. A tenant identifier might be `waltid.tenant1` ## Services Services provide the functional capabilities for decentralized identity and ID wallets. They can be created inside of tenants and sub-tenants. Each service has its own configuration. Therefore, you can have services of the same type for different purposes. For example, you could have an issuer for issuing only W3C credentials and another for only SD-JWT VCs. Think of these services as Lego blocks: each one has a focused responsibility, and you can compose them to match the credential lifecycle you need—issuance, storage, verification, and everything in between. ### Service types In the Enterprise Stack there are two broad categories of services. You often run both side by side to balance "do the work" versus "persist the state." **Operational services (do things)** These are the engines that execute credential and identity flows. - [Issuer](https://docs.walt.id/enterprise-stack/services/issuer-service/overview.md) – Sign and issue credentials via OID4VC. - [Verifier](https://docs.walt.id/enterprise-stack/services/verifier-service/overview.md) – Verify credentials using the draft OpenID4VP specifications. - [Wallet Service](https://docs.walt.id/enterprise-stack/services/wallet-service/overview.md) – Custodial wallet for storing and managing credentials and keys. - [KMS](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) – Manage keys for wallet & credential signing. - [DID Service](https://docs.walt.id/enterprise-stack/services/did-service/overview.md) – Create DIDs based on various methods (did:key, did:web, ...). **Storage & registry services (store and expose data)** These persist the data that operational services depend on and expose it via APIs. - [Credential Store Service](https://docs.walt.id/enterprise-stack/services/credential-store-service/overview.md) – Store and query credentials. - [DID Store](https://docs.walt.id/enterprise-stack/services/did-store-service/overview.md) – Store and query DID documents. By mixing operational and storage services you can scale different parts of your solution independently while keeping responsibilities cleanly separated. **Note:** The above lists highlight the main services in each category but is not fully exhaustive. For a complete overview of all available services and their features, please visit the [Service Catalog](https://docs.walt.id/enterprise-stack/services.md). ### Service dependencies Services might also depend on other services to provide their functionality. For example, an issuer needs keys for signing credentials and would, therefore, require a KMS service under the same tenant or sub-tenant. A service identifier might be `waltid.tenant1.issuer1` ## Service Data Each service can hold different types of data. An issuer, for example, might hold issuer configurations like the credentials the issuer can offer or a list of open credential offers and their related sessions. ## Accounts, API Keys and the Super Admin To access the Enterprise Stack, you either need an Account (used mainly for UI access), an API Key (used for machine access), or a Super Admin. Accounts and API Keys can hold different roles. Roles are simply a set of grouped permissions (e.g., create key, delete service). Therefore, when your Account or API Key has assigned the role with the required permissions to perform the necessary actions, you can operate and use the Enterprise Stack via the Admin Interfaces or the Enterprise APIs. The Super Admin can do everything and doesn't require any roles. If you want to learn more checkout the Accounts, API-Keys and Super Admin section under [Administration](https://docs.walt.id/enterprise-stack/administration.md). -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/capabilities RAW: https://docs.walt.id/enterprise-stack/capabilities.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Enterprise Stack Capabilites The capabilities listed below apply across all Enterprise Stack services. For service-specific capabilities, see: - **[Issuer Capabilities](https://docs.walt.id/enterprise-stack/capabilities/issuer.md)** – Core features for credential issuance, including data sourcing, templates & types, branding, lifecycle management, and delivery flows. - **[Verifier Capabilities](https://docs.walt.id/enterprise-stack/capabilities/verifier.md)** – Features for credential verification and presentation request handling. - **[Wallet Capabilities](https://docs.walt.id/enterprise-stack/capabilities/wallet.md)** – Features for credential management, presentation & sharing, and key management. -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/capabilities/issuer RAW: https://docs.walt.id/enterprise-stack/capabilities/issuer.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview Unlike the Community Stack, the Enterprise Stack isn’t split into three separate products (Issuer, Verifier, Wallet). It’s a single, unified stack that can power different digital identity use cases. One of those is running one or many credential issuers via its [multi-tenant model](https://docs.walt.id/enterprise-stack/foundations.md#tenants). Below is the full list of Enterprise Stack capabilities that are relevant for building credential issuance solutions. ## Credential Issuance Capabilites -------------------------------------------------------------------------------- PAGE: Issuer Identifiers URL: https://docs.walt.id/enterprise-stack/capabilities/issuer/did-identifiers RAW: https://docs.walt.id/enterprise-stack/capabilities/issuer/did-identifiers.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Issuer Identifiers The Enterprise Stack supports multiple identifier methods for your issuer services—from decentralized identifiers (DIDs) with built-in hosting to traditional X.509 certificates. This gives you flexibility to align with your ecosystem's trust model, regulatory requirements, and operational constraints. Each issuer service can use a different identifier method, and you can operate multiple issuers with distinct identity strategies within a single deployment. **Note:** "How do my issuer services identify themselves in credentials?" ## What's included The Enterprise Stack supports three main approaches to issuer identification, each with different operational characteristics. ### DID-based Identifiers Your issuer service 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** (via the [DID Service](https://docs.walt.id/enterprise-stack/services/did-service/overview.md)) 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, with built-in hosting through the [DID Registry Service](https://docs.walt.id/enterprise-stack/services/did-registry/overview.md) The DID Service creates DIDs on-demand (stateless). DIDs can be persisted using the [DID Store Service](https://docs.walt.id/enterprise-stack/services/did-store-service/overview.md) for reuse across issuance operations. ### X.509 Certificate-based Identifiers Your issuer service 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 service 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. ## Multi-Tenant Identifier Support Each tenant (or sub-tenant) in the Enterprise Stack can operate multiple issuer services, and each issuer service can use a different identifier method: - **Tenant A** might operate one issuer service using `did:web` (hosted by the DID Registry), and another using X.509 certificates. - **Tenant B** might use `did:key` for all its issuer services. This means you can: - Host multiple customers (organizations) in one deployment, each with distinct identity strategies - Support different trust models within a single organization (e.g., decentralized DIDs for open credentials, PKI certificates for regulated credentials) ## Key Management Integration Regardless of which issuer identifier method you choose, the underlying cryptographic keys used for signing are stored and managed through the Enterprise Stack's [Key Management Service (KMS)](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md). For proof-of-concepts and testing, local key storage (using the Enterprise Stack database) is available. For production deployments, external key management systems keep key material secure (Hashicorp Vault, Azure Key Vault, AWS KMS, etc.). Learn more [here](https://docs.walt.id/enterprise-stack/services/key-management-service/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. **Can different issuer services in my tenant use different identifier methods?** Yes. Each issuer service has its own identifier configuration. One service can use `did:web`, another X.509 certificates, and a third `did:key` – all within the same tenant. **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.). **Can I share a DID across multiple issuer services?** Yes. You can use organization-level DIDs to share an identifier across multiple tenants (e.g., departments within a company), or tenant-level DIDs to share across multiple issuer services within the same tenant. ## Get Started - **[Set up a Key Management Service](https://docs.walt.id/enterprise-stack/services/key-management-service/setup.md)** – Create a KMS service to manage keys for your issuer identifiers. - **[Create DIDs](https://docs.walt.id/enterprise-stack/services/did-service/did-creation/did-key.md)** – Learn how to create DIDs using the DID Service. - **[Set up DID Web hosting](https://docs.walt.id/enterprise-stack/services/did-registry/did-web-registry-setup-guide.md)** – Configure the DID Registry Service to host `did:web` identifiers. - **[Set up an issuer service](https://docs.walt.id/enterprise-stack/services/issuer-service/setup.md)** – Create an issuer service. - **[Issue your first credential](https://docs.walt.id/enterprise-stack/services/issuer-service/credential-issuance/vc-oid4vc.md)** – See how to configure the identifier in a credential. -------------------------------------------------------------------------------- PAGE: Data & Identity Sourcing URL: https://docs.walt.id/enterprise-stack/capabilities/issuer/data-sourcing RAW: https://docs.walt.id/enterprise-stack/capabilities/issuer/data-sourcing.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Data & Identity Sourcing Data & Identity Sourcing describes how the Enterprise 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. Instead of forcing you into a single model, the Enterprise Stack lets you decide where subject data lives and when it is fetched, so you can 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 Enterprise 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 Enterprise Stack Issuer when creating a credential offer. **This means:** - You stay in control of where identity data lives (databases, registries, etc.). - The Issuer service receives a complete, ready-to-sign data structure and focuses on protocol handling, signing, and lifecycle management. #### 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/enterprise-stack/services/issuer-service/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 service can use the resulting tokens to fetch user claims and map them into credential attributes. This capability is available in the Enterprise Stack. - 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. ## Tenant-Aware Data Sourcing Each tenant or issuer service in the Enterprise Stack can define its own data sourcing strategy (which backends, which webhooks, which authorization server) to reflect organizational boundaries and business models. This means you can operate multiple issuers with completely different data sourcing approaches within the same Enterprise Stack deployment – for example, one tenant fetching data from a CRM via webhooks, another using an OIDC authorization server, and a third receiving all attributes from their backend up front. ## 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 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. Using an IdP/Authorization Server becomes relevant when you want to tie credential issuance to a fresh authentication event or reuse existing IAM policies and claims. - **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, webhooks, and Authorization Servers as requirements become more sophisticated – without changing the underlying credential formats or wallet integration. ## A Note on PII Regardless of which sourcing approach you use, issuance sessions in the Enterprise Stack can contain personally identifiable information (PII) – for example, subject attributes passed during offer creation, data fetched via webhooks, or claims obtained from an authorization server. By default, the Issuer service stores session data (offer payloads, subject attributes, interaction logs) to enable features like audit trails, debugging, and session management. However, this means PII may persist in your database longer than necessary. **What you can do:** The Enterprise Stack provides a configurable data retention capability that lets you define how long issuer session data should be kept and when it should be automatically purged. This allows you to balance operational needs (logging, troubleshooting) with compliance and privacy requirements (GDPR, data minimization principles). For details on how to configure retention windows, purge schedules, and safety guardrails, see [Data Retention and Auto-Purge](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/data-retention.md). ## Get Started Ready to implement data sourcing in your issuance flows? Here are the next steps: - **[Issue a credential using pre-defined data](https://docs.walt.id/enterprise-stack/services/issuer-service/credential-issuance/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/enterprise-stack/services/issuer-service/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/enterprise-stack/capabilities/issuer/credential-branding RAW: https://docs.walt.id/enterprise-stack/capabilities/issuer/credential-branding.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Credential branding The Enterprise Stack 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. - **Custom Branding per Tenant** Branding lives in tenant-scoped issuer metadata, so different tenants or issuer services can present the same credential type with their own visuals. - **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 the configured display metadata of a specific credential type into an issuance request using the `display` data function in the mapping object of the issuance request. This lets you embed visuals, defined at issuer level, per credential instance without re-specifying them in the payload. Learn more [here](https://docs.walt.id/enterprise-stack/services/issuer-service/data-functions.md#display). 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 - **[Set up an issuer service](https://docs.walt.id/enterprise-stack/services/issuer-service/setup.md)** – Learn how to create and configure an issuer service where you can define credential branding. - **[Configure credential display metadata](https://docs.walt.id/enterprise-stack/services/issuer-service/configurations/openid-metadata/credential-display.md)** – Define colors, logos, descriptions, and display fields for each credential type. - **[Use display data functions](https://docs.walt.id/enterprise-stack/services/issuer-service/data-functions.md#display)** – Learn how to embed display metadata dynamically during credential issuance. -------------------------------------------------------------------------------- PAGE: Credential Lifecycle URL: https://docs.walt.id/enterprise-stack/capabilities/issuer/credential-lifecycle RAW: https://docs.walt.id/enterprise-stack/capabilities/issuer/credential-lifecycle.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Credential lifecycle The Enterprise Stack let's you issue credentials across major standards (W3C VC, SD-JWT VC, ISO mdoc) with revocation, suspension, or custom status using globally adopted mechanisms like TokenStatusList. Host status lists on external registries like S3 or control credential lifetime via static validFrom and validUntil fields. ## What's included - **Standardized status credentials** Define and sign status credentials using standard formats (Bitstring Status List v1.0, TokenStatusList, StatusList2021) to support revocation, suspension, or custom states across credential types (W3C VC, SD-JWT VC, ISO mdoc). - **Hosted & auto-published status credentials** Host and auto-publish status credentials to your preferred registry (AWS S3, Azure Blob Storage, GCS) so verifiers and wallets can reliably consume them. - **Secure KMS-backed signing keys** Sign status credentials with keys stored in an external KMS (AWS KMS, Azure Key Vault, OCI Vault, …) so private keys never leave your KMS/HSM. - **One-call status updates for issued credentials** Suspend, resume, or revoke issued credentials with a single API call—the system flips the relevant bit in the status credential, re-signs it, and republishes it to your configured registry. - **Configure validFrom / validUntil on issuance** Set validFrom and validUntil per credential—statically or via data functions—to control when a credential becomes valid and when it expires. This doesn’t require a status credential, as it’s stored directly in the issued/signed credential. ## 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. ## Get Started - **[Set up a credential status service](https://docs.walt.id/enterprise-stack/services/credential-status-service/setup.md)** – Learn how to create and configure a status service with AWS S3, Azure Blob, or GCS registry. - **[Create and manage status credentials](https://docs.walt.id/enterprise-stack/services/credential-status-service/manage.md)** – Learn how to create status credential configurations and update credential status (revoke, suspend, resume). -------------------------------------------------------------------------------- PAGE: Credential Templates, Types, and Schemas URL: https://docs.walt.id/enterprise-stack/capabilities/issuer/credential-templates-types-schemas RAW: https://docs.walt.id/enterprise-stack/capabilities/issuer/credential-templates-types-schemas.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Credential Templates, Types, and Schemas Credential Templates, Types, and Schemas explains how the Enterprise 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 Enterprise 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 When you register a credential type in the Enterprise Stack, you always 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 Enterprise Stack, you: - Register **credential types** 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 registered within a particular [issuer service](https://docs.walt.id/enterprise-stack/services/issuer-service/overview.md), any issuance flow (authorization code, pre-authorized, etc.) 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 Enterprise Stack assumes: - Schemas are defined and managed in your or another external 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 Enterprise Stack checks that the credential is a valid W3C VC in JWT/SD-JWT form; it does not impose extra “template rules” on top. Optionally, you can reference an externally hosted schema, and the issuer will validate that the data in the issuance request conforms to it. **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. - Today, in the Enterprise Stack, you can freely define and use your own `vct` values; the Enterprise Stack does not restrict them and does not manage the associated Type Metadata. If you rely on externally hosted Type Metadata documents, your own systems are responsible for ensuring that issuance payloads conform to those definitions. We are planning a future capability that will let you define this metadata in the Enterprise Stack and optionally validate provided attributes in issuance requests against it. **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 Enterprise Stack? You combine three layers: 1. **Configuration level (in the Enterprise Stack)** - Register a credential type (e.g. BankID) + format (e.g. W3C VC). - Optionally edit issuer metadata for labels and branding. - Use the registred 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. - 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. ## Configuring Credential Types per Issuer & Tenant Each tenant or issuer service in the Enterprise Stack can define its own: - Supported **formats** (e.g. only SD-JWT VC, or SD-JWT VC + W3C VC + mdoc). - **Credential types** per format (e.g. Tenant A: `DigitalDiploma`, `StudentID`; Tenant B: `identity_credential`, `EmployeeID`). - Type-specific metadata (display names, descriptions, branding). This means: - One deployment can host many issuers with very different catalogs of credential types. - You can phase in new formats and types gradually (e.g. add SD-JWT VC identity_credential next to existing W3C VC diplomas). - You keep type naming and schema definitions aligned with your domain, standards, and regulatory context. ## 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 Enterprise 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 register type/format pairs (e.g. BankID as W3C VC; identity_credential as SD-JWT VC). - **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. - 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. - **How does this compare to systems with rigid templates?** In template-heavy systems, changing a credential often means editing templates inside the product. In the Enterprise Stack, the issuer stays **schema-agnostic** and focuses on issuance, lifecycle, and interoperability. You keep full control over schemas, “templates,” and evolution in your own domain. ## Get Started - **[Set up an issuer service](https://docs.walt.id/enterprise-stack/services/issuer-service/setup.md)** – Learn how to create and configure an issuer service with your credential types and formats. - **[Issue your first credential](https://docs.walt.id/enterprise-stack/services/issuer-service/credential-issuance/vc-oid4vc.md)** – Step-by-step guide showing how to issue credentials using your registered types. - **[Configure credential data sourcing](https://docs.walt.id/enterprise-stack/capabilities/issuer/data-sourcing.md)** – Learn how to populate credential claims dynamically from your identity providers or data sources. - **[Understand credential delivery](https://docs.walt.id/enterprise-stack/capabilities/issuer/credential-delivery.md)** – Explore the issuance flows and device journeys for delivering credentials to wallets. - **[Try the 30-minute tutorial](https://docs.walt.id/enterprise-stack/home/tutorial-30-min.md)** – Walk through a complete issuance scenario from type registration to credential delivery. - **[Explore issuer configurations](https://docs.walt.id/enterprise-stack/services/issuer-service/configurations/general-config.md)** – Customize your issuer metadata, credential types, and format-specific settings. -------------------------------------------------------------------------------- PAGE: Credential Delivery URL: https://docs.walt.id/enterprise-stack/capabilities/issuer/credential-delivery RAW: https://docs.walt.id/enterprise-stack/capabilities/issuer/credential-delivery.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Credential Delivery Credential Delivery describes how a credential gets from the Enterprise Stack issuer 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 Enterprise Stack does not constrain how you integrate issuance into your applications. It exposes simple API endpoints that return a 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 Enterprise 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):** - Authorization Code Flow - Pre-Authorized Code Flow (with and without PIN) **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. 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 Enterprise Stack issuer. - You can combine this with [Data & Identity Sourcing](https://docs.walt.id/enterprise-stack/capabilities/issuer/data-sourcing.md) to fetch user claims from the Authorization Server and inject them into the credential at issuance time. #### 2. 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 Enterprise 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. ### 3. Device Journeys: Same-device & Cross-device Offers Regardless of which **issuance flow** you use (authorization code or pre-authorized code, with or without PIN), 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 Enterprise Stack:** - The [issuer API](https://docs.walt.id/enterprise-stack/services/issuer-service/overview.md) 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 chosen issuance flow. You can use any device journey with authorization code or pre-authorized code (with or without PIN). ## Credential Delivery for Different Issuers and Tenants Each [tenant](https://docs.walt.id/enterprise-stack/administration/tenants/overview.md) or [issuer service](https://docs.walt.id/enterprise-stack/services/issuer-service/overview.md) in the Enterprise Stack can define its own combination of issuance flows and device journeys: - Which issuance flows are used (authorization code, pre-authorized code, or both). - Whether a PIN is required for pre-authorized offers. - How credential offers are embedded into frontends (e.g. portals, emails, kiosks) via QR codes or links. - How delivery interacts with other capabilities such as Data & Identity Sourcing or Credential Status. This means you can operate multiple issuers with completely different delivery and UX patterns in the same Enterprise Stack deployment – for example: - Issuer Service A: authorization code issuance flow, cross-device QR codes on desktop. - Issuer Service B: pre-authorized code issuance flow with PIN, offers delivered via email links (same-device). - Issuer Service C: a mix of both issuance flows, with frontends choosing between QR and deep links per journey. ## FAQs - **Is a PIN required for the pre-authorized code issuance flow?** - No. The PIN is optional. In the Enterprise 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 Enterprise Stack require a specific wallet?** - No. The issuer service uses standard OID4VCI endpoints and credential offer semantics. Any wallet that implements the relevant OID4VCI versions and issuance flows can claim credentials from the Enterprise Stack. - **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 (authorization code or pre-authorized 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 Ready to deliver credentials to wallets? Here are the next steps: - **[Set up an issuer service](https://docs.walt.id/enterprise-stack/services/issuer-service/setup.md)** – Learn how to create and configure an issuer service in the Enterprise Stack. - **[Issue your first credential](https://docs.walt.id/enterprise-stack/services/issuer-service/credential-issuance/vc-oid4vc.md)** – Step-by-step guide showing how to generate credential offers using authorization code or pre-authorized code flows. - **[Configure credential data sourcing](https://docs.walt.id/enterprise-stack/capabilities/issuer/data-sourcing.md)** – Learn how to dynamically populate credential claims from your identity providers or data sources. - **[Try the 30-minute tutorial](https://docs.walt.id/enterprise-stack/home/tutorial-30-min.md)** – Walk through a complete issuance scenario with credential delivery. - **[Explore issuer configurations](https://docs.walt.id/enterprise-stack/services/issuer-service/configurations/general-config.md)** – Customize your issuer metadata and OpenID settings to match your use case. -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/capabilities/verifier RAW: https://docs.walt.id/enterprise-stack/capabilities/verifier.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview Unlike the Community Stack, the Enterprise Stack isn't split into three separate products (Issuer, Verifier, Wallet). It's a single, unified stack that can power different digital identity use cases. One of those is running one or many credential verifiers via its [multi-tenant model](https://docs.walt.id/enterprise-stack/foundations.md#tenants). Below is the full list of Enterprise Stack capabilities that are relevant for building credential verification solutions. ## Credential Verification Capabilites -------------------------------------------------------------------------------- PAGE: Credential Lifecycle URL: https://docs.walt.id/enterprise-stack/capabilities/verifier/credential-lifecycle RAW: https://docs.walt.id/enterprise-stack/capabilities/verifier/credential-lifecycle.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Credential Lifecycle The Enterprise Stack Verifier 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 registries (S3, Azure Blob, etc.) - Supports industry-standard formats: Bitstring Status List v1.0, TokenStatusList, StatusList2021 - 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 tenant or use case ## FAQs - **Which status list formats are supported?** — The Verifier supports W3C Bitstring Status List v1.0, TokenStatusList (IETF), and StatusList2021, covering all major standards for both JWT and JSON-LD credentials. - **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. ## Where to go next - **[Status policy](https://docs.walt.id/enterprise-stack/services/verifier-service/policies/parameterized-verification-policies.md#credential-status)** – Learn about the credential status policy to check revocation and suspension status across multiple formats. - **[Expiration & validity policies](https://docs.walt.id/enterprise-stack/services/verifier-service/policies/static-verification-policies.md#expiredn)** – Learn about the expiration and validity policies to check credential validity windows. -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/capabilities/wallet RAW: https://docs.walt.id/enterprise-stack/capabilities/wallet.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview Unlike the Community Stack, the Enterprise Stack isn't split into three separate products (Issuer, Verifier, Wallet). It's a single, unified stack that can power different digital identity use cases. One of those is running one or many digital wallets via its [multi-tenant model](https://docs.walt.id/enterprise-stack/foundations.md#tenants). Below is the full list of Enterprise Stack capabilities that are relevant for building digital wallet solutions. ## Wallet Capabilites -------------------------------------------------------------------------------- PAGE: Setup URL: https://docs.walt.id/enterprise-stack/setup RAW: https://docs.walt.id/enterprise-stack/setup.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Setup Learn how to configure, setup and run the Enterprise Stack in different environments. ## Get Started - [Local Setup](https://docs.walt.id/enterprise-stack/setup/local-development.md) - How to run the Enterprise Stack in your local environment. - [Deployment](https://docs.walt.id/enterprise-stack/setup/deployment/overview.md) - How to deploy the Enterprise Stack via Kubernetes. -------------------------------------------------------------------------------- PAGE: Local Development URL: https://docs.walt.id/enterprise-stack/setup/local-development RAW: https://docs.walt.id/enterprise-stack/setup/local-development.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Local Development To run the enterprise stack locally, you need [Docker](https://www.docker.com/) (v20.10.14) and a [MongoDB database](https://www.mongodb.com/). Below, you find instructions on how to: 1. use our quickstart repo to spin up the Enterprise API next to a local MongoDB database using docker-compose. 2. run the Enterprise API without the local MongoDB. This setup can be useful if you want to connect to a custom MongoDB instance. ## Quickstart 1. Clone the [enterprise quickstart repository](https://github.com/walt-id/waltid-enterprise-quickstart) ```bash git clone https://github.com/walt-id/waltid-enterprise-quickstart.git ``` 2. Login to docker `$ docker login -u waltid` 3. At the password prompt, enter the personal access token you received from us. 4. Start docker-compose ```bash docker-compose up ``` Once docker-compose is running, you can visit [enterprise.localhost:3000/swagger](http://enterprise.localhost:3000/swagger/index.html) to access the Enterprise APIs. ### Next Steps - [Enterprise Foundations](https://docs.walt.id/enterprise-stack/foundations.md) - Learn more about the Enterprise Stack core building blocks - [Administration](https://docs.walt.id/enterprise-stack/administration.md) - Learn about organizations, tenants, and the details of authentication and access management. - [Services](https://docs.walt.id/enterprise-stack/services.md) - Explore various services (e.g., issuer, verifier) and their detailed usage. ## Running Enterprise API (without MongoDB) **Pulling the Enterprise API Docker Image** 1. Login to docker `$ docker login -u waltid` 2. At the password prompt, enter the personal access token you received from us. 1. Pull the image `docker pull waltid/waltid-enterprise-api:{version}` **Running The Image** 1. Configuration Files Please prepare all the configuration files required to start the enterprise stack in a folder called `config` . You can use the `config` folder in our [`waltid-enterprise-quickstart`](https://github.com/walt-id/waltid-enterprise-quickstart) repo as a reference. The folder should include the following files: - `_features.conf` - `auth.conf` - `database.conf` - `enterprise.conf` - `superadmin-registration.conf` - `web.conf` You can learn more about each of them in the [configuration section](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/overview.md). 1. Running the image With the command below, we start the enterprise stack API, mount the folder with all the required configuration files into it, and make the API accessible via [localhost:3000](http://localhost:3000) ```json docker run -p 3000:3000 -it -v $(pwd)/config: /config -t waltid/waltid-enterprise-api: 0.1.0 ``` 2. Using host aliases Sometimes you may want to use a custom domain for your enterprise stack. You can do this by adding a host alias to the enterprise stack. Use cases include: - Testing local issuance and verification flows against remote wallets (e.g. EUDI wallet) - External DID Web Hosting. See more here: [External DID Web Hosting](https://docs.walt.id/enterprise-stack/services/did-registry/external-did-web-hosting.md) To add a host alias, you can use the following command: ```bash curl -X 'POST' \ 'https://{orgID}.enterprise.cloud/v1/{target}/host-alias-api/host-aliases/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "domain": "your-custom-domain.com" }' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `waltid`, your default Base URL will be `waltid.enterprise.cloud`. - `target`: _resourceIdentifier_ - The target indicates the tenant for which to create the host alias (`{organizationID}.{tenantID}.{host_alias_id}), e.g. `waltid.tenant.my_alias` **Body Parameters** - `domain`: _String_ - The custom domain that you want to use for your tenant. ### Issuer Service Configuration When configuring the issuer service, you need to use the host alias in the `baseUrl` parameter. ```json { "baseUrl": "https://your-custom-domain.com" } ``` ### Verifier Service Configuration When configuring the verifier service, you need to use the host alias in the `baseUrl` parameter. ```json { "baseUrl": "https://your-custom-domain.com" } ``` ### Verifier2 Service Configuration When configuring the verifier2 service, you need to use the host alias in the `url_host` parameter when creating the verifier2 service or creating new verification sessions. ```json { "url_config": { "url_prefix": "https://your-custom-domain.com/v1/{target}/verifier2-service-api", "url_host": "haip-vp://authorize" }, } ``` Where `{target}` is the resource identifier target of the verifier2 service you are using. -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/setup/deployment/overview RAW: https://docs.walt.id/enterprise-stack/setup/deployment/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview Learn how to deploy the Enterprise Stack in different environments. ## Get Started - [Docker](https://docs.walt.id/enterprise-stack/setup/deployment/docker-deployment.md) - General docker image example. - [Kubernetes](https://docs.walt.id/enterprise-stack/setup/deployment/kubernetes-deployment.md) - General kubernetes deployment.yml example. - [AWS](https://docs.walt.id/enterprise-stack/setup/deployment/aws-deployment.md) - Instructions for the AWS deployment. - [Azure](https://docs.walt.id/enterprise-stack/setup/deployment/azure-deployment.md) - Instructions for the Azure deployment. -------------------------------------------------------------------------------- PAGE: Docker Deployment URL: https://docs.walt.id/enterprise-stack/setup/deployment/docker-deployment RAW: https://docs.walt.id/enterprise-stack/setup/deployment/docker-deployment.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Docker Deployment This document provides instructions for deploying the [walt.id](http://walt.id) Enterprise API in a Docker environment. It includes configuration details and deployment steps using `deployment.yaml`. ## Prerequisites A Docker environment including Docker Daemon, Client and access to Docker Hub is available. Furthermore, the ***config***-folder with the corresponding config-file must be available to be mounted as volume as shown in the following commands: # Running the Docker Container The Docker container can be started by the following command: ``` docker run -p 3000:3000 -it -v $(pwd)/config:/config -t waltid/waltid-enterprise-api ``` The environment variable JAVA_TOOL_OPTIONS can be set to pass custom Java options to the application. The following example shows how to add some JVM option as well has how to override some properties from the database configuration. ``` docker run -p 3000:3000 -it -v $(pwd)/config:/config -e "JAVA_TOOL_OPTIONS=-Xms1024M -XX:+PrintFlagsFinal -Dconfig.override.mongodb.ssl.enable=true -Dconfig.override.mongodb.ssl.trustStoreLocation=config/aws-truststore.jks -Dconfig.override.mongodb.ssl.trustStorePassword=123456 -Dconfig.override.mongodb.ssl.invalidHostNameAllowed=true" -t waltid/waltid-enterprise-api ``` In order to connect to a MongoDB instance running on the same host then the application, it might be useful to add the parameter "--network=host" (if supported by the environment). **Note:** Please ensure that all `openid/*` endpoints of the Enterprise Stack API are publicly accessible. This is necessary for successful credential exchanges with external systems. -------------------------------------------------------------------------------- PAGE: Kubernetes Deployment URL: https://docs.walt.id/enterprise-stack/setup/deployment/kubernetes-deployment RAW: https://docs.walt.id/enterprise-stack/setup/deployment/kubernetes-deployment.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Kubernetes Deployment This document provides instructions for deploying the [walt.id](http://walt.id) Enterprise API in a Kubernetes environment. It includes configuration details and deployment steps using `deployment.yaml`. ## Prerequisites - Access to a Kubernetes cluster - `kubectl` configured to interact with your cluster - Familiarity with Kubernetes concepts such as Deployments, Services, and Ingress ## Configuration Overview The `deployment.yaml` file is structured into several key sections: - **ConfigMap**: Defines configuration settings for the enterprise API. - **Deployment**: Specifies the deployment settings, including the container image and volume mounts. - **Service**: Exposes the API to the network. - **Ingress**: Manages external access to the services ### The Config Map The `ConfigMap` defines configurations of the walt.id Enterprise API. Modify the values as needed for your environment. You can learn more about each configuration file in the configurations section [here](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/features.md) ```bash apiVersion: v1 kind: ConfigMap metadata: name: waltid-enterprise-api-config data: _features.conf: | enabledFeatures = [ admin dev-mode ] auth.conf: | requireHttps = false database.conf: | databaseType = mongodb fileStorage = { path = "data" } mongodb = { connectionString = "mongodb://root:password@localhost:27017/" } enterprise.conf: | baseDomain = "enterprise-demo.waltid.dev" web.conf: | webHost = "0.0.0.0" webPort = 3000 ``` ### API Deployment ```bash kind: Deployment apiVersion: apps/v1 metadata: name: waltid-enterprise-api spec: replicas: 1 selector: matchLabels: app: waltid-enterprise-api template: metadata: labels: app: waltid-enterprise-api annotations: deployment/id: "_DEFAULT_DEPLOYMENT_" spec: imagePullSecrets: - name: waltid-regcred containers: - name: waltid-enterprise-api image: waltid/waltid-enterprise-api ports: - containerPort: 3000 name: http-api volumeMounts: - name: waltid-enterprise-api-config mountPath: /config/_features.conf readOnly: true subPath: _features.conf - name: waltid-enterprise-api-config mountPath: /config/auth.conf readOnly: true subPath: auth.conf - name: waltid-enterprise-api-config mountPath: /config/database.conf readOnly: true subPath: database.conf - name: waltid-enterprise-api-config mountPath: /config/enterprise.conf readOnly: true subPath: enterprise.conf - name: waltid-enterprise-api-config mountPath: /config/web.conf readOnly: true subPath: web.conf volumes: - name: waltid-enterprise-api-config configMap: name: waltid-enterprise-api-config ``` ### API Service ```bash kind: Service apiVersion: v1 metadata: name: waltid-enterprise-api spec: ports: - name: http port: 80 targetPort: http-api protocol: TCP selector: app: waltid-enterprise-api ``` ### UI Deployment ```bash kind: Deployment apiVersion: apps/v1 metadata: name: waltid-enterprise-ui spec: replicas: 1 selector: matchLabels: app: waltid-enterprise-ui template: metadata: labels: app: waltid-enterprise-ui annotations: deployment/id: "_DEFAULT_DEPLOYMENT_" spec: imagePullSecrets: - name: waltid-regcred containers: - name: waltid-enterprise-ui image: waltid/waltid-enterprise-ui env: - name: NUXT_PUBLIC_BASE_DOMAIN value: "enterprise-demo.waltid.dev" ports: - containerPort: 3000 name: http-api ``` ### UI Service ```bash kind: Service apiVersion: v1 metadata: name: waltid-enterprise-ui spec: ports: - name: http port: 80 targetPort: http-api protocol: TCP selector: app: waltid-enterprise-ui ``` ### Ingress **Note:** Please ensure that all `openid/*` endpoints of the Enterprise Stack API are publicly accessible. This is necessary for successful credential exchanges with external systems.
```bash apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: waltid-enterprise-ingress annotations: cert-manager.io/cluster-issuer: letsencrypt-prod spec: ingressClassName: "traefik" tls: - hosts: - enterprise-demo.waltid.dev secretName: enterprise-demo-tls-secret - hosts: - waltid.enterprise-demo.waltid.dev secretName: waltid-enterprise-demo-tls-secret rules: - host: enterprise-demo.waltid.dev http: paths: - path: /auth/ pathType: Prefix backend: service: name: waltid-enterprise-api port: name: http - path: /v1/ pathType: Prefix backend: service: name: waltid-enterprise-api port: name: http - path: /.well-known/vct/v1/ pathType: Prefix backend: service: name: waltid-enterprise-api port: name: http - path: /swagger pathType: Prefix backend: service: name: waltid-enterprise-api port: name: http - path: /api.json pathType: Prefix backend: service: name: waltid-enterprise-api port: name: http - path: /livez pathType: Prefix backend: service: name: waltid-enterprise-api port: name: http - path: /features/ pathType: Prefix backend: service: name: waltid-enterprise-api port: name: http - path: /debug/ pathType: Prefix backend: service: name: waltid-enterprise-api port: name: http - path: / pathType: Prefix backend: service: name: waltid-enterprise-ui port: name: http - host: waltid.enterprise-demo.waltid.dev http: paths: - path: /auth/ pathType: Prefix backend: service: name: waltid-enterprise-api port: name: http - path: /v1/ pathType: Prefix backend: service: name: waltid-enterprise-api port: name: http - path: /.well-known/vct/v1/ pathType: Prefix backend: service: name: waltid-enterprise-api port: name: http - path: /swagger pathType: Prefix backend: service: name: waltid-enterprise-api port: name: http - path: /api.json pathType: Prefix backend: service: name: waltid-enterprise-api port: name: http - path: /livez pathType: Prefix backend: service: name: waltid-enterprise-api port: name: http - path: /features/ pathType: Prefix backend: service: name: waltid-enterprise-api port: name: http - path: /debug/ pathType: Prefix backend: service: name: waltid-enterprise-api port: name: http - path: / pathType: Prefix backend: service: name: waltid-enterprise-ui port: name: http ``` ## Complete deployment.yml ```bash apiVersion: v1 kind: ConfigMap metadata: name: waltid-enterprise-api-config data: _features.conf: | enabledFeatures = [ admin dev-mode ] auth.conf: | requireHttps = false database.conf: | databaseType = mongodb fileStorage = { path = "data" } mongodb = { connectionString = "mongodb://root:password@localhost:27017/" } enterprise.conf: | baseDomain = "enterprise-demo.waltid.dev" web.conf: | webHost = "0.0.0.0" webPort = 3000 --- kind: Deployment apiVersion: apps/v1 metadata: name: waltid-enterprise-api spec: replicas: 1 selector: matchLabels: app: waltid-enterprise-api template: metadata: labels: app: waltid-enterprise-api annotations: deployment/id: "_DEFAULT_DEPLOYMENT_" spec: imagePullSecrets: - name: waltid-regcred containers: - name: waltid-enterprise-api image: waltid/waltid-enterprise-api ports: - containerPort: 3000 name: http-api volumeMounts: - name: waltid-enterprise-api-config mountPath: /config/_features.conf readOnly: true subPath: _features.conf - name: waltid-enterprise-api-config mountPath: /config/auth.conf readOnly: true subPath: auth.conf - name: waltid-enterprise-api-config mountPath: /config/database.conf readOnly: true subPath: database.conf - name: waltid-enterprise-api-config mountPath: /config/enterprise.conf readOnly: true subPath: enterprise.conf - name: waltid-enterprise-api-config mountPath: /config/web.conf readOnly: true subPath: web.conf volumes: - name: waltid-enterprise-api-config configMap: name: waltid-enterprise-api-config --- kind: Service apiVersion: v1 metadata: name: waltid-enterprise-api spec: ports: - name: http port: 80 targetPort: http-api protocol: TCP selector: app: waltid-enterprise-api --- kind: Deployment apiVersion: apps/v1 metadata: name: waltid-enterprise-ui spec: replicas: 1 selector: matchLabels: app: waltid-enterprise-ui template: metadata: labels: app: waltid-enterprise-ui annotations: deployment/id: "_DEFAULT_DEPLOYMENT_" spec: imagePullSecrets: - name: waltid-regcred containers: - name: waltid-enterprise-ui image: waltid/waltid-enterprise-ui env: - name: NUXT_PUBLIC_BASE_DOMAIN value: "enterprise-demo.waltid.dev" ports: - containerPort: 3000 name: http-api --- kind: Service apiVersion: v1 metadata: name: waltid-enterprise-ui spec: ports: - name: http port: 80 targetPort: http-api protocol: TCP selector: app: waltid-enterprise-ui --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: waltid-enterprise-ingress annotations: cert-manager.io/cluster-issuer: letsencrypt-prod spec: ingressClassName: "traefik" tls: - hosts: - enterprise-demo.waltid.dev secretName: enterprise-demo-tls-secret - hosts: - waltid.enterprise-demo.waltid.dev secretName: waltid-enterprise-demo-tls-secret rules: - host: enterprise-demo.waltid.dev http: paths: - path: /auth/ pathType: Prefix backend: service: name: waltid-enterprise-api port: name: http - path: /v1/ pathType: Prefix backend: service: name: waltid-enterprise-api port: name: http - path: /.well-known/vct/v1/ pathType: Prefix backend: service: name: waltid-enterprise-api port: name: http - path: /swagger pathType: Prefix backend: service: name: waltid-enterprise-api port: name: http - path: /api.json pathType: Prefix backend: service: name: waltid-enterprise-api port: name: http - path: /livez pathType: Prefix backend: service: name: waltid-enterprise-api port: name: http - path: /features/ pathType: Prefix backend: service: name: waltid-enterprise-api port: name: http - path: /debug/ pathType: Prefix backend: service: name: waltid-enterprise-api port: name: http - path: / pathType: Prefix backend: service: name: waltid-enterprise-ui port: name: http - host: waltid.enterprise-demo.waltid.dev http: paths: - path: /auth/ pathType: Prefix backend: service: name: waltid-enterprise-api port: name: http - path: /v1/ pathType: Prefix backend: service: name: waltid-enterprise-api port: name: http - path: /.well-known/vct/v1/ pathType: Prefix backend: service: name: waltid-enterprise-api port: name: http - path: /swagger pathType: Prefix backend: service: name: waltid-enterprise-api port: name: http - path: /api.json pathType: Prefix backend: service: name: waltid-enterprise-api port: name: http - path: /livez pathType: Prefix backend: service: name: waltid-enterprise-api port: name: http - path: /features/ pathType: Prefix backend: service: name: waltid-enterprise-api port: name: http - path: /debug/ pathType: Prefix backend: service: name: waltid-enterprise-api port: name: http - path: / pathType: Prefix backend: service: name: waltid-enterprise-ui port: name: http ``` -------------------------------------------------------------------------------- PAGE: AWS Deployment URL: https://docs.walt.id/enterprise-stack/setup/deployment/aws-deployment RAW: https://docs.walt.id/enterprise-stack/setup/deployment/aws-deployment.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # AWS Deployment The Docker container for the walt.id can be deployed in any Docker environment. Within the AWS cloud, it is recommended to use the ECS or EKS services, but it is also possible to set up test-environments directly on an EC2 instance. The following picture depicts the AWS deployment of the walt.id enterprise backend as well as the used services in AWS: ![image](https://docs.walt.id/img/enterprise-stack/concepts/aws-deployment.png) AWS Identity and Access Management - for configuring role-based access control from the application to the KMS. AWS Key Management Service - for managing keys and performing cryptographic operations. Continue reading how to configure access to the KMS [here](https://docs.walt.id/community-stack/wallet/key-management/aws-kms.md). Amazon S3 - as persistence layer for the Revocation Registry. AWS DocumentDB Service - for hosting the MongoDB. Continue reading, how to set up the connection [here](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/database.md). **Note:** Please ensure that all `openid/*` endpoints of the Enterprise Stack API are publicly accessible. This is necessary for successful credential exchanges with external systems. -------------------------------------------------------------------------------- PAGE: Azure Deployment URL: https://docs.walt.id/enterprise-stack/setup/deployment/azure-deployment RAW: https://docs.walt.id/enterprise-stack/setup/deployment/azure-deployment.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Azure Deployment The image shows a possible architecture of walt.id Enterprise deployed in the Azure Cloud. ![image](https://docs.walt.id/img/enterprise-stack/concepts/azure-deployment.png) ## Infrastructure This infrastructure is optional. It is needed when you deploy AKS or CosmosDB Cluster in private mode (not public accessible) * __Bastion:__ All sensitive endpoints are not publicly exposed. In order to connect to internal services,you need to connect to the jumphost via the bastion first. If you want to restrict access of some user to internal service, you just need to revoke access to the bastion. The user might still possess credentials for the services, but can not log on, because he cannot connect to the service. * __JumpHost:__ This is a linux vm which serves as SOCSK5 proxy by tunneling through SSH connection. * __VNET:__ A VNET which connects the bastion with the jumpHost. The vnet is peered with all environment VNETs ## Walt.Id Environment * __AKS:__ Hosts the Walt.Id Enterprise service * __Application Gateway:__ Exposes Walt.id Enterprise service to public. In Azure are several options available. * __CosmosDB Mongo__ * __VNET:__ The VNET connects CosmosDB, AKS and Application Gateway **Note:** This is only one possible architecture. The Walt.Id Enterprise Service can be easily integrated in any already existing Azure Cloud infrastructure. **Note:** Terraform examples can be provided, which demonstrates how to set up enterprise stack. -------------------------------------------------------------------------------- PAGE: Feature Manager URL: https://docs.walt.id/enterprise-stack/setup/configurations/feature-manager RAW: https://docs.walt.id/enterprise-stack/setup/configurations/feature-manager.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Feature Manager Use the feature manager to control which Enterprise 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 super admin feature. **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/enterprise-stack/setup/configurations/config-files/features.md). A list of available features can be found here. -------------------------------------------------------------------------------- PAGE: Logging configuration URL: https://docs.walt.id/enterprise-stack/setup/configurations/logging-configuration RAW: https://docs.walt.id/enterprise-stack/setup/configurations/logging-configuration.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Logging configuration The Walt.id Enterprise Service uses [Klogging](https://klogging.io/) as logging provider. The logging can be configured by the use of predefined logging configurations or by specifying a klogging configuration file. ## Use predefined logging configuration ### Configure log-level Following predefined loglevel profiles exists: `default`, `error`, `debug`, `trace` The log-level profile can be set with commandline argument `--log-level`, `--logLevel`, or `-l` Docker Compose example (long commandline argument): ```yaml # other services omitted waltid-enterprise: image: waltid/waltid-enterprise-api extra_hosts: - "localhost:host-gateway" ports: - "3000:3000" command: "--log-level=trace" # skipped additional required options ``` Docker Compose example (short commandline argument): ```yaml # other services omitted waltid-enterprise: image: waltid/waltid-enterprise-api extra_hosts: - "localhost:host-gateway" ports: - "3000:3000" command: "-l trace" # skipped additional required options ``` The logging profile can also be set with environment variable `LOG_LEVEL` Docker Compose example (environment variable): ```yaml # other services omitted waltid-enterprise: image: waltid/waltid-enterprise-api extra_hosts: - "localhost:host-gateway" ports: - "3000:3000" environment: LOG_LEVEL: "debug" # skipped additional required options ``` ### Configure log-message rendering _Klogging_ offers a variety of [pre-defined log-message formats](https://klogging.io/docs/configuration/built-ins/) The message format can be configured with command line argument (`--log-type`, `--logType`) or environment variable `LOG_TYPE` Options: | LOG_TYPE option | Klogging [build in renderers](https://klogging.io/docs/configuration/built-ins/#rendering) (`renderWith` option in `klogging.json`) | |-----------------|-------------------------------------------------------------------------------------------------------------------------------------| | SIMPLE | RENDER_SIMPLE | | ISO8601 | RENDER_ISO8601 | | ANSI | RENDER_ANSI | | ANSI_LONG | RENDER_ANSI { contextWidth=15, loggerWidth=35 } | | CLEF | RENDER_CLEF | | GELF | RENDER_GELF | | ECS | RENDER_ECS | | ECS_DOTNET | RENDER_ECS_DOTNET | | STANDARD | RENDER_STANDARD | Docker Compose example (environment variable): ```yaml # other services omitted waltid-enterprise: image: waltid/waltid-enterprise-api extra_hosts: - "localhost:host-gateway" ports: - "3000:3000" environment: LOG_LEVEL: "default" # the option is only taken into account when LOG_LEVEL is set LOG_TYPE: "ISO8601" # skipped additional required options ``` ## Use custom logging configuration Customized logging configuration can be done by configuration file. Information about configuration file can be found here: https://klogging.io/docs/configuration/ The configuration file can be configured by setting environment variable `KLOGGING_CONFIG_PATH` If the environment variable is set, it overrides log-level profile configuration > If the environment variable `KLOGGING_CONFIG_PATH` is set, look for a file at that absolute path. Load the contents if found. Example log configuration `klogging.json`: ```json { "kloggingMinLogLevel": "INFO", "minDirectLogLevel": "INFO", "sinks": { "stdout": { "renderWith": "RENDER_SIMPLE", "sendTo": "STDOUT" }, "stderr": { "renderWith": "RENDER_SIMPLE", "sendTo": "STDERR" } }, "logging": [ { "comment": "Root logger", "levelRanges": [ { "fromMinLevel": "WARN", "toSinks": [ "stderr" ] } ] }, { "comment": "Default loglevel for all id.walt loggers", "fromLoggerBase": "id.walt", "levelRanges": [ { "fromMinLevel": "INFO", "toMaxLevel": "INFO", "toSinks": [ "stdout" ] } ] } ] } ``` Example Docker configuration to use log-file: ```yaml # other services omitted waltid-enterprise: image: waltid/waltid-enterprise-api extra_hosts: - "localhost:host-gateway" ports: - "3000:3000" environment: LOG_LEVEL: "default" LOG_TYPE: "ISO8601" KLOGGING_CONFIG_PATH: /klogging.json volumes: - ./klogging.json:/klogging.json - ./config:/config # skipped additional required options ``` Example Kubernetes configuration: Log configuration configMap: ```yaml apiVersion: v1 kind: ConfigMap metadata: name: waltid-enterprise-api-config namespace: enterprise-main data: _features.conf: | ... auth.conf: | ... database.conf: | ... enterprise.conf: | ... # log only to STDOUT # log format: "RENDER_STANDARD" (JSON) so it can be picked up and parsed by log aggregator klogging.json: | { "kloggingMinLogLevel": "INFO", "minDirectLogLevel": "INFO", "sinks": { "stdout": { "renderWith": "RENDER_STANDARD", "sendTo": "STDOUT" } }, "logging": [ { "comment": "Root logger", "levelRanges": [ { "fromMinLevel": "WARN", "toSinks": [ "stdout" ] } ] }, { "comment": "Walt.id service info", "matchLogger": "FeatureManager|ConfigManager|Web\s*exception", "levelRanges": [ { "fromMinLevel": "INFO", "toSinks": [ "stdout" ] } ] }, { "comment": "Default loglevel for all id.walt loggers", "fromLoggerBase": "id.walt", "levelRanges": [ { "fromMinLevel": "INFO", "toMaxLevel": "INFO", "toSinks": [ "stdout" ] } ] }, { "comment": "Default loglevel for all loggers except Request/Response logger: INFO", "matchLogger": "^(?!(io\.ktor\.client|WebLog|io\.ktor\.server\.plugins\.cors)).*$", "levelRanges": [ { "fromMinLevel": "INFO", "toMaxLevel": "WARN", "toSinks": [ "stdout" ] } ] }, { "comment": "Loglevel for http client requests - set it to INFO if you want enable it", "fromLoggerBase": "io.ktor.client", "levelRanges": [ { "fromMinLevel": "WARN", "toSinks": [ "stout" ] } ] } ] } resource-access.conf: | ... superadmin-registration.conf: | ... web.conf: | ... ``` -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/setup/configurations/config-files/overview RAW: https://docs.walt.id/enterprise-stack/setup/configurations/config-files/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview The Enterprise Stack requires a specific set of configuration files to operate. These files allow you to enable or disable features, configure authentication settings, set token expiration times, specify the endpoints that will be exposed and more. Below you can find a list of config files required to run the stack. ### Config Files - [Features](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/features.md) - control which Enterprise API features are enabled. - [Auth](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/auth.md) - controls settings for user authentication and tokens. - [Database](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/database.md) - configurations for the database used. - [Enterprise](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/enterprise.md) - enterprise service conf. - [Super Admin Registration](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/superadmin-registration.md) - configurations for admin tokens and auth data. - [Web](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/web.md) - host and port settings. - [Pagination](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/pagination.md) - default and maximum bounds for list endpoint pagination. -------------------------------------------------------------------------------- PAGE: Features URL: https://docs.walt.id/enterprise-stack/setup/configurations/config-files/features RAW: https://docs.walt.id/enterprise-stack/setup/configurations/config-files/features.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Features With the `_features.conf` you can control which Enterprise API features are enabled. They are managed via the [feature manager](https://docs.walt.id/enterprise-stack/setup/configurations/feature-manager.md). ## Available Features - `account-registration` - Enable or disable account registration - `admin` - Administration endpoints - `auth` - Configure authentication - `authservice` - Enables user authentication - `data-retention` - Enable or disable data retention - `database` - Configure database - `debug-endpoints` - Enables various debug endpoints - `dev-mode` - Development mode - `enterprise` - Enterprise base - `feature-flag-information-endpoint` - Enables endpoints related to showing information about available features on this service instance - `healthchecks` - Enables healthcheck endpoints - `issuer2` - Enable or disable the beta issuer2 service - `oidc-bridge` - Enable or disable the OIDC Bridge Service for VC-based IAM integration - `openapi` - Enables openapi endpoints - `organization-creation` - Enable or disable organization creation - `persistence` - Storage - `superadmin-registration` - Enable or disable superadmin registration - `telemetry` - Enable or disable telemetry - `trust-registry` - Enable or disable the Trust Registry Service - `web` - Web service - `x509` - Enable or disable the X.509 Certificate Service & Store services - `transaction-data-profiles` - Enable or disable the Enterprise Verifier2 transaction-data profile registry and discovery endpoint ### Example Setup ```editorconfig[_features.conf] enabledFeatures = [ admin // adds routes for administration of globals dev-mode // adds routes useful for development superadmin-registration // allows registering superadmins ] disabledFeatures = [ # debug-endpoints # account-registration ] ``` -------------------------------------------------------------------------------- PAGE: Auth URL: https://docs.walt.id/enterprise-stack/setup/configurations/config-files/auth RAW: https://docs.walt.id/enterprise-stack/setup/configurations/config-files/auth.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Auth This configuration file controls user authentication, password management, token expiration, external identity provider (OIDC) login, and external role-to-enterprise-role mapping. ### Example File **Error:** `signingKey`, `verificationKey`, `pepper`, and all OIDC settings are example values and must be replaced.
```[auth.conf] # Will secure login cookies with `Secure` context, enable HTTPS and HTTP->HTTPS redirect requireHttps = false # Key (all waltid-crypto supported) to sign login token - has to be key allowing signing (private key) signingKey = {"type": "jwk", "jwk": {"kty": "OKP", "d": "L_2RuCSFUu818ZzM6Xml6uxesqTcxo8323-Q2S_qq4c", "use": "sig", "crv": "Ed25519", "x": "vvCN3xMAb0ZCt4sWIdtKDhkVHSERJZeBxybN-eSRkgw", "alg": "EdDSA"}} # Key (all waltid-crypto supported) to verify incoming login tokens - public key is ok. verificationKey = {"type": "jwk", "jwk": {"kty": "OKP", "d": "L_2RuCSFUu818ZzM6Xml6uxesqTcxo8323-Q2S_qq4c", "use": "sig", "crv": "Ed25519", "x": "vvCN3xMAb0ZCt4sWIdtKDhkVHSERJZeBxybN-eSRkgw", "alg": "EdDSA"}} # Provide pepper to use for additional password salting (unique string for your deployment, # has to be shared between instances). pepper = "waltid-enterprise12345678" # Hash algorithm to use for passwords for signing. # You can choose from algorithms like: ARGON2, PBKDF2, PBKDF2_COMPRESSED, BCRYPT, SCRYPT, BALLON_HASHING, MESSAGE_DIGEST, NONE hashAlgorithm = ARGON2 # If you previously used other (older) password hash algorithms, you # can use this function to migrate old hashes to new hash algorithms. This # works at login-time: When a user logs in with a password that uses a hash algorithm # on this list, the password will be re-hashed in the specified replacement algorithm. # If null is used as hash algorithm selector, all algorithms expect for the target # algorithm will be converted automatically. hashMigrations = { MESSAGE_DIGEST: ARGON2 # E.g.: Convert all the MD5 hashes to Argon2 hashes } authFlows = [ { method = "email" expiration = "7d" success = true }, { method = "oidc" config = { openIdConfigurationUrl = "http://keycloak:8080/realms/waltid/.well-known/openid-configuration" clientId = "waltid_enterprise" clientSecret = "waltid-enterprise-dev-secret" callbackUri = "http://waltid.enterprise.localhost:3000/auth/account/oidc/callback" accountIdentifierClaim = "sub" pkceEnabled = true # Post-login redirect (optional) redirectAfterLogin = "http://localhost:5180/" allowedRedirectUrls = [ "http://localhost:5180/*", "http://demo.enterprise.localhost:5180/*" ] # Post-logout redirect (optional) postLogoutRedirectUri = "http://localhost:5180/" allowedPostLogoutRedirectUrls = [ "http://localhost:5180/*" ] # External role extraction from OIDC tokens (optional) externalRoleExtraction = { enabled = true realmRolesClaimPath = "realm_access.roles" clientRolesClaimPath = "resource_access" } } success = true } ] # External role mapping (OPTIONAL - can be managed via API instead) # externalRoleMapping = { # enabled = true # strict = true # mappings = [ # { externalRole = "tenant-admin", roleId = "waltid.tenant1.BW_ADMIN" } # ] # } ``` ## Signing & Verification Keys When users log into the Enterprise Stack, authentication tokens (JWTs) are generated. These tokens are signed with a private key (`signingKey`) and verified using a public key (`verificationKey`). Both keys are defined in `auth.conf`. ### Why Signing & Verification Keys? In multi-instance deployments, tokens issued in one instance must be verifiable in another. To make this work reliably, all instances must use the same signing/verification key pair. ### Types of Signing & Verification Keys `signingKey` and `verificationKey` can be provided as a local key (RAW JWK) or a reference to an external KMS-managed key (for example AWS KMS). ### Examples **Local** ```json { "type": "jwk", "jwk": { "kty": "OKP", "d": "L_2RuCSFUu818ZzM6Xml6uxesqTcxo8323-Q2S_qq4c", "use": "sig", "crv": "Ed25519", "x": "vvCN3xMAb0ZCt4sWIdtKDhkVHSERJZeBxybN-eSRkgw", "alg": "EdDSA" } } ``` **AWS** ```json { "type": "aws", "config": { "auth": { "roleName": "AccessRole", "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" } ``` ## Auth Flows The Enterprise Stack uses [waltid-ktor-authnz](https://github.com/walt-id/waltid-identity/tree/main/waltid-libraries/auth/waltid-ktor-authnz) for authentication and supports multiple methods in sequence via `authFlows`. ### Why `authFlows` (plural) Use `authFlows` as a list of supported login methods. Typical enterprise setups include both: - `email` (local account login) - `oidc` (external identity provider login) ### Email flow fields | Field | Required | Description | |-------|----------|-------------| | `method` | Yes | Set to `"email"` for email/password login | | `expiration` | No | Login token validity duration (e.g., `"7d"`) | | `success` | Yes | Set to `true` to mark as successful terminal method | ### OIDC flow fields (`method = "oidc"`) | Field | Required | Default | Description | |-------|----------|---------|-------------| | `openIdConfigurationUrl` | Yes | — | URL to the IdP OpenID Connect discovery document | | `clientId` | Yes | — | OIDC client ID registered at your IdP | | `clientSecret` | Yes | — | OIDC client secret | | `callbackUri` | Yes | — | Callback URL handled by Enterprise Stack after IdP login | | `accountIdentifierClaim` | No | `"sub"` | Claim used to uniquely identify the external account | | `pkceEnabled` | No | `true` | Enables PKCE (recommended) | | `redirectAfterLogin` | No | — | Default URL to redirect after successful login | | `allowedRedirectUrls` | No | `[]` | Allowed patterns for client-specified `redirect_to` parameter | | `postLogoutRedirectUri` | No | — | Default URL to redirect after logout | | `allowedPostLogoutRedirectUrls` | No | `[]` | Allowed patterns for client-specified post-logout redirects | | `externalRoleExtraction` | No | — | Configuration for extracting roles from OIDC tokens | | `success` | Yes | — | Set to `true` to mark as successful terminal method | ### Dynamic Redirect URLs Clients can specify where to redirect after login by passing a `redirect_to` query parameter: ``` GET /auth/account/oidc/auth?redirect_to=https://app.example.com/callback ``` The URL is validated against `allowedRedirectUrls` patterns. Wildcards are supported: - `"https://app.example.com/*"` — allows any path under this origin - `"https://*.example.com/callback"` — allows subdomains If no `redirect_to` is specified or validation fails, `redirectAfterLogin` is used as fallback. ### OIDC Logout The Enterprise Stack supports RP-Initiated Logout. Call the logout endpoint: ``` GET /auth/account/oidc/logout?post_logout_redirect_uri=https://app.example.com/ ``` This terminates the local session and returns the IdP's `end_session_url` for full SSO logout. ### External Role Extraction (`externalRoleExtraction`) Extract roles from OIDC ID tokens (e.g., Keycloak realm/client roles): | Field | Required | Default | Description | |-------|----------|---------|-------------| | `enabled` | No | `false` | Enable role extraction from OIDC tokens | | `realmRolesClaimPath` | No | `"realm_access.roles"` | JSON path to realm roles in the ID token | | `clientRolesClaimPath` | No | `"resource_access"` | JSON path to client roles in the ID token | | `clientId` | No | (parent clientId) | Client ID for filtering client roles | **Info:** The `clientId` in `externalRoleExtraction` defaults to the parent `config.clientId`, so you typically don't need to specify it. ### Duration format for `expiration` Set expiration using kotlinx.datetime duration syntax (for example `5h`, `1d 12h`, `1h 0m 30.340s`) or simplified ISO-8601 duration (`P1DT2H3M4.058S`). **Note:** Expiration applies to login tokens issued through account login. API key expiration is configured separately in API key settings. ## External Role Mapping (`externalRoleMapping`) Use `externalRoleMapping` to map roles from an external IdP (for example Keycloak roles/groups) to Enterprise Stack role IDs. **Info:** **This section is optional.** External role mappings can also be managed via the REST API (database), which is recommended for production deployments as it allows changes without restarting the server. ### Fields | Field | Required | Default | Description | |-------|----------|---------|-------------| | `enabled` | No | `false` | Turns external role mapping on/off | | `strict` | No | `true` | If `true`, authentication fails when no mapping matches | | `expectedIssuer` | No | — | Only accept tokens from this issuer | | `expectedClientId` | No | — | Only map roles from this client | | `mappings` | No | `[]` | List of `{ externalRole, roleId }` entries | ### Mapping entry format - `externalRole`: role value received from the external provider. - `roleId`: Enterprise Stack role identifier to assign. ### Example ```hocon externalRoleMapping = { enabled = true strict = true expectedIssuer = "http://keycloak:8080/realms/waltid" mappings = [ { externalRole = "tenant-admin", roleId = "waltid.tenant1.BW_ADMIN" }, { externalRole = "wallet-operator", roleId = "waltid.tenant1.BW_OPERATOR" } ] } ``` This means: if the IdP asserts `tenant-admin`, the authenticated user gets Enterprise role `waltid.tenant1.BW_ADMIN`. ### Config vs API Mappings External role mappings can be managed in two ways: 1. **Config file** (`auth.conf`): Define mappings in the `externalRoleMapping` section. Requires restart to change. 2. **REST API** (recommended): Create mappings via the roles API: ```bash curl -X PUT \ 'https://{host}/v1/{roleId}/roles-api/roles/external-mappings/{externalRole}' \ -H 'Authorization: Bearer {token}' \ -d '{"enabled": true}' ``` **Resolution order:** 1. Database mappings are checked first 2. If database has any mappings, those are used (config is ignored) 3. If database has no mappings, config-based mappings are used as fallback ### Tenant-aware mapping In multi-tenant deployments, define tenant-scoped role IDs (`waltid..`) and map each tenant's external roles explicitly: ```hocon mappings = [ # acme tenant { externalRole = "acme-tenant-admin", roleId = "waltid.acme.BW_ADMIN" }, { externalRole = "acme-wallet-operator", roleId = "waltid.acme.BW_OPERATOR" }, # globex tenant { externalRole = "globex-tenant-admin", roleId = "waltid.globex.BW_ADMIN" }, { externalRole = "globex-wallet-operator", roleId = "waltid.globex.BW_OPERATOR" } ] ``` -------------------------------------------------------------------------------- PAGE: Database URL: https://docs.walt.id/enterprise-stack/setup/configurations/config-files/database RAW: https://docs.walt.id/enterprise-stack/setup/configurations/config-files/database.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Database ```[database.conf] # Default datbase configuration databaseType = mongodb # Recommended storage driver that stores data in MongoDB. MongoDB Community Edition is supported. mongodb = { # Refer to https://www.mongodb.com/docs/manual/reference/connection-string/ connectionString = "mongodb://root:password@mongo:27017/" # Connection String quick reference: # - if username/password contain special characters, they must be converted with percent encoding (RFC3986 2.1) # # Format: mongodb+srv://[username:password@]host[/[defaultauthdb][?options]] # # EXAMPLES: # # Standard: # 1. Standalone: mongodb://user:password@host:port/ # 2. Shared cluster: mongodb://user:password@host1:port1,host2:port2,host3:port3/ (`mongos` hosts in connection string) # 3. Replica set: mongodb://user:password@host1:port1,host2:port2,host3:port3/?&replicaSet=replicaSetName # # SRV format (see https://www.mongodb.com/docs/manual/reference/connection-string/#std-label-connections-dns-seedlist) # "DNS-constructed seed list to construct available servers list" # Examples: same as above, with `mongodb+srv://` instead of `mongodb://` # Database name database = "waltid-enterprise" ssl = { # To enable SSL enable = false # Location of the JKS Trust Store file trustStoreLocation = "" # Trust Store password trustStorePassword = "" # Toggle SSL certificate host name validation invalidHostNameAllowed = false } } ``` # Database access Within the section ***mongodb*** the connection to the MongoDB database is configured. The property ***connectionString*** defines the authentication as well as the domain and port where the database is hosted. Furthermore, MongoDB options can be passed as URL parameters e.g. &readPreference=secondaryPreferred&retryWrites=false. The ***database*** property indicates what database is selected within the database server. # SSL configuration The section ***ssl*** is for configuring the SSL connection as well as the corresponding Trust Store. In order to enable ssl, the property ***enable*** must be set to true. A Trust Store is attached by setting the corresponding path to a Java Key Store (JKS) file. Note, that the AWS Trust Store is already part of the package and can be referenced by "config/aws-truststore.jks" and by setting the password to "123456". It is however recommend to substitute this file by providing an own Trust Store. The property ***invalidHostNameAllowed*** may be toggled if the hostname of the provided certificates should be matched against the servers host name or not. In production settings it should be set to false. -------------------------------------------------------------------------------- PAGE: Enterprise URL: https://docs.walt.id/enterprise-stack/setup/configurations/config-files/enterprise RAW: https://docs.walt.id/enterprise-stack/setup/configurations/config-files/enterprise.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Enterprise ```[enterprise.conf] # walt.id Enterprise Service # Base configuration # Base domain of this service, e.g. `waltid.cloud` baseDomain = "enterprise.localhost" baseSsl = false ``` -------------------------------------------------------------------------------- PAGE: Super Admin Registration URL: https://docs.walt.id/enterprise-stack/setup/configurations/config-files/superadmin-registration RAW: https://docs.walt.id/enterprise-stack/setup/configurations/config-files/superadmin-registration.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Super Admin Registration Using the `superadmin-registration.conf` file you can provide a list of super admin registration tokens mapped to an email and password. The super admin can be activated as explained [here](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/super-admin/activate.md) and login as shown [here](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/super-admin/authenticate.md). ```[superadmin-registration.conf] tokens = { "token-according-super-admin-conf": { expiration = "2029-01-01T00:00:00Z", # Token has to be used by this expiration date account = { type = "email" identifier = { email = "superadmin@walt.id" } data = { password = "pw-according-super-admin-conf" } } } } ``` -------------------------------------------------------------------------------- PAGE: Telemetry URL: https://docs.walt.id/enterprise-stack/setup/configurations/config-files/telemetry RAW: https://docs.walt.id/enterprise-stack/setup/configurations/config-files/telemetry.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Telemetry Using the `telemetry.conf` file, you can configure the telemetry for the Enterprise API. This is an optional feature which can be enabled by adding it to the enabledFeatures list in the `_features.conf` file. When enabled, the Enterprise API will collect telemetry data and send it to the configured OpenTelemetry collector. When making API calls, you can include the `traceparent` header to pass a trace id in W3C Trace Context format. This will allow you to trace the request through the system. We only support HTTP based exporters for now. ```[telemetry.conf] # Telemetry configuration for OpenTelemetry # Service name for telemetry serviceName = "waltid-enterprise-api" # Service version serviceVersion = "1.0.0" # Deployment environment environment = "development" # OpenTelemetry configuration openTelemetry { # OTLP exporter endpoint otlpEndpoint = "http://localhost:4318/v1/traces" # Tracer name tracerName = "waltid-enterprise-api" } ``` -------------------------------------------------------------------------------- PAGE: Web URL: https://docs.walt.id/enterprise-stack/setup/configurations/config-files/web RAW: https://docs.walt.id/enterprise-stack/setup/configurations/config-files/web.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Web ```[web.conf] webHost = "0.0.0.0" webPort = 8000 ``` -------------------------------------------------------------------------------- PAGE: Data Retention URL: https://docs.walt.id/enterprise-stack/setup/configurations/config-files/data-retention RAW: https://docs.walt.id/enterprise-stack/setup/configurations/config-files/data-retention.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Data Retention ## Data Retention and Auto-Purge (PII) The Enterprise API can automatically purge old issuer and verifier session data to reduce PII retention. This is disabled by default and can be enabled via feature flags and configuration. ### Enabling the feature 1. Ensure the `data-retention` feature is enabled in the [Features](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/features.md) configuration file. 2. Ensure the optional feature `data-retention` is enabled (defaults to disabled). You can enable it by either: - Setting the feature flag at runtime (if supported in your deployment), or - Using configuration: add the following to your run configuration or environment to keep default enabled state false and set `enabled: true` inside the config file below. 3. Provide the data retention configuration file at `waltid-enterprise-api/config/data-retention.conf` (mounted into the container or placed in working directory). ### Configuration file File: `config/data-retention.conf` Example: ``` { enabled: true, maxIssuerSessionAge: "30d", maxVerifierSessionAge: "30d", schedule: "0 0 * * *", dryRun: true, maxDeletePerRun: 500, logging: { enabled: true, level: "INFO" } ha: { enabled: true, lockTtlMinutes: 10, instanceId: null } } ``` Option reference: - enabled: Toggles the purge job on/off. When false, nothing is scheduled. - maxIssuerSessionAge: Retention window (e.g. 1h, 1d, 1w, 1m) for issuer sessions. Entries older than this cutoff are considered expired. - maxVerifierSessionAge: Retention window (e.g. 1h, 1d, 1w, 1m) for verifier sessions (presentation sessions). - schedule: Cron-like string. - dryRun: When true, the job only logs which entries would be deleted without deleting them. - maxDeletePerRun: Safety limit for how many root session entries are deleted per run. Deletion is recursive per session path. - logging.enabled: Enables/disables logging for the job. - logging.level: Log level for job messages (TRACE, DEBUG, INFO, WARN, ERROR). - ha.enabled: (Optional) Enable distributed locking to prevent concurrent purge jobs when running multiple instances. - ha.lockTtlMinutes: (Optional) Lock TTL in minutes. Should be longer than expected purge duration to prevent multiple instances from running simultaneously. - ha.instanceId: (Optional) Unique identifier for this instance. Auto-generated if not set. Recommended for containerized deployments via environment variable. ### How it works - The job scans the `organization_trees` MongoDB collection for expired session entries: - Issuer sessions: documents with `_t = "stored-issuance-session"` and `timestamp` older than the cutoff. - Verifier sessions: documents with `_t = "stored-presentation-session"` and `timestamp` older than the cutoff. - For each selected session (up to `maxDeletePerRun`), the job deletes the session document and all child documents under its hierarchical `_id` path. - If MongoDB is not configured/available, the job logs a warning and skips the run. ### High Availability (HA) Support When running multiple instances of the Enterprise API (e.g., in a Kubernetes deployment or load-balanced environment), enable the HA configuration to prevent concurrent purge jobs from running simultaneously. - Distributed locking ensures only one instance runs the purge job at a time - If the lock holder instance fails, another instance will acquire the lock after `lockTtlMinutes` expires - Set `lockTtlMinutes` longer than your typical purge duration (e.g., if purging takes ~2 minutes, set to 5-10 minutes) - For containerized deployments, set `instanceId` via environment variable (e.g., `${INSTANCE_ID}`) to ensure stable instance identification ### Timestamps Issuer and verifier session records include a `timestamp` set at creation time, which the purge job uses to determine expiry. ## Zero-retention mode (maxAge = "0s") When `maxIssuerSessionAge` or `maxVerifierSessionAge` is set to `"0s"`, the system will skip storing PII data entirely: **For Issuance Sessions (`maxIssuerSessionAge: "0s"`):** - Credential data (`issuanceRequest.credentialData`) will not be stored in the database - The rest of the session metadata is preserved for revocation tracking purposes - This is useful when you want to minimize PII exposure while maintaining revocation capabilities **For Presentation Sessions (`maxVerifierSessionAge: "0s"`):** - VP token data (`vpToken`) will not be stored in the database - Users can still receive VP data through: - Callback URLs configured in the session - The rest of the session metadata is preserved for verification tracking This mode provides the strongest PII protection by preventing sensitive data from being persisted to the database at all. -------------------------------------------------------------------------------- PAGE: Resource Access URL: https://docs.walt.id/enterprise-stack/setup/configurations/config-files/resource-access RAW: https://docs.walt.id/enterprise-stack/setup/configurations/config-files/resource-access.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Resource Access The `resource-access.conf` file defines reusable credential profiles for external storage and key management services. Instead of embedding credentials inline in every API call, you define named profiles here and reference them via `configRef` when creating services. **Note:** **Recommended:** Use **Managed Identity** where possible for production deployments. Direct credential configuration (shown below) is best suited for development or environments where managed identity is unavailable. --- ## How It Works 1. Define a named profile in `resource-access.conf` (e.g. `s3-storage`, `aws-kms`) 2. Reference the profile by name using `"configRef": ""` when creating a service via the API 3. At runtime, the Enterprise API resolves the `configRef` and injects the appropriate credentials This centralizes credential management — rotating secrets only requires updating `resource-access.conf`, not every service definition. --- ## File Location ``` config/resource-access.conf ``` --- ## Storage Profiles Storage profiles are used by services such as **Credential Status** that need to read and write data to a bucket or blob container. ### AWS S3 ```hocon resourceAccess = { s3-storage = { _type = "AwsS3Access" id = "s3-storage" region = "eu-north-1" bucket = { bucketName = "my-bucket" } credentials = { _type = "AwsCredentials" accessKeyId = "abc" secretKey = "xyz" } } } ``` ### Azure Blob Storage ```hocon resourceAccess = { azure-storage = { _type = "AzureBlobAccess" id = "azure-storage" bucket = { bucketName = "documents" bucketUrl = "https://example.blob.core.windows.net/" } credentials = { _type = "AzureCredentials" connectionString = "DefaultEndpointsProtocol=https;AccountName=example;AccountKey=..." } } } ``` ### Google Cloud Storage ```hocon resourceAccess = { gcp-storage-main = { _type = "GcpStorageAccess" id = "gcp-storage-main" projectId = "my-project" bucket = { bucketName = "gcp-bucket-main" } credentials = { _type = "GcpCredentials" serviceAccountKeyJson = { type = "service_account" project_id = "my-project" private_key_id = "abc123..." private_key = "-----BEGIN PRIVATE KEY----- ... -----END PRIVATE KEY----- " client_email = "service-account@my-project.iam.gserviceaccount.com" client_id = "1234567890" auth_uri = "https://accounts.google.com/o/oauth2/auth" token_uri = "https://oauth2.googleapis.com/token" auth_provider_x509_cert_url = "https://www.googleapis.com/oauth2/v1/certs" client_x509_cert_url = "https://www.googleapis.com/robot/v1/metadata/x509/service-account%40my-project.iam.gserviceaccount.com" universe_domain = "googleapis.com" } } } } ``` --- ## KMS Profiles KMS profiles are used when creating key management services for AWS KMS or Azure Key Vault. ### AWS KMS ```hocon resourceAccess = { aws-kms = { _type = "AwsKmsAccess" id = "aws-kms" region = "eu-central-1" credentials = { _type = "AwsCredentials" accessKeyId = "AK....F" secretKey = "6Y....Sr" } } } ``` ### Azure Key Vault ```hocon resourceAccess = { azure-kms = { _type = "AzureKeyVaultAccess" id = "azure-kms" keyVaultUrl = "https://n2-keyvault.vault.azure.net/" credentials = { _type = "AzureCredentials" tenantId = "a8......46d" clientId = "3b......c2c" clientSecret = "1ZQ.......sc1n" } } } ``` --- ## Using Profiles in API Calls Once a profile is defined, reference it by name using `configRef` in your service creation request. ### Credential Status Service (Storage) ```bash curl -X POST \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/services/create' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "credential-status", "config": { "configRef": "s3-storage" } }' ``` ### KMS Service ```bash curl -X POST \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/services/create' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "kms", "configRef": "aws-kms" }' ``` --- ## Configuration Reference ### Common Fields | Field | Description | |---|---| | `_type` | Profile type. See supported values below. | | `id` | Unique identifier for this profile. Must match the key name in `resourceAccess`. | ### Supported `_type` Values | `_type` | Provider | Use case | |---|---|---| | `AwsS3Access` | AWS | S3 bucket storage | | `AzureBlobAccess` | Azure | Blob container storage | | `GcpStorageAccess` | GCP | Cloud Storage bucket | | `AwsKmsAccess` | AWS | Key Management Service | | `AzureKeyVaultAccess` | Azure | Key Vault | ### Storage-Specific Fields | Field | Provider | Description | |---|-------------|--------------------------------------------------------------| | `bucket.bucketName` | Azure | Name of the blob container | AWS, GCP | Name of the bucket | | `bucket.bucketUrl` | AWS , Azure | The URL of the registry bucket custom domain | | `region` | AWS | AWS region (e.g. `eu-central-1`) | | `endpointUrl` | AWS | Optional S3-compatible endpoint (e.g. `https://s3-mock.com`) | | `projectId` | GCP | Google Cloud project ID | ### Credentials Fields | Field | Provider | Description | |---|---|---| | `credentials.accessKeyId` | AWS | AWS access key ID | | `credentials.secretKey` | AWS | AWS secret access key | | `credentials.connectionString` | Azure (Storage) | Azure Blob Storage connection string | | `credentials.tenantId` | Azure (KMS) | Azure AD tenant ID | | `credentials.clientId` | Azure (KMS) | Azure AD client/application ID | | `credentials.clientSecret` | Azure (KMS) | Azure AD client secret | | `credentials.serviceAccountKeyJson` | GCP | Full GCP service account key object | | `keyVaultUrl` | Azure (KMS) | URL of the Azure Key Vault instance | -------------------------------------------------------------------------------- PAGE: Pagination URL: https://docs.walt.id/enterprise-stack/setup/configurations/config-files/pagination RAW: https://docs.walt.id/enterprise-stack/setup/configurations/config-files/pagination.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Pagination The `pagination.conf` file defines default and maximum pagination limits used by Enterprise list endpoints that support `limit`, `offset`, and, on some endpoints, `sort`. ## Configuration ```editorconfig[pagination.conf] { "defaultLimit": 100, "maxLimit": 1000 } ``` ## Fields - `defaultLimit`: applied when `limit` is not provided by the client. - `maxLimit`: upper bound accepted for `limit`. `offset` is not configurable — it always defaults to `0` when omitted. ## Query parameters - `limit` (integer, optional): number of entries to return. Defaults to `defaultLimit`; must be greater than `0` and less than or equal to `maxLimit`. - `offset` (integer, optional): number of entries to skip. Defaults to `0`; must be greater than or equal to `0`. - `sort` (string, optional): only accepted on endpoints marked **Sortable** in the table below. Supported values: - `createdDateTime` or `createdDateTime-` — newest created first (the bare value is shorthand for `-`, descending) - `createdDateTime+` — oldest created first (ascending) - `updatedDateTime` or `updatedDateTime-` — most recently updated first (descending) - `updatedDateTime+` — least recently updated first (ascending) Passing `sort` on an endpoint that isn't marked Sortable returns `400 Bad Request`. When `sort` is omitted, each endpoint falls back to its own default order, which isn't necessarily insertion order — pass `sort` explicitly if your client depends on ordering. **warning:** The `+` in `createdDateTime+` / `updatedDateTime+` must be URL-encoded as `%2B`. A literal, unencoded `+` in a query string is decoded as a space by the server, so `?sort=createdDateTime+` pasted directly into a browser or a raw HTTP client is received as `"createdDateTime "` and rejected with `400 Bad Request`. HTTP client libraries that percent-encode query parameters for you (most SDKs, `curl --data-urlencode`) are unaffected. ## Validation behavior For list endpoints using this pagination contract, the API returns `400 Bad Request` when: - `limit <= 0` - `limit > maxLimit` - `offset < 0` - `limit` or `offset` is not an integer - `sort` is provided on an endpoint that doesn't support it - `sort` is provided with a value outside the supported list above ## Endpoint behavior The following list endpoints support this contract: | Endpoint | Sortable | |---|---| | `GET /v1/{target}/resource-api/resources/list` | No | | `GET /v1/{target}/credential-store-service-api/credentials/list` | Yes | | `GET /v1/{target}/credential-store-service-api/credentials/list-ids` | No | | `GET /v1/{target}/did-store-service-api/dids` | Yes | | `GET /v1/{target}/did-store-service-api/dids/list-did-ids` | No | | `GET /v1/{target}/kms-service-api/keys/list` | Yes | | `GET /v1/{target}/x509-store-api/certificates` | Yes | | `GET /v1/{target}/x509-store-api/certificates/ids` | No | | `GET /v2/{target}/issuer-service-api/sessions` | Yes | | `GET /v1/{target}/verifier2-service-api/sessions` | Yes | | `GET /v1/{target}/policy-store-service-api/policies/list` | No | | `GET /v1/{target}/policy-store-service-api/policies/list-ids` | No | | `GET /v1/{target}/credential-status-service-api/status-credential/list` | No | | `GET /v1/{target}/vical-service-api/versions/ids` | No | Responses remain arrays (no pagination envelope is added). See also: [List Service Resources](https://docs.walt.id/enterprise-stack/administration/services/list-resources.md) and [Manage Certificates](https://docs.walt.id/enterprise-stack/services/x509-store-service/manage-certificates.md) for worked examples. -------------------------------------------------------------------------------- PAGE: Transaction Data Profiles URL: https://docs.walt.id/enterprise-stack/setup/configurations/config-files/transaction-data-profiles RAW: https://docs.walt.id/enterprise-stack/setup/configurations/config-files/transaction-data-profiles.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Transaction Data Profiles `transaction-data-profiles.conf` defines the transaction data types your Enterprise Verifier2 service recognizes for OpenID4VP `transaction_data` requests. The `transaction-data-profiles` feature is enabled by default in the Enterprise API. Transaction data types act as namespaces that: - Enforce which `transaction_data.type` values the verifier accepts - Define the fields exposed by the Verifier2 transaction-data discovery endpoint - Drive the Enterprise Verifier2 creation UI, which renders transaction-data inputs from these profiles - Define the DeviceSigned namespace used for `mso_mdoc` transaction-data binding ## File Location The Enterprise Stack reads this configuration from: `waltid-enterprise-api/config/transaction-data-profiles.conf` ## 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 `mso_mdoc` DeviceSigned binding. Example: `"org.waltid.transaction-data.payment-authorization"` | | `displayName` | String | Human-readable name for this type. Used in the Enterprise Verifier2 UI and discovery endpoint responses. Defaults to `type` if not specified. | | `fields` | Array | List of field names expected for this type. Used for UI generation and documentation. Example: `["amount", "currency", "payee"]` | ## Built-in Profiles The default Enterprise 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"] } ] ``` ## Discovery Endpoint Enterprise Verifier2 exposes the configured profiles at: `GET /v1/{target}/verifier2-service-api/transaction-data-profiles` This endpoint requires the `CREATE_VERIFIER_SESSION` permission on the Verifier2 service target. ## Adding Custom Types To add a custom transaction data type: 1. Add a new entry to `transactionDataProfiles` in `waltid-enterprise-api/config/transaction-data-profiles.conf` 2. Restart the Enterprise API so the new configuration is loaded 3. If your holders use a wallet you manage, ensure that wallet recognizes the same transaction-data type identifiers **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:** Only wallets that recognize the same transaction-data type identifiers can successfully present these requests. Keep your verifier and wallet type registries aligned. ## Usage in Verification Requests Once configured, use the profile `type` in the verification session request: ```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/enterprise-stack/services/verifier2-service/credential-verification/sd-jwt-vc-oid4vp.md#transaction-data-authorization) for request constraints and end-to-end examples. -------------------------------------------------------------------------------- PAGE: Administration URL: https://docs.walt.id/enterprise-stack/administration RAW: https://docs.walt.id/enterprise-stack/administration.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Administration Learn about and how to use administrative features of the Enterprise Stack. ## Get Started - [Organizations](https://docs.walt.id/enterprise-stack/administration/organizations/overview.md) - The starting point. - [Tenants](https://docs.walt.id/enterprise-stack/administration/tenants/overview.md) - Container for services. - [Services](https://docs.walt.id/enterprise-stack/administration/services/overview.md) - Core digital identity features like credential issuance & verification. - [Access & Permissions](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/overview.md) - Secure your enterprise infrastructure. -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/administration/organizations/overview RAW: https://docs.walt.id/enterprise-stack/administration/organizations/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview The Organization is your account. Under the Organization you can group customers as tenants, sub-tenants, or create a tenant of your own. This way services and their data can be kept virtually separate, enabling you to easily manage different customers or services of your own under one roof. Also, this structure allows you to build and sell B2B2C products (e.g., you can resell to business clients who offer consumer products). While the Enterprise Stack allows for multiple Organizations, on-prem Enterprise deployments should always have one Organization. An organization identifier might be `waltid` ## Get Started - [Create Organization](https://docs.walt.id/enterprise-stack/administration/organizations/create.md) - Learn how to create an organization. -------------------------------------------------------------------------------- PAGE: Create Organizations URL: https://docs.walt.id/enterprise-stack/administration/organizations/create RAW: https://docs.walt.id/enterprise-stack/administration/organizations/create.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Create Organizations This documentation outlines the process and rules for creating organizations within the walt.id Enterprise API. ## Creation Permissions ### Default Behavior - By default, only super-admin users are allowed to create an organization via the `/v1/admin/organizations` endpoint. ### Allow Anyone To Create An Organization - When the `create-organization` feature is enabled via the Feature Manager: - **Every Account (without any permissions)** is permitted to create new organizations ## Automatic Role Assignment ### Admin Role Creation - When a new organization is created, the system automatically generates an admin role - The admin role follows the naming convention: `{organizationID}.admin` ### Creator Role Assignment - The user who creates the organization is automatically assigned the automatically created admin role (`{organizationID}.admin`) ## Create a new Organization (As Super Admin) **Option: CURL** **Endpoint:** `/v1/admin/organizations` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Administration%20(Global)%20%2F%20Organizations/post_v1_admin_organizations) **Example Request** ```bash curl -X 'POST' \ 'https://enterprise.sandbox.walt.id/v1/admin/organizations' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "_id": "test", "profile": { "name": "Test GmbH" }, "billing": { "billingCountry": "AT", "billingAddress": "test 111/115, 1090 Vienna", "vatNr": "test" } }' ``` **Body** ```json { "_id": "string", "profile": { "name": "string" }, "billing": { "billingCountry": "", "billingAddress": "", "vatNr": "", "iban": "", "swift": "" } } ``` **Body Parameters** - `_id` - `type`: _String_ - Unique identifier for the organization, e.g. `waltid` - `profile` - `name`: _String_ - Human readable name for the organization. - `billing`: Billing information --- **Response Codes** - `201` - Organization created successfully. ## Create a new Organization (As Regular User) **Note:** Please note, this requires the optional feature`create-organization` to be enabled via the Feature Manager.
**Option: CURL** **Endpoint:** `/v1/admin/organizations` **Example Request** ```bash curl -X 'POST' \ 'https://enterprise.sandbox.walt.id/v1/admin/organizations' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "_id": "test", "profile": { "name": "Test GmbH" }, "billing": { "billingCountry": "AT", "billingAddress": "test 111/115, 1090 Vienna", "vatNr": "test" } }' ``` **Body** ```json { "_id": "string", "profile": { "name": "string" }, "billing": { "billingCountry": "", "billingAddress": "", "vatNr": "", "iban": "", "swift": "" } } ``` **Body Parameters** - `_id` - `type`: _String_ - Unique identifier for the organization, e.g. `waltid` - `profile` - `name`: _String_ - Human readable name for the organization. - `billing`: Billing information --- **Response Codes** - `201` - Organization created successfully. -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/administration/tenants/overview RAW: https://docs.walt.id/enterprise-stack/administration/tenants/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview Tenants enable you and your customers to launch enterprise services like issuer, verifier, KMS, Credential Web Registry, and more. You can also create sub-tenants inside of tenants and sub-tenants. At this point, there is no limit to the level of nested sub-tenants. Services and their data inside tenants and sub-tenants are kept separate. A tenant identifier might be `waltid.tenant1` ## Get Started - [Create tenants](https://docs.walt.id/enterprise-stack/administration/tenants/create.md) - Learn how to create tenants. -------------------------------------------------------------------------------- PAGE: Create Tenants URL: https://docs.walt.id/enterprise-stack/administration/tenants/create RAW: https://docs.walt.id/enterprise-stack/administration/tenants/create.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Create Tenants This document outlines how to create a new tenant within an existing organization via the walt.id Enterprise API. If you don't yet have an organization, please go [here](https://docs.walt.id/enterprise-stack/administration/organizations/create.md) to learn more. ## Create a New Tenant **Option: CURL** Endpoint:`/v1/{target}/resource-api/tenants/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Resource/post_v1__target__resource_api_tenants_create) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/tenants/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "name": "My Tenant" }' ``` **Body** ```json { "name": "My Tenant" } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization in which to create the new tenant and the tenant's ID (`{organizationID}.[YourID]`), e.g. `waltid.tenant1` **Body Parameters** - `name`: _String_ - Human readable name for the tenant. --- **Response Codes** - `201` - Tenant created successfully. -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/administration/services/overview RAW: https://docs.walt.id/enterprise-stack/administration/services/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview Services provide the functional capabilities for decentralized identity and ID wallets. They can be created inside of tenants and sub-tenants, then managed through the similar administration interfaces documented on this page. This section is intentionally focused on service operations—creating instances, inspecting configuration, wiring dependencies, and managing metadata. Use the links below if you need other entry points before diving into the admin details: **Note:** - Need the high-level mental model for services (Lego blocks, operational vs storage)? Visit [Enterprise Foundations → Services](https://docs.walt.id/enterprise-stack/foundations.md#services). - Looking for issuance, verification, or per-service walkthroughs? Open the [Service Catalog](https://docs.walt.id/enterprise-stack/services.md). ## All services have a common set of endpoints Many of the Enterprise Stack APIs look and behave the same, regardless of whether you are calling the Issuer, the Verifier, a KMS, or another service. Every endpoint is mounted under `/v1/{target}/{service-name}-api/...`, where `target` is the dot-separated path to the organization, tenant, and service instance (for example `acme.sandbox.issuer-eu`). ### 1. Provisioning & lifecycle All services are created, listed, or deleted through the **resource-api**. - [**Service creation**](https://docs.walt.id/enterprise-stack/administration/services/create.md) – create a service using the `/resource-api/services/create` endpoint. - [**View Service Resources**](https://docs.walt.id/enterprise-stack/administration/services/list-resources.md) - list child service resoures using the `/resource-api/resources/list` endpoint. - [**Service deletion**](https://docs.walt.id/enterprise-stack/administration/services/delete.md) - delete a service (and optionally everything underneath it) using the `/resource-api/resources/delete` and `/delete-recursive` endpoints. All all service provising & lifeclye operations go through the same controller, you never have to learn a service-specific “create” endpoint. ### 2. Configuration Once a service exists you can always introspect or replace its configuration: - [**View Configuration**](https://docs.walt.id/enterprise-stack/administration/services/configuration/view.md) - call `/configuration/view` on any service to inspect is config. - [**Update Configuration**](https://docs.walt.id/enterprise-stack/administration/services/configuration/update.md) - call `/configuration/update` on any service to update the present config. This is the way to rotate signing keys, update issuer metadata, point a wallet to a new credential store, etc., without having to recreate the service. ### 3. Dependency Services Services can depend on other services, depending on how your use case is built. For example, the DID Service may require the DID Store Service to persist DID Documents. Each service exposes its dependency management via `/dependencies/...` endpoints. - [**View Dependencies**](https://docs.walt.id/enterprise-stack/administration/services/dependency-services/view.md) – see which services are linked as dependencies of current service. - [**Add Dependency**](https://docs.walt.id/enterprise-stack/administration/services/dependency-services/add.md) - attach a new dependency service to current service. - [**Remove Dependency**](https://docs.walt.id/enterprise-stack/administration/services/dependency-services/remove.md) - remove a dependency service from current service. If a service does not use any dependency services, these endpoints will simply not be available. To see which dependencies a specific service needs, check its section [here](https://docs.walt.id/enterprise-stack/services.md). ### 4. Metadata management for child resources Certain services—like KMS, DID, and Credential Store—automatically add a metadata block to their child resources (for example Keys, DIDs, and Credentials). You can tell whether a service supports metadata for its child resources by checking if it exposes `/metadata` endpoints. - [**View metadata**](https://docs.walt.id/enterprise-stack/administration/services/metadata/view.md) – `/metadata/view` returns the JSON blob stored next to a child resource. - **[Set](https://docs.walt.id/enterprise-stack/administration/services/metadata/set.md) or [update](https://docs.walt.id/enterprise-stack/administration/services/metadata/update.md) metadata** – `/metadata/set` replaces the blob, and `/metadata/update` applies a JSON Patch style update. - [**Remove metadata**](https://docs.walt.id/enterprise-stack/administration/services/metadata/purge.md) – `/metadata/purge` clears the metadata for that resource. - [**Query metadata**](https://docs.walt.id/enterprise-stack/administration/services/metadata/query.md) – `/metadata/query` runs constraint-based searches across all children underneath the service. This gives you a uniform way to label stored keys, DIDs, or credentials (for example with usage, or environment tags) and then search for them later. ### 5. List pagination Most service and resource listing endpoints support the same pagination query parameters: - `limit` (default `100`) - `offset` (default `0`) Some list endpoints also support `sort`, with values `createdDateTime`, `createdDateTime+`, `createdDateTime-`, `updatedDateTime`, `updatedDateTime+`, or `updatedDateTime-`. Passing `sort` on an endpoint that doesn't support it returns `400 Bad Request`. If the input is invalid (`limit <= 0`, `limit > maxLimit`, `offset < 0`, or non-integer values), the API returns `400 Bad Request`. The configurable defaults and bounds are documented in the [`pagination.conf` reference](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/pagination.md). ## Get Started With a Specific Service Check out the service overview page [here](https://docs.walt.id/enterprise-stack/services.md). -------------------------------------------------------------------------------- PAGE: Create Service URL: https://docs.walt.id/enterprise-stack/administration/services/create RAW: https://docs.walt.id/enterprise-stack/administration/services/create.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Create Service Use the `resource-api/services/create` endpoint to provision new Issuer, Verifier, KMS, DID, or other Enterprise Stack services under an existing tenant. **Note:** For service-specific request/response samples and configuration guidance, check the **Setup section** inside each [service’s docs](https://docs.walt.id/enterprise-stack/services.md) (e.g., Issuer, Verifier, KMS). Those pages show the exact payloads you’ll need once you choose a service type. ## Create a Service (KMS example) **Option: CURL** Endpoint: `/v1/{target}/resource-api/services/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Resource/post_v1__target__resource_api_services_create) **Example Request** ```bash curl -X 'POST' \ 'https://{orgHost}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "kms" }' ``` **Body** ```json { "type": "kms" } ``` **Path Parameters** - `orgID`: Use the organization’s base URL / host alias. If your org is `test`, the sandbox host will be `test.enterprise-sandbox.walt.dev`. - `target`: _resourceIdentifier_ – The full path for the service you are about to create: `organization.tenant.serviceId` (e.g., `acme.sandbox.kms-eu`). The final segment becomes the stored service ID, so choose it carefully before calling the endpoint. **Body Parameters** - `type`: Service type (`kms`, `issuer`, `verifier`, `did-store`, etc.). --- **Example Response** `201 Created` ```json { "type": "kms", "traversable": true, "_id": "test.tenant1.kms1", "defaultKeyGenerationRequests": {}, "parent": "test.tenant1" } ``` -------------------------------------------------------------------------------- PAGE: List Service Resources URL: https://docs.walt.id/enterprise-stack/administration/services/list-resources RAW: https://docs.walt.id/enterprise-stack/administration/services/list-resources.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # List Service Resources Use `resource-api/resources/list` to inspect which child resources exist beneath a specific target—this can be a tenant (to see all services) or a service (to see internal resources such as keys, issuance sessions, etc.). Think of it as traversing the organization tree one level at a time. The API returns the full objects for each child, so the shape of each entry depends on the resource type. ## List child resources **Option: CURL** Endpoint: `/v1/{target}/resource-api/resources/list` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Resource/get_v1__target__resource_api_resources_list) **Example Request** ```bash curl -X 'GET' \ 'https://{orgHost}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/resources/list?limit=50&offset=0' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' ``` **Path Parameters** - `orgID`: Use the organization’s base URL / host alias (e.g., `test.enterprise-sandbox.waltid.dev` in sandbox). - `target`: _resourceIdentifier_ – The node whose immediate children you want to inspect. Examples: - Tenant: `test.sandbox` → returns all services under that tenant. - Service: `test.sandbox.kms-eu` → returns resources stored under that service (e.g., `test.sandbox.kms-eu.key1`). **Query Parameters** - `limit`: _Integer (optional)_ – Maximum number of entries to return. Defaults to `100`. - `offset`: _Integer (optional)_ – Number of entries to skip. Defaults to `0`. Invalid values (`limit <= 0`, `limit` above the configured maximum, `offset < 0`, or non-integer values) return `400 Bad Request`. See the [pagination reference](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/pagination.md) for the full contract. --- **Example Response** `200 OK` Listing the children of a KMS service returns the stored keys: ```json [ { "type": "key", "_id": "test.sandbox.kms-eu.key1", "key": { "type": "jwk", "jwk": { "kty": "OKP", "d": "rvg7ubJTALaDY-tTEj-67swXNJGQ_GqOkiwGOnfVtxw", "crv": "Ed25519", "kid": "lLOWUTsTh7Dyy39eBTtNgBe5iZ6FHE0gUpQ85xNDc-s", "x": "o3wj5Lhpi8im7BzujhKVvW_WOCMF3u5zAmg5FTjezGM" } }, "parent": "test.sandbox.kms-eu" } ] ``` Treat this endpoint as a generic “show me the next level down” helper and then pass the returned objects (or their `_id`s) into `/metadata`, `/configuration`, or service-specific APIs for further actions. -------------------------------------------------------------------------------- PAGE: Delete Service URL: https://docs.walt.id/enterprise-stack/administration/services/delete RAW: https://docs.walt.id/enterprise-stack/administration/services/delete.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Delete Service When you need to delete a service (and optionally everything underneath it), you can use the `resource-api/resources/delete` or `/resource-api/resources/delete-recursive` endpoints. ## Delete a Service **Option: CURL** Endpoint: `/v1/{target}/resource-api/resources/delete` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Resource/delete_v1__target__resource_api_resources_delete) **Example Request** ```bash curl -X 'DELETE' \ 'https://{orgHost}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/resources/delete' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' ``` **Path Parameters** - `orgID`: Use the organization’s base URL / host alias (e.g., `test.enterprise-sandbox.waltid.dev` in sandbox). - `target`: _resourceIdentifier_ – Fully qualified service path (`{organizationID}.{tenantID}.{serviceId}`), e.g., `test.sandbox.kms-eu`. --- **Response Codes** - `202` The resource was deleted. - `401` Invalid authentication Use `/resource-api/resources/delete-recursive` if you need to cascade the removal to nested resources (for example, wiping stored keys when deleting a KMS). The request/response shape stays the same; only the endpoint path changes. -------------------------------------------------------------------------------- PAGE: View Service Configuration URL: https://docs.walt.id/enterprise-stack/administration/services/configuration/view RAW: https://docs.walt.id/enterprise-stack/administration/services/configuration/view.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # View Service Configuration Each Enterprise Stack service provides a `/configuration/view` endpoint on its service API (such as `kms-service-api`, `issuer-service-api`, etc.). Calling this endpoint returns the complete configuration object, exactly as it exists in the organization tree. This lets you review and adjust service configurations after they have been created—for example, updating supported credential types for the Issuer or changing default key generation parameters in the KMS. ## View configuration (KMS example) **Option: CURL** Endpoint: `/v1/{target}/kms-service-api/keys/configuration/view` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Key%20Management/get_v1__target__kms_service_api_configuration_view) **Example Request** ```bash curl -X 'GET' \ 'https://{orgHost}.enterprise-sandbox.waltid.dev/v1/{target}/kms-service-api/keys/configuration/view' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' ``` **Path Parameters** - `orgHost`: _String_ - Organization host alias to call against. When you are working inside the sandbox tenant `test`, your base URL becomes `https://test.enterprise-sandbox.waltid.dev`. - `target`: _resourceIdentifier_ - Full path to the service you are viewing the config of, following `{organization}.{tenant}.{serviceId}`. For example, the sandbox KMS target would be `test.tenant1.kms1`. **Response** - `200` - The configuration for this service ```json { "traversable": true, "_id": "test.tenant1.kms1", "defaultKeyGenerationRequests": {}, "parent": "test.tenant1" } ``` - `400` – Invalid or incomplete configuration payload. - `401/403` – Authentication or authorization failure. -------------------------------------------------------------------------------- PAGE: Update Service Configuration URL: https://docs.walt.id/enterprise-stack/administration/services/configuration/update RAW: https://docs.walt.id/enterprise-stack/administration/services/configuration/update.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Update Service Configuration `/configuration/update` replaces the configuration of a service. You must send the full service document (not a JSON Patch). This is how you update for example the default key generation options in a KMS or the supported credentials in an issuer service. **Note:** For service-specific configuration guidance, check the **Setup section** inside each [service’s docs](https://docs.walt.id/enterprise-stack/services.md) (e.g., Issuer, Verifier, KMS). Those pages show the concreate config options for a particular service that you can later update. ## Update configuration (KMS example) **Option: CURL** Endpoint: `/v1/{target}/kms-service-api/keys/configuration/update` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Key%20Management/put_v1__target__kms_service_api_configuration_update) **Example Request** ```bash curl -X 'PUT' \ 'https://{orgHost}.enterprise-sandbox.waltid.dev/v1/{target}/kms-service-api/keys/configuration/update' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "traversable": true, "_id": "test.tenant1.kms1", "defaultKeyGenerationRequests": {}, "parent": "test.tenant1" }' ``` **Path Parameters** - `orgHost`: _String_ - Organization host alias to call against. When you are working inside the sandbox tenant `test`, your base URL becomes `https://test.enterprise-sandbox.waltid.dev`. - `target`: _resourceIdentifier_ - Full path to the service you are updating, following `{organization}.{tenant}.{serviceId}`. For example, the sandbox KMS target would be `test.tenant1.kms1`. **Body** ```json { "traversable": true, "_id": "test.tenant1.kms1", "defaultKeyGenerationRequests": {}, "parent": "test.tenant1" } ``` **Body Parameters** - `_id`: Must be identical to `{target}` - `defaultKeyGenerationRequests`: Optional, default generation payloads. - `parent`: The identifier of the tenant under which the service lives. **Response Codes** - `200` – Configuration replaced successfully (response body is empty). - `400` – Invalid or incomplete configuration payload. - `401/403` – Authentication or authorization failure. -------------------------------------------------------------------------------- PAGE: View Service Dependencies URL: https://docs.walt.id/enterprise-stack/administration/services/dependency-services/view RAW: https://docs.walt.id/enterprise-stack/administration/services/dependency-services/view.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # View Service Dependencies Enterprise Stack services often rely on other services to provide their full functionality. For instance, a Wallet Service might depend on KMS, DID, DID Store, and Credential Store services, and a DID Service can depend on a DID Store. You can view all current dependencies of a service via the `/dependencies/list` endpoint on its service API. ## List dependencies (DID Service example) **Option: CURL** Endpoint: `/v1/{target}/did-service-api/dids/dependencies/list` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/DID%20Service/get_v1__target__did_service_api_dids_dependencies_list) **Example Request** ```bash curl -X 'GET' \ 'https://{orgHost}.enterprise-sandbox.waltid.dev/v1/{target}/did-service-api/dids/dependencies/list' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' ``` **Path Parameters** - `orgHost`: _String_ - Organization host alias to call against. When you are working in the sandbox organization `test`, the base URL becomes `https://test.enterprise-sandbox.waltid.dev`. - `target`: _resourceIdentifier_ - Full path to the DID service you want to inspect, following `{organization}.{tenant}.{serviceId}`. For example, the sandbox DID service target would be `test.tenant1.did-service-1`. **Example Response** `200 OK` ```json [ "test.tenant1.did-store-1" ] ``` - Each entry is the fully-qualified service path of a dependency. The list is empty when no dependencies are configured. -------------------------------------------------------------------------------- PAGE: Add Service Dependency URL: https://docs.walt.id/enterprise-stack/administration/services/dependency-services/add RAW: https://docs.walt.id/enterprise-stack/administration/services/dependency-services/add.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Add Service Dependency To link one service to another, call the `/dependencies/add` endpoint and specify the service you want to attach as a dependency. For details on which dependency services are supported, refer to the documentation page of the service you are configuring. ## Attach dependency (DID Service example) **Option: CURL** Endpoint: `/v1/{target}/did-service-api/dids/dependencies/add` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/DID%20Service/post_v1__target__did_service_api_dids_dependencies_add) **Example Request** ```bash curl -X 'POST' \ 'https://{orgHost}.enterprise-sandbox.waltid.dev/v1/{target}/did-service-api/dids/dependencies/add' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d 'test.tenant1.did-store-1' ``` **Path Parameters** - `orgHost`: _String_ - Organization host alias to call against. When you are working in the sandbox organization `test`, the base URL becomes `https://test.enterprise-sandbox.waltid.dev`. - `target`: _resourceIdentifier_ - Full path to the DID service you want to update, following `{organization}.{tenant}.{serviceId}`. For example, `test.tenant1.did-service-1`. **Body Parameters** - `dependency`: _String_ - JSON string containing the fully-qualified path of the service to link (for example `test.tenant1.did-store-1`). The referenced service must already exist and be accessible in the same org. **Response Codes** - `201` – Dependency attached successfully. - `400` – Invalid path or dependency cannot be added. - `401/403` – Authentication or authorization failure. -------------------------------------------------------------------------------- PAGE: Remove Service Dependency URL: https://docs.walt.id/enterprise-stack/administration/services/dependency-services/remove RAW: https://docs.walt.id/enterprise-stack/administration/services/dependency-services/remove.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Remove Service Dependency Call `/dependencies/remove` to detach a dependency service from the current service. ## Detach dependency (DID Service example) **Option: CURL** Endpoint: `/v1/{target}/did-service-api/dids/dependencies/remove` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/DID%20Service/post_v1__target__did_service_api_dids_dependencies_remove) **Example Request** ```bash curl -X 'POST' \ 'https://{orgHost}.enterprise-sandbox.waltid.dev/v1/{target}/did-service-api/dids/dependencies/remove' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d 'test.tenant1.did-store-1' ``` **Path Parameters** - `orgHost`: _String_ - Organization host alias to call against. When you are working in the sandbox organization `test`, the base URL becomes `https://test.enterprise-sandbox.waltid.dev`. - `target`: _resourceIdentifier_ - Full path to the DID service you want to update, following `{organization}.{tenant}.{serviceId}`. For example, `test.tenant1.did-service-1`. **Body Parameters** - `dependency`: _String_ - JSON string containing the fully-qualified path of the dependency you want to detach (for example `test.tenant1.did-store-1`). **Response Codes** - `204` – Dependency detached successfully. - `400` – Invalid path or dependency does not exist. - `401/403` – Authentication or authorization failure. -------------------------------------------------------------------------------- PAGE: Full list of dependency services URL: https://docs.walt.id/enterprise-stack/administration/services/dependency-services/full-list RAW: https://docs.walt.id/enterprise-stack/administration/services/dependency-services/full-list.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Full list of dependency services Our stack is designed to be modular and scalable through a building block style approach. This means you always have the choice to pick and choose which services you need for your solution and configure them as you see fit. When you want to use our services together, you need to configure them as dependencies of each other. This means that the services will be able to access the resources of the other services. Below is a full catalog of all of our services and the optional dependency services that they can have. ## Key Management Service Currently does not have any dependency services. ## DID Service - [Key Management Service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) for referencing keys from a KMS service. - [DID Store Service](https://docs.walt.id/enterprise-stack/services/did-store-service/overview.md) for persisting DIDs ## Issuer Service - [Key Management Service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) for referencing keys from a KMS service. - [DID Store Service](https://docs.walt.id/enterprise-stack/services/did-store-service/overview.md) for referencing DIDs from a DID store service. ## Verifier2 Service - [Key Management Service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) for referencing keys from a KMS service. - [DID Store Service](https://docs.walt.id/enterprise-stack/services/did-store-service/overview.md) for referencing DIDs from a DID store service. - [X509 Store Service](https://docs.walt.id/enterprise-stack/services/x509-store-service/overview.md) for referencing X.509 certificates from a X.509 store service. ## Wallet Service - [Key Management Service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) for referencing keys from a KMS service. - [DID Service](https://docs.walt.id/enterprise-stack/services/did-service/overview.md) for referencing DIDs from a DID service. - [DID Store Service](https://docs.walt.id/enterprise-stack/services/did-store-service/overview.md) for persisting DIDs - [Credential Store Service](https://docs.walt.id/enterprise-stack/services/credential-store-service/overview.md) for persisting credentials - [X509 Store Service](https://docs.walt.id/enterprise-stack/services/x509-store-service/overview.md) for using X.509 certificates ## Credential Status Service - [Key Management Service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) for referencing keys from a KMS service. - [DID Store Service](https://docs.walt.id/enterprise-stack/services/did-store-service/overview.md) for referencing DIDs from a DID store service. - [X509 Store Service](https://docs.walt.id/enterprise-stack/services/x509-store-service/overview.md) for referencing X.509 certificates from a X.509 store service. ## DID Store Service Currently does not have any dependency services. ## Credential Store Service Currently does not have any dependency services. ## DID Registry Service - [DID Store Service](https://docs.walt.id/enterprise-stack/services/did-store-service/overview.md) for referencing and serving DID:web documents. ## Holder Policy Service Currently does not have any dependency services. ## Issuer2 Service - [Key Management Service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) for referencing keys from a KMS service. - [DID Store Service](https://docs.walt.id/enterprise-stack/services/did-store-service/overview.md) for referencing DIDs from a DID store service. - [Credential Status Service](https://docs.walt.id/enterprise-stack/services/credential-status-service/overview.md) for referencing credential status lists. - [X509 Store Service](https://docs.walt.id/enterprise-stack/services/x509-store-service/overview.md) for referencing X.509 certificates from a X.509 store service. ## x509 Store Service Currently does not have any dependency services. ## x509 Service - [Key Management Service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) for referencing keys from a KMS service. - [X.509 Store Service](https://docs.walt.id/enterprise-stack/services/x509-store-service/overview.md) for persisting X.509 certificates. ## Verifier Service Currently does not have any dependency services. -------------------------------------------------------------------------------- PAGE: View Resource Metadata URL: https://docs.walt.id/enterprise-stack/administration/services/metadata/view RAW: https://docs.walt.id/enterprise-stack/administration/services/metadata/view.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # View Resource Metadata Several storage services—such as DID Store, Credential Store, and KMS—support free-form metadata on their child resources. This lets you add tags, descriptions, or other custom details. The `/metadata/view` endpoint returns the full metadata JSON for a resource so you can review or process it as needed. ## View metadata (KMS key example) **Option: CURL** Endpoint: `/v1/{target}/kms-service-api/keys/metadata/view` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Key%20Management/get_v1__target__kms_service_api_metadata_view) **Example Request** ```bash curl -X 'GET' \ 'https://{orgHost}.enterprise-sandbox.waltid.dev/v1/{target}/kms-service-api/keys/metadata/view' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' ``` **Path Parameters** - `orgHost`: _String_ - Organization host alias to call against. In sandbox, the `test` organization resolves to `https://test.enterprise-sandbox.waltid.dev`. - `target`: _resourceIdentifier_ - Full path to the KMS key (or other resource) whose metadata you want to retrieve, e.g. `test.tenant1.kms-eu.key-auth`. **Example Response** `200 OK` ```json { "description": "Primary signing key for sandbox issuer", "tags": [ "issuing", "production" ] } ``` - If no metadata exists, the service returns `404 Not Found`. - Metadata is always a JSON object; structure is completely up to you. -------------------------------------------------------------------------------- PAGE: Set Resource Metadata URL: https://docs.walt.id/enterprise-stack/administration/services/metadata/set RAW: https://docs.walt.id/enterprise-stack/administration/services/metadata/set.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Set Resource Metadata Use `/metadata/set` to replace the complete metadata object associated with a stored resource. This is handy when you want to initialize metadata for a KMS key, DID, or credential or when you want to overwrite all metadata fields in one go. ## Set metadata (KMS key example) **Option: CURL** Endpoint: `/v1/{target}/kms-service-api/keys/metadata/set` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Key%20Management/put_v1__target__kms_service_api_metadata_set) **Example Request** ```bash curl -X 'PUT' \ 'https://{orgHost}.enterprise-sandbox.waltid.dev/v1/{target}/kms-service-api/keys/metadata/set' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "description": "Primary signing key for sandbox issuer", "tags": ["issuing", "production"] }' ``` **Path Parameters** - `orgHost`: _String_ - Organization host alias to call against. In sandbox, `test` maps to `https://test.enterprise-sandbox.waltid.dev`. - `target`: _resourceIdentifier_ - Full path to the KMS key (or other resource) whose metadata you want to replace, e.g. `test.tenant1.kms-eu.key-auth`. **Body** ```json { "description": "Primary signing key for sandbox issuer", "tags": [ "issuing", "production" ] } ``` **Body Parameters** - `metadata`: _JSON object_ - Arbitrary key/value pairs describing the resource. Send `{}` to clear all fields in a single step. **Response Codes** - `200` – Metadata stored successfully. - `400` – Malformed JSON payload. - `401/403` – Authentication or authorization failure. -------------------------------------------------------------------------------- PAGE: Update Resource Metadata (JSON Patch) URL: https://docs.walt.id/enterprise-stack/administration/services/metadata/update RAW: https://docs.walt.id/enterprise-stack/administration/services/metadata/update.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Update Resource Metadata (JSON Patch) `/metadata/update` lets you modify specific metadata fields without replacing the entire object. It accepts [RFC 6902 JSON Patch documents](https://www.rfc-editor.org/rfc/rfc6902), so you can add, replace, or remove individual fields atomically. ## Patch metadata (KMS key example) **Option: CURL** Endpoint: `/v1/{target}/kms-service-api/keys/metadata/update` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Key%20Management/post_v1__target__kms_service_api_metadata_update) **Example Request** ```bash curl -X 'POST' \ 'https://{orgHost}.enterprise-sandbox.waltid.dev/v1/{target}/kms-service-api/keys/metadata/update' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '[ { "op": "replace", "path": "/description", "value": "Updated description" } ]' ``` **Path Parameters** - `orgHost`: _String_ - Organization host alias to call against. In sandbox, `test` maps to `https://test.enterprise-sandbox.waltid.dev`. - `target`: _resourceIdentifier_ - Full path to the KMS key (or other resource) whose metadata you want to patch, e.g. `test.tenant1.kms-eu.key-auth`. **Body** ```json [ { "op": "replace", "path": "/description", "value": "Updated description" } ] ``` **Body Parameters** - `patch`: _JSON object_ - A single JSON Patch operation (`op`, `path`, `value`, etc.). To apply multiple operations, send an array (per RFC 6902). **Response Codes** - `200` – Patch applied successfully. - `400` – Invalid JSON Patch document or patch could not be applied. - `401/403` – Authentication or authorization failure. -------------------------------------------------------------------------------- PAGE: Purge Resource Metadata URL: https://docs.walt.id/enterprise-stack/administration/services/metadata/purge RAW: https://docs.walt.id/enterprise-stack/administration/services/metadata/purge.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Purge Resource Metadata `/metadata/purge` removes the metadata block attached to a stored resource. This is useful when you want to reset tags and descriptions entirely, or before re-initializing metadata with fresh context. ## Purge metadata (KMS key example) **Option: CURL** Endpoint: `/v1/{target}/kms-service-api/keys/metadata/purge` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Key%20Management/delete_v1__target__kms_service_api_metadata_purge) **Example Request** ```bash curl -X 'DELETE' \ 'https://{orgHost}.enterprise-sandbox.waltid.dev/v1/{target}/kms-service-api/keys/metadata/purge' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' ``` **Path Parameters** - `orgHost`: _String_ - Organization host alias to call against. In sandbox, `test` maps to `https://test.enterprise-sandbox.waltid.dev`. - `target`: _resourceIdentifier_ - Full path to the KMS key (or other resource) whose metadata you want to delete, e.g. `test.tenant1.kms-eu.key-auth`. **Response Codes** - `200` – Metadata purged successfully. - `404` – No metadata existed for that resource. - `401/403` – Authentication or authorization failure. -------------------------------------------------------------------------------- PAGE: Query Resources by Metadata URL: https://docs.walt.id/enterprise-stack/administration/services/metadata/query RAW: https://docs.walt.id/enterprise-stack/administration/services/metadata/query.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Query Resources by Metadata Use `/metadata/query` to find child resources (keys, credentials, DIDs, etc.) that match specific metadata constraints. Constraints follow the Presentation Definition format: each field lists a JSON path and an optional JSON Schema filter. ## Query metadata (Credential Store example) **Option: CURL** Endpoint: `/v1/{target}/credential-store-service-api/credentials/metadata/query` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Credential%20Store/post_v1__target__credential_store_service_api_metadata_query) **Example Request** ```bash curl -X 'POST' \ 'https://{orgHost}.enterprise-sandbox.waltid.dev/v1/{target}/credential-store-service-api/credentials/metadata/query' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '[ { "path": ["$.tags"], "filter": { "type": "array", "contains": { "const": "production" } } } ]' ``` **Path Parameters** - `orgHost`: _String_ – Organization host alias. In sandbox, `test` resolves to `https://test.enterprise-sandbox.waltid.dev`. - `target`: _resourceIdentifier_ – Credential Store service you want to search (`{organization}.{tenant}.{serviceId}`), e.g. `test.tenant1.credential-store-1`. **Body Parameters** - `constraints`: _Array_ – List of Presentation Definition `field` objects. Each object contains: - `path`: JSON path expressions to inspect (prefixed with `$`). - `filter`: Optional JSON Schema fragment used to match the metadata value. **Example Response** `200 OK` ```json { "test.tenant1.credential-store-1.credential-123": { "description": "Employee badge", "tags": ["production", "badge"] } } ``` - Keys are resource targets; values are the matched metadata objects. Empty object indicates no matches. -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/overview RAW: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview The walt.id **Enterprise Stack** provides fine-grained access control using **Role-Based Access Control (RBAC)** with optional integration into external **Identity and Access Management (IAM)** systems. This model clearly separates: - **Authentication**: Who a caller is - **Authorization**: What a caller is allowed to do ## Authentication Methods Authentication can be performed using: | Method | Use Case | |--------|----------| | Local Accounts | Email/password authentication for users | | API Keys | Machine-to-machine authentication | | External IAM (OIDC) | Integration with enterprise identity providers | ## Authorization Model Authorization is enforced by the Enterprise Stack using RBAC. 1. Request arrives with authentication token 2. Resolve identity (account, API key, or OIDC user) 3. Load assigned roles and their permissions 4. Check allow rules: Does user have required permission? ## Two-Layer Enforcement The Enterprise Stack enforces permissions at two layers: 1. **Route-Level Authorization**: Each API endpoint requires specific permissions. Requests without the required permission receive `403 Forbidden`. 2. **Persistence-Level Authorization**: Database operations are filtered based on user permissions, ensuring users can only access resources they're authorized to see. This dual-layer approach provides defense in depth, ensuring comprehensive security. ## Resource Hierarchy Resources in the Enterprise Stack follow a hierarchical structure: ``` Organization │ ├── Tenant A │ ├── Issuer Service │ ├── Verifier Service │ └── KMS Service │ └── Tenant B └── Wallet Service ``` Permissions propagate **top-down**: - A permission granted at the organization level applies to all tenants and services - A permission granted at the tenant level applies to all services within that tenant ## Security Best Practices For production deployments: | Practice | Description | |----------|-------------| | Use External IAM | Integrate with your enterprise identity provider (OIDC) for workforce authentication | | Least Privilege | Grant only the permissions necessary for each role | | Narrow Scopes | Assign permissions at the narrowest scope needed | | Rotate API Keys | Establish regular rotation schedules for API keys | | Short-Lived Tokens | Use short-lived access tokens where possible | | Avoid Super Admin | Use Super Admin only for initial setup, not for day-to-day operations | | Monitor Audit Logs | Review logs for privileged operations and anomalies | | Use Deny Rules | Apply deny rules to create exceptions for sensitive operations | ## Learn More ### Core Concepts - [RBAC Model](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/rbac-model.md) - Understand roles, permissions, and evaluation logic - [Permissions](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/permissions/overview.md) - Complete reference of all available permissions ### Managing Access - [Roles](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/roles/overview.md) - Create and manage role definitions - [Accounts](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/accounts/overview.md) - Manage local user accounts - [API Keys](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/api-keys/overview.md) - Create keys for machine-to-machine access - [Super Admin](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/super-admin/overview.md) - Unrestricted administrative access ### Integration - [IAM Integration](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/iam-integration.md) - Connect to external identity providers via OIDC - [External Role Mapping](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping.md) - Automatically assign permissions based on IdP roles - [Permissions](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/permissions/overview.md) - Discover available permissions and their scope - [Roles](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/roles/overview.md) - Define and manage user roles and their permission sets - [Accounts](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/accounts/overview.md) - Manage local user accounts and authentication - [API Keys](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/api-keys/overview.md) - Create and manage API keys for machine-to-machine access - [Super Admin](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/super-admin/overview.md) - Grant unrestricted administrative access with caution -------------------------------------------------------------------------------- PAGE: RBAC Model URL: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/rbac-model RAW: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/rbac-model.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # RBAC Model The Enterprise Stack uses **Role-Based Access Control (RBAC)** to authorize access to APIs and administrative operations. ## Core Concepts RBAC in the Enterprise Stack is built around four core concepts: | Concept | Description | |---------|-------------| | Permissions | Individual operations that can be performed (e.g., `issuer-credential-issue`, `list-keys`) | | Roles | Named collections of permissions that can be assigned to principals | | Principals | Identities such as Accounts, API Keys, or external IAM users | | Scopes | Organizational boundaries (organization, tenant, service) where permissions apply | ``` Principal → Role → Permissions → API Access ``` ## Permission Scope Roles are always assigned within a **scope**. Scopes define where permissions are effective and follow a hierarchical structure. | Scope | Description | |-------|-------------| | Organization | Access across all tenants in an organization | | Tenant | Access limited to one tenant | | Sub-tenant | Access limited to nested tenant structures | | Service | Access limited to a specific service instance | ### Hierarchical Inheritance Permissions propagate **top-down** through the resource hierarchy: ``` Organization (waltid) │ ├── Tenant A (waltid.tenantA) │ ├── Issuer Service (waltid.tenantA.issuer1) │ └── KMS Service (waltid.tenantA.kms1) │ └── Tenant B (waltid.tenantB) └── Verifier Service (waltid.tenantB.verifier1) ``` A permission granted at `waltid` (organization level) automatically applies to all resources below it. A permission granted at `waltid.tenantA` applies only to Tenant A and its services. ## Allow and Deny Rules ### Allow Rules (ADD) Grant permissions to perform specific operations: ```json { "target": "waltid.tenantA", "action": "issuer-credential-issue", "operation": "ADD" } ``` ### Deny Rules (REMOVE) Explicitly revoke permissions, overriding any allow rules: ```json { "target": "waltid.tenantA.issuer1", "action": "issuer-credential-issue", "operation": "REMOVE" } ``` **Deny always wins.** Even if a user has `ALL` permissions at the organization level, a deny rule on a specific resource will block access to that operation. ## User Types The Enterprise Stack recognizes different user types with different permission models: | User Type | Permission Model | |-----------|-----------------| | Super Admin | Bypasses all permission checks (full access) | | Regular User | Permissions evaluated via allow/deny trees | | Anonymous User | No permissions granted (public endpoints only) | ## Example Role Configurations ### Organization Administrator Full access to manage the entire organization: ```json { "name": "Organization Admin", "permissions": [ { "target": "waltid", "action": "all", "operation": "ADD" } ] } ``` ### Tenant Administrator Full access within a specific tenant: ```json { "name": "Tenant A Admin", "permissions": [ { "target": "waltid.tenantA", "action": "all", "operation": "ADD" } ] } ``` ### Issuer Operator Limited to issuing credentials with a specific issuer service: ```json { "name": "Issuer Operator", "permissions": [ { "target": "waltid.tenantA.issuer1", "action": "issuer-credential-issue", "operation": "ADD" }, { "target": "waltid.tenantA.issuer1", "action": "issuer-session-view", "operation": "ADD" } ] } ``` ### Restricted Admin Full access with specific operations blocked: ```json { "name": "Restricted Admin", "permissions": [ { "target": "waltid.tenantA", "action": "all", "operation": "ADD" }, { "target": "waltid.tenantA", "action": "delete-resource-recursive", "operation": "REMOVE" } ] } ``` ### Auditor (Read-Only) View-only access for compliance and monitoring: ```json { "name": "Auditor", "permissions": [ { "target": "waltid", "action": "view-events", "operation": "ADD" }, { "target": "waltid", "action": "view-resource-tree", "operation": "ADD" } ] } ``` ## Authorization Flow Example **Scenario**: User requests to issue a credential 1. **Authentication**: User authenticates with account credentials 2. **Identity Resolution**: System identifies user as `user@example.org` 3. **Role Loading**: System loads assigned roles (e.g., "Issuer Operator") 4. **Permission Check**: - Required permission: `issuer-credential-issue` on `waltid.tenantA.issuer1` - Check allow tree: Found match via role grant - Check deny tree: No matching deny rule 5. **Result**: Request allowed, credential issued If the user had a deny rule or no matching allow rule, the request would return `403 Forbidden`. ## Best Practices 1. **Principle of Least Privilege**: Grant only the permissions necessary for each role 2. **Use Scopes Appropriately**: Assign permissions at the narrowest scope needed 3. **Leverage Deny Rules**: Use deny rules to create exceptions in broad permission grants 4. **Regular Audits**: Review role assignments and permissions periodically 5. **Separate Duties**: Create distinct roles for different responsibilities (issuing vs. verification) ## Related - [Permissions](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/permissions/overview.md) - Complete list of available permissions - [Roles](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/roles/overview.md) - How to create and manage roles - [Accounts](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/accounts/overview.md) - Managing user accounts - [API Keys](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/api-keys/overview.md) - Machine-to-machine authentication - [Super Admin](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/super-admin/overview.md) - Unrestricted administrative access - [IAM Integration](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/iam-integration.md) - External identity provider integration -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/roles/overview RAW: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/roles/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview Roles are named collections of permissions in the Enterprise Stack RBAC model. They provide a way to group related permissions together and assign them to principals (Accounts, API Keys, or external IAM identities). ## Key Concepts ### Role Structure A role consists of: - **Name**: A human-readable identifier (e.g., "Issuer Operator") - **ID**: A resource path identifier (e.g., `waltid.tenantA.issuer-role`) - **Permissions**: A collection of permission grants and denials ### Permission Operations Each permission in a role has an **operation** type: | Operation | Description | |-----------|-------------| | `ADD` | Grants the permission (allow rule) | | `REMOVE` | Denies the permission (deny rule that overrides allows) | ### Role Assignment Roles can be assigned to: - **Accounts**: User accounts authenticated via email/password or external IAM - **API Keys**: Machine-to-machine authentication tokens A principal can have multiple roles assigned, and their effective permissions are the combination of all role grants minus any denials. ## Common Role Patterns ### Administrative Roles | Role | Typical Permissions | |------|---------------------| | Organization Admin | `all` on organization scope | | Tenant Admin | `all` on specific tenant scope | | Service Admin | `all` on specific service scope | ### Operational Roles | Role | Typical Permissions | |------|---------------------| | Issuer Operator | Issue credentials, view sessions | | Verifier Operator | Create verification sessions, view results | | Key Manager | Generate, import, and manage cryptographic keys | ### Compliance Roles | Role | Typical Permissions | |------|---------------------| | Auditor | View events and resource trees (read-only) | | Security Officer | View configurations, manage policies | ## Role Hierarchy Example ``` Organization: waltid │ ├── Role: waltid.org-admin (Organization Admin) │ └── Permissions: all on waltid │ ├── Tenant: waltid.production │ │ │ ├── Role: waltid.production.tenant-admin │ │ └── Permissions: all on waltid.production │ │ │ ├── Role: waltid.production.issuer-ops │ │ └── Permissions: issuer-credential-issue, issuer-session-view │ │ │ └── Role: waltid.production.auditor │ └── Permissions: view-events, view-resource-tree │ └── Tenant: waltid.sandbox │ └── Role: waltid.sandbox.tenant-admin └── Permissions: all on waltid.sandbox ``` ## Best Practices 1. **Create purpose-specific roles**: Instead of granting broad permissions, create roles tailored to specific job functions 2. **Use descriptive names**: Name roles clearly to indicate their purpose (e.g., "Production Issuer Operator" rather than "Role 1") 3. **Apply least privilege**: Grant only the minimum permissions required for the role's function 4. **Use deny rules sparingly**: Deny rules (`REMOVE` operation) are useful for creating exceptions but can make permission management complex 5. **Document role purposes**: Maintain documentation of what each role is intended for and who should have it ## Get Started - [Creating Roles](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/roles/create-roles.md) - Step-by-step guide to create and configure roles - [Permissions Reference](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/permissions/overview.md) - Complete list of available permissions - [Assign Role to Account](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/accounts/assign-role.md) - How to assign roles to user accounts - [Assign Role to API Key](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/api-keys/assign-role.md) - How to assign roles to API keys -------------------------------------------------------------------------------- PAGE: Create Roles URL: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/roles/create-roles RAW: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/roles/create-roles.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Create Roles Roles are collections of permissions that can be assigned to principals (Accounts, API Keys, or external IAM identities) within a specific organizational scope. ## Creating a Role **Option: CURL** Endpoint: `/v1/{target}/roles-api/roles/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%2F%20Roles/post_v1__target__roles_api_roles_create) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/roles-api/roles/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "name": "Issuer Operator", "permissions": [ { "target": "waltid.production.issuer1", "action": "issuer-credential-issue", "operation": "ADD" }, { "target": "waltid.production.issuer1", "action": "issuer-session-view", "operation": "ADD" } ] }' ``` **Path Parameters** - `orgID`: Your organization's Base URL. For example, if your organization is named `myorg`, your Base URL will be `myorg.enterprise-sandbox.waltid.dev` in the sandbox environment. - `target`: The resource identifier for the new role, formatted as `{organizationID}.{roleID}` (e.g., `waltid.issuer-operator`) **Body Parameters** - `name`: _String_ - Human-readable name for the role - `permissions`: _Array_ - List of permission objects defining what this role can and cannot do **Permission Object** | Field | Type | Description | |-------|------|-------------| | `target` | String | The scope where the permission applies (organization, tenant, or service path) | | `action` | String | The permission name (e.g., `issuer-credential-issue`, `all`). See [Permissions Reference](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/permissions/overview.md) | | `operation` | String | `ADD` to grant the permission (allow), `REMOVE` to deny the permission | --- **Response** ```json { "_id": "waltid.issuer-operator", "name": "Issuer Operator", "permissions": [...] } ``` **Response Codes** - `201` - Role created successfully ## Permission Operations ### ADD (Allow) Grants the specified permission. When a principal with this role attempts the operation, the permission check will pass (unless overridden by a deny rule). ```json { "target": "waltid.production", "action": "issuer-credential-issue", "operation": "ADD" } ``` ### REMOVE (Deny) Creates an explicit deny rule. This overrides any allow rules, even from other roles or broader scopes. Use this to create exceptions in broad permission grants. ```json { "target": "waltid.production.sensitive-issuer", "action": "issuer-credential-issue", "operation": "REMOVE" } ``` ## Example: Full Admin with Restrictions Create a tenant admin who can do everything except delete resources recursively: ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/waltid.restricted-admin/roles-api/roles/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "name": "Restricted Tenant Admin", "permissions": [ { "target": "waltid.production", "action": "all", "operation": "ADD" }, { "target": "waltid.production", "action": "delete-resource-recursive", "operation": "REMOVE" } ] }' ``` ## Adding Permissions to an Existing Role You can add additional permissions to a role after creation: **Option: CURL** Endpoint: `/v1/{target}/roles-api/permissions/permissions/add-to-role` **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/roles-api/permissions/permissions/add-to-role' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "target": "waltid.production.issuer1", "action": "issuer-credential-profile-view" }' ``` **Path Parameters** - `target`: The role ID to modify (e.g., `waltid.issuer-operator`) **Body Parameters** - `target`: The scope for the permission - `action`: The permission to add - `operation`: (Optional) Defaults to `ADD`. Set to `REMOVE` to add a deny rule. --- **Response Codes** - `200` - Permission added successfully ## Adding a Deny Rule to an Existing Role To revoke a specific permission (create a deny rule): ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/waltid.admin-role/roles-api/permissions/permissions/add-to-role' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "target": "waltid.production", "action": "delete-resource", "operation": "REMOVE" }' ``` Users with this role will be denied the `delete-resource` permission on `waltid.production` and all resources below it, even if they have broader `all` permissions. ## Viewing a Role **Option: CURL** Endpoint: `/v1/{target}/roles-api/roles/view` **Example Request** ```bash curl -X 'GET' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/roles-api/roles/view' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' ``` **Path Parameters** - `target`: The role ID to view (e.g., `waltid.issuer-operator`) --- **Response** ```json { "_id": "waltid.issuer-operator", "name": "Issuer Operator", "permissions": [ { "target": "waltid.production.issuer1", "action": "issuer-credential-issue", "operation": "ADD" } ] } ``` ## Listing Roles **Option: CURL** Endpoint: `/v1/{target}/roles-api/roles/list` **Example Request** ```bash curl -X 'GET' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/roles-api/roles/list' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' ``` **Path Parameters** - `target`: The scope to list roles from (e.g., `waltid` for organization-level roles, `waltid.production` for tenant-level roles) ## Deleting a Role **Option: CURL** Endpoint: `/v1/{target}/roles-api/roles/delete` **Example Request** ```bash curl -X 'DELETE' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/roles-api/roles/delete' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' ``` **Path Parameters** - `target`: The role ID to delete (e.g., `waltid.issuer-operator`) --- **Response Codes** - `200` - Role deleted successfully **Error:** Deleting a role removes it from all principals who have it assigned. Make sure no active users or API keys depend on the role before deletion. ## Related - [Permissions Reference](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/permissions/overview.md) - Complete list of available permissions - [Assign Role to Account](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/accounts/assign-role.md) - [Assign Role to API Key](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/api-keys/assign-role.md) -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/permissions/overview RAW: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/permissions/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview Permissions are the most granular building blocks of the Enterprise Stack RBAC model. Each protected operation on a resource is tied to a specific permission, enabling fine-grained control across different levels of the system. ## Two-Layer Permission Enforcement The Enterprise Stack enforces permissions at two layers to ensure comprehensive security: 1. **Route-Level Authorization**: When a request arrives at an API endpoint, the system checks whether the caller has the required permission for that operation. 2. **Persistence-Level Authorization**: When data is read from or written to the database, the system applies additional permission checks to ensure the caller can access the specific resources. This dual-layer approach ensures that even if a request passes the initial route check, it cannot access data the user is not authorized to see. ## Permission Scope Permissions are always assigned within a **scope**. Scopes define where permissions are effective, following a hierarchical structure: | Scope | Description | |------|-------------| | Organization | Access across all tenants in an organization | | Tenant | Access limited to one tenant | | Sub-tenant | Access limited to nested tenant structures | | Service | Access limited to a specific service instance | Example: A permission granted at **Tenant A** level applies to Tenant A and all services within it, but not to Tenant B. ### Permission Propagation In the tree-structured resource hierarchy, permissions follow a **top-down** logic: - When a permission is granted at a parent node (e.g., organization), it automatically propagates to all child nodes (tenants, services, resources) - This allows administrators to manage access for many resources by setting a single rule at a high level ### Allow and Deny Rules The Enterprise Stack supports both **allow** and **deny** rules: - **Allow rules** grant access to perform specific operations - **Deny rules** explicitly revoke access, even if an allow rule exists **Deny always overrides allow.** This ensures that restricted access to sensitive resources cannot be accidentally granted by higher-level permissions. ## Permission Evaluation Logic When a user attempts to access a resource, the system evaluates the "Effective Permission" using these steps: 1. Does the user have a matching **allow** rule for this resource? If no → **Denied** 2. Does the user have a matching **deny** rule for this resource? If yes → **Denied** 3. If allowed but not denied → **Granted** ## Wildcards - `ALL` - Grants all permissions under the specified scope. Use with caution and consider applying deny rules to restrict specific sensitive operations. ## Assigning Permissions to Roles Permissions are assigned to roles using the Roles API. See [Creating Roles](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/roles/create-roles.md) for detailed instructions. **Example: Create a role with issuer permissions** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{roleId}/roles-api/roles/create' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "name": "Issuer Operator", "permissions": [ { "target": "waltid.tenant1.issuer1", "action": "issuer-credential-issue" }, { "target": "waltid.tenant1.issuer1", "action": "issuer-session-view" } ] }' ``` ## Revoking Permissions (Deny Rules) To deny a specific permission, add it with `"operation": "REMOVE"`: ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{roleId}/roles-api/permissions/permissions/add-to-role' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "target": "waltid.tenant1", "action": "delete-resource-recursive", "operation": "REMOVE" }' ``` This creates a deny rule that prevents users with this role from deleting resources recursively in the specified tenant, even if they have broader `ALL` permissions. ## Finding Required Permissions You can find the required permissions for API endpoints by visiting the [Swagger documentation](https://enterprise.sandbox.walt.id/swagger/index.html). In the description of each endpoint, look for the **Operation** field which indicates the specific permission needed. ## Related - [RBAC Model](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/rbac-model.md) - [Roles](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/roles/overview.md) - [Accounts](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/accounts/overview.md) - [API Keys](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/api-keys/overview.md) -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/accounts/overview RAW: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/accounts/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview Accounts allow users to authenticate with the Enterprise API using email and password credentials. ## Key Concepts ### Account Creation Accounts can be created in two ways: | Method | Endpoint | Access | |--------|----------|--------| | Protected Route | `/v1/admin/account/register` | Requires Super Admin or admin credentials | | Self-Registration | `/v1/account/register` | Public (controlled by feature flag) | - A Super Admin (or any admin with the right permissions) typically creates accounts via the protected administration route. - Optional self-service signup is available via an unprotected endpoint that is controlled by the `account-registration` feature flag. The flag defaults to **enabled**, so public signup is exposed unless you disable it. ### Initial State - New accounts start **without any roles**, so they cannot perform any operations until roles are assigned. - Accounts must be assigned roles to gain access to resources. ## Account Scope Accounts are **global resources** and are not bound to a single organization or tenant: - The same account can be granted access to multiple organizations, tenants, sub-tenants, or services - What an account can do is entirely determined by the roles assigned to it - Permissions are evaluated based on the combination of all assigned roles ## Self-Registration The unprotected `/v1/account/register` endpoint is controlled by the `account-registration` feature flag: | Flag State | Behavior | |------------|----------| | Enabled (default) | Public signup is available | | Disabled | Only protected admin route works | **Recommendation**: Disable this flag in production environments to prevent unauthorized account creation. Only leave it enabled for sandboxes, demos, or controlled environments. Toggle the feature via the [feature manager](https://docs.walt.id/enterprise-stack/setup/configurations/feature-manager.md) or `_features.conf`, depending on your deployment workflow. ## Account vs API Key | Feature | Account | API Key | |---------|---------|---------| | Authentication | Email + password | Bearer token | | Use case | Human users | Machine-to-machine | | Session management | Yes (with session tokens) | Stateless | | Self-registration | Optional (feature flag) | No (admin only) | | Expiration | No (unless manually disabled) | Optional TTL | ## Get Started - [Create Account](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/accounts/create-account.md) - Provision an account via the protected admin APIs (or optional self-registration) - [Assign Roles](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/accounts/assign-role.md) - Give an account permissions scoped to specific organizations, tenants, or services -------------------------------------------------------------------------------- PAGE: Create Account (email) URL: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/accounts/create-account RAW: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/accounts/create-account.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Create Account (email) Accounts can be used to access the Enterprise API using an email address and password. ## Create Account (Protected Route) Use the protected route `/v1/admin/account/register` to provision accounts. This endpoint is always available (assuming you have administrator credentials) and is the expected way to create accounts in production environments, especially if you have disabled public signup. **Note:** Please make sure to use a Super Admin when using this endpoint.
**Option: CURL** Endpoint:`/v1/admin/account/register` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Administration%20(Global)%20%2F%20Accounts/post_v1_admin_account_register) **Example Request** ```bash curl -X 'POST' \ 'https://enterprise.sandbox.walt.id/v1/admin/account/register' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "profile": { "name": "Max Mustermann", "email": "max.mustermann@example.org", "addressCountry": "AT", "address": "Liechtensteinstraße 111/115, 1090 Vienna" }, "preferences": { "timeZone": "UTC", "languagePreference": "EN" }, "initialAuth": { "type": "email", "identifier": { "type": "email", "email": "max.mustermann@example.org" }, "data": { "type": "email", "password": "password123456" } } }' ``` **Body** ```json { "profile": { "name": "Max Mustermann", "email": "max.mustermann@example.org", "addressCountry": "AT", "address": "Liechtensteinstraße 111/115, 1090 Vienna" }, "preferences": { "timeZone": "UTC", "languagePreference": "EN" }, "initialAuth": { "type": "email", "identifier": { "type": "email", "email": "max.mustermann@example.org" }, "data": { "type": "email", "password": "password123456" } } } ``` **Body Parameters** - `profile`: _Object_ - User's personal information - `name`: _String_ - Full name of the user - `email`: _String_ - User's email address - `addressCountry`: _String_ - Two-letter country code - `address`: _String_ - Full postal address - `preferences`: _Object_ - User's system preferences - `timeZone`: _String_ - Preferred time zone - `languagePreference`: _String_ - Preferred language code - `initialAuth`: _Object_ - Authentication information - `type`: _String_ - `email` - `identifier`: _Object_ - Authentication identifier details - `type`: _String_ - `email` - `email`: _String_ - Email used for identification - `data`: _Object_ - Authentication credentials - `type`: _String_ - `email` - `password`: _String_ - User's password --- **Response** ```json { "_id": "5e4eb66b-36c3-46bc-bc03-9a5eefc582f0", "type": "USER", "profile": { "name": "Max Mustermann", "email": "max.mustermann@example.org", "addressCountry": "AT", "address": "Liechtensteinstraße 111/115, 1090 Vienna" }, "accountPreferences": { "timeZone": "UTC", "languagePreference": "EN" }, "flags": [], "roles": {} } ``` ## Create Account (Unprotected Route) The unprotected route `/v1/account/register` exists whenever the `account-registration` feature flag is enabled (it defaults to **enabled**, so new deployments expose it until you turn the flag off). Leave it enabled only when you intentionally want end users to self-register (for example in sandbox or demo environments), and disable it elsewhere to prevent unrestricted signups. **Option: CURL** Endpoint:`/v1/account/register` **Example Request** ```bash curl -X 'POST' \ 'https://enterprise.sandbox.walt.id/v1/account/register' \ -H 'accept: */*' \ -H 'Content-Type: application/json' \ -d '{ "profile": { "name": "Max Mustermann", "email": "max.mustermann@example.org", "addressCountry": "AT", "address": "Liechtensteinstraße 111/115, 1090 Vienna" }, "preferences": { "timeZone": "UTC", "languagePreference": "EN" }, "initialAuth": { "type": "email", "identifier": { "type": "email", "email": "max.mustermann@example.org" }, "data": { "type": "email", "password": "password123456" } } }' ``` **Body** ```json { "profile": { "name": "Max Mustermann", "email": "max.mustermann@example.org", "addressCountry": "AT", "address": "Liechtensteinstraße 111/115, 1090 Vienna" }, "preferences": { "timeZone": "UTC", "languagePreference": "EN" }, "initialAuth": { "type": "email", "identifier": { "type": "email", "email": "max.mustermann@example.org" }, "data": { "type": "email", "password": "password123456" } } } ``` **Body Parameters** - `profile`: _Object_ - User's personal information - `name`: _String_ - Full name of the user - `email`: _String_ - User's email address - `addressCountry`: _String_ - Two-letter country code - `address`: _String_ - Full postal address - `preferences`: _Object_ - User's system preferences - `timeZone`: _String_ - Preferred time zone - `languagePreference`: _String_ - Preferred language code - `initialAuth`: _Object_ - Authentication information - `type`: _String_ - `email` - `identifier`: _Object_ - Authentication identifier details - `type`: _String_ - `email` - `email`: _String_ - Email used for identification - `data`: _Object_ - Authentication credentials - `type`: _String_ - `email` - `password`: _String_ - User's password --- **Response Codes** - `201` - Your account has been registered successfully. -------------------------------------------------------------------------------- PAGE: Assign Role URL: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/accounts/assign-role RAW: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/accounts/assign-role.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Assign Role [Roles](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/roles/overview.md) are a set of [permissions](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/permissions/overview.md) that can be assigned to Accounts and API-Keys. If you don't have a role yet, you can learn how to create one [here](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/roles/create-roles.md). ## Assigning a Role to an Account **Option: CURL** Endpoint:`/v1/{target}/roles-api/roles/account/assign` **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/roles-api/roles/account/assign' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d ' { "assigneeEmail": "max.mustermann@example.org" }' ``` **Body** ```json { "assigneeEmail": "max.mustermann@example.org" } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the ID of the role to assign to an account. e.g. `waltid.admin` **Body Parameters** - `assigneeEmail`: _String_ - Email of the account to assign the role to. --- **Response Codes** - `200` - Role assigned successfully. -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/super-admin/overview RAW: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/super-admin/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview The Super Admin is a special privileged account with unrestricted access to the Enterprise Stack. Unlike regular accounts, Super Admins cannot be created via the API—they can only be activated using a pre-configured token. ## Key Characteristics | Aspect | Description | |--------|-------------| | Creation | Activated via token (not created via API) | | Configuration | Defined in `superadmin-registration.conf` | | Permissions | Bypasses all permission checks (full access) | | Scope | Global access across all organizations and tenants | ## Super Admin Privileges The Super Admin has unrestricted access to: - All organizations and tenants - All enterprise services - All administrative operations - All API endpoints **The Super Admin bypasses the entire RBAC permission system.** No allow or deny rules apply to Super Admin accounts. ## When to Use Super Admin | Appropriate Use | Avoid Using For | |-----------------|-----------------| | Initial system setup | Day-to-day operations | | Emergency recovery | Routine administration | | Creating initial admin accounts | Automated processes | | Troubleshooting access issues | Production API calls | ## Security Recommendations **Error:** The Super Admin should be used sparingly and with caution due to its unrestricted access. | Recommendation | Rationale | |----------------|-----------| | Use only for initial setup | Create regular admin accounts for ongoing administration | | Protect credentials | Store Super Admin credentials securely, separate from other secrets | | Monitor usage | Review audit logs for Super Admin activity | | Avoid in automation | Never use Super Admin credentials in automated scripts or CI/CD | | Consider disabling | In highly secure environments, disable after initial setup | ## Configuration The Super Admin is configured in `superadmin-registration.conf`, which must be provided alongside other Enterprise API configuration files. This file defines: - Super Admin email address - Super Admin password - Activation token See [Configure Super Admin Accounts](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/superadmin-registration.md) for configuration details. ## Get Started 1. [Configure Super Admin](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/superadmin-registration.md) - Set up the configuration file 2. [Activate Super Admin](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/super-admin/activate.md) - Activate the account using the token 3. [Authenticate](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/super-admin/authenticate.md) - Log in and obtain a session token -------------------------------------------------------------------------------- PAGE: Activate Super Admins Accounts URL: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/super-admin/activate RAW: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/super-admin/activate.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Activate Super Admins Accounts Using the super-admin-token defined in the `superadmin-registration.conf` file, we can activate the super admin account. ## Activate Super Admin **Option: CURL** Endpoint:`/v1/superadmin/create-by-token` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Administration%20%2F%20Superadmin/post_v1_superadmin_create_by_token) **Example Request** ```bash curl -X 'POST' \ 'https://enterprise.sandbox.walt.id/v1/superadmin/create-by-token' \ -H 'accept: */*' \ -H 'Content-Type: text/plain' \ -d '' ``` **Body** ```text ``` **Body Content**
Please provide the super-admin-token in the body without any quotation marks. Use the token map key from `superadmin-registration.conf` as value (eg. `super-admin-token`). --- **Response Codes** - `200` - Super admin account activated successfully -------------------------------------------------------------------------------- PAGE: Use The Super Admin Accounts URL: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/super-admin/authenticate RAW: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/super-admin/authenticate.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Use The Super Admin Accounts When we have [activated](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/super-admin/activate.md) the super admin account, we can use it to authenticate ourselves against the Enterprise APIs or login in the UI. ## Login as Super Admin via API **Option: CURL** Endpoint:`/auth/account/emailpass` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Authentication/post_auth_account_emailpass) **Example Request** ```bash curl -X 'POST' \ 'https://enterprise.sandbox.walt.id/auth/account/emailpass' \ -H 'accept: */*' \ -H 'Content-Type: application/json' \ -d '{ "email": "superadmin@walt.id", "password": "pw-according-super-admin-conf" }' ``` **Body** ```json { "email": "superadmin@walt.id", "password": "pw-according-super-admin-conf" } ``` **Body Parameters** - `email`: _String_ - Email of the super admin account. Defined in `superadmin-registration.conf`. - `password`: _String_ - Password of the super admin account. Defined in `superadmin-registration.conf` --- **Response** ```json { "session_id": "39ed1ecd-aa61-4401-bf35-ca0a14a39c8e", "status": "OK", "token": "eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiJmZmZmZmZmZi1mZmZmLWZmZmYtZmZmZi1mZmZmZmZmZmZmZmYiLCJzZXNzaW9uIjoiMzllZDFlY2QtYWE2MS00NDAxLWJmMzUtY2EwYTE0YTM5YzhlIn0.NEKj737AqG7T7SAfQ2RovhWSjBsXCWCLPmkLtr_O5i8q_42BFX7pSGZbIslRy1PEQ3HKrnXib6D06D1kZugvCw" } ``` The token we receive can be used in subsequent requests as a Bearer token. ## Authenticate as Super Admin via API To access all protected endpoints of the Enterprise API, we will use the token obtained from the response of the previous request. This token should be included in the header of each subsequent request. **Example Request** ```bash curl -X 'POST' \ 'https://enterprise.sandbox.walt.id/v1/...' \ -H 'Authorization: Bearer {tokenReceived}' -d '{ ... }' ``` -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/api-keys/overview RAW: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/api-keys/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview API Keys provide machine-to-machine authentication for the Enterprise API. They are ideal for automated systems, integrations, and services that need to interact with the API programmatically. ## Key Concepts ### API Key Creation API Keys can only be created by users with appropriate permissions (Super Admin or users with the `create-apikey` permission). ### Initial State By default, new API Keys do not have any roles assigned, which means they cannot perform any operations. They must be assigned roles to gain access to specific operations within organizations, tenants, or services. ### Expiration API Keys can optionally have an expiration time: - If no expiration is set, the key remains valid indefinitely - Expired keys are automatically rejected by the API ## API Key vs Account | Feature | API Key | Account | |---------|---------|---------| | Authentication | Bearer token | Email + password | | Use case | Machine-to-machine | Human users | | Session management | Stateless | Yes (with session tokens) | | Creation | Admin only | Admin or self-registration | | Expiration | Optional TTL | No (unless manually disabled) | ## Security Best Practices | Practice | Description | |----------|-------------| | Set Expiration | Always set an expiration time for production API keys | | Regular Rotation | Establish a rotation schedule (e.g., every 90 days) | | Least Privilege | Assign only the permissions necessary for the key's purpose | | Secure Storage | Store API keys securely; never commit them to version control | | Monitor Usage | Review API key usage in audit logs regularly | | Immediate Revocation | Delete compromised keys immediately | ## Get Started - [Create API Key](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/api-keys/create-api-key.md) - How to create a new API key - [Assign Role](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/api-keys/assign-role.md) - How to assign roles to an API key -------------------------------------------------------------------------------- PAGE: Create API-Key URL: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/api-keys/create-api-key RAW: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/api-keys/create-api-key.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Create API-Key API-Keys can be used to access the enterprise API. ## Create API-Key
**Option: CURL** Endpoint:`/v1/{target}/apikeys-api/api-keys/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%2F%20API%20Keys/post_v1__target__apikeys_api_api_keys_create) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/apikeys-api/api-keys/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "name": "string", "expiration": "30d" }' ``` **Body** ```json { "name": "string", "expiration": "30d" } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization in which to create the new API Key and the API-Key's ID (`{organizationID}.[YourID]`), e.g. `waltid.myApiKey` **Body Parameters** - `name`: _String_ - Human readable name for API-Key. - `expiration`: (optional) _String_ - Expiration time of the API-Key. - Set expiration times using the kotlinx.datetime Duration Syntax. Valid units are days (d), hours (h), minutes (m), and seconds (s), with finer granularity using milliseconds (ms), microseconds (us), and nanoseconds (ns). Examples include `5h`, `1d 12h`, and `1h 0m 30.340s`. - Alternatively, you can use a simplified ISO-8601 duration format (PThHmMs.fS), such as P1DT2H3M4.058S. In this format: - The largest non-time designator is days (D). Years (Y), weeks (W), and months (M) are not parsed. Instead, use 365d for years, 7d for weeks, and 30d for months. - A day is always assumed to be 24 hours (24-hour clock time scale). - There is currently no alternative week-based representation like ["P"][number]["W"]. --- **Response** ```json { "_id": "waltid.test1", "name": "test", "token": "eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiJhYjNhZjc0Zi0wNmNhLTRkY2EtYWJlZi1iNjA3MTJjOTM5YzEiLCJzZXNzaW9uIjoiYXBpLWFiM2FmNzRmLTA2Y2EtNGRjYS1hYmVmLWI2MDcxMmM5MzljMSJ9.FHEmayjhrfZMnOnEecNnOa6ADDwBm1jL_tue64PhxCTk1DZJeIdz-KPvuwWdSlLgw538fqAIoW8ulv6-B91ZAw" } ``` The returned token can be used as a Bearer token in API requests. **Note:** If you want to check the expiration time of the API-Key you can use the `/v1/{target}/apikeys-api/api-keys/view` endpoint. -------------------------------------------------------------------------------- PAGE: Assign Role URL: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/api-keys/assign-role RAW: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/api-keys/assign-role.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Assign Role [Roles](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/roles/overview.md) are a set of [permissions](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/permissions/overview.md) that can be assigned to Accounts and API-Keys. If you don't have a role yet, you can learn how to create one [here](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/roles/create-roles.md). ## Assigning a Role to an API-Key **Option: CURL** Endpoint:`/v1/{target}/roles-api/roles/apikey/assign` **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/roles-api/roles/apikey/assign' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d ' { "apikey": "waltid.myTestAPIKey" }' ``` **Body** ```json { "apikey": "waltid.myTestAPIKey" } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the ID of the role to assign to an API-Key. e.g. `waltid.admin` **Body Parameters** - `apikey`: _resourceIdentifier_ - Specifies the ID of the API-Key to assign the role to. --- **Response Codes** - `200` - Role assigned successfully. -------------------------------------------------------------------------------- PAGE: IAM Integration URL: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/iam-integration RAW: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/iam-integration.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # IAM Integration The Enterprise Stack integrates with external **Identity Providers (IdP)** for centralized user authentication and access management. See the [access and permissions overview](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/overview.md) to learn about the broader security architecture. Supported providers include: - Keycloak - Microsoft Entra ID (Azure AD) - Okta - Auth0 - Any OpenID Connect (OIDC) compatible provider In this setup: - The IAM provider handles **authentication** - The Enterprise Stack handles **authorization** using RBAC ## Authentication Flow 1. User authenticates with the IAM provider 2. IAM provider issues an OIDC token (JWT) 3. Client sends the token with the API request 4. Enterprise Stack validates the token 5. RBAC evaluates roles and permissions for the requested operation ## Configuring OIDC Login OIDC login is configured in [`auth.conf`](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/auth.md). Add an entry with `method = "oidc"` to the `authFlows` list. ### Minimal Configuration ```hocon authFlows = [ { method = "oidc" config = { openIdConfigurationUrl = "https://your-idp/.well-known/openid-configuration" clientId = "your_client_id" clientSecret = "your_client_secret" callbackUri = "https://your-enterprise-host/auth/account/oidc/callback" pkceEnabled = true externalRoleExtraction = { enabled = true realmRolesClaimPath = "realm_access.roles" } } success = true } ] ``` **Info:** For the complete list of OIDC configuration options including dynamic redirects and logout settings, see the [auth.conf reference](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/auth.md#oidc-flow-fields-method--oidc). ## Mapping IAM Roles to Enterprise Permissions When users authenticate via OIDC, their external roles can be automatically mapped to Enterprise Stack permissions. This is called **External Role Mapping**. ### How It Works 1. User logs in via your IdP with roles (e.g., `tenant-admin`) 2. Enterprise Stack extracts roles from the ID token (via `externalRoleExtraction`) 3. Role mappings are evaluated to find matching Enterprise roles 4. User receives permissions from all matched Enterprise roles ### Managing Role Mappings External role mappings can be managed via **REST API** (recommended) or **config file**. #### REST API (Recommended) ```bash # Create a mapping: external role → Enterprise role curl -X PUT \ 'https://{host}/v1/waltid.tenant1.BW_ADMIN/roles-api/roles/external-mappings/tenant-admin' \ -H 'Authorization: Bearer {token}' \ -d '{"enabled": true}' ``` This maps the external role `tenant-admin` to the Enterprise role `waltid.tenant1.BW_ADMIN`. #### Config File (Optional) See [auth.conf - External Role Mapping](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/auth.md#external-role-mapping-externalrolemapping) for config-based mappings. ### Key Features - **Provider-agnostic** — Works with any OIDC provider - **Conditional mappings** — Restrict by email domain or provider - **Multi-tenant support** — Different mappings per tenant - **Dynamic management** — Create, update, delete via API (no restart needed) ### Learn More See the [External Role Mapping](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping.md) section for complete documentation: - [Overview](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping/overview.md) — Understand mapping concepts - [Configure IdP](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping/configure-idp.md) — Set up role claims in your IdP - [Create Mappings](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping/create-mappings.md) — Define mappings via API - [Manage Mappings](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping/manage-mappings.md) — List, update, delete mappings - [Test Mappings](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping/test-mappings.md) — Verify mappings work - [Conditional Mappings](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping/conditional-mappings.md) — Add restrictions ## IdP Configuration Requirements For external role mapping to work, your IdP must include roles in the ID token. Common configurations: | Provider | Role Claim Location | |----------|---------------------| | Keycloak | `realm_access.roles` | | Azure AD | `roles` or `groups` | | Okta | `groups` claim | | Auth0 | Custom claims via Actions | See [Configure IdP](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping/configure-idp.md) for detailed setup instructions for each provider. ## Related - [Auth configuration (`auth.conf`)](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/auth.md) — Complete auth.conf reference - [External Role Mapping](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping.md) — Complete mapping documentation - [RBAC Model](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/rbac-model.md) - [Roles](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/roles/overview.md) - [Permissions](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/permissions/overview.md) -------------------------------------------------------------------------------- PAGE: External Role Mapping URL: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping RAW: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # External Role Mapping External Role Mapping automatically assigns Enterprise Stack permissions to users based on their roles in an external Identity Provider (IdP). When users authenticate via OIDC, their IdP roles are mapped to Enterprise roles, granting them the appropriate permissions without manual role assignment. ## Why Use External Role Mapping? - **Centralized Access Control** — Manage user permissions in your existing IAM system - **Automatic Provisioning** — Users get correct permissions on first login - **Consistent Security** — Single source of truth for role definitions - **Reduced Administration** — No need to manually assign Enterprise roles to each user ## How It Works ```text ┌────────────────────┐ ┌─────────────────────┐ ┌────────────────────┐ │ Identity │ │ Enterprise │ │ Enterprise │ │ Provider (IdP) │ │ Stack API │ │ Permissions │ │ │ │ │ │ │ │ User logs in │─────▶│ Validates token │─────▶│ Grants mapped │ │ with role: │ │ Extracts roles │ │ permissions │ │ "tenant-admin" │ │ from claims │ │ │ │ │ │ │ │ Role: BW_ADMIN │ │ ID Token contains:│ │ Finds mapping: │ │ Permissions: │ │ realm_access: │ │ tenant-admin → │ │ - wallet:create │ │ roles: │ │ waltid.tenant1 │ │ - wallet:read │ │ - tenant-admin │ │ .BW_ADMIN │ │ - ... │ └────────────────────┘ └─────────────────────┘ └────────────────────┘ ``` 1. User authenticates with IdP (Keycloak, Okta, Azure AD, etc.) 2. IdP issues an ID token containing the user's roles 3. User presents the token to Enterprise Stack 4. Enterprise Stack extracts roles from the token 5. Role mappings are evaluated to find matching Enterprise roles 6. User receives permissions from all matched Enterprise roles ## Supported Identity Providers External Role Mapping works with any OIDC-compliant Identity Provider: | Provider | Role Claim Location | |----------|---------------------| | Keycloak | `realm_access.roles` or `resource_access.{client}.roles` | | Microsoft Entra ID | `roles` or `groups` | | Okta | `groups` or custom claims | | Auth0 | Custom claims via Rules/Actions | | Google Workspace | Custom claims | | Any OIDC Provider | Configurable claim path | ## Get Started 1. [Overview](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/overview.md) — Understand mapping concepts and flow 2. [Configure IdP](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping/configure-idp.md) — Set up your Identity Provider to include roles in tokens 3. [Create Mappings](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping/create-mappings.md) — Define role mappings via API 4. [Manage Mappings](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping/manage-mappings.md) — List, update, and delete mappings 5. [Test Mappings](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping/test-mappings.md) — Verify mappings before deploying 6. [Conditional Mappings](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping/conditional-mappings.md) — Add conditions like email domain restrictions ## Configuration Options External role mappings can be managed in two ways: ### Option 1: REST API (Recommended) Create and manage mappings dynamically via API — no config changes or restarts needed: ```bash curl -X PUT \ 'https://{host}/v1/waltid.tenant1.BW_ADMIN/roles-api/roles/external-mappings/tenant-admin' \ -H 'Authorization: Bearer {token}' \ -H 'Content-Type: application/json' \ -d '{ "enabled": true }' ``` ### Option 2: Config File (Optional) Add `externalRoleMapping` to `auth.conf`: ```hocon externalRoleMapping = { enabled = true strict = false mappings = [ { externalRole = "admin", roleId = "org.tenant1.ADMIN" } ] } ``` **Info:** The `externalRoleMapping` config section is **optional**. If omitted, it defaults to `enabled=false`. API-based mappings are recommended for production. ## Quick Example Map Keycloak role `tenant-admin` to Enterprise role `waltid.tenant1.BW_ADMIN`: ```bash curl -X PUT \ 'https://{host}/v1/waltid.tenant1.BW_ADMIN/roles-api/roles/external-mappings/tenant-admin' \ -H 'Authorization: Bearer {token}' \ -H 'Content-Type: application/json' \ -d '{ "enabled": true }' ``` Now when a user logs in via OIDC with the `tenant-admin` role, they automatically receive all permissions from `BW_ADMIN`. ## Related - [IAM Integration](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/iam-integration.md) — Configure OIDC providers - [Roles](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/roles/overview.md) — Create and manage Enterprise roles - [Permissions](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/permissions/overview.md) — Available permission types -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping/overview RAW: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview External Role Mapping bridges your Identity Provider's role system with the Enterprise Stack's permission model. This page explains the core concepts and how the mapping process works. ## Key Concepts ### External Roles External roles are role identifiers that come from your Identity Provider (IdP). They appear in the ID token after a user authenticates: ```json { "sub": "user-uuid", "email": "alice@company.com", "realm_access": { "roles": ["tenant-admin", "user"] } } ``` In this example, `tenant-admin` and `user` are external roles. ### Enterprise Roles Enterprise roles are defined within the Enterprise Stack and contain specific permissions. They follow a hierarchical naming pattern: ``` {organization}.{tenant}.{role-name} ``` For example: `waltid.tenant1.BW_ADMIN` ### Mappings A mapping connects an external role to an Enterprise role: | External Role | → | Enterprise Role | |---------------|---|-----------------| | `tenant-admin` | → | `waltid.tenant1.BW_ADMIN` | | `wallet-operator` | → | `waltid.tenant1.BW_OPERATOR` | ## Mapping Resolution When a user authenticates via OIDC, the Enterprise Stack: 1. **Extracts external roles** from the ID token claims 2. **Queries active mappings** within the relevant scope 3. **Evaluates conditions** (if any) for each mapping 4. **Collects Enterprise role IDs** from all matching mappings 5. **Loads permissions** from the matched Enterprise roles 6. **Grants access** based on combined permissions ```text ID Token Mappings Permissions ─────────── ──────── ─────────── realm_access.roles: tenant-admin BW_ADMIN permissions: - tenant-admin ───▶ → waltid.t1.BW_ADMIN ───▶ - ES_WALLET_CREATE - user user - ES_WALLET_READ → (no mapping) - ES_KEY_CREATE - ... ``` ## Scope Hierarchy Mappings are scoped to specific targets in your organization hierarchy: ```text Organization (waltid) ├── Tenant A (waltid.tenantA) │ ├── Role: BW_ADMIN ← Mapping can target this │ └── Role: BW_OPERATOR ← Or this ├── Tenant B (waltid.tenantB) │ └── Role: BW_USER └── Org-level Role (waltid.GLOBAL_ADMIN) ``` When resolving mappings, the system checks: 1. Mappings on the specific role 2. Mappings on the tenant scope 3. Mappings on the organization scope ## Multiple Mappings A single external role can map to multiple Enterprise roles, and a user can have multiple external roles: ```text User's External Roles Mappings Effective Permissions ───────────────────── ──────── ───────────────────── [tenant-admin] ───▶ tenant-admin → BW_ADMIN ───▶ All BW_ADMIN permissions + [wallet-reader] ───▶ wallet-reader → READER ───▶ All READER permissions ``` The user receives the **union** of all permissions from all matched roles. ## Direct vs Mapped Roles Users can have both: - **Direct roles**: Explicitly assigned to their Enterprise account - **Mapped roles**: Automatically granted based on external role mapping The effective permissions are the combination of both. ```text User Permissions = Direct Role Permissions + Mapped Role Permissions ``` ## Mapping Lifecycle Mappings can be: - **Created** — Define a new external-to-enterprise mapping - **Enabled/Disabled** — Toggle without deleting - **Updated** — Modify conditions or settings - **Deleted** — Remove permanently Changes take effect on the **next login**. Existing sessions retain their original permissions until the token expires. ## Provider-Agnostic Design The mapping system is designed to work with any OIDC provider: | Provider | How to Configure Roles | |----------|----------------------| | **Keycloak** | Realm roles appear in `realm_access.roles` by default | | **Azure AD** | App roles in `roles` claim, groups in `groups` claim | | **Okta** | Groups in `groups` claim, or custom claims | | **Auth0** | Custom claims via Rules or Actions | The Enterprise Stack extracts roles from the configured claim paths and matches them against your mappings. ## Next Steps - [Configure IdP](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping/configure-idp.md) — Ensure your IdP includes roles in tokens - [Create Mappings](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping/create-mappings.md) — Define your first mapping -------------------------------------------------------------------------------- PAGE: Configure Identity Provider URL: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping/configure-idp RAW: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping/configure-idp.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Configure Identity Provider Before creating external role mappings, your Identity Provider (IdP) must be configured to include roles in the ID token. This page covers configuration for common providers. ## Requirements For external role mapping to work, your IdP must: 1. **Include roles in the ID token** — Not just the access token 2. **Use a consistent claim path** — Enterprise Stack must know where to find the roles 3. **Issue roles as an array** — Even for single roles ## Keycloak Keycloak includes realm roles by default, but you need to ensure they appear in the ID token. ### Step 1: Create Realm Roles 1. Go to **Realm roles** in your realm 2. Create roles: `tenant-admin`, `wallet-operator`, etc. 3. Assign roles to users via **Users → Role mapping** ### Step 2: Configure Client Mappers Keycloak 26.x requires explicit mappers to include roles in tokens: 1. Go to **Clients → your-client → Client scopes** 2. Click on the dedicated scope (e.g., `your-client-dedicated`) 3. Go to **Mappers → Add mapper → By configuration** 4. Select **User Realm Role** 5. Configure: | Field | Value | |-------|-------| | Name | `realm-roles` | | Token Claim Name | `realm_access.roles` | | Add to ID token | **ON** | | Add to access token | **ON** | ### Step 3: Verify Token After login, the ID token should contain: ```json { "realm_access": { "roles": ["tenant-admin", "wallet-operator"] } } ``` ## Microsoft Entra ID (Azure AD) ### Option A: App Roles 1. Go to **App registrations → your-app → App roles** 2. Create app roles: `TenantAdmin`, `WalletOperator` 3. Assign roles via **Enterprise applications → Users and groups** Roles appear in the `roles` claim: ```json { "roles": ["TenantAdmin", "WalletOperator"] } ``` ### Option B: Security Groups 1. Go to **App registrations → your-app → Token configuration** 2. Add **groups** claim 3. Select **Security groups** Groups appear in the `groups` claim (as GUIDs or names depending on configuration): ```json { "groups": ["group-guid-1", "group-guid-2"] } ``` **Info:** When using groups, map the group identifier (GUID or name) as the external role in your mappings. ## Okta ### Step 1: Create Groups 1. Go to **Directory → Groups** 2. Create groups: `TenantAdmins`, `WalletOperators` 3. Add users to groups ### Step 2: Configure Groups Claim 1. Go to **Applications → your-app → Sign On** 2. Edit **OpenID Connect ID Token** 3. Add **Groups claim**: - Claim name: `groups` - Filter: Matches regex `.*` (or specific pattern) - Include in: ID Token Token includes: ```json { "groups": ["TenantAdmins", "WalletOperators"] } ``` ### Alternative: Custom Claims Use Okta Expression Language for custom role claims: 1. Go to **Security → API → Authorization Servers** 2. Select your server → **Claims** 3. Add claim: - Name: `roles` - Value: `Arrays.flatten(getFilteredGroups({"TenantAdmins", "WalletOperators"}, "group.name", 10))` - Include in: ID Token ## Auth0 Auth0 requires a Rule or Action to add roles to tokens. ### Using Actions (Recommended) 1. Go to **Actions → Flows → Login** 2. Add a custom action: ```javascript exports.onExecutePostLogin = async (event, api) => { const namespace = 'https://your-app.com/'; // Get user's roles from metadata or app_metadata const roles = event.user.app_metadata?.roles || []; // Add to ID token api.idToken.setCustomClaim(namespace + 'roles', roles); }; ``` 3. Assign roles in user `app_metadata`: ```json { "roles": ["tenant-admin", "wallet-operator"] } ``` Token includes: ```json { "https://your-app.com/roles": ["tenant-admin", "wallet-operator"] } ``` **Error:** Auth0 requires namespaced custom claims. Configure Enterprise Stack to use the full claim path. ## Google Workspace Google Workspace doesn't natively support roles in OIDC tokens. Options: 1. **Use Google Groups** — Map group membership via directory API 2. **Custom attributes** — Use admin-defined custom attributes 3. **External service** — Use a token exchange or enrichment service ## Generic OIDC Provider For any OIDC provider: 1. **Check documentation** for how to include custom claims in ID tokens 2. **Create roles/groups** in the provider 3. **Configure claim mapping** to include roles in ID token 4. **Note the claim path** for Enterprise Stack configuration Common claim paths: - `roles` — Simple array at top level - `groups` — Group membership - `realm_access.roles` — Keycloak style - `resource_access.{client}.roles` — Keycloak client roles - `https://namespace/roles` — Namespaced custom claims ## Verify Token Contents After configuring your IdP, verify the token contains roles: ### Method 1: jwt.io 1. Complete an OIDC login 2. Capture the ID token 3. Paste at [jwt.io](https://jwt.io) to decode ### Method 2: Enterprise Demo App 1. Login via OIDC in the Demo App 2. Check "Realm Roles (from IdP)" in auth details ### Method 3: Token Introspection ```bash # Decode JWT payload echo $ID_TOKEN | cut -d'.' -f2 | base64 -d 2>/dev/null | jq ``` ## Enterprise Stack Configuration ## Enterprise Stack Configuration Once your IdP is configured, tell Enterprise Stack where to find the roles. In `auth.conf`, add `externalRoleExtraction` to your OIDC config: ```hocon authFlows = [ { method = "oidc" config = { openIdConfigurationUrl = "https://your-idp/.well-known/openid-configuration" clientId = "your-client-id" clientSecret = "your-client-secret" callbackUri = "https://your-enterprise-host/auth/account/oidc/callback" pkceEnabled = true externalRoleExtraction = { enabled = true realmRolesClaimPath = "realm_access.roles" # Adjust for your IdP clientRolesClaimPath = "resource_access" # Optional } } success = true } ] ``` ### Configuration Fields | Field | Default | Description | |-------|---------|-------------| | `enabled` | `false` | Extract roles from OIDC tokens | | `realmRolesClaimPath` | `"realm_access.roles"` | Path to realm/global roles | | `clientRolesClaimPath` | `"resource_access"` | Path to client-specific roles | | `clientId` | (parent clientId) | Which client's roles to extract | **Info:** The `clientId` in `externalRoleExtraction` defaults to the parent `config.clientId`, so you don't need to specify it twice. ## Next Steps - [Create Mappings](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping/create-mappings.md) — Map external roles to Enterprise roles -------------------------------------------------------------------------------- PAGE: Create Mappings URL: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping/create-mappings RAW: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping/create-mappings.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Create Mappings This page explains how to create external role mappings using the REST API. ## Prerequisites Before creating mappings: 1. Your IdP is [configured to include roles](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping/configure-idp.md) in ID tokens 2. The target Enterprise role exists (see [Create Roles](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/roles/create-roles.md)) 3. You have admin permissions on the target scope ## API Endpoint ``` PUT /v1/{role}/roles-api/roles/external-mappings/{externalRole} ``` This endpoint creates or updates a mapping using REST semantics (idempotent). ## Basic Mapping Map an external role to an Enterprise role: **Option: CURL** ```bash curl -X PUT \ 'https://{host}/v1/waltid.tenant1.BW_ADMIN/roles-api/roles/external-mappings/tenant-admin' \ -H 'Authorization: Bearer {token}' \ -H 'Content-Type: application/json' \ -d '{ "enabled": true }' ``` **Option: Request Body** ```json { "enabled": true } ``` **Path Parameters** | Parameter | Description | Example | |-----------|-------------|---------| | `role` | Target Enterprise role ID | `waltid.tenant1.BW_ADMIN` | | `externalRole` | External role name from IdP | `tenant-admin` | **Body Parameters** | Field | Type | Required | Description | |-------|------|----------|-------------| | `enabled` | boolean | No | Whether mapping is active (default: `true`) | | `providerId` | string | No | Restrict to specific OIDC provider | | `conditions` | object | No | Additional conditions (see [Conditional Mappings](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping/conditional-mappings.md)) | **Response** ``` 201 Created — New mapping created 200 OK — Existing mapping updated ``` ## Multiple Mappings Create mappings for different roles: ```bash # Map tenant-admin → BW_ADMIN curl -X PUT \ 'https://{host}/v1/waltid.tenant1.BW_ADMIN/roles-api/roles/external-mappings/tenant-admin' \ -H 'Authorization: Bearer {token}' \ -d '{"enabled": true}' # Map wallet-operator → BW_OPERATOR curl -X PUT \ 'https://{host}/v1/waltid.tenant1.BW_OPERATOR/roles-api/roles/external-mappings/wallet-operator' \ -H 'Authorization: Bearer {token}' \ -d '{"enabled": true}' # Map viewer → BW_VIEWER curl -X PUT \ 'https://{host}/v1/waltid.tenant1.BW_VIEWER/roles-api/roles/external-mappings/viewer' \ -H 'Authorization: Bearer {token}' \ -d '{"enabled": true}' ``` ## Provider-Specific Mappings If you have multiple IdPs, restrict mappings to a specific provider: ```bash curl -X PUT \ 'https://{host}/v1/waltid.tenant1.BW_ADMIN/roles-api/roles/external-mappings/admin' \ -H 'Authorization: Bearer {token}' \ -d '{ "enabled": true, "providerId": "keycloak-prod" }' ``` This mapping only applies when users authenticate through the `keycloak-prod` provider. ## Multi-Tenant Mappings In multi-tenant deployments, create separate mappings for each tenant: ```bash # ACME tenant mappings curl -X PUT \ 'https://{host}/v1/waltid.acme.TENANT_ADMIN/roles-api/roles/external-mappings/acme-admin' \ -H 'Authorization: Bearer {token}' \ -d '{"enabled": true}' # Globex tenant mappings curl -X PUT \ 'https://{host}/v1/waltid.globex.TENANT_ADMIN/roles-api/roles/external-mappings/globex-admin' \ -H 'Authorization: Bearer {token}' \ -d '{"enabled": true}' ``` **Info:** Use descriptive external role names that indicate the tenant (e.g., `acme-admin`, `globex-admin`) to avoid conflicts. ## One External Role to Multiple Enterprise Roles A single external role can map to multiple Enterprise roles by creating multiple mappings: ```bash # Map super-admin to both admin and operator roles curl -X PUT \ 'https://{host}/v1/waltid.tenant1.BW_ADMIN/roles-api/roles/external-mappings/super-admin' \ -H 'Authorization: Bearer {token}' \ -d '{"enabled": true}' curl -X PUT \ 'https://{host}/v1/waltid.tenant1.BW_OPERATOR/roles-api/roles/external-mappings/super-admin' \ -H 'Authorization: Bearer {token}' \ -d '{"enabled": true}' ``` Users with `super-admin` will receive permissions from **both** roles. ## Best Practices 1. **Create Enterprise roles first** — Mappings reference role IDs that must exist 2. **Use descriptive external role names** — Makes debugging easier 3. **Start with disabled mappings** — Create with `"enabled": false`, test, then enable 4. **Document your mappings** — Keep a record of external role → Enterprise role mappings 5. **Use tenant-scoped roles** — For multi-tenant setups, scope roles to specific tenants ## Common Patterns ### Pattern 1: Role Hierarchy Map IdP roles to a permission hierarchy: | External Role | Enterprise Role | Permissions | |---------------|-----------------|-------------| | `admin` | `BW_ADMIN` | Full access | | `operator` | `BW_OPERATOR` | Create, read, update | | `viewer` | `BW_VIEWER` | Read only | ### Pattern 2: Department-Based Map department groups to appropriate roles: | External Role | Enterprise Role | |---------------|-----------------| | `engineering` | `DEVELOPER` | | `operations` | `OPERATOR` | | `support` | `SUPPORT_AGENT` | ### Pattern 3: Tenant Isolation Each tenant has dedicated external roles: | External Role | Enterprise Role | |---------------|-----------------| | `tenant-a-admin` | `waltid.tenantA.ADMIN` | | `tenant-a-user` | `waltid.tenantA.USER` | | `tenant-b-admin` | `waltid.tenantB.ADMIN` | | `tenant-b-user` | `waltid.tenantB.USER` | ## Next Steps - [Manage Mappings](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping/manage-mappings.md) — List, view, and delete mappings - [Test Mappings](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping/test-mappings.md) — Verify mappings work correctly - [Conditional Mappings](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping/conditional-mappings.md) — Add email domain restrictions -------------------------------------------------------------------------------- PAGE: Manage Mappings URL: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping/manage-mappings RAW: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping/manage-mappings.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Manage Mappings This page covers how to list, view, update, and delete external role mappings. ## List All Mappings Get all mappings within a scope (organization or tenant): **Option: CURL** ```bash curl -X GET \ 'https://{host}/v1/waltid/roles-api/roles/external-mappings' \ -H 'Authorization: Bearer {token}' ``` **Option: Response** ```json [ { "externalRole": "tenant-admin", "roleId": "waltid.tenant1.BW_ADMIN", "enabled": true, "providerId": null, "conditions": null }, { "externalRole": "wallet-operator", "roleId": "waltid.tenant1.BW_OPERATOR", "enabled": true, "providerId": null, "conditions": null } ] ``` **Path Parameters** | Parameter | Description | Example | |-----------|-------------|---------| | `scope` | Organization or tenant scope | `waltid` or `waltid.tenant1` | ## List Mappings for a Specific Role Get mappings targeting a specific Enterprise role: ```bash curl -X GET \ 'https://{host}/v1/waltid.tenant1.BW_ADMIN/roles-api/roles/external-mappings' \ -H 'Authorization: Bearer {token}' ``` Response: ```json [ { "externalRole": "tenant-admin", "roleId": "waltid.tenant1.BW_ADMIN", "enabled": true }, { "externalRole": "super-admin", "roleId": "waltid.tenant1.BW_ADMIN", "enabled": true } ] ``` ## Update a Mapping Update an existing mapping by calling PUT with new values: ```bash curl -X PUT \ 'https://{host}/v1/waltid.tenant1.BW_ADMIN/roles-api/roles/external-mappings/tenant-admin' \ -H 'Authorization: Bearer {token}' \ -H 'Content-Type: application/json' \ -d '{ "enabled": false }' ``` Common updates: - **Disable temporarily**: `{"enabled": false}` - **Add provider restriction**: `{"enabled": true, "providerId": "keycloak"}` - **Add conditions**: `{"enabled": true, "conditions": {"emailDomains": ["company.com"]}}` ## Delete a Mapping Remove a mapping permanently: ```bash curl -X DELETE \ 'https://{host}/v1/waltid.tenant1.BW_ADMIN/roles-api/roles/external-mappings/tenant-admin' \ -H 'Authorization: Bearer {token}' ``` **Response** ``` 204 No Content — Mapping deleted (or didn't exist) ``` **Info:** DELETE is idempotent — calling it on a non-existent mapping succeeds without error. ## Bulk Operations ### Clear All Mappings for a Role Delete all mappings targeting a specific role: ```bash curl -X DELETE \ 'https://{host}/v1/waltid.tenant1.BW_ADMIN/roles-api/roles/external-mappings' \ -H 'Authorization: Bearer {token}' ``` ### Replace All Mappings for a Role Replace all mappings with a new set: ```bash curl -X PUT \ 'https://{host}/v1/waltid.tenant1.BW_ADMIN/roles-api/roles/external-mappings' \ -H 'Authorization: Bearer {token}' \ -H 'Content-Type: application/json' \ -d '[ {"externalRole": "admin", "enabled": true}, {"externalRole": "super-admin", "enabled": true} ]' ``` This removes any existing mappings and creates the specified ones. ## Enable/Disable Mappings Toggle mappings without deleting them: ### Disable a Mapping ```bash curl -X PUT \ 'https://{host}/v1/waltid.tenant1.BW_ADMIN/roles-api/roles/external-mappings/tenant-admin' \ -H 'Authorization: Bearer {token}' \ -d '{"enabled": false}' ``` ### Re-enable a Mapping ```bash curl -X PUT \ 'https://{host}/v1/waltid.tenant1.BW_ADMIN/roles-api/roles/external-mappings/tenant-admin' \ -H 'Authorization: Bearer {token}' \ -d '{"enabled": true}' ``` ## Audit Mappings To review all mappings in your organization: ```bash # List all mappings at org level curl -X GET \ 'https://{host}/v1/{organization}/roles-api/roles/external-mappings' \ -H 'Authorization: Bearer {token}' | jq ``` Example output formatted: ```json [ { "externalRole": "tenant-admin", "roleId": "waltid.tenant1.BW_ADMIN", "enabled": true }, { "externalRole": "tenant-admin", "roleId": "waltid.tenant2.BW_ADMIN", "enabled": true }, { "externalRole": "viewer", "roleId": "waltid.tenant1.BW_VIEWER", "enabled": false } ] ``` ## Mapping States | State | Description | |-------|-------------| | **Active** | `enabled: true` — Mapping is evaluated during login | | **Disabled** | `enabled: false` — Mapping exists but is not evaluated | | **Deleted** | Mapping removed — Does not appear in lists | ## Effect on Active Sessions **Error:** Changes to mappings take effect on the **next login**. Users with active sessions retain their original permissions until: - Their session expires - They log out and log back in - Their token is refreshed (if using refresh tokens) ## Next Steps - [Test Mappings](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping/test-mappings.md) — Verify mappings before relying on them - [Conditional Mappings](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping/conditional-mappings.md) — Add restrictions to mappings -------------------------------------------------------------------------------- PAGE: Test Mappings URL: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping/test-mappings RAW: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping/test-mappings.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Test Mappings Before relying on external role mappings in production, test them to ensure they work correctly. The Enterprise Stack provides a resolution endpoint for this purpose. ## Resolution Endpoint Test which Enterprise roles would be assigned for a given set of external roles: ``` POST /v1/{scope}/roles-api/roles/external-mappings/resolve ``` This endpoint simulates the mapping process without requiring an actual OIDC login. ## Basic Test **Option: CURL** ```bash curl -X POST \ 'https://{host}/v1/waltid/roles-api/roles/external-mappings/resolve' \ -H 'Authorization: Bearer {token}' \ -H 'Content-Type: application/json' \ -d '{ "externalRoles": ["tenant-admin"] }' ``` **Option: Response** ```json { "resolvedRoleIds": [ "waltid.tenant1.BW_ADMIN" ], "matchedMappings": [ { "externalRole": "tenant-admin", "roleId": "waltid.tenant1.BW_ADMIN", "enabled": true } ] } ``` **Request Body** | Field | Type | Required | Description | |-------|------|----------|-------------| | `externalRoles` | string[] | Yes | List of external roles to test | | `email` | string | No | Test email for condition evaluation | | `providerId` | string | No | Test specific provider | ## Test Multiple Roles Test how multiple external roles resolve: ```bash curl -X POST \ 'https://{host}/v1/waltid/roles-api/roles/external-mappings/resolve' \ -H 'Authorization: Bearer {token}' \ -d '{ "externalRoles": ["tenant-admin", "wallet-operator", "viewer"] }' ``` Response shows all matched Enterprise roles: ```json { "resolvedRoleIds": [ "waltid.tenant1.BW_ADMIN", "waltid.tenant1.BW_OPERATOR" ], "matchedMappings": [ { "externalRole": "tenant-admin", "roleId": "waltid.tenant1.BW_ADMIN" }, { "externalRole": "wallet-operator", "roleId": "waltid.tenant1.BW_OPERATOR" } ], "unmatchedRoles": ["viewer"] } ``` Note: `viewer` appears in `unmatchedRoles` because no mapping exists for it. ## Test with Email Conditions If you have mappings with email domain conditions, test them: ```bash curl -X POST \ 'https://{host}/v1/waltid/roles-api/roles/external-mappings/resolve' \ -H 'Authorization: Bearer {token}' \ -d '{ "externalRoles": ["contractor"], "email": "john@contractor.com" }' ``` The mapping for `contractor` might only match if the email domain is allowed. ## Test Provider-Specific Mappings If mappings are restricted to specific providers: ```bash curl -X POST \ 'https://{host}/v1/waltid/roles-api/roles/external-mappings/resolve' \ -H 'Authorization: Bearer {token}' \ -d '{ "externalRoles": ["admin"], "providerId": "keycloak-prod" }' ``` ## Verification Checklist Before deploying to production, verify: ### 1. All Expected Roles Map Correctly ```bash # Test each external role you expect users to have for role in tenant-admin wallet-operator viewer; do echo "Testing: $role" curl -s -X POST "https://{host}/v1/waltid/roles-api/roles/external-mappings/resolve" \ -H "Authorization: Bearer {token}" \ -d "{\"externalRoles\": [\"$role\"]}" | jq '.resolvedRoleIds' done ``` ### 2. No Unexpected Mappings ```bash # Test roles that should NOT map curl -X POST "https://{host}/v1/waltid/roles-api/roles/external-mappings/resolve" \ -H "Authorization: Bearer {token}" \ -d '{"externalRoles": ["unknown-role"]}' ``` Expected: `resolvedRoleIds` should be empty. ### 3. Conditions Work ```bash # Test with matching email domain curl -X POST "https://{host}/v1/waltid/roles-api/roles/external-mappings/resolve" \ -H "Authorization: Bearer {token}" \ -d '{"externalRoles": ["employee"], "email": "user@company.com"}' # Test with non-matching email domain curl -X POST "https://{host}/v1/waltid/roles-api/roles/external-mappings/resolve" \ -H "Authorization: Bearer {token}" \ -d '{"externalRoles": ["employee"], "email": "user@other.com"}' ``` ### 4. Provider Restrictions Work ```bash # Test with correct provider curl -X POST "https://{host}/v1/waltid/roles-api/roles/external-mappings/resolve" \ -H "Authorization: Bearer {token}" \ -d '{"externalRoles": ["admin"], "providerId": "keycloak"}' # Test with wrong provider curl -X POST "https://{host}/v1/waltid/roles-api/roles/external-mappings/resolve" \ -H "Authorization: Bearer {token}" \ -d '{"externalRoles": ["admin"], "providerId": "okta"}' ``` ## End-to-End Testing After verifying with the resolution endpoint, test with actual OIDC login: ### 1. Login via OIDC ``` GET https://{host}/auth/account/oidc/auth ``` Complete login in the IdP with a test user. ### 2. Check Effective Permissions After login, verify the user's permissions: ```bash curl -X GET \ 'https://{host}/v1/permissions/list-role-and-permissions' \ -H 'Authorization: Bearer {user-token}' ``` Response should show: - `mappedRoleIds` — Roles from external mapping - `directRoleIds` — Directly assigned roles (if any) - `effectivePermissions` — Combined permissions ### 3. Test API Access Verify the user can perform expected operations: ```bash # Should succeed if user has wallet:create permission curl -X POST 'https://{host}/v1/{target}/wallet-service-api/wallets/create' \ -H 'Authorization: Bearer {user-token}' \ -d '{...}' ``` ## Debugging Failed Mappings If mappings don't work as expected: 1. **Check the ID token** — Are roles present in the expected claim? 2. **Verify claim path** — Does Enterprise Stack config match IdP output? 3. **Check mapping exists** — Use list endpoint to verify 4. **Check mapping is enabled** — `enabled: true`? 5. **Check conditions** — Are email domain or provider conditions met? 6. **Check target role exists** — Does the Enterprise role ID exist? ## Next Steps - [Conditional Mappings](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping/conditional-mappings.md) — Add email domain restrictions - [Troubleshooting](../../../troubleshooting) — Common issues and solutions -------------------------------------------------------------------------------- PAGE: Conditional Mappings URL: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping/conditional-mappings RAW: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping/conditional-mappings.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Conditional Mappings Conditional mappings add restrictions to when an external role maps to an Enterprise role. This enables fine-grained control over permission assignment. ## Use Cases - **Email domain restrictions** — Only map roles for users from specific domains - **Provider restrictions** — Only map roles from specific Identity Providers - **Claim-based restrictions** — Map based on additional token claims ## Email Domain Conditions Restrict a mapping to users with specific email domains: **Option: CURL** ```bash curl -X PUT \ 'https://{host}/v1/waltid.tenant1.BW_ADMIN/roles-api/roles/external-mappings/admin' \ -H 'Authorization: Bearer {token}' \ -H 'Content-Type: application/json' \ -d '{ "enabled": true, "conditions": { "emailDomains": ["company.com", "subsidiary.com"] } }' ``` **Option: Request Body** ```json { "enabled": true, "conditions": { "emailDomains": ["company.com", "subsidiary.com"] } } ``` With this mapping: - User `alice@company.com` with role `admin` → Gets `BW_ADMIN` - User `bob@subsidiary.com` with role `admin` → Gets `BW_ADMIN` - User `eve@contractor.io` with role `admin` → **No mapping** (domain not allowed) ## Provider Restrictions Restrict a mapping to a specific Identity Provider: ```bash curl -X PUT \ 'https://{host}/v1/waltid.tenant1.BW_ADMIN/roles-api/roles/external-mappings/admin' \ -H 'Authorization: Bearer {token}' \ -d '{ "enabled": true, "providerId": "keycloak-production" }' ``` With this mapping: - User logging in via `keycloak-production` with role `admin` → Gets `BW_ADMIN` - User logging in via `azure-ad` with role `admin` → **No mapping** **Info:** Use provider restrictions when you have the same role name across different IdPs but want different Enterprise mappings. ## Combining Conditions Combine multiple conditions — all must be met: ```bash curl -X PUT \ 'https://{host}/v1/waltid.tenant1.BW_ADMIN/roles-api/roles/external-mappings/admin' \ -H 'Authorization: Bearer {token}' \ -d '{ "enabled": true, "providerId": "keycloak-production", "conditions": { "emailDomains": ["company.com"] } }' ``` This mapping applies only when: 1. User authenticates via `keycloak-production` **AND** 2. User's email ends with `@company.com` **AND** 3. User has the `admin` role ## Required Claims Conditions Require specific claims in the token: ```bash curl -X PUT \ 'https://{host}/v1/waltid.tenant1.BW_ADMIN/roles-api/roles/external-mappings/admin' \ -H 'Authorization: Bearer {token}' \ -d '{ "enabled": true, "conditions": { "requiredClaims": { "department": "engineering", "level": "senior" } } }' ``` The user's ID token must contain: ```json { "department": "engineering", "level": "senior" } ``` ## Practical Examples ### Example 1: Partner Access Allow partners to access a limited role, but only from their domain: ```bash # Partners get BW_VIEWER, restricted to their domains curl -X PUT \ 'https://{host}/v1/waltid.tenant1.BW_VIEWER/roles-api/roles/external-mappings/partner' \ -H 'Authorization: Bearer {token}' \ -d '{ "enabled": true, "conditions": { "emailDomains": ["partner1.com", "partner2.com"] } }' ``` ### Example 2: Multi-Provider Setup Different IdPs map the same external role to different Enterprise roles: ```bash # Keycloak 'admin' → Full admin curl -X PUT \ 'https://{host}/v1/waltid.tenant1.BW_ADMIN/roles-api/roles/external-mappings/admin' \ -H 'Authorization: Bearer {token}' \ -d '{ "enabled": true, "providerId": "keycloak" }' # Azure AD 'admin' → Limited admin curl -X PUT \ 'https://{host}/v1/waltid.tenant1.BW_LIMITED_ADMIN/roles-api/roles/external-mappings/admin' \ -H 'Authorization: Bearer {token}' \ -d '{ "enabled": true, "providerId": "azure-ad" }' ``` ### Example 3: Contractor Restrictions External contractors get access, but only during their contract period: ```bash # Contractors with valid department claim curl -X PUT \ 'https://{host}/v1/waltid.tenant1.BW_CONTRACTOR/roles-api/roles/external-mappings/contractor' \ -H 'Authorization: Bearer {token}' \ -d '{ "enabled": true, "conditions": { "emailDomains": ["contractor-agency.com"], "requiredClaims": { "contract_status": "active" } } }' ``` ### Example 4: Department-Based Access Map based on department from IdP: ```bash # Engineering department → Developer role curl -X PUT \ 'https://{host}/v1/waltid.tenant1.DEVELOPER/roles-api/roles/external-mappings/employee' \ -H 'Authorization: Bearer {token}' \ -d '{ "enabled": true, "conditions": { "requiredClaims": { "department": "engineering" } } }' # Finance department → Finance role curl -X PUT \ 'https://{host}/v1/waltid.tenant1.FINANCE/roles-api/roles/external-mappings/employee' \ -H 'Authorization: Bearer {token}' \ -d '{ "enabled": true, "conditions": { "requiredClaims": { "department": "finance" } } }' ``` ## Testing Conditional Mappings Use the resolution endpoint to test conditions: ```bash # Test with matching email domain curl -X POST \ 'https://{host}/v1/waltid/roles-api/roles/external-mappings/resolve' \ -H 'Authorization: Bearer {token}' \ -d '{ "externalRoles": ["admin"], "email": "user@company.com" }' # Test with non-matching email domain curl -X POST \ 'https://{host}/v1/waltid/roles-api/roles/external-mappings/resolve' \ -H 'Authorization: Bearer {token}' \ -d '{ "externalRoles": ["admin"], "email": "user@other.com" }' ``` ## Condition Reference | Condition | Type | Description | |-----------|------|-------------| | `emailDomains` | string[] | User email must end with one of these domains | | `requiredClaims` | object | Token must contain these claim key-value pairs | | `providerId` | string | Must authenticate via this specific provider | ## Best Practices 1. **Start simple** — Add conditions only when needed 2. **Test thoroughly** — Use resolution endpoint before deploying 3. **Document conditions** — Keep track of why each condition exists 4. **Audit regularly** — Review conditions to ensure they're still needed 5. **Fail securely** — When in doubt, use stricter conditions ## Related - [Create Mappings](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping/create-mappings.md) — Basic mapping creation - [Test Mappings](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping/test-mappings.md) — Verify mappings work - [Manage Mappings](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/external-role-mapping/manage-mappings.md) — Update and delete mappings -------------------------------------------------------------------------------- PAGE: Host Aliases URL: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/host-alias RAW: https://docs.walt.id/enterprise-stack/administration/access-and-permissions/host-alias.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Host Aliases Host aliases enable custom domain routing in the Enterprise Stack, allowing organizations and tenants to be accessed via their own domain names instead of the default subdomain structure. ## Overview By default, organizations are accessed using subdomains of the base domain (e.g., `myorg.enterprise-sandbox.waltid.dev`). Host aliases let you map custom domains to specific organizations or tenants, enabling: - **Custom branding** — Use your own domain (e.g., `credentials.example.com`) - **Tenant isolation** — Give each tenant their own subdomain or domain - **Simplified URLs** — Shorter, more memorable endpoints for your services When a request arrives at the Enterprise Stack, the system looks up the incoming host in the host alias table to determine which organization and tenant context to use. ## How It Works 1. A request arrives at `tenant1.example.com` 2. The system looks up `tenant1.example.com` in the host alias table 3. The alias maps to organization `myorg` with path prefix `myorg.tenant1` 4. All API calls are routed to that organization/tenant context 5. Service URLs (e.g., credential offer URLs) use the custom domain ### Host Alias Structure A host alias contains: | Field | Description | |-------|-------------| | `id` | The custom domain (e.g., `tenant1.example.com`) | | `organization` | The organization ID this alias belongs to | | `target` | The full resource path in the organization tree | | `pathPrefix` | Optional path prefix for tenant-level aliases | ## Managing Host Aliases Host aliases are managed via the Host Alias API. You need the appropriate permissions to create, view, or delete aliases. ### Required Permissions | Operation | Permission | |-----------|------------| | Create | `create-host-alias` | | View | `view-host-alias` | | Delete | `delete-host-alias` | ### Create a Host Alias Create a host alias to map a custom domain to an organization or tenant. **Option: CURL** **Endpoint:** `POST /v1/{target}/host-alias-api/host-aliases/create` **Example Request** ```bash curl -X 'POST' \ 'https://{orgId}.enterprise.sandbox.walt.id/v1/{aliasId}/host-alias-api/host-aliases/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "domain": "tenant1.example.com" }' ``` **Path Parameters** - `orgId`: The organization ID (e.g., `waltid`) - `aliasId`: A unique identifier for this host alias resource (e.g., `tenant1-example-com`) **Body** ```json { "domain": "tenant1.example.com" } ``` **Body Parameters** - `domain`: _String_ — The custom domain to map. This is the hostname that will route to this organization/tenant. --- **Response Codes** - `201` — Host alias created successfully - `400` — Invalid target path or alias already exists at the location - `401` — Authentication required - `403` — Insufficient permissions ### Create a Tenant-Level Host Alias You can also create host aliases at the tenant level, allowing each tenant to have its own custom domain. **Option: CURL** **Endpoint:** `POST /v1/{target}/host-alias-api/host-aliases/create` **Example Request** ```bash curl -X 'POST' \ 'https://{orgId}.enterprise.sandbox.walt.id/v1/{tenantId}.{aliasId}/host-alias-api/host-aliases/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "domain": "credentials.acme.com" }' ``` **Path Parameters** - `orgId`: The organization ID (e.g., `waltid`) - `tenantId`: The tenant ID (e.g., `tenant1`) - `aliasId`: A unique identifier for this host alias resource (e.g., `acme-credentials`) --- **Response Codes** - `201` — Host alias created successfully ### View a Host Alias Retrieve information about an existing host alias. **Option: CURL** **Endpoint:** `GET /v1/{target}/host-alias-api/host-aliases/view` **Example Request** ```bash curl -X 'GET' \ 'https://{orgId}.enterprise.sandbox.walt.id/v1/{aliasId}/host-alias-api/host-aliases/view' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' ``` --- **Example Response** ```json { "_id": "waltid.tenant1-example-com", "domain": "tenant1.example.com" } ``` **Response Fields** - `_id`: The full path of the host alias resource in the organization tree - `domain`: The custom domain mapped by this alias --- **Response Codes** - `200` — Success - `404` — No host alias found at the target path - `401` — Authentication required - `403` — Insufficient permissions ### Delete a Host Alias Remove an existing host alias mapping. **Option: CURL** **Endpoint:** `DELETE /v1/{target}/host-alias-api/host-aliases/delete` **Example Request** ```bash curl -X 'DELETE' \ 'https://{orgId}.enterprise.sandbox.walt.id/v1/{aliasId}/host-alias-api/host-aliases/delete' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' ``` --- **Response Codes** - `200` — Host alias deleted successfully - `404` — No host alias found at the target path - `401` — Authentication required - `403` — Insufficient permissions ## DNS Configuration After creating a host alias, you need to configure your DNS to point the custom domain to the Enterprise Stack. ### Option 1: CNAME Record (Recommended) Create a CNAME record pointing your custom domain to the Enterprise Stack base domain: ``` tenant1.example.com. CNAME enterprise-sandbox.waltid.dev. ``` ### Option 2: A Record If you cannot use CNAME (e.g., for apex domains), create an A record pointing to the Enterprise Stack IP address. Contact support for the current IP addresses. **Info:** DNS changes can take up to 48 hours to propagate globally. Test your configuration using `dig` or `nslookup` before relying on the custom domain. ## Use Cases ### Multi-Tenant White-Labeling Give each tenant their own branded domain for credential issuance: ``` # Tenant 1: ACME Corp acme.credentials.example.com → waltid.tenant-acme # Tenant 2: Globex Inc globex.credentials.example.com → waltid.tenant-globex ``` ### Service-Specific Domains Use different domains for different services: ``` # Issuer service issuer.example.com → waltid.tenant1.issuer1 # Verifier service verify.example.com → waltid.tenant1.verifier1 ``` ### Regional Deployments Route regional domains to region-specific tenants: ``` eu.example.com → waltid.tenant-eu us.example.com → waltid.tenant-us ``` ## Impact on Service URLs When a host alias is active, all generated URLs (such as credential offer URLs) will use the custom domain instead of the default subdomain structure. **Without host alias:** ``` openid-credential-offer://waltid.enterprise-sandbox.waltid.dev/?credential_offer_uri=... ``` **With host alias (`tenant1.example.com`):** ``` openid-credential-offer://tenant1.example.com/?credential_offer_uri=... ``` This ensures that wallets and other clients interact with your branded domain throughout the entire credential lifecycle. ## Related - [Organizations](https://docs.walt.id/enterprise-stack/administration/organizations/overview.md) — Learn about organization structure - [Tenants](https://docs.walt.id/enterprise-stack/administration/tenants/overview.md) — Learn about multi-tenancy - [RBAC Model](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/rbac-model.md) — Understand permissions and roles - [Roles](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/roles/overview.md) — Configure roles with host alias permissions -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/administration/events-and-metrics/overview RAW: https://docs.walt.id/enterprise-stack/administration/events-and-metrics/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview **Track events across all enterprise stack services and features.** Events may include actions such as issuing credentials, creating keys, or the creation of a status services. **Each event is accompanied by a timestamp and additional metadata. You can filter events by specific tenants or services, and aggregate them for metrics, such as the total number of successful issuance requests.** Below is a list of currently supported events that can be tracked. **Supported Events:** - [**IssuanceEvent**](https://docs.walt.id/enterprise-stack/administration/events-and-metrics/event-types/issuance-event.md) - This event tracks the status of credential offers that are initiated through the `credentials/issue` endpoint of the issuer service. - [**VerificationEvent**](https://docs.walt.id/enterprise-stack/administration/events-and-metrics/event-types/verification-event.md) - This event tracks the status of credential requests that are initiated via the `credentials/verify` endpoint of the verifier service. ## Get Started - [**Query Events**](https://docs.walt.id/enterprise-stack/administration/events-and-metrics/query-events.md): Learn how to request a list of events within a specific organization. - [**Retrieve Metrics**](https://docs.walt.id/enterprise-stack/administration/events-and-metrics/retrieve-metrics.md): Understand how to aggregate query information to generate metrics. -------------------------------------------------------------------------------- PAGE: How to query events URL: https://docs.walt.id/enterprise-stack/administration/events-and-metrics/query-events RAW: https://docs.walt.id/enterprise-stack/administration/events-and-metrics/query-events.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # How to query events In this guide we will learn how to retrieve a specific set of events (e.g. VerificationEvent, IssuanceEvent) in a specific scope of the enterprise stack (e.g. in an organization or tenant). Events are enabled by default, meaning as soon as you start the Enterprise Stack events start to be recorded and stored in the connected Enterprise Stack database. At the moment, we support the events shown below. **Supported Events** - [**IssuanceEvent**](https://docs.walt.id/enterprise-stack/administration/events-and-metrics/event-types/issuance-event.md): This event tracks the status of credential offers that are initiated through the `credentials/issue` endpoint of the issuer service. - [**VerificationEvent**](https://docs.walt.id/enterprise-stack/administration/events-and-metrics/event-types/verification-event.md): This event tracks the status of credential requests that are initiated via the `credentials/verify` endpoint of the verifier service. ## Query events without filters To retrieve a set of events, we can use the `/events/query` endpoint along with an optional [filter](#crafting-the-filter) in our request. In this example, we will not apply any [filter](#crafting-the-filter); instead, we will send an empty `{}` in the body of the request. As a result, this will return all events that occurred within the organization specified in the query parameter. **Note:** We can only request events from one specific organization at a time. It is not possible to request events from two different organizations simultaneously.
**Option: CURL** Endpoint: `/v1/events/query` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Events/post_v1_events_query) Ensure the organization is set to one your user has read access on when filtering for events. **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise.sandbox.walt.id/v1/events/query?organization={orgID}' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{}' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. **Body** ```json {} ``` --- **Response** - `200` - Events retrieved successfully. **Example Response** ```json [ { "type": "id.walt.commons.events.IssuanceEvent", "eventType": "IssuanceEvent", "_id": "2a7b554a-37c7-4ae6-809f-2559bfa47913", "originator": null, "organization": "waltid", "target": "waltid.mycustomer.issuer2", "timestamp": 1741192586004, "action": { "type": "init_session" }, "status": { "type": "Open" }, "callId": "436a8a95-3290-4b65-94b9-a0970012d91c", "error": null, "sessionId": "fb517c9d-5178-48a9-8d39-9fd89d857d53", "credentialConfigurationId": "identity_credential_vc+sd-jwt", "format": "vc+sd-jwt" }, { "type": "id.walt.commons.events.IssuanceEvent", "eventType": "IssuanceEvent", "_id": "a8d78606-b297-484d-af2f-48a56b0b4732", "originator": null, "organization": "waltid", "target": "waltid.mycustomer.issuer2", "timestamp": 1741192721792, "action": { "type": "process_request" }, "status": { "type": "Success" }, "callId": "af341708-5722-4e04-b3c2-6b99008c7316", "error": null, "sessionId": "fb517c9d-5178-48a9-8d39-9fd89d857d53", "credentialConfigurationId": "identity_credential_vc+sd-jwt", "format": "vc+sd-jwt", "proofType": "jwt", "holderId": "hHAsP0_f0yRK3tKfH854d-ZG7GlApwYNpH1JFaStPuc" }, { "type": "id.walt.commons.events.VerificationEvent", "eventType": "VerificationEvent", "_id": "e7a33f7b-024c-4ff7-980b-d47f7ed825e7", "originator": null, "organization": "waltid", "target": "waltid.mycustomer.verifier1", "timestamp": 1741252466353, "action": { "type": "init_session" }, "status": { "type": "Open" }, "callId": "9c2f6b21-8d99-4a41-8950-66f5aa4485d2", "error": null, "sessionId": "bec96d15-6fe9-4553-a873-82b2ef270b15", "format": "vc+sd-jwt", "signatureAlgorithm": "", "credentialType": "identity_credential" }, { "type": "id.walt.commons.events.VerificationEvent", "eventType": "VerificationEvent", "_id": "735c7ae3-8e2b-4a5c-a257-95eec651d133", "originator": null, "organization": "waltid", "target": "waltid.mycustomer.verifier1", "timestamp": 1741252709251, "action": { "type": "process_request" }, "status": { "type": "Failure" }, "callId": "c6a15ee3-b5b9-4294-a942-049b68bda0d8", "error": null, "sessionId": "bec96d15-6fe9-4553-a873-82b2ef270b15", "format": "vc+sd-jwt", "signatureAlgorithm": "", "credentialType": "identity_credential", "holderId": "DbEu8NYzFYZ8WSMapj1Y5JJCRXWdjF1r8I_UyFrBe-Q" } ] ``` The response we received includes two distinct events: the `IssuanceEvent` and the `VerificationEvent`. Each of these events appears twice as they move through different stages. Initially, the status property is set to `Open`, and it changes to either `Success` or `Failure` as the events progress. To learn more about each event's data structure in detail, pleas go [here](https://docs.walt.id/enterprise-stack/administration/events-and-metrics/event-types/event-types-overview.md). ## Query events with filter To retrieve a set of events, we can use the `/events/query` endpoint along with an optional [filter](#crafting-the-filter) in our request. **In this example, we will use a [filter](#crafting-the-filter) to only see events that are coming from a specific issuer service.** **Note:** We can only request events from one specific organization at a time. It is not possible to request events from two different organizations simultaneously.
**Option: CURL** Endpoint: `/v1/events/query` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Events/post_v1_events_query) Ensure the organization is set to one your user has read access on when filtering for events. **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise.sandbox.walt.id/v1/events/query?organization={orgID}' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "target": [ "waltid.mycustomer.issuer2" ] }' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. **Body** ```json { "target": [ "waltid.mycustomer.issuer2" ] } ``` Here we specify that we only want to receive events that happened in a specific target. Learn more about how to craft your filter [here](#crafting-the-filter) --- **Response** - `200` - Events retrieved successfully. **Example Response** ```json [ { "type": "id.walt.commons.events.IssuanceEvent", "eventType": "IssuanceEvent", "_id": "2a7b554a-37c7-4ae6-809f-2559bfa47913", "originator": null, "organization": "waltid", "target": "waltid.mycustomer.issuer2", "timestamp": 1741192586004, "action": { "type": "init_session" }, "status": { "type": "Open" }, "callId": "436a8a95-3290-4b65-94b9-a0970012d91c", "error": null, "sessionId": "fb517c9d-5178-48a9-8d39-9fd89d857d53", "credentialConfigurationId": "identity_credential_vc+sd-jwt", "format": "vc+sd-jwt" }, { "type": "id.walt.commons.events.IssuanceEvent", "eventType": "IssuanceEvent", "_id": "a8d78606-b297-484d-af2f-48a56b0b4732", "originator": null, "organization": "waltid", "target": "waltid.mycustomer.issuer2", "timestamp": 1741192721792, "action": { "type": "process_request" }, "status": { "type": "Success" }, "callId": "af341708-5722-4e04-b3c2-6b99008c7316", "error": null, "sessionId": "fb517c9d-5178-48a9-8d39-9fd89d857d53", "credentialConfigurationId": "identity_credential_vc+sd-jwt", "format": "vc+sd-jwt", "proofType": "jwt", "holderId": "hHAsP0_f0yRK3tKfH854d-ZG7GlApwYNpH1JFaStPuc" } ] ``` ## Crafting the Filter When making requests to the `events/query` endpoint, you can include a filter object `{}` within the request body. This filter allows you to specify criteria for selecting the events you are interested in. ### Filter Structure The filter comprises **general filters** applicable to all event types and **specific filters** tailored for certain event types. Each filter consists of a key (representing the field to be filtered) and an array of one or more values to match against. ### General Filters - **`eventType`**: Specifies the type of event. Possible values are `VerificationEvent` or `IssuanceEvent`. - **`status`**: Indicates the status of the event. Possible values are `Open`, `Success`, or `Failure`. - **`action`**: Describes the action taken. Possible values are `init_session` or `process_request`. - **`fromTimestamp`**: A Unix timestamp in milliseconds marking the start of the time range, e.g., `1741192586004`. - **`toTimestamp`**: A Unix timestamp in milliseconds marking the end of the time range, e.g., `1741192586004`. - **`callId`**: A UUID representing the call, e.g., `"436a8a95-3290-4b65-94b9-a0970012d91c"`. - **`target`**: The service or tenant where the event was triggered, e.g., `waltid.tenant1` or `waltid.tenant1.issuer1`. ### Event-Specific Filters For specific event types, you need to include additional filters within a nested object. This nested object should be named according to the event type: `issuanceEventFilter` for Issuance Events and `verificationEventFilter` for Verification Events. #### Issuance Event Filters These filters should be included in an object called `issuanceEventFilter`: - **`credentialConfigurationId`**: The ID of the credential configuration being issued, e.g., `identity_credential_vc+sd-jwt`. - **`format`**: The format of the credential being issued, e.g., `jwt_vc_json` or `vc+sd-jwt`. - **`sessionId`**: The OID4VC session ID. #### Verification Event Filters These filters should be included in an object called `verificationEventFilter`: - **`format`**: The format of the credential being requested, e.g., `jwt_vc_json` or `vc+sd-jwt`. - **`sessionId`**: The OID4VP session ID. - **`signatureAlgorithm`**: The signature algorithm used. - **`credentialType`**: The type of credential requested, e.g., `identity_credential`. - **`holderId`**: The ID of the holder, e.g., `DbEu8NYzFYZ8WSMapj1Y5JJCRXWdjF1r8I_UyFrBe`. ### Example Queries **Verification Event Filter Example:** ```json { // GENERAL EVENT FILTERS "eventType": [ "VerificationEvent" ], "status": [ "Open", "Success", "Failure" ], "action": [ "init_session", "process_request" ], // EVENT SPECIFIC FILTERS "verificationEventFilter": { "format": [ "vc+sd-jwt", "jwt_vc_json" ] } } ``` **Issuance Event Filter Example:** ```json { // GENERAL EVENT FILTERS "eventType": [ "IssuanceEvent" ], "status": [ "Open", "Success", "Failure" ], "action": [ "init_session", "process_request" ], // EVENT SPECIFIC FILTERS "issuanceEventFilter": { "format": [ "vc+sd-jwt", "jwt_vc_json" ] } } ``` -------------------------------------------------------------------------------- PAGE: Event Types Overview URL: https://docs.walt.id/enterprise-stack/administration/events-and-metrics/event-types/event-types-overview RAW: https://docs.walt.id/enterprise-stack/administration/events-and-metrics/event-types/event-types-overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Event Types Overview ## Events - [**IssuanceEvent**](https://docs.walt.id/enterprise-stack/administration/events-and-metrics/event-types/issuance-event.md) - This event tracks the status of credential offers that are initiated through the `credentials/issue` endpoint of the issuer service. - [**VerificationEvent**](https://docs.walt.id/enterprise-stack/administration/events-and-metrics/event-types/verification-event.md) - This event tracks the status of credential requests that are initiated via the `credentials/verify` endpoint of the verifier service. -------------------------------------------------------------------------------- PAGE: Issuance Event URL: https://docs.walt.id/enterprise-stack/administration/events-and-metrics/event-types/issuance-event RAW: https://docs.walt.id/enterprise-stack/administration/events-and-metrics/event-types/issuance-event.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Issuance Event The `IssuanceEvent` tracks the status of credential offers that are initiated through the `credentials/issue` endpoint of the issuer service. ## Example Issuance Event ```json { "type": "id.walt.commons.events.IssuanceEvent", "eventType": "IssuanceEvent", "_id": "2a7b554a-37c7-4ae6-809f-2559bfa47913", "originator": null, "organization": "waltid", "target": "waltid.mycustomer.issuer2", "timestamp": 1741192586004, "action": { "type": "init_session" }, "status": { "type": "Open" }, "callId": "436a8a95-3290-4b65-94b9-a0970012d91c", "error": null, "sessionId": "fb517c9d-5178-48a9-8d39-9fd89d857d53", "credentialConfigurationId": "identity_credential_vc+sd-jwt", "format": "dc+sd-jwt" } ``` ### IssuanceEvent Attributes - **type**: Specifies the Kotlin class name of the event. - **eventType**: Indicates the type of event. In our case `"IssuanceEvent"`. - **_id**: A unique identifier for the event, represented as a UUID, e.g. `"2a7b554a-37c7-4ae6-809f-2559bfa47913"`. - **originator**: Attribute identifying the user or system that initiated the event. - **organization**: The organization responsible for the event, e.g.`"waltid"`. - **target**: The service in which the event was triggered, e.g. `"waltid.mycustomer.issuer2"`. - **timestamp**: The time the event occurred, represented as a Unix timestamp in milliseconds, e.g. `1741192586004`. - **action**: This describes the specific action of the event, e.g. `"init_session"`. - **status**: The current state of the event. It is represented as an object with a `type` of `"Open"`, indicating that the event is in progress. It can also be `Success` or `Failure` - **callId**: An optional identifier for tracking HTTP calls, represented as a UUID. Here, it is `"436a8a95-3290-4b65-94b9-a0970012d91c"`. - **error**: An optional attribute that contains an error message if the event encountered an issue. - **sessionId**: A unique identifier for the OID4VC session associated with the event, represented as a UUID. In this example, it is `"fb517c9d-5178-48a9-8d39-9fd89d857d53"`. - **credentialConfigurationId**: The configuration ID of credential being issued. Here, it is `"identity_credential_vc+sd-jwt"`. - **format**: The format of the credential being issued. In this example, it is `"dc+sd-jwt"`. -------------------------------------------------------------------------------- PAGE: Verification Event URL: https://docs.walt.id/enterprise-stack/administration/events-and-metrics/event-types/verification-event RAW: https://docs.walt.id/enterprise-stack/administration/events-and-metrics/event-types/verification-event.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Verification Event The `VerificationEvent` tracks the status of credential requests that are initiated via the `credentials/verify` endpoint of the verifier service. ## Example Verification Event ```json { "type": "id.walt.commons.events.VerificationEvent", "eventType": "VerificationEvent", "_id": "e7a33f7b-024c-4ff7-980b-d47f7ed825e7", "originator": null, "organization": "waltid", "target": "waltid.mycustomer.verifier1", "timestamp": 1741252466353, "action": { "type": "init_session" }, "status": { "type": "Open" }, "callId": "9c2f6b21-8d99-4a41-8950-66f5aa4485d2", "error": null, "sessionId": "bec96d15-6fe9-4553-a873-82b2ef270b15", "format": "vc+sd-jwt", "signatureAlgorithm": "", "credentialType": "identity_credential" } ``` ### VerificationEvent Attributes - **type**: Specifies the Kotlin class name of the event. - **eventType**: Indicates the type of event. In our case `"VerificationEvent"`. - **_id**: A unique identifier for the event, represented as a UUID, e.g. `"e7a33f7b-024c-4ff7-980b-d47f7ed825e7"`. - **originator**: Attribute identifying the user or system that initiated the event. - **organization**: The organization responsible for the event, e.g. `"waltid"`. - **target**: The service in which the event was triggered, e.g. `"waltid.mycustomer.verifier1"`. - **timestamp**: The time the event occurred, represented as a Unix timestamp in milliseconds, e.g. `1741252466353`. - **action**: This describes the specific action of the event, e.g. `"init_session"`. - **status**: The current state of the event. It is represented as an object with a `type` of `"Open"`, indicating that the event is in progress. It can also be `Success` or `Failure`. - **callId**: An optional identifier for tracking HTTP calls, represented as a UUID. Here, it is `"9c2f6b21-8d99-4a41-8950-66f5aa4485d2"`. - **error**: An optional attribute that contains an error message if the event encountered an issue. - **sessionId**: A unique identifier for the session associated with the event, represented as a UUID. In this example, it is `"bec96d15-6fe9-4553-a873-82b2ef270b15"`. - **format**: The format of the credential being verified. In this example, it is `"vc+sd-jwt"`. - **signatureAlgorithm**: The algorithm used for signing. - **credentialType**: The type of credential being verified, e.g. `"identity_credential"`. -------------------------------------------------------------------------------- PAGE: How to retrieve Metrics URL: https://docs.walt.id/enterprise-stack/administration/events-and-metrics/retrieve-metrics RAW: https://docs.walt.id/enterprise-stack/administration/events-and-metrics/retrieve-metrics.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # How to retrieve Metrics **Metrics are derived from events** tracked by the Enterprise Stack. To explore the different types of events available, please click [here](https://docs.walt.id/enterprise-stack/administration/events-and-metrics/event-types/event-types-overview.md). By utilizing the `events/query` endpoint, we can not only fetch the events that have occurred, but we can also **aggregate these events into metrics** through our group by function. In the following section, we will see **how to retrieve the number all successful issuance events** ## Retrieve Metrics We will use the `/events/query` endpoint to specify a filter. This filter will ensure that we only retrieve the events we want to include in our metric. Also, we specify a group by function that should be applied to a specified field in our filtered events. **Option: CURL** Endpoint: `/v1/events/query` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Events/post_v1_events_query) Ensure the organization is set to one your user has read access on when filtering for events. **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/events/query?organization={orgID}&groupBy={pathToField}' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "target": [ "waltid.mycustomer.issuer2" ] }' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `pathToField`: Specifies the field of the events we want to group by, in our example `status.type`. **Body** ```json { "target": [ "waltid.mycustomer.issuer2" ] } ``` Here we specify that we only want to receive events that happened in a specific target. Learn more about how to craft your filter [here](https://docs.walt.id/enterprise-stack/administration/events-and-metrics/query-events.md#crafting-the-filter) --- **Response** - `200` - Events with group option retrieved successfully. **Example Response** ```json [ { "_id": "Open", "count": 2 }, { "_id": "Success", "count": 2 } ] ``` Now in the response we can see that we have issued two credentials successfully: ```json { "_id": "Success", "count": 2 } ``` -------------------------------------------------------------------------------- PAGE: Database Migrations URL: https://docs.walt.id/enterprise-stack/administration/data/db-migrations RAW: https://docs.walt.id/enterprise-stack/administration/data/db-migrations.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Database Migrations The Enterprise Stack includes a MongoDB migration system that keeps the database structure, indexes, and stored data in sync with the version of the Enterprise API you are running. Migrations are executed automatically when the Enterprise API starts. They can also be inspected and scheduled manually through the global administration API. **Info:** Database migrations are currently supported for MongoDB deployments. The in-memory database does not support migration execution. ## How Migrations Work Each migration has a numeric version, a description, a list of affected collections, and a set of allowed actions. Migrations are registered in version order and are designed to be safe to run in clustered deployments. When an Enterprise API node starts: 1. The node connects to MongoDB. 2. The migration framework checks the current cluster database version. 3. Required startup migrations are executed before the application continues. 4. Background migrations are started asynchronously after startup. 5. The node records progress and results in the `migrations` collection. Only one node executes migrations at a time. The migration system uses a MongoDB-backed lock named `migration_lock` in the `cluster_locks` collection. Other nodes wait until the lock is released or expires. ## Migration Types Migrations are grouped by impact: - `SCHEMA` - creates or updates collections and document structure. - `INDEX` - creates, updates, or drops database indexes. - `DATA` - transforms existing records. These migrations can take longer on large collections. ## Automatic Startup Behavior Most migrations are applied automatically. The Enterprise API distinguishes between two execution modes: - **Startup migrations** run before the application completes database initialization. - **Background migrations** run after startup, so the API can become available while lower-risk migrations continue. Background migrations are used when the application can safely handle both the old and new data shape during the migration. **Note:** Before upgrading production environments, create a database backup and review the release notes for migrations introduced by the target version. ## Cluster Safety and Recovery The migration framework is cluster-aware: - Only one node can hold the migration lock and execute migrations. - Lock ownership is refreshed while a migration is running. - If a node stops while holding the lock, the lock expires and another node can continue. - Migration runs without a `completedAt` timestamp are treated as aborted and can be recovered. - If a node with an older database version leaves the cluster, the missing migrations are scheduled for execution. This allows rolling deployments while preventing two nodes from applying the same migration at the same time. ## Get Migration Status Use the migration status endpoint to list registered migrations and their run history. **Option: CURL** Endpoint: `/v1/admin/admin/migrations` **Example Request** ```bash curl -X 'GET' \ 'https://{host}/v1/admin/admin/migrations' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {superAdminToken}' ``` --- **Example Response** ```json [ { "version": 4, "allowedActions": [ "APPLY", "RUN", "DRY_RUN", "VALIDATE_PRECONDITIONS" ], "description": "Migrate 'timestamp' (millis from epoch) field to 'created_at' and 'updated_at' (string ISO date)", "affectsCollections": [ "organization_trees" ], "runs": [ { "runId": "1a5a4a42-2a3f-48df-a571-8a4b68c9a941", "action": "APPLY", "remark": "Startup of Application", "startedAt": "2026-06-25T09:15:00.000Z", "completedAt": "2026-06-25T09:15:03.000Z", "totalItems": 1200, "processedItems": 1200, "success": true, "messages": [], "errorMessage": null } ] } ] ``` ### Response Fields - `version`: Numeric migration version. - `allowedActions`: Actions that can be scheduled for this migration. - `description`: Human-readable summary of what the migration does. - `affectsCollections`: MongoDB collections touched by the migration. - `runs`: Previous or currently running migration attempts. - `runId`: Unique identifier for one migration run. - `startedAt`: Time when execution started. If this is empty, the action is scheduled but not yet started. - `completedAt`: Time when execution finished. If this is empty after `startedAt` is set, the run is still active or was interrupted. - `totalItems` and `processedItems`: Progress counters for migrations that report item-level progress. - `success`: `true` for successful runs, `false` for failed runs, and empty while the run is still pending or active. - `messages`: Informational messages written by the migration. - `errorMessage`: Error details for failed runs. ## Schedule a Migration Action Migration actions are scheduled through the administration API. The endpoint returns `202 Accepted` after the action has been recorded. Execution starts once a node acquires the database migration lock. **Option: CURL** Endpoint: `/v1/admin/admin/migrations/{dbVersion}` **Example Request** ```bash curl -X 'POST' \ 'https://{host}/v1/admin/admin/migrations/{dbVersion}' \ -H 'accept: */*' \ -H 'Authorization: Bearer {superAdminToken}' \ -H 'Content-Type: application/json' \ -d '{ "action": "DRY_RUN", "remark": "Check migration impact before production upgrade" }' ``` **Path Parameters** - `dbVersion`: Version of the migration to schedule, for example `4`. **Body Parameters** - `action`: Migration action to schedule. - `remark`: Optional note stored with the migration run history. --- **Response** - `202 Accepted` - The migration action was scheduled. ## Migration Actions The following actions can appear in `allowedActions`: - `APPLY`: Applies the migration and updates the cluster database version after success. This is the normal automatic startup/background action. - `RUN`: Executes the migration logic without updating the cluster database version. - `DRY_RUN`: Executes the migration's dry-run logic where supported. Use it to inspect impact before applying a migration. - `VALIDATE_PRECONDITIONS`: Runs precondition checks and records messages or errors without applying the migration. - `ROLLBACK`: Runs the rollback logic where supported. Not every migration supports rollback. **Warning:** Only schedule actions listed in `allowedActions` for the migration. Unsupported actions are rejected by the API. ## Dry Run and Rollback Support Dry run and rollback support is defined per migration. Always check the `allowedActions` field returned by `GET /v1/admin/admin/migrations` before scheduling either action. ### Dry Run `DRY_RUN` is used to preview the impact of a migration without applying persistent changes. A dry run still creates a migration run history entry, so you can inspect `messages`, `totalItems`, `processedItems`, `success`, and `errorMessage` after it finishes. Use dry runs when you want to: - Check that migration preconditions pass. - Estimate how many records are affected. - Review informational messages before applying a data migration. **Option: CURL** Endpoint: `/v1/admin/admin/migrations/{dbVersion}` **Example Request** ```bash curl -X 'POST' \ 'https://{host}/v1/admin/admin/migrations/{dbVersion}' \ -H 'accept: */*' \ -H 'Authorization: Bearer {superAdminToken}' \ -H 'Content-Type: application/json' \ -d '{ "action": "DRY_RUN", "remark": "Preview migration impact before applying it" }' ``` ### Rollback `ROLLBACK` executes a migration's rollback logic where that migration supports it. Rollbacks do not update the cluster database version. They are intended for targeted operational recovery, not as a replacement for a database backup. **Warning:** Not every migration can be rolled back. Data transformations may not have enough information to reconstruct the previous state. For production upgrades, always keep a MongoDB backup even if a migration lists `ROLLBACK` in `allowedActions`. **Option: CURL** Endpoint: `/v1/admin/admin/migrations/{dbVersion}` **Example Request** ```bash curl -X 'POST' \ 'https://{host}/v1/admin/admin/migrations/{dbVersion}' \ -H 'accept: */*' \ -H 'Authorization: Bearer {superAdminToken}' \ -H 'Content-Type: application/json' \ -d '{ "action": "ROLLBACK", "remark": "Rollback migration after operational validation" }' ``` ## Common Operational Flow For a production upgrade: 1. Back up the MongoDB database. 2. Deploy the new Enterprise API version. 3. Watch the application logs for migration progress. 4. Call `GET /v1/admin/admin/migrations` to verify each required migration has `success: true`. 5. Investigate any run with `success: false` or an `errorMessage`. For a manual check before applying a specific migration: 1. Call `GET /v1/admin/admin/migrations` and find the migration version. 2. Schedule `VALIDATE_PRECONDITIONS` if it is listed in `allowedActions`. 3. Schedule `DRY_RUN` if it is listed in `allowedActions`. 4. Review the `runs` entry for messages, progress, and errors. 5. Schedule `APPLY` only when you are ready to execute the migration. ## Current Migration Coverage The Enterprise API currently registers migrations for: - Initial MongoDB collection creation. This migration supports `DRY_RUN`. - Base indexes for `organization_trees`, `host_aliases`, and `accounts`. - Extended indexes for `organization_trees`. - Event collection indexes. - Migration of legacy `timestamp` fields to `createdAt` and `updatedAt` on `organization_trees`. This migration supports `DRY_RUN`. - An `updatedAt` index for sorting and querying `organization_trees`. This migration supports `ROLLBACK`. New Enterprise API releases may add more migrations. Always use the status endpoint as the source of truth for the migrations available in your running version. ## Troubleshooting ### Migration Is Waiting If a migration is scheduled but has no `startedAt`, another node may currently hold the migration lock. The scheduled run will start when a node acquires the lock. ### Migration Was Interrupted If a migration has `startedAt` but no `completedAt`, the node may have stopped before finishing. The framework treats this as an aborted run and will attempt recovery on startup or when a migration recovery job is triggered. ### Migration Failed If `success` is `false`, check `errorMessage` and the application logs for the root cause. After resolving the issue, you can schedule the same supported action again. ### No Migrations Are Listed If the status endpoint returns an empty list, verify that the deployment is using MongoDB as its database. Migration execution is not supported for the in-memory database. -------------------------------------------------------------------------------- PAGE: Service Catalog URL: https://docs.walt.id/enterprise-stack/services RAW: https://docs.walt.id/enterprise-stack/services.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Service Catalog Services provide the functional capabilities for decentralized identity and ID wallets. They live inside tenants or sub-tenants, and each instance gets its own configuration so you can run multiple variants of the same service (for example, one issuer for W3C VCs and another for SD-JWT VCs). Use this catalog once you know which capability you want to deploy and need the corresponding guide. ### Need a different starting point? **Note:** - Want the conceptual model (resource tree, service types, dependency patterns)? Go to [Enterprise Foundations → Services](https://docs.walt.id/enterprise-stack/foundations.md#services). - Ready to manage lifecycle, configuration, or metadata across existing services? See [Administration → Service Operations](https://docs.walt.id/enterprise-stack/administration/services/overview.md). ### How the catalog is organized The list below is split into **operational** services (they handle the runtime operations–issuance, verification, wallet actions) and **storage & registry** services (they persist state and expose it). Pick whichever category matches the capability you're looking for, then dive into the linked guide. ## Operational services - [KMS Service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) - Manage cryptographic keys for signing and encryption workloads. - [Issuer2 Service](https://docs.walt.id/enterprise-stack/services/issuer2-service/overview.md) - Sign & issue digital credentials (W3C VC, SD-JWT VC, mDL) using the OpenID4VCI 1.0 specification. - [Issuer Service](https://docs.walt.id/enterprise-stack/services/issuer-service/overview.md) - Sign & issue digital credentials (W3C VC, SD-JWT VC, mDL) with Draft 11, 13 OpenID4VCI specifications. - [Verifier2 Service](https://docs.walt.id/enterprise-stack/services/verifier2-service/overview.md) - Request & verify digital credentials using the OpenID4VP 1.0 specification. - [Verifier Service](https://docs.walt.id/enterprise-stack/services/verifier-service/overview.md) - Request & verify digital credentials using the OpenID4VP Draft14, 20 specifications. - [Wallet Service](https://docs.walt.id/enterprise-stack/services/wallet-service/overview.md) - Run identity wallets for individuals or organizations. Supports Draft 11, 13 OpenID4VCI specifications + Draft 14, 20 OpenID4VP specifications + OpenID4VP 1.0 specification. - [DID Service](https://docs.walt.id/enterprise-stack/services/did-service/overview.md) - Create decentralized identifiers across supported DID methods. - [X.509 Certificate Service](https://docs.walt.id/enterprise-stack/services/x509-service/overview.md) - Issue ISO/IEC 18013-5 IACA and Document Signer certificates and optionally persist them to attached X.509 Stores. - [Credential Status Service](https://docs.walt.id/enterprise-stack/services/credential-status-service/overview.md) - Manage credential status lists (revocation, suspension, etc.). - [Client Attestation Service](https://docs.walt.id/enterprise-stack/services/client-attestation-service/overview.md) - Issue and verify wallet attestations for secure credential issuance flows. ## Storage & registry services - [DID Store Service](https://docs.walt.id/enterprise-stack/services/did-store-service/overview.md) - Store and manage DIDs and DID documents. - [Credential Store Service](https://docs.walt.id/enterprise-stack/services/credential-store-service/overview.md) - Store and query issued credentials. - [X.509 Store Service](https://docs.walt.id/enterprise-stack/services/x509-store-service/overview.md) - Store and manage generic X.509 certificates and VICAL entries. - [VICAL Service](https://docs.walt.id/enterprise-stack/services/vical-service/overview.md) - Publish signed VICAL feeds and expose latest or version-specific registry endpoints backed by linked X.509 stores. - [Trust Registry Service](https://docs.walt.id/enterprise-stack/services/trust-registry-service/overview.md) - Manage ETSI trust lists (TSL, LoTE) for credential verification against official trust frameworks. - [Holder Policy Store Service](https://docs.walt.id/enterprise-stack/services/holder-policy-store-service/overview.md) - Store and manage holder policies for silent issuance or wallet-driven verification. - [DID Registry](https://docs.walt.id/enterprise-stack/services/did-registry/overview.md) - Host did:web documents and expose them over HTTPS. -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/services/key-management-service/overview RAW: https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview The Key Management Service (KMS) is used for creating, storing, and managing cryptographic keys. It is used by various other services, including: - The Decentralized Identifier (DID) service, which creates DIDs. - The Issuer Service, which signs and issues credentials. - The Verifier Service, which verifies credentials. - The Credential Status Service, which signs status credentials. When setting up the KMS, you have the option to choose where to store your keys. You can either use a local key store, which uses the database of the Enterprise Stack, or you can opt for an external KMS solution, such as AWS KMS, Azure Key Vault, HashiCorp Vault Transit Secrets Engine, or Oracle KMS. For production use-cases we recommend the usage of an external KMS. ## External KMS Key Type Support The Enterprise Stack accepts walt.id key type names in `keyType` and maps them to each provider's native key type names when creating or resolving external KMS keys. | Backend | Supported `keyType` values | Notes | |---|---|---| | `tse` | `Ed25519`, `secp256r1`, `secp384r1`, `secp521r1`, `RSA`, `RSA3072`, `RSA4096` | Uses HashiCorp Vault Transit Secrets Engine. `secp256k1` is not supported by the TSE integration. | | `aws` / `aws-rest-api` | `secp256r1`, `secp384r1`, `secp521r1`, `secp256k1`, `RSA`, `RSA3072`, `RSA4096` | `Ed25519` is not supported by AWS KMS signing keys. | | `azure` / `azure-rest-api` | `secp256r1`, `secp384r1`, `secp521r1`, `secp256k1`, `RSA`, `RSA3072`, `RSA4096` | `secp256k1` maps to Azure's `P-256K` curve. `Ed25519` is not supported. | | `jwk` | `Ed25519`, `secp256k1`, `secp256r1`, `secp384r1`, `secp521r1`, `RSA`, `RSA3072`, `RSA4096` | Keys are generated and stored by the walt.id KMS service. | **Info:** Provider support only covers key generation and signing operations that the backing KMS exposes. If a provider rejects a key type, the API returns an explicit unsupported key type error instead of making a failing provider request. ## Get Started - [**Local**](https://docs.walt.id/enterprise-stack/services/key-management-service/local.md): Manage and store keys in the Enterprise database. - [**AWS KMS**](https://docs.walt.id/enterprise-stack/services/key-management-service/aws-kms.md): Connect the service to AWS Key Management Service. - [**HashiCorp Vault**](https://docs.walt.id/enterprise-stack/services/key-management-service/hashicorp-vault.md): Manage and store keys in HashiCorp Vault. - [**Azure Key Vault**](https://docs.walt.id/enterprise-stack/services/key-management-service/azure-key-vault.md): Manage and store keys in Azure Key Vault. -------------------------------------------------------------------------------- PAGE: Setup URL: https://docs.walt.id/enterprise-stack/services/key-management-service/setup RAW: https://docs.walt.id/enterprise-stack/services/key-management-service/setup.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Setup **Note:** **Tip:** Instead of passing AWS KMS or Azure Key Vault credentials inline in every request, you can define reusable **KMS profiles** in `resource-access.conf` and reference them by name via `configRef`. This centralizes credential management and makes rotation easier. [Learn more about KMS Profiles →](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/resource-access.md#kms-profiles) ## Setup Key Service In the following section, we will setup a KMS service inside a tenant. If you don't have a tenant yet, you can learn how to create one [here](https://docs.walt.id/enterprise-stack/administration/tenants/create.md). **Option: CURL** Endpoint: `/v1/{target}/resource-api/services/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Resource/post_v1__target__resource_api_services_create) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "kms" }' ``` **Body** ```json { "type": "kms" } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.waltid.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant in which to create the new KMS service and the service's ID (`{organizationID}.{tenantID}.[NewKmsServiceID]`), e.g. `waltid.tenant1.kms1` **Body Parameters** - `type`: _serviceType_ - Specifies the type of service to create. In our case `kms` --- **Response Codes** - `201` - Service created successfully. -------------------------------------------------------------------------------- PAGE: Local URL: https://docs.walt.id/enterprise-stack/services/key-management-service/local RAW: https://docs.walt.id/enterprise-stack/services/key-management-service/local.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Local When using the local configuration for the Key Management Service (KMS), all key information is stored in the Enterprise Stack database. This option is ideal for getting started since it doesn't require any external dependencies. However, for production use cases, we recommend using an external KMS solution. This approach ensures that your secrets remain secure and are not exposed outside of a protected environment, which helps reduce the risk of key compromises. **Key Algorithms Supported**
The local KMS configuration allows the creation of these key types: - ed25519 - secp256k1 - secp256r1 - RSA **Option: CURL** Endpoint: `/v1/{target}/resource-api/services/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Resource/post_v1__target__resource_api_services_create) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "kms" }' ``` **Body** ```json { "type": "kms" } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant in which to create the new KMS service and the service's ID (`{organizationID}.{tenantID}.[NewKmsServiceID]`), e.g. `waltid.tenant1.kms1` **Body Parameters** - `type`: _serviceType_ - Specifies the type of service to create. In our case `kms` --- **Response Codes** - `201` - Service created successfully. ## Create Key **Option: CURL** Endpoint: `/v1/{target}/kms-service-api/keys/generate` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Key%20Management/post_v1__target__kms_service_api_keys_generate) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/kms-service-api/keys/generate' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "backend": "jwk", "keyType": "Ed25519" }' ``` **Body** ```json { "backend": "jwk", "keyType": "Ed25519" } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + kmsService in which to create the new key and the key's ID (`{organizationID}.{tenantID}.{kmsServiceID}.[newKeyID]`), e.g. `waltid.tenant1.kms1.key1` **Body Parameters** - `backend`: _String_ - Specifies the storage type of key. `jwk` means the local storage option is used and the key is stored in the Enterprise Stack backend. - `keyType`: _String_ - the algorithm used to generate the key. For local, it can be ed25519, secp256k1, secp256r1, or RSA. --- **Response** `201` - Key created successfully. ```json { "_id": "waltid.tenant1.kms1.key1", "key": { "type": "jwk", "jwk": { "kty": "OKP", "d": "ywmoRVTD9fexMtGW0lKE3o9_0ulfzGXr9xHGL0lPhhA", "crv": "Ed25519", "kid": "IQO7DILxtagpTLXkuHkRkJURb2GqcUIwXYZAcGwW1AU", "x": "JOsiIE7ME9UZ8y2H-P5RSuYAUiIfs1ywtdBjMGN7I5s" } }, "parent": "waltid.tenant1.kms1" } ``` -------------------------------------------------------------------------------- PAGE: Azure Key Vault URL: https://docs.walt.id/enterprise-stack/services/key-management-service/azure-key-vault RAW: https://docs.walt.id/enterprise-stack/services/key-management-service/azure-key-vault.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Azure Key Vault 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 direct azure access to Key Vault 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 **Option: Request** Endpoint:`/v1/{target}/kms-service-api/keys/generate` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Key%20Management/post_v1__target__kms_service_api_keys_generate) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/kms-service-api/keys/generate' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "backend": "azure-rest-api", "keyType": "secp256r1", "config": { "auth": { "clientId": "your-client-id", "clientSecret": "your-client-secret", "tenantId": "your-tenant-id", "keyVaultUrl": "https://your-key-vault-name.vault.azure.net/" }, "tags": { "key": "value" } } }' ``` **Body** ```json { "backend": "azure-rest-api", "keyType": "secp256r1", "config": { "auth": { "clientId": "your-client-id", "clientSecret": "your-client-secret", "tenantId": "your-tenant-id", "keyVaultUrl": "https://your-key-vault-name.vault.azure.net/" }, "tags": { "key": "value" } } } ``` **Body Parameters** - `backend`: _String_ - Specifies the storage type of key. It can be `jwk` (managed by you), `azure-rest-api` (managed by Azure Key Vault using Rest api requests ) and others. Learn more about different types [here](https://docs.walt.id/enterprise-stack/services/key-management-service/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). **Option: Response** **Example Response** The generate key endpoint will return an object containing the JWK format and the Id of the key. ```json { "_id": "{target}.https://waltid.vault.azure.net/keys/589323424/e135335b738f4e8cab81faa408329afe", "key": { "type": "azure-rest-api", "id": "https://waltid.vault.azure.net/keys/589323424/e135335b738f4e8cab81faa408329afe", "auth": { "clientId": "your-client-id", "clientSecret": "your-client-secret", "tenantId": "your-tenant-id", "keyVaultUrl": "https://your-key-vault-name.vault.azure.net/" }, "_keyType": "secp256r1", "_publicKey": { "type": "jwk", "jwk": { "kty": "EC", "crv": "P-256", "kid": "https://waltid.vault.azure.net/keys/589323424/e135335b738f4e8cab81faa408329afe", "key_ops": [ "sign", "verify" ], "x": "jHHX9NRLXN9ccd2a7gJFWlpQlXIFkToDT6kKR5kv_vQ", "y": "E1Pl9WoCZoyTE74Myr_SlvmWL_FYQPZdq_Q76pXa7Wo" } } }, "parent": "{target}.https://waltid.vault.azure" } ``` **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. -`auth`: _Object_ - The authentication configuration for 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. - `keyVaultUrl`: _String_ - The URL of the Azure Key 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. **Response Codes** - `201` - Key created and stored in KMS. ## Setup environment managed access to Key Vault - 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) - Managed Identity authentication (recommended for production) ## Key Generation **Option: Request** Endpoint:`/v1/{target}/kms-service-api/keys/generate` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Key%20Management/post_v1__target__kms_service_api_keys_generate) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/kms-service-api/keys/generate' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "backend": "azure", "keyType": "secp256r1", "config": { "auth": { "keyVaultUrl": "https://your-key-vault-name.vault.azure.net/" }, "tags": { "key": "value" } } }' ``` **Body** ```json { "backend": "azure", "keyType": "secp256r1", "config": { "auth": { "keyVaultUrl": "https://your-key-vault-name.vault.azure.net/" }, "tags": { "key": "value" } } } ``` **Body Parameters** - `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/enterprise-stack/services/key-management-service/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` - `auth`: _Object_ - The authentication configuration for the Azure Key Vault. - `keyVaultUrl`: _String_ - The URL of the 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). **Option: Response** **Example Response** The generate key endpoint will return an object containing the JWK format and the Id of the key. ```json { "_id": "{target}.https://waltid.vault.azure.net/keys/589323424/e135335b738f4e8cab81faa408329afe", "key": { "type": "azure", "id": "https://waltid.vault.azure.net/keys/589323424/e135335b738f4e8cab81faa408329afe", "config": { "auth" : { "keyVaultUrl": "https://your-key-vault-name.vault.azure.net/" } }, "_keyType": "secp256r1", "_publicKey": { "type": "jwk", "jwk": { "kty": "EC", "crv": "P-256", "kid": "https://waltid.vault.azure.net/keys/589323424/e135335b738f4e8cab81faa408329afe", "key_ops": [ "sign", "verify" ], "x": "jHHX9NRLXN9ccd2a7gJFWlpQlXIFkToDT6kKR5kv_vQ", "y": "E1Pl9WoCZoyTE74Myr_SlvmWL_FYQPZdq_Q76pXa7Wo" } } }, "parent": "{target}.https://waltid.vault.azure" } ``` **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`: _Object_ - The authentication configuration for the Azure Key Vault. - `keyVaultUrl`: _String_ - The URL of the Azure Key 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. **Response Codes** - `201` - Key created and stored in KMS. -------------------------------------------------------------------------------- PAGE: HashiCorp Vault Transit Secrets Engine URL: https://docs.walt.id/enterprise-stack/services/key-management-service/hashicorp-vault RAW: https://docs.walt.id/enterprise-stack/services/key-management-service/hashicorp-vault.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # HashiCorp Vault Transit Secrets Engine When using the HashiCorp Vault integration with the Enterprise Stack Key Management Service, the Enterprise Stack 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, services like issuer or credential-status can sign credentials whilst keeping the secrets (signing keys) in an external secure environment. ## HashiCorp Vault Setup 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 Enterprise Stack. #### 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). ``` ## KMS Service Setup in the Enterprise Stack ### Setup Key Service In the following section, we will setup a KMS service inside of a tenant. If you don't have a tenant yet, you can learn how to create one [here](https://docs.walt.id/enterprise-stack/administration/tenants/create.md). When configuring the KMS service, you can set default access credentials for the Enterprise API to authenticate with HashiCorp Vault. This means that whenever you call the key generation endpoint without specifying any authentication credentials for HashiCorp Vault, the system will automatically use the default credentials you provided during setup. **Option: Without Access Credentials** **Option: CURL** Endpoint: `/v1/{target}/resource-api/services/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Resource/post_v1__target__resource_api_services_create) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "kms" }' ``` **Body** ```json { "type": "kms" } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.waltid.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant in which to create the new KMS service and the service's ID (`{organizationID}.{tenantID}.[NewKmsServiceID]`), e.g. `waltid.tenant1.kms1` **Body Parameters** - `type`: _serviceType_ - Specifies the type of service to create. In our case `kms` --- **Response Codes** - `201` - Service created successfully. **Option: With Access Credentials** Coming soon ### Key Creation Use `backend: "tse"` to create keys in the HashiCorp Vault Transit Secrets Engine. The Enterprise API accepts walt.id `keyType` values and maps them to the matching Vault Transit key type before calling Vault. | 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: Without Default Access Credentials** **Option: CURL** Endpoint:`/v1/{target}/kms-service-api/keys/generate` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Key%20Management/post_v1__target__kms_service_api_keys_generate) **Example Request** **Option: AppRole Auth** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/kms-service-api/keys/generate' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "backend": "tse", "keyType": "Ed25519", "config": { "server": "http://127.0.0.1:8200/v1/transit", "auth": { "roleId": "6823b3c7-60f7-db0b-c663-7359f17c0c30", "secretId": "aba4f28b-524c-db63-bae2-67a0e094f46a" } } }' ``` **Body** ```json { "backend": "tse", "keyType": "Ed25519", "config": { "server": "http://127.0.0.1:8200/v1/transit", "auth": { "roleId": "6823b3c7-60f7-db0b-c663-7359f17c0c30", "secretId": "aba4f28b-524c-db63-bae2-67a0e094f46a" } } } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.waltid.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + kmsService in which to create the new key and the key's ID (`{organizationID}.{tenantID}.{kmsServiceID}.[newKeyID]`), e.g. `waltid.tenant1.kms1.key1` **Body Parameters** - `backend`: _String_ - in our case `tse` indicating the key will be managed by HashiCorp Vault. - `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. **Option: User/Pass Auth** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/kms-service-api/keys/generate' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "backend": "tse", "keyType": "Ed25519", "config": { "server": "http://127.0.0.1:8200/v1/transit", "auth": { "username": "myuser", "password": "mypassword" } } }' ``` **Body** ```json { "backend": "tse", "keyType": "Ed25519", "config": { "server": "http://127.0.0.1:8200/v1/transit", "auth": { "username": "myuser", "password": "mypassword" } } } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.waltid.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + kmsService in which to create the new key and the key's ID (`{organizationID}.{tenantID}.{kmsServiceID}.[newKeyID]`), e.g. `waltid.tenant1.kms1.key1` **Body Parameters** - `backend`: _String_ - in our case `tse` indicating the key will be managed by HashiCorp Vault. - `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. **Response Codes** - `201` - Key created successfully. **Option: With Default Access Credentials** **Option: CURL** Endpoint:`/v1/{target}/kms-service-api/keys/generate` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%2F%20Key%20Management/post_v1__target__kms_service_api_keys_generate) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/kms-service-api/keys/generate' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "backend": "tse", "keyType": "Ed25519" }' ``` **Body** ```json { "backend": "tse", "keyType": "Ed25519" } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.waltid.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + kmsService in which to create the new key and the key's ID (`{organizationID}.{tenantID}.{kmsServiceID}.[newKeyID]`), e.g. `waltid.tenant1.kms1.key1` **Body Parameters** - `backend`: _String_ - in our case `tse` indicating the key will be managed by HashiCorp Vault. - `keyType`: _String_ - the algorithm used to generate the key. For Vault TSE, use one of `Ed25519`, `secp256r1`, `secp384r1`, `secp521r1`, `RSA`, `RSA3072`, or `RSA4096`. **Response Codes** - `201` - Key created successfully. -------------------------------------------------------------------------------- PAGE: AWS KMS URL: https://docs.walt.id/enterprise-stack/services/key-management-service/aws-kms RAW: https://docs.walt.id/enterprise-stack/services/key-management-service/aws-kms.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # AWS KMS This guide shows you how to create and manage signing keys in AWS KMS through the Enterprise Stack KMS service. ## What you can do - Generate signing keys in AWS KMS and use them across Enterprise Stack services (credential issuance, DID operations, etc.) - Use IRSA (IAM Roles for Service Accounts) for secure, credential-free authentication on EKS - Create multi-region keys with automatic failover for high-availability deployments ## Prerequisites Setting up AWS KMS involves two separate places: your Enterprise Stack config (step 1) and your own AWS account (step 2, and optionally IRSA below, done via the AWS Console, CLI, or IaC — not via any Enterprise Stack file). ### 1. Enable the crypto-aws feature **Note:** The `crypto-aws` feature flag is **enabled by default**, so you usually don't need to touch this — it only needs action if your deployment has explicitly disabled it. Check `disabledFeatures` in your [`_features.conf`](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/features.md) (found at `waltid-enterprise-api/config/_features.conf` in a local checkout) and remove `crypto-aws` from that list if present: ```hocon [_features.conf] disabledFeatures = [ # crypto-aws ] ``` The API must be restarted for feature flag changes to take effect. ### 2. Set up IAM permissions in your AWS account Create an IAM role or user in your own AWS account with these KMS permissions: ```json { "Version": "2012-10-17", "Statement": [ { "Sid": "WaltIDKMSBasic", "Effect": "Allow", "Action": [ "kms:CreateKey", "kms:CreateAlias", "kms:DeleteAlias", "kms:Describe*", "kms:Get*", "kms:List*", "kms:Sign", "kms:Verify", "kms:TagResource", "kms:UntagResource" ], "Resource": "*" } ] } ``` **For multi-region keys**, add: ```json "kms:ReplicateKey", "kms:ScheduleKeyDeletion" ``` ### IRSA setup for EKS (recommended) For production EKS deployments, use IRSA instead of long-lived access keys: **1. Create IAM role with the KMS policy above** **2. Add trust relationship for your service account:** ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::YOUR_ACCOUNT:oidc-provider/oidc.eks.YOUR_REGION.amazonaws.com/id/YOUR_OIDC_ID" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "oidc.eks.YOUR_REGION.amazonaws.com/id/YOUR_OIDC_ID:sub": "system:serviceaccount:YOUR_NAMESPACE:YOUR_SERVICE_ACCOUNT" } } } ] } ``` **3. Annotate your Kubernetes service account:** ```yaml apiVersion: v1 kind: ServiceAccount metadata: name: waltid-enterprise-api namespace: your-namespace annotations: eks.amazonaws.com/role-arn: arn:aws:iam::YOUR_ACCOUNT:role/waltid-eks-kms-role ``` **4. Use `"backend": "aws"` in requests** - IRSA credentials are used automatically. ## Supported key algorithms - `secp256r1` (P-256) - `secp256k1` - `secp384r1` (P-384) - `secp521r1` (P-521) - `RSA` (2048-bit) - `RSA3072` (3072-bit) - `RSA4096` (4096-bit) ## Create a basic key ### With IRSA / environment credentials (recommended) **Option: CURL** **Endpoint:** `POST /v1/{target}/kms-service-api/keys/generate` ##### Example Request ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/waltid.tenant1.kms1/kms-service-api/keys/generate' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "backend": "aws", "keyType": "secp256r1", "config": { "auth": { "region": "eu-central-1" }, "keyName": "my-signing-key", "tags": { "environment": "production", "purpose": "credential-signing" } } }' ``` **Body Parameters** - `backend`: `"aws"` for SDK-based (supports IRSA) - `keyType`: One of the supported algorithms above - `config.auth.region`: AWS region where to create the key (e.g., `"eu-central-1"`) - `config.keyName`: _(optional)_ Human-readable alias (must be unique) - `config.tags`: _(optional)_ The metadata tags to add to the key for [AWS](https://docs.aws.amazon.com/kms/latest/developerguide/tagging-keys.html) --- ##### Example Response ```json { "_id": "waltid.tenant1.kms1.my-signing-key", "key": { "type": "aws", "config": { "auth": { "region": "eu-central-1" } }, "id": "a1b2c3d4-5678-90ab-cdef-EXAMPLE", "_keyType": "secp256r1" }, "parent": "waltid.tenant1.kms1" } ``` **Response codes:** - `201 Created` – Success - `400 Bad Request` – Invalid configuration - `401 Unauthorized` – Missing/invalid token - `403 Forbidden` – Insufficient permissions - `409 Conflict` – Key alias already exists ### With explicit access keys Use when running outside AWS or without IAM roles. Same endpoint as above — only the request body's `backend` and `config.auth` shape change: ```json { "backend": "aws-rest-api", "keyType": "secp256r1", "config": { "auth": { "accessKeyId": "AKIAIOSFODNN7EXAMPLE", "secretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", "region": "eu-central-1" } } } ``` **Note:** Rotate access keys regularly. IRSA is preferred for production. Instead of passing keys inline in every request, you can also define a reusable **KMS profile** in `resource-access.conf` and reference it by name via `configRef` — see [KMS Profiles](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/resource-access.md#kms-profiles) for details. **Alternative: instance role name (EC2/ECS)** If the host running the Enterprise Stack already has an IAM role attached (EC2/ECS instance profile), you can omit `accessKeyId`/`secretAccessKey` and provide `config.auth.roleName` (matching that role's name) instead. The `aws-rest-api` backend then fetches temporary credentials from the instance metadata service (IMDSv2) for that role: ```json { "backend": "aws-rest-api", "keyType": "secp256r1", "config": { "auth": { "roleName": "my-instance-role", "region": "eu-central-1" } } } ``` **Always set `roleName` explicitly to the instance's actual attached role** — if it doesn't match, the request fails with a role mismatch error. ## Error handling ### 409 Conflict - Alias already exists ```json { "exception": true, "id": "KeyAlreadyExistsException", "status": "Conflict", "code": "409", "message": "A key with name 'my-signing-key' already exists." } ``` The Enterprise Stack attempts to clean up the orphaned key. If cleanup fails due to missing `kms:ScheduleKeyDeletion` permission, the error is logged but 409 is still returned. ### 400 Bad Request - Invalid configuration ```json { "exception": true, "id": "IllegalArgumentException", "status": "Bad Request", "code": "400", "message": "Invalid replica region 'us-central-1'. Valid AWS regions: ..." } ``` Common causes: - Typo in region name (e.g., `us-central-1` vs `us-west-1`) - Multi-region config without `multiRegion: true` - Invalid failover configuration ## Advanced: Multi-region keys with failover For high-availability deployments, AWS KMS supports [multi-region keys](https://docs.aws.amazon.com/kms/latest/developerguide/multi-region-keys-overview.html) - keys that share the same cryptographic material across multiple regions. ### Benefits - **Disaster recovery**: Continue operations if a region becomes unavailable - **Lower latency**: Place replicas closer to workloads - **Compliance**: Keep keys in required regions while maintaining DR ### How it works **Primary key**: Created in your chosen region. Supports all operations. **Replica keys**: Created in additional regions. Share: - Same key material (can verify signatures from any region) - Same key ID (starts with `mrk-`) - Independent policies and metadata **Failover**: When enabled, signing operations automatically retry in replica regions if the primary is unavailable. ### Configuration | Field | Required | Description | |-------|----------|-------------| | `multiRegion` | Yes | Set to `true` | | `replicaRegions` | Optional | AWS regions for replicas, e.g., `["eu-west-1", "us-east-1"]` | | `enableFailover` | Optional | Auto-retry in replicas when primary unavailable | | `failoverOrder` | Optional | Custom region order. Default: `[primary, ...replicaRegions]` | ### Validation rules Configuration is validated **before** creating any keys: - All regions must be valid AWS regions - `multiRegion: true` required for replica/failover config - Replica regions cannot duplicate primary - `failoverOrder` regions must exist in primary + replicas - `enableFailover` requires `replicaRegions` Validation failures return `400 Bad Request` with a helpful message. ### Example: Multi-region key with failover ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/waltid.tenant1.kms1/kms-service-api/keys/generate' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "backend": "aws", "keyType": "secp256r1", "config": { "auth": { "region": "eu-central-1" }, "keyName": "production-signing-key", "multiRegion": true, "replicaRegions": [ "eu-west-1", "us-east-1" ], "enableFailover": true, "failoverOrder": [ "eu-central-1", "eu-west-1", "us-east-1" ], "tags": { "environment": "production", "ha-enabled": "true" } } }' ``` **What this creates:** 1. Primary key in `eu-central-1` 2. Replica in `eu-west-1` 3. Replica in `us-east-1` 4. Automatic failover: tries regions in order ### Failover behavior **Normal operation:** - All requests use the primary region - No performance impact **Primary unavailable:** - Automatically retries in first replica - Transparent to the caller **Multiple regions unavailable:** - Continues through `failoverOrder` - Returns error only if all regions fail **Performance:** Failover latency is added only during actual outages. ## Next steps - [Use KMS keys for credential issuance](https://docs.walt.id/enterprise-stack/services/issuer-service/credential-issuance/vc-oid4vc.md) - [Configure DID methods with KMS](https://docs.walt.id/enterprise-stack/services/did-service/overview.md) -------------------------------------------------------------------------------- PAGE: Full API Reference URL: https://docs.walt.id/enterprise-stack/services/key-management-service/full-api-reference RAW: https://docs.walt.id/enterprise-stack/services/key-management-service/full-api-reference.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Full API Reference Find it [here](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Key%20Management) -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/services/did-service/overview RAW: https://docs.walt.id/enterprise-stack/services/did-service/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview The DID service enables users to create and resolve various types of Decentralized Identifiers (DIDs). Currently, it supports the following DID methods: - did:key - did:jwk - did:web Please note that the DID service is stateless. This means that while the service can create DIDs and their corresponding DID Documents, it does not store them. ## Service Dependencies To create a DID and its associated DID Document, the DID service requires keys. In the Enterprise Stack, keys are managed through the Key Management Service (KMS). Therefore, before using the DID service, ensure that you have set up a KMS service within the same tenant as the DID service. ## Get Started - [Setup](https://docs.walt.id/enterprise-stack/services/did-service/setup.md) - How to setup a DID service - [did:key create](https://docs.walt.id/enterprise-stack/services/did-service/did-creation/did-key.md) - How to generate a did:key + DID Document - [DID storage](https://docs.walt.id/enterprise-stack/services/did-service/did-storage.md) - How to store created DIDs & DID Documents. ## Operational endpoints All lifecycle, configuration, dependency, and metadata operations for this service are documented under Administration → Service Operations: - [Create this service](https://docs.walt.id/enterprise-stack/administration/services/create.md) - [View or update configuration](https://docs.walt.id/enterprise-stack/administration/services/configuration/view.md) - [Manage dependency services](https://docs.walt.id/enterprise-stack/administration/services/dependency-services/view.md) -------------------------------------------------------------------------------- PAGE: Setup URL: https://docs.walt.id/enterprise-stack/services/did-service/setup RAW: https://docs.walt.id/enterprise-stack/services/did-service/setup.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Setup We will setup a DID service inside of a tenant. If you don't have a tenant yet, you can learn how to create one [here](https://docs.walt.id/enterprise-stack/administration/tenants/create.md). **Option: CURL** Endpoint: `/v1/{target}/resource-api/services/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Resource/post_v1__target__resource_api_services_create) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "did" }' ``` **Body** ```json { "type": "did" } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant in which to create the new DID service and the service's ID (`{organizationID}.{tenantID}.[NewDIDServiceID]`), e.g. `waltid.tenant1.did1` **Body Parameters** - `type`: _serviceType_ - Specifies the type of service to create. In our case `did` --- **Response Codes** - `201` - Service created successfully. -------------------------------------------------------------------------------- PAGE: Using KMS for DID Creation URL: https://docs.walt.id/enterprise-stack/services/did-service/did-creation/using-kms RAW: https://docs.walt.id/enterprise-stack/services/did-service/did-creation/using-kms.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Using KMS for DID Creation **By default, the DID service can only utilise in-line JWK keys for generating DIDs**. However, if you would like to use keys managed by your [KMS service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) you can do so by properly configuring it as a dependency of the DID service and ensuring you have the proper permissions to access the keys. Once attached, you will be able to reference the keys in your DID creation requests. There is also the option to attach more then one KMS service, in this case you can reference any key from any of the attached KMS services during DID creation. ## Link KMS Service to DID Service If you don't have a KMS service yet, you can learn how to create on [here](https://docs.walt.id/enterprise-stack/services/key-management-service/setup.md). **Option: CURL** Endpoint: `/v1/{target}/did-service-api/dids/dependencies/add` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/DID%20Service/post_v1__target__did_service_api_dids_dependencies_add) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/did-service-api/dids/dependencies/add' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d 'waltid.tenant1.kms1' ``` **Body** ``` waltid.tenant1.kms1 ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + DID service to which to add an existing KMS Service (`{organizationID}.{tenantID}.{didServiceID}`), e.g. `waltid.tenant1.did-service-1` **Body Parameters** - Specifies the ID of the KMS service that should be linked. --- **Response Codes** - `201` - Unit attached. -------------------------------------------------------------------------------- PAGE: DID Key URL: https://docs.walt.id/enterprise-stack/services/did-service/did-creation/did-key RAW: https://docs.walt.id/enterprise-stack/services/did-service/did-creation/did-key.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # DID Key ## Create a did:key **Option: CURL** Endpoint: `/v1/{target}/did-service-api/dids/create/key` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/DID%20Service/post_v1__target__did_service_api_dids_create_key) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/did-service-api/dids/create/key' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "keyId": "waltid.tenant1.kms1.key1", "useJwkJcsPub": false, "didId": "did1" }' ``` **Body** ```json { "keyId": "waltid.tenant1.kms1.key1", "useJwkJcsPub": false, "didId": "did1" } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + DID service in which to execute the DID creation (`{organizationID}.{tenantID}.{didServiceID}`), e.g. `waltid.tenant1.didservice` **Body Parameters** - `keyId`: _resourceIdentifier_ - Specifies the key for which to generate a DID + DID Document. Please make sure that the key is stored in a KMS service under the same tenant as the DID service and is attached following the [Using KMS for DID Creation](https://docs.walt.id/enterprise-stack/services/did-service/did-creation/using-kms.md) guide. - `useJwkJcsPub`: _boolean_ - If set to `true` it generates an EBSI compliant DID:key + DID Document. This is only important if you are operating within the EBSI ecosystem. - `didId`: _resourceIdentifier_ - Specifies the identifier of the did to be stored when a DID Store is attached. If not provided, the identifier will be generated automatically. --- **Response** - `201` - DID created successfully. ```json { "id": "waltid.tenant1.didstore.did1", "did": "did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt", "document": { "@context": [ "https://www.w3.org/ns/did/v1", "https://w3id.org/security/suites/jws-2020/v1" ], "id": "did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt", "verificationMethod": [ { "id": "did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt#z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt", "type": "JsonWebKey2020", "controller": "did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt", "publicKeyJwk": { "kty": "EC", "crv": "P-256", "kid": "zY9AzWFTMKveeXIdHWSsvX16Cso1JXMEVKGFUuw_LkQ", "x": "PMzrrJU6Gh5JZK_6aIpiWWHcGoWymGFkYptM10a1sec", "y": "6-hp5Y7Hpb9nFfovyeycXSPubEJ7LWdaGNoCcKZCIyA" } } ], "assertionMethod": [ "did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt#z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt" ], "authentication": [ "did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt#z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt" ], "capabilityInvocation": [ "did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt#z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt" ], "capabilityDelegation": [ "did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt#z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt" ], "keyAgreement": [ "did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt#z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbptHxPSZ7khwjrFU8X6aCeeqW3jTNdoAMWZCHMFmVeZYDYbeCZgy5mYuY2a3X3GrAKGkSW31bxMSV5n2okQJd9UckKn7mEdqnDSv6xFfejCekE2oSEE1hUX62Ww2pUn39zt" ] } } ``` **Note:** Please make sure to save the generated DID (Decentralized Identifier) along with the key ID for future reference. This information is necessary, for example, when issuing credentials through the Issuer Service. We are working on a solution to have this referenced stored automatically for easier access across all services. -------------------------------------------------------------------------------- PAGE: DID Web URL: https://docs.walt.id/enterprise-stack/services/did-service/did-creation/did-web RAW: https://docs.walt.id/enterprise-stack/services/did-service/did-creation/did-web.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # DID Web ## Create a did:web Use the DID Service to generate a did:web identifier and store its DID Document. The endpoint does not publish the document to your domain; make sure the requested `domain` and `path` are reachable (e.g., via the DID Registry) so the DID can be resolved. **Option: CURL** Endpoint: `/v1/{target}/did-service-api/dids/create/web` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/DID%20Service/post_v1__target__did_service_api_dids_create_web) **Example Request** ```bash curl -X 'POST' \ 'https://{orgId}.enterprise.test.waltid.cloud/v1/{target}/did-service-api/dids/create/web' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -d '{ "type": "single_key_id", "keyId": "waltid.users.kms1.secp256r1", "domain": "waltid.enterprise.test.waltid.cloud", "path" : "users/alice", "didId": "did1" }' ``` **Body** ```json { "type": "single_key_id", "keyId": "waltid.users.kms1.secp256r1", "domain": "waltid.enterprise.test.waltid.cloud", "path" : "users/alice", "didId": "did1" } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + DID service in which to execute the DID creation (`{organizationID}.{tenantID}.{didServiceID}`), e.g. `waltid.users.didservice` **Body Parameters** - `type`: _string_ - Defines how keys are assigned to verification relationships: - `single_key_id` — Use one key for all verification relationships; include `keyId`. - `key_id_set` — Provide multiple keys to distribute across relationships; include `keyIdSet` (array of key IDs). - `verification_key_id_set_config` — Specify per-relationship keys; include `verificationKeyIdSetConfiguration` (map with `authentication`, `assertionMethod`, `capabilityInvocation`, `capabilityDelegation`, `keyAgreement` keys). - `keyId`: _resourceIdentifier_ - Required when `type` is `single_key_id`. Ensure the key is stored in a KMS service under the same tenant as the DID service and is attached following the [Using KMS for DID Creation](https://docs.walt.id/enterprise-stack/services/did-service/did-creation/using-kms.md) guide. - `keyIdSet`: _string[]_ - Required when `type` is `key_id_set`. Set of key IDs to spread across verification methods. - `verificationKeyIdSetConfiguration`: _object_ - Required when `type` is `verification_key_id_set_config`. A map of verification relationship names to arrays of key IDs, letting you assign distinct keys per relationship. - `domain`: _string_ - The domain name to be used for the DID:web. This is the domain under which the DID will be registered. - `path`: _string_ - The path to be used for the DID:web. This is the path under which the DID will be registered: - for [tenant level DIDs](https://docs.walt.id/enterprise-stack/services/did-registry/tenant-level-did.md), use `{tenant}/user`, e.g., `users/alice`. - for [organization level DIDs](https://docs.walt.id/enterprise-stack/services/did-registry/organization-level-did.md), use the `user` directly, e.g., `alice`. - `serviceConfigurationSet`: _array_ - Optional service entries (e.g., DIDCommMessaging) to include in the DID Document. - `rootCustomProperties`: _object_ - Optional map of additional top-level DID Document properties. - `context`: _array_ - Optional extra JSON-LD contexts to add to the DID Document. - `didId`: _resourceIdentifier_ - Specifies the identifier of the did to be stored when a DID Store is attached. If not provided, the identifier will be generated automatically. --- **Response** - `201` - DID created successfully. ```json { "id": "waltid.users.didstore.did1", "did": "did:web:waltid.enterprise.test.waltid.cloud:users:alice", "document": { "context": [ "https://www.w3.org/ns/did/v1", "https://w3id.org/security/suites/jws-2020/v1" ], "id": "did:web:waltid.enterprise.test.waltid.cloud:users:alice", "verificationMethod": [ { "id": "did:web:waltid.enterprise.test.waltid.cloud:users:alice#RbI8c98cFuu16mtpoeb3Axcx62ST5y4pd-aUX-PffqA", "type": "JsonWebKey2020", "controller": "did:web:waltid.enterprise.test.waltid.cloud:users:alice", "publicKeyJwk": { "kty": "EC", "crv": "P-256", "kid": "RbI8c98cFuu16mtpoeb3Axcx62ST5y4pd-aUX-PffqA", "x": "S-AzRQsvoO6TZ-hzQP3Zg7mZIoBdYO9ni3exxM2PTKY", "y": "32ImttHl0wU52b0RjlBfdzCWFMXIkFSehhzu_HjwI6M" } } ], "assertionMethod": [ "did:web:waltid.enterprise.test.waltid.cloud:users:alice#RbI8c98cFuu16mtpoeb3Axcx62ST5y4pd-aUX-PffqA" ], "authentication": [ "did:web:waltid.enterprise.test.waltid.cloud:users:alice#RbI8c98cFuu16mtpoeb3Axcx62ST5y4pd-aUX-PffqA" ], "capabilityDelegation": [ "did:web:waltid.enterprise.test.waltid.cloud:users:alice#RbI8c98cFuu16mtpoeb3Axcx62ST5y4pd-aUX-PffqA" ], "capabilityInvocation": [ "did:web:waltid.enterprise.test.waltid.cloud:users:alice#RbI8c98cFuu16mtpoeb3Axcx62ST5y4pd-aUX-PffqA" ], "keyAgreement": [ "did:web:waltid.enterprise.test.waltid.cloud:users:alice#RbI8c98cFuu16mtpoeb3Axcx62ST5y4pd-aUX-PffqA" ] } } ``` -------------------------------------------------------------------------------- PAGE: DID JWK URL: https://docs.walt.id/enterprise-stack/services/did-service/did-creation/did-jwk RAW: https://docs.walt.id/enterprise-stack/services/did-service/did-creation/did-jwk.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # DID JWK ## Create a did:jwk **Option: CURL** Endpoint: `/v1/{target}/did-service-api/dids/create/jwk` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/DID%20Service/post_v1__target__did_service_api_dids_create_jwk) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/did-service-api/dids/create/jwk' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "keyId": "waltid.tenant1.kms1.key1", "didId": "did1" }' ``` **Body** ```json { "keyId": "waltid.tenant1.kms1.key1", "didId": "did1" } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + DID service in which to execute the DID creation (`{organizationID}.{tenantID}.{didServiceID}`), e.g. `waltid.tenant1.didservice` **Body Parameters** - `keyId`: _resourceIdentifier_ - Specifies the key for which to generate a DID + DID Document. Please make sure that the key is stored in a KMS service under the same tenant as the DID service and is attached following the [Using KMS for DID Creation](https://docs.walt.id/enterprise-stack/services/did-service/did-creation/using-kms.md) guide. - `didId`: _resourceIdentifier_ - Specifies the identifier of the did to be stored when a DID Store is attached. If not provided, the identifier will be generated automatically. --- **Response** - `201` - DID created successfully. ```json { "id": "waltid.tenant1.didstore.did1", "did": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoielRBdUxpbUpvVUtOOHAtVTRib0tTQzNQUXJwOThnMXNoSnZqa2VtRTR1VSIsIngiOiJCVEJjdjRKSHp3V1VWMldCcGFmQ2hSTE5BR1VmSWZYNTZvZGlHem9KcXdNIiwieSI6InNtZjdKeTd5S1VJWUtMM2loaFluM25DNDBoS1FUYXloWC1SUm1XS2tQNmMifQ", "document": { "@context": [ "https://www.w3.org/ns/did/v1", "https://w3id.org/security/suites/jws-2020/v1" ], "id": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoielRBdUxpbUpvVUtOOHAtVTRib0tTQzNQUXJwOThnMXNoSnZqa2VtRTR1VSIsIngiOiJCVEJjdjRKSHp3V1VWMldCcGFmQ2hSTE5BR1VmSWZYNTZvZGlHem9KcXdNIiwieSI6InNtZjdKeTd5S1VJWUtMM2loaFluM25DNDBoS1FUYXloWC1SUm1XS2tQNmMifQ", "verificationMethod": [ { "id": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoielRBdUxpbUpvVUtOOHAtVTRib0tTQzNQUXJwOThnMXNoSnZqa2VtRTR1VSIsIngiOiJCVEJjdjRKSHp3V1VWMldCcGFmQ2hSTE5BR1VmSWZYNTZvZGlHem9KcXdNIiwieSI6InNtZjdKeTd5S1VJWUtMM2loaFluM25DNDBoS1FUYXloWC1SUm1XS2tQNmMifQ#0", "type": "JsonWebKey2020", "controller": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoielRBdUxpbUpvVUtOOHAtVTRib0tTQzNQUXJwOThnMXNoSnZqa2VtRTR1VSIsIngiOiJCVEJjdjRKSHp3V1VWMldCcGFmQ2hSTE5BR1VmSWZYNTZvZGlHem9KcXdNIiwieSI6InNtZjdKeTd5S1VJWUtMM2loaFluM25DNDBoS1FUYXloWC1SUm1XS2tQNmMifQ", "publicKeyJwk": { "kty": "EC", "crv": "P-256", "kid": "zTAuLimJoUKN8p-U4boKSC3PQrp98g1shJvjkemE4uU", "x": "BTBcv4JHzwWUV2WBpafChRLNAGUfIfX56odiGzoJqwM", "y": "smf7Jy7yKUIYKL3ihhYn3nC40hKQTayhX-RRmWKkP6c" } } ], "assertionMethod": [ "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoielRBdUxpbUpvVUtOOHAtVTRib0tTQzNQUXJwOThnMXNoSnZqa2VtRTR1VSIsIngiOiJCVEJjdjRKSHp3V1VWMldCcGFmQ2hSTE5BR1VmSWZYNTZvZGlHem9KcXdNIiwieSI6InNtZjdKeTd5S1VJWUtMM2loaFluM25DNDBoS1FUYXloWC1SUm1XS2tQNmMifQ#0" ], "authentication": [ "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoielRBdUxpbUpvVUtOOHAtVTRib0tTQzNQUXJwOThnMXNoSnZqa2VtRTR1VSIsIngiOiJCVEJjdjRKSHp3V1VWMldCcGFmQ2hSTE5BR1VmSWZYNTZvZGlHem9KcXdNIiwieSI6InNtZjdKeTd5S1VJWUtMM2loaFluM25DNDBoS1FUYXloWC1SUm1XS2tQNmMifQ#0" ], "capabilityInvocation": [ "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoielRBdUxpbUpvVUtOOHAtVTRib0tTQzNQUXJwOThnMXNoSnZqa2VtRTR1VSIsIngiOiJCVEJjdjRKSHp3V1VWMldCcGFmQ2hSTE5BR1VmSWZYNTZvZGlHem9KcXdNIiwieSI6InNtZjdKeTd5S1VJWUtMM2loaFluM25DNDBoS1FUYXloWC1SUm1XS2tQNmMifQ#0" ], "capabilityDelegation": [ "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoielRBdUxpbUpvVUtOOHAtVTRib0tTQzNQUXJwOThnMXNoSnZqa2VtRTR1VSIsIngiOiJCVEJjdjRKSHp3V1VWMldCcGFmQ2hSTE5BR1VmSWZYNTZvZGlHem9KcXdNIiwieSI6InNtZjdKeTd5S1VJWUtMM2loaFluM25DNDBoS1FUYXloWC1SUm1XS2tQNmMifQ#0" ], "keyAgreement": [ "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoielRBdUxpbUpvVUtOOHAtVTRib0tTQzNQUXJwOThnMXNoSnZqa2VtRTR1VSIsIngiOiJCVEJjdjRKSHp3V1VWMldCcGFmQ2hSTE5BR1VmSWZYNTZvZGlHem9KcXdNIiwieSI6InNtZjdKeTd5S1VJWUtMM2loaFluM25DNDBoS1FUYXloWC1SUm1XS2tQNmMifQ#0" ] } } ``` **Note:** Please make sure to save the generated DID (Decentralized Identifier) along with the key ID for future reference. This information is necessary, for example, when issuing credentials through the Issuer Service. We are working on a solution to have this referenced stored automatically for easier access across all services. -------------------------------------------------------------------------------- PAGE: DID Storage URL: https://docs.walt.id/enterprise-stack/services/did-service/did-storage RAW: https://docs.walt.id/enterprise-stack/services/did-service/did-storage.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # DID Storage **By default, the DID service does not save the DIDs (Decentralized Identifiers) and their corresponding DID documents that are created**. However, you can enable this feature by using the [DID store service](https://docs.walt.id/enterprise-stack/services/did-store-service/overview.md). Once the DID store service is attached to the DID service, all created DIDs and their associated DID documents will be stored in the designated DID store. There is also the option to attach more then one DID store, in this case the created DIDs & Documents will be stored in all attached DID stores. ## Link DID Store to DID Service If you don't have a DID store yet, you can learn how to create on [here](https://docs.walt.id/enterprise-stack/services/did-store-service/setup.md). **Option: CURL** Endpoint: `/v1/{target}/did-service-api/dids/dependencies/add` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/DID%20Service/post_v1__target__did_service_api_dids_dependencies_add) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/did-service-api/dids/dependencies/add' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d 'waltid.tenant1.did-store1' ``` **Body** ``` waltid.tenant1.did-store1 ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + DID service to which to add an existing DID Store (`{organizationID}.{tenantID}.{didServiceID}`), e.g. `waltid.tenant1.did1` **Body Parameters** - Specifies the ID of the DID store that should be linked. --- **Response Codes** - `201` - Unit attached. -------------------------------------------------------------------------------- PAGE: Full API Reference URL: https://docs.walt.id/enterprise-stack/services/did-service/full-api-reference RAW: https://docs.walt.id/enterprise-stack/services/did-service/full-api-reference.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Full API Reference Find it [here](https://enterprise.sandbox.walt.id/swagger/index.html#/DID%20Service) -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/services/issuer-service/overview RAW: https://docs.walt.id/enterprise-stack/services/issuer-service/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview The **Issuer Service** is walt.id's enterprise solution for creating, signing and distributing verifiable digital credentials based on various formats and standards. ## 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), [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 | --- ## Core Features ### Credential Exchange The Issuer Service supports credential exchange protocols based on: - **OID4VCI:** multiple draft versions (e.g. 11, 13) and flows such as Pre-Authorized Code Flow (with or without PIN) and Authorization Code Flow (with ID Token, VP Token, username/password login or integration with external authorization servers like Keycloak). - **ISO/IEC 18013-7:** remote issuance of mobile Driver's Licenses (mDL). ### 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 initiating the offer. - **After Credential Offer Creation & Before Credential Signing** – enrich credentials dynamically using [data functions](https://docs.walt.id/enterprise-stack/services/issuer-service/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. ### Credential Branding Credential appearance in wallets can be defined via: - [**Issuer Metadata**](https://docs.walt.id/enterprise-stack/services/issuer-service/configurations/openid-metadata/credential-display.md) – branding per credential type ( background color, text color, logo, description). - **Embedded in Credential** – include branding directly in the issued credential for case-specific styling. ### Credential Status & Lifecycle - Built‑in status management (revocation or suspension) through the [Credential Status Service](https://docs.walt.id/enterprise-stack/services/credential-status-service/overview.md), supporting standards such as Bitstring Status List, Token Status List and StatusList2021. - Credentials can include expiration and "valid from" dates. - Lifecycle events and issuance sessions can be viewed in the GUI and via webhooks. ### Keys & DIDs - **Issuer Keys** – store and manage keys via the [KMS Service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md). The KMS service can use external KMS providers (e.g. Azure Key Vault, AWS KMS, OCI, Hashicorp Vault, ...) or store the keys in the Enterprise Stack database (only recommended for non-production use-cases). - **DIDs** – create and store DIDs via the [DID Service](https://docs.walt.id/enterprise-stack/services/did-service/overview.md) and the [DID Registry](https://docs.walt.id/enterprise-stack/services/did-registry/overview.md). ### Session data retention & auto-purge - Issuance sessions can contain personally identifiable information (PII) such as offer payloads, subject data, and interaction logs. Enable the optional data-retention job to define a maximum issuer-session age, purge schedule, and safety guardrails so expired sessions are removed automatically. - Configuration lives in the [Enterprise Stack → Setup → Data Retention](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/data-retention.md) file where you can set `maxIssuerSessionAge`, cron `schedule`, dry-run behavior, and deletion limits to meet your compliance requirements. --- ## Getting Started - [Setup](https://docs.walt.id/enterprise-stack/services/issuer-service/setup.md) – Configure an issuer service inside your tenant - [Issue a W3C Credential via OID4VCI](https://docs.walt.id/enterprise-stack/services/issuer-service/credential-issuance/vc-oid4vc.md) – Walkthrough for issuing a W3C VC - [Issue an SD‑JWT VC via OID4VCI](https://docs.walt.id/enterprise-stack/services/issuer-service/credential-issuance/sd-jwt-vc-oid4vc.md) – Example for SD‑JWT VC issuance - [Configurations](https://docs.walt.id/enterprise-stack/services/issuer-service/configurations/overview.md) – Manage supported credential types and metadata -------------------------------------------------------------------------------- PAGE: Setup URL: https://docs.walt.id/enterprise-stack/services/issuer-service/setup RAW: https://docs.walt.id/enterprise-stack/services/issuer-service/setup.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Setup We will setup an Issuer service inside of a tenant. If you don't have a tenant yet, you can learn how to create one [here](https://docs.walt.id/enterprise-stack/administration/tenants/create.md). **Option: CURL** Endpoint: `/v1/{target}/resource-api/services/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Resource/post_v1__target__resource_api_services_create) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "issuer", "baseUrl": "http://waltid.enterprise.localhost:3000", "kms": "waltid.tenant1.kms1", "tokenKeyId": "waltid.tenant1.kms1.key1", "supportedCredentialTypes": { "identity_credential_vc+sd-jwt": { "format": "vc+sd-jwt", "vct": "{vctBaseURL}/identity_credential", "cryptographic_binding_methods_supported": [ "jwk" ], "credential_signing_alg_values_supported": [ "ES256" ], "sdJwtVcTypeMetadata": { "name": "Identity Credential", "description": "The Identity Verifiable Credential", "vct": "{vctBaseURL}/identity_credential" } }, "OpenBadgeCredential_jwt_vc_json": { "format": "jwt_vc_json", "cryptographic_binding_methods_supported": [ "did" ], "credential_signing_alg_values_supported": [ "ES256" ], "credential_definition": { "type": [ "VerifiableCredential", "OpenBadgeCredential" ] } } }, "displayConfigurations": [ { "name": "walt.id Enterprise Issuer Service", "locale": "en-US", "logo": { "uri": "http://cdn.walt.id/issuer/logo.png", "alt_text": "logo specific text goes here" } } ] }' ``` **Body** ```json { "type": "issuer", "baseUrl": "http://waltid.enterprise.localhost:3000", "kms": "waltid.tenant1.kms1", "tokenKeyId": "waltid.tenant1.kms1.key1", "supportedCredentialTypes": { "identity_credential_vc+sd-jwt": { "format": "vc+sd-jwt", "vct": "{vctBaseURL}/identity_credential", "cryptographic_binding_methods_supported": [ "jwk" ], "credential_signing_alg_values_supported": [ "ES256" ], "sdJwtVcTypeMetadata": { "name": "Identity Credential", "description": "The Identity Verifiable Credential", "vct": "{vctBaseURL}/identity_credential" } }, "OpenBadgeCredential_jwt_vc_json": { "format": "jwt_vc_json", "cryptographic_binding_methods_supported": [ "did" ], "credential_signing_alg_values_supported": [ "ES256" ], "credential_definition": { "type": [ "VerifiableCredential", "OpenBadgeCredential" ] } } }, "displayConfigurations": [ { "name": "walt.id Enterprise Issuer Service", "locale": "en-US", "logo": { "uri": "http://cdn.walt.id/issuer/logo.png", "alt_text": "logo specific text goes here" } } ] } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant in which to create the new Issuer service and the service's ID (`{organizationID}.{tenantID}.[NewIssuerServiceID]`), e.g. `waltid.tenant1.issuer1` **Body Parameters** - `type`: _serviceType_ - Specifies the type of service to create. In our case `issuer` - `baseUrl`: _String_ - This URL will be included in the generated OIDC4VC offer, allowing the wallet to know how to reach the issuer. It should reflect your organization's base URL. The general format for this URL is `https://{orgID}.yourEnterpriseStackUrl.com`. For example, if your organization is named **myorg** and the Enterprise Stack is hosted at the domain **enterprise-stack.com**, your base URL would be: https://myorg.enterprise-stack.com. - `kms`: - _resourceIdentifier_ - A [KMS service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) you want to connect with the to be created Issuer service. It should be setup under the same tenant. - `tokenKeyId`: - _resourceIdentifier_ - A key in the connected [KMS service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) used to sign the access token, which is used to get the credential from the credential endpoint - `supportedCredentialTypes`: _Object_ - A map of credentials the issuer supports. This list will be used to generate the [issuer metadata](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-credential-issuer-metadata). This list of supported credentials can be updated also after the initial setup. For detailed configuration options for each credential type (W3C JWT, SD-JWT VC, ISO mDocs), see [Credential Types Configuration](https://docs.walt.id/enterprise-stack/services/issuer-service/configurations/general-config.md#credential-types). - `displayConfigurations` (optional) - an optional list of objects, where each object contains specific display information for different languages. Including the local option is not mandatory; you can simply provide one object if localization is not required. For detailed information about display configuration properties and examples, see [Issuer Display Configuration](https://docs.walt.id/enterprise-stack/services/issuer-service/configurations/general-config.md#display-config-object). --- **Response Codes** - `201` - Service created successfully. -------------------------------------------------------------------------------- PAGE: Issue W3C Credentials (JWT / SD-JWT) via OID4VC URL: https://docs.walt.id/enterprise-stack/services/issuer-service/credential-issuance/vc-oid4vc RAW: https://docs.walt.id/enterprise-stack/services/issuer-service/credential-issuance/vc-oid4vc.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Issue W3C Credentials (JWT / SD-JWT) via OID4VC This guide provides a 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 Enterprise issuer API. The issuance 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 credentials and present them in a way that's consistent and secure across platforms ensuring interoperability. ## Enterprise Service Dependencies To issue a credential, you need to have the following enterprise services setup: 1. [Issuer Service](https://docs.walt.id/enterprise-stack/services/issuer-service/overview.md) - Have a running issuer service. Setup instructions can be found [here](https://docs.walt.id/enterprise-stack/services/issuer-service/setup.md). 2. [KMS Service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) - Have a running KMS service. Setup instructions can be found [here](https://docs.walt.id/enterprise-stack/services/key-management-service/setup.md). 3. [DID Service](https://docs.walt.id/enterprise-stack/services/did-service/overview.md) - Have a running DID service. Setup instructions can be found [here](https://docs.walt.id/enterprise-stack/services/did-service/setup.md). ## 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 & DID**: A cryptographic key used to sign the credential, confirming its authenticity and integrity during verification and a correlated DID. During [the setup of issuer service](https://docs.walt.id/enterprise-stack/services/issuer-service/setup.md), you have the option to specify which credential types the issuer will support. It is important to ensure that the credential type you wish to issue is included in this selection. You can also update supported credential types after setup via [configuration management](https://docs.walt.id/enterprise-stack/services/issuer-service/configurations/general-config.md). For the purposes of this guide, we will focus on using a W3C `UniversityDegree` credential. ### Example Credential: Verifiable University Degree ```json { // The contextual information required for data integrity and 1.api "@context": [ "https://www.w3.org/ns/credentials/v2", "https://www.w3.org/ns/credentials/examples/v2" ], "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) }, "validFrom": "2020-03-10T04:24:12.164Z", // When the credential becomes valid "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`, `validFrom`, 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. ### Step 1: Get a Signing Key & Issuer DID Please use the KMS service to create a key (see [KMS Service Key Creation](https://docs.walt.id/enterprise-stack/services/key-management-service/local.md#create-key)) and the DID service to [generate a DID](../../did-service/did-creation/overview) for that key and have them at hand for the next section. ### Step 2: Issue the Credential Now, we'll issue a verifiable credential using the key and DID obtained. You can choose to either issue the W3C credential as JWT or SD-JWT, which are two different signature types. JWT referring to a JSON-Web-Token and SD-JWT to a Selective-Disclosure JSON Web Token. 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, 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, SD-JWT). 2) The information about the singed credential will be embedded into the OID Credential Offer URL, which we can send off to our users to claim the credential(s). **Note:** You can receive updates on your issuance progress, including notifications for successfully claimed credentials, by providing the callback header parameter.
**Option: JWT** [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Issuer%20Service/post_v1__target__issuer_service_api_credentials_issue) **Option: CURL** Endpoint: `/v1/{target}/issuer-service-api/credentials/issue` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Issuer%20Service/post_v1__target__issuer_service_api_credentials_issue) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/issuer-service-api/credentials/issue' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'statusCallbackUri: https://example.com/$id' \ -H 'Content-Type: application/json' \ -d '{ "issuerKeyId": "waltid.tenant1.kms1.key1", "issuerDid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp", "authenticationMethod": "PRE_AUTHORIZED", "credentialConfigurationId": "UniversityDegree_jwt_vc_json", "w3cVersion": "W3CV2", "credentialData": { "@context": [ "https://www.w3.org/ns/credentials/v2", "https://www.w3.org/ns/credentials/examples/v2" ], "id": "http://example.gov/credentials/3732", "type": [ "VerifiableCredential", "UniversityDegree" ], "issuer": { "id": "did:web:vc.transmute.world" }, "validFrom": "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": "" }, "validFrom": "", "validUntil": "" } }' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + issuer service with which to execute the credential issuance (`{organizationID}.{tenantID}.{issuerServiceID}`), e.g. `waltid.tenant1.issuer1` **Header Parameters** - `statusCallbackUri`: _URL_ - Receive updates on the created issuance process, e.g. when a credential was successfully claimed. The parameter expects a URL which can accept a JSON POST request. The URL can also hold a `$id`, which will be replaced by the issuance session id. For example: `https://myurl.com/$id`, `https://myurl.com` 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 { "issuerKeyId": "waltid.tenant1.kms1.key1", "issuerDid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp", "authenticationMethod": "PRE_AUTHORIZED", "credentialConfigurationId": "UniversityDegree_jwt_vc_json", "w3cVersion": "W3CV2", "credentialData": { "@context": [ "https://www.w3.org/ns/credentials/v2", "https://www.w3.org/ns/credentials/examples/v2" ], "id": "http://example.gov/credentials/3732", "type": [ "VerifiableCredential", "UniversityDegree" ], "issuer": { "id": "did:web:vc.transmute.world" }, "validFrom": "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": "" }, "validFrom": "", "validUntil": "" } } ``` **Body Parameters** - `issuerKeyId`: _resourceIdentifier_ - The keyID of a key stored in a [KMS service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) under the same tenant. E.g. `waltid.tenant1.kms1.key1` - `issuerDid`: _String_ - The DID related to the provided key. - `credentialConfigurationId`: _String_ - Reference to a specific credential configuration the issuer supports. Configured during [issuer service setup](https://docs.walt.id/enterprise-stack/services/issuer-service/setup.md). - `w3cVersion`: _String_ - The W3C Verifiable Credentials Data Model version to use. Accepted values: `W3CV11` or `W3CV2`. **Note:** The `w3cVersion` parameter controls the format of the **issued credential**. When set to `W3CV2`, the service automatically applies the correct VCDM v2 `@context` (`https://www.w3.org/ns/credentials/v2`) and uses `validFrom`/`validUntil` in place of `issuanceDate`/`expirationDate`, regardless of what is provided in `credentialData`. - `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/enterprise-stack/services/issuer-service/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 - `expiresInSeconds` - (Optional) _Integer_ - The number of seconds the credential offer URL will be valid for. If not provided, the default value is 5 minutes. if the value is set to -1, the URL will not expire. - `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/enterprise-stack/services/verifier-service/policies/static-verification-policies.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 a Credential Offer URL. The offer is provided by reference — the URL contains a `credential_offer_uri` pointing to the full offer object rather than embedding it inline. **Plain Response** ``` openid-credential-offer://{orgID}.enterprise-sandbox.waltid.dev/?credential_offer_uri=https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/issuer-service-api/openid4vc/draft13/credential-offer?id=c4420557-023d-4de0-84bf-9fafa351f103 ``` **Decoded** ``` openid-credential-offer://{orgID}.enterprise-sandbox.waltid.dev/? credential_offer_uri=https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/issuer-service-api/openid4vc/draft13/credential-offer?id=c4420557-023d-4de0-84bf-9fafa351f103 ``` **Resolved Offer** Opening the `credential_offer_uri` returns the full credential offer object: ```json { "credential_issuer": "https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/issuer-service-api/openid4vc/draft13", "grants": { "urn:ietf:params:oauth:grant-type:pre-authorized_code": { "pre-authorized_code": "eyJraWQiOiJqOHBiWjl3UEVpVnZkOHFGa1oyQ0FxMHJNLUI5bWRBMVQ0OTk5UGFZeF9FIiwidHlwIjoiand0IiwiYWxnIjoiRWREU0EifQ.eyJzdWIiOiJjNDQyMDU1Ny0wMjNkLTRkZTAtODRiZi05ZmFmYTM1MWYxMDMiLCJpc3MiOiJodHRwczovL3dhbHRpZC5mZWF0dXJlLXdhbC05MjEuZW50ZXJwcmlzZS50ZXN0LndhbHRpZC5jbG91ZC92MS93YWx0aWQudGVuYW50MS5pc3N1ZXItMi9pc3N1ZXItc2VydmljZS1hcGkvb3BlbmlkNHZjL2RyYWZ0MTMiLCJhdWQiOiJUT0tFTiJ9.cvxzTzo3aTyhCvcB10NDrgIKMaiXqxo3iChAEFdre3wkdzx4s_s4ZKdOM0S8VD9z7GjAFRcyBNfInfuMaTjXBQ" } }, "credential_configuration_ids": [ "UniversityDegree_jwt_vc_json" ] } ``` **Option: SD-JWT** [API Reference](https://issuer.demo.walt.id/swagger/index.html#/Credential%20Issuance/post_openid4vc_sdjwt_issue) **Option: CURL** Endpoint: `/v1/{target}/issuer-service-api/credentials/issue` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%2F%20Issuer%20Service/post_v1__target__issuer_service_api_credentials_issue) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/issuer-service-api/credentials/issue' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'statusCallbackUri: https://example.com/$id' \ -H 'Content-Type: application/json' \ -d '{ "issuerKeyId": "waltid.tenant1.kms1.key1", "issuerDid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp", "credentialConfigurationId": "UniversityDegree_jwt_vc_json", "w3cVersion": "W3CV2", "credentialData": { "@context": [ "https://www.w3.org/ns/credentials/v2", "https://www.w3.org/ns/credentials/examples/v2" ], "id": "http://example.gov/credentials/3732", "type": [ "VerifiableCredential", "UniversityDegree" ], "issuer": { "id": "did:web:vc.transmute.world" }, "validFrom": "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": "" }, "validFrom": "", "validUntil": "" }, "authenticationMethod": "PRE_AUTHORIZED", "selectiveDisclosure": { "fields": { "validFrom": { "sd": true }, "credentialSubject": { "sd": false, "children": { "fields": { "degree": { "sd": false, "children": { "fields": { "name": { "sd": true } } } } } } } } } }' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + issuer service with which to execute the credential issuance (`{organizationID}.{tenantID}.{issuerServiceID}`), e.g. `waltid.tenant1.issuer1` **Header Parameters** - `statusCallbackUri`: _URL_ - Receive updates on the created issuance process, e.g. when a credential was successfully claimed. The parameter expects a URL which can accept a JSON POST request. The URL can also hold a `$id`, which will be replaced by the issuance session id. For example: `https://myurl.com/$id`, `https://myurl.com` 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 { "issuerKeyId": "waltid.tenant1.kms1.key1", "issuerDid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp", "credentialConfigurationId": "UniversityDegree_jwt_vc_json", "w3cVersion": "W3CV2", "credentialData": { "@context": [ "https://www.w3.org/ns/credentials/v2", "https://www.w3.org/ns/credentials/examples/v2" ], "id": "http://example.gov/credentials/3732", "type": [ "VerifiableCredential", "UniversityDegree" ], "issuer": { "id": "did:web:vc.transmute.world" }, "validFrom": "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": "" }, "validFrom": "", "validUntil": "" }, "authenticationMethod": "PRE_AUTHORIZED", "selectiveDisclosure": { "fields": { "validFrom": { "sd": true }, "credentialSubject": { "sd": false, "children": { "fields": { "degree": { "sd": false, "children": { "fields": { "name": { "sd": true } } } } } } } } } } ``` **Body Parameters** - `issuerKeyId`: _resourceIdentifier_ - The keyID of a key stored in a [KMS service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) under the same tenant. E.g. `waltid.tenant1.kms1.key1` - `issuerDid`: _String_ - The DID related to the provided key. - `credentialConfigurationId`: _String_ - Reference to a specific credential configuration the issuer supports. 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__well_known_openid_credential_issuer). - `w3cVersion`: _String_ - The W3C Verifiable Credentials Data Model version to use. Accepted values: `W3CV11` or `W3CV2`. **Note:** The `w3cVersion` parameter controls the format of the **issued credential**. When set to `W3CV2`, the service automatically applies the correct VCDM v2 `@context` (`https://www.w3.org/ns/credentials/v2`) and uses `validFrom`/`validUntil` in place of `issuanceDate`/`expirationDate`, regardless of what is provided in `credentialData`. - `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/enterprise-stack/services/issuer-service/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 - `expiresInSeconds` - (Optional) _Integer_ - The number of seconds the credential offer URL will be valid for. If not provided, the default value is 5 minutes. if the value is set to -1, the URL will not expire. - `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/enterprise-stack/services/verifier-service/policies/static-verification-policies.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": { "validFrom": { "sd": true }, "credentialSubject": { "sd": false, "children": { "fields": { "degree": { "sd": false, "children": { "fields": { "name": { "sd": true } } } } } } } } } ``` **Example Response** The issuer endpoint will respond with a Credential Offer URL. The offer is provided by reference — the URL contains a `credential_offer_uri` pointing to the full offer object rather than embedding it inline. **Plain Response** ``` openid-credential-offer://{orgID}.enterprise-sandbox.waltid.dev/?credential_offer_uri=https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/issuer-service-api/openid4vc/draft13/credential-offer?id=c4420557-023d-4de0-84bf-9fafa351f103 ``` **Decoded** ``` openid-credential-offer://{orgID}.enterprise-sandbox.waltid.dev/? credential_offer_uri=https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/issuer-service-api/openid4vc/draft13/credential-offer?id=c4420557-023d-4de0-84bf-9fafa351f103 ``` **Resolved Offer** Opening the `credential_offer_uri` returns the full credential offer object: ```json { "credential_issuer": "https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/issuer-service-api/openid4vc/draft13", "grants": { "urn:ietf:params:oauth:grant-type:pre-authorized_code": { "pre-authorized_code": "eyJraWQiOiJqOHBiWjl3UEVpVnZkOHFGa1oyQ0FxMHJNLUI5bWRBMVQ0OTk5UGFZeF9FIiwidHlwIjoiand0IiwiYWxnIjoiRWREU0EifQ.eyJzdWIiOiJjNDQyMDU1Ny0wMjNkLTRkZTAtODRiZi05ZmFmYTM1MWYxMDMiLCJpc3MiOiJodHRwczovL3dhbHRpZC5mZWF0dXJlLXdhbC05MjEuZW50ZXJwcmlzZS50ZXN0LndhbHRpZC5jbG91ZC92MS93YWx0aWQudGVuYW50MS5pc3N1ZXItMi9pc3N1ZXItc2VydmljZS1hcGkvb3BlbmlkNHZjL2RyYWZ0MTMiLCJhdWQiOiJUT0tFTiJ9.cvxzTzo3aTyhCvcB10NDrgIKMaiXqxo3iChAEFdre3wkdzx4s_s4ZKdOM0S8VD9z7GjAFRcyBNfInfuMaTjXBQ" } }, "credential_configuration_ids": [ "UniversityDegree_jwt_vc_json" ] } ``` 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 Now you can receive the credential offer via the wallet service API. See [Receive Credential Offer](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/legacy-draft13/vc-oid4vci.md) for more details. 🎉 Congratulations, you've issued a W3C-compliant Verifiable Credential using OID4VCI! 🎉 -------------------------------------------------------------------------------- PAGE: Issue SD-JWT VC Credentials via OID4VC URL: https://docs.walt.id/enterprise-stack/services/issuer-service/credential-issuance/sd-jwt-vc-oid4vc RAW: https://docs.walt.id/enterprise-stack/services/issuer-service/credential-issuance/sd-jwt-vc-oid4vc.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Issue SD-JWT VC Credentials via OID4VC 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 Enterprise Issuer API. If you are interested to learn how to issue an W3C Verifiable Credentials, please go [here](https://docs.walt.id/enterprise-stack/services/issuer-service/credential-issuance/vc-oid4vc.md). The issuance process will utilize the OID4VCI protocol, an extension of OpenID, facilitating secure and standardized communication between identities. ## Enterprise Service Dependencies To issue a credential, you need to have the following enterprise services setup: 1. [Issuer Service](https://docs.walt.id/enterprise-stack/services/issuer-service/overview.md) - Have a running issuer service. Setup instructions can be found [here](https://docs.walt.id/enterprise-stack/services/issuer-service/setup.md). 2. [KMS Service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) - Have a running KMS service. Setup instructions can be found [here](https://docs.walt.id/enterprise-stack/services/key-management-service/setup.md). 3. [DID Service](https://docs.walt.id/enterprise-stack/services/did-service/overview.md) - Have a running DID service. Setup instructions can be found [here](https://docs.walt.id/enterprise-stack/services/did-service/setup.md). ## 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. During [the setup of issuer service](https://docs.walt.id/enterprise-stack/services/issuer-service/setup.md), you can specify which credential types the issuer will support. It is important to ensure that the credential type you wish to issue is included in this selection. You can also update supported credential types after setup via [configuration management](https://docs.walt.id/enterprise-stack/services/issuer-service/configurations/general-config.md). For this guide, we will focus on using a SD-JWT-VC Identity Credential. ### 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 related Enterprise APIs. ### Step 1: Get a Signing Key & Issuer DID Please use the KMS service to [create a key](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) and the DID service to [generate a DID](https://docs.walt.id/enterprise-stack/services/did-service/overview.md) for that key and have them at hand for the next section. ### 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. The Enterprise 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://waltid.enterprise-demo.waltid.dev/.well-known/vct/v1/waltid.tenant1.issuer1/issuer-service-api/openid4vc/identity_credential`). Find more info [here](https://docs.walt.id/concepts/digital-credentials/sd-jwt-vc.md#verifiable-credential-type-and-type-metadata)
**Note:** You can receive updates on your issuance progress, including notifications for successfully claimed credentials, by providing the callback header parameter.
**Option: CURL** Endpoint: `/v1/{target}/issuer-service-api/credentials/issue` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%2F%20Issuer%20Service/post_v1__target__issuer_service_api_credentials_issue) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/issuer-service-api/credentials/issue' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'statusCallbackUri: https://example.com/$id' \ -H 'Content-Type: application/json' \ -d '{ "issuerKeyId": "waltid.tenant1.kms1.key1", "issuerDid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp", "authenticationMethod": "PRE_AUTHORIZED", "credentialConfigurationId": "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 } }' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + issuer service with which to execute the credential issuance (`{organizationID}.{tenantID}.{issuerServiceID}`), e.g. `waltid.tenant1.issuer1` **Header Parameters** - `statusCallbackUri`: _URL_ - Receive updates on the created issuance process, e.g. when a credential was successfully claimed. The parameter expects a URL which can accept a JSON POST request. The URL can also hold a `$id`, which will be replaced by the issuance session id. For example: `https://myurl.com/$id`, `https://myurl.com` 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 { "issuerKeyId": "waltid.tenant1.kms1.key1", "issuerDid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp", "authenticationMethod": "PRE_AUTHORIZED", "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 } } ``` **Body Parameters** - `issuerKeyId`: _resourceIdentifier_ - The keyID of a key stored in a [KMS service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) under the same tenant. E.g. `waltid.tenant1.kms1.key1` - `issuerDid`: _String_ - The DID related to the provided key. - `credentialConfigurationId`: _String_ - Reference to a specific credential configuration the issuer supports. 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__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/enterprise-stack/services/issuer-service/data-functions.md). 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 - `expiresInSeconds` - (Optional) _Integer_ - The number of seconds the credential offer URL will be valid for. If not provided, the default value is 5 minutes. if the value is set to -1, the URL will not expire. - `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/enterprise-stack/services/verifier-service/policies/static-verification-policies.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/sd-jwt/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. The Enterprise 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://waltid.enterprise-demo.waltid.dev/.well-known/vct/v1/waltid.tenant1.issuer1/issuer-service-api/openid4vc/identity_credential`). Find more info [here](https://docs.walt.id/concepts/digital-credentials/sd-jwt-vc.md#verifiable-credential-type-and-type-metadata)
**Note:** You can receive updates on your issuance progress, including notifications for successfully claimed credentials, by providing the callback header parameter.
**Option: CURL** Endpoint: `/v1/{target}/issuer-service-api/credentials/issue` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%2F%20Issuer%20Service/post_v1__target__issuer_service_api_credentials_issue) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/issuer-service-api/credentials/issue' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'statusCallbackUri: https://example.com/$id' \ -H 'Content-Type: application/json' \ -d '{ "issuerKeyId": "waltid.tenant1.kms1.key1", "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": { "name": { "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" ] }' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + issuer service with which to execute the credential issuance (`{organizationID}.{tenantID}.{issuerServiceID}`), e.g. `waltid.tenant1.issuer1` **Header Parameters** - `statusCallbackUri`: _URL_ - Receive updates on the created issuance process, e.g. when a credential was successfully claimed. The parameter expects a URL which can accept a JSON POST request. The URL can also hold a `$id`, which will be replaced by the issuance session id. For example: `https://myurl.com/$id`, `https://myurl.com` 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 { "issuerKeyId": "waltid.tenant1.kms1.key1", "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": { "name": { "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** - `issuerKeyId`: _resourceIdentifier_ - The keyID of a key stored in a [KMS service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) under the same tenant. E.g. `waltid.tenant1.kms1.key1` - `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__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/enterprise-stack/services/issuer-service/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 - `expiresInSeconds` - (Optional) _Integer_ - The number of seconds the credential offer URL will be valid for. If not provided, the default value is 5 minutes. if the value is set to -1, the URL will not expire. - `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/enterprise-stack/services/verifier-service/policies/static-verification-policies.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 Now you can receive the credential offer via the wallet service API. See [Receive Credential Offer](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/legacy-draft13/sd-jwt-vc-oid4vci.md) for more details. 🎉 Congratulations, you've issued a SD-JWT VC Credential using OID4VCI! 🎉 -------------------------------------------------------------------------------- PAGE: Issue Mobile Driver's Licenses (ISO/IEC 18013-5 mDL) via OID4VCI URL: https://docs.walt.id/enterprise-stack/services/issuer-service/credential-issuance/mdl-oid4vc RAW: https://docs.walt.id/enterprise-stack/services/issuer-service/credential-issuance/mdl-oid4vc.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # How to Issue Mobile Driver's Licenses (ISO/IEC 18013-5 mDLs) via OID4VCI with walt.id Enterprise 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 Enterprise Issuer API. The issuance process will utilize the [OID4VCI protocol](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-ID1.html). ## Enterprise Service Dependencies To issue a credential, you need to have the following enterprise services setup: 1. [Issuer Service](https://docs.walt.id/enterprise-stack/services/issuer-service/overview.md) - Have a running issuer service. Setup instructions can be found [here](https://docs.walt.id/enterprise-stack/services/issuer-service/setup.md). 2. [KMS Service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) - Have a running KMS service. Setup instructions can be found [here](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md). ## 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 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 using the Enterprise Stack Issuer API. **Note**: At the moment, you can only use `secp256r1` keys to issue mDLs. ### Prerequisites Before issuing mDLs, you need to have: 1. **IACA and Document Signer Certificates**: The Enterprise Stack does not provide onboarding endpoints for IACA and Document Signer certificates. You need to obtain these certificates through external means or use the Community Stack onboarding functionality. **For IACA and Document Signer onboarding**, you can: - Use the [Community Stack mDL onboarding guide](https://docs.walt.id/community-stack/issuer/credential-issuance/mdl-oid4vc.md) to generate the required certificates - Import existing certificates into your Enterprise Stack key management system - Use external PKI services to generate the required certificates 2. **Issuer Service Configuration**: Ensure your issuer service is properly configured with the required mDL signing keys. ### Step 1: Configure mDL Credential Type First, ensure your issuer service supports the mDL credential type. When creating an issuer service, include the mDL configuration: ```json { "type": "issuer", "baseUrl": "https://your-issuer.example.com", "kms": "your-kms-service", "tokenKeyId": "your-signing-key", "supportedCredentialTypes": { "org.iso.18013.5.1.mDL": { "format": "mso_mdoc", "credentialDefinition": { "type": ["org.iso.18013.5.1.mDL"] }, "docType": "org.iso.18013.5.1.mDL" } } } ``` ### Step 2: Issue an mDL Credential Use the Enterprise Stack credential issuance endpoint to issue mDL credentials: **Option: CURL** **Endpoint:** `/v1/{target}/issuer-service-api/credentials/issue` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Issuer%20Service/post_v1__target__issuer_service_api_credentials_issue) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/issuer-service-api/credentials/issue' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -d '{ "issuerKeyId": "your-signing-key-id", "credentialConfigurationId": "org.iso.18013.5.1.mDL", "credentialData": { "issuerKeyId": "waltid.tenant2.kms1.secp-r1", "credentialConfigurationId": "org.iso.18013.5.1.mDL", "credentialData": { "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" } }, "x5Chain": [ "-----BEGIN CERTIFICATE-----\nMIICCDCCAa2gAwIBAgIUDo8kr194t6sttt6KL3YcnMtcaYYwCgYIKoZIzj0EAwIwJDELMAkGA1UEBhMCVVMxFTATBgNVBAMMDEV4YW1wbGUgSUFDQTAeFw0yNTA1MjkwNzE4MzlaFw0yNjA4MjkwNzE4MzlaMCIxCzAJBgNVBAYTAlVTMRMwEQYDVQQDDApFeGFtcGxlIERTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEGWKpdL3jPoPJ5wKgSA+jxS2jgp+ZUDE6sIQbeB86vF0XfEDAf3r/FVyJXuYVCyVT95FCA/7mlJmI1/xuMygZ+qOBvjCBuzAfBgNVHSMEGDAWgBSMIxGx+iVN4rkOzoyo5aPk3HTUFDAdBgNVHQ4EFgQU7S49LSeg/e0onfT44FVbL/rSKnswDgYDVR0PAQH/BAQDAgeAMCMGA1UdEgQcMBqGGGh0dHBzOi8vaWFjYS5leGFtcGxlLmNvbTAVBgNVHSUBAf8ECzAJBgcogYxdBQECMC0GA1UdHwQmMCQwIqAgoB6GHGh0dHBzOi8vaWFjYS5leGFtcGxlLmNvbS9jcmwwCgYIKoZIzj0EAwIDSQAwRgIhAMuSq75BPBXXBWGtIMd57fhRqpKf3Yzl3ldDdoQsK2xEAiEA/dmWLMLiJPV3UzmQS5MUHtn611z0VlL/k3YAdaVJ51c=\n-----END CERTIFICATE-----\n" ], "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" }, "portrait": { "type": "string", "conversionType": "base64StringToByteString" }, "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" } } } ] } } } } }' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + issuer service with which to execute the credential issuance (`{organizationID}.{tenantID}.{issuerServiceID}`), e.g. `waltid.tenant1.issuer1` **Body** ```json { "issuerKeyId": "your-signing-key-id", "credentialConfigurationId": "org.iso.18013.5.1.mDL", "credentialData": { "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" } }, "x5Chain": [ "-----BEGIN CERTIFICATE-----\nMIICCDCCAa2gAwIBAgIUDo8kr194t6sttt6KL3YcnMtcaYYwCgYIKoZIzj0EAwIwJDELMAkGA1UEBhMCVVMxFTATBgNVBAMMDEV4YW1wbGUgSUFDQTAeFw0yNTA1MjkwNzE4MzlaFw0yNjA4MjkwNzE4MzlaMCIxCzAJBgNVBAYTAlVTMRMwEQYDVQQDDApFeGFtcGxlIERTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEGWKpdL3jPoPJ5wKgSA+jxS2jgp+ZUDE6sIQbeB86vF0XfEDAf3r/FVyJXuYVCyVT95FCA/7mlJmI1/xuMygZ+qOBvjCBuzAfBgNVHSMEGDAWgBSMIxGx+iVN4rkOzoyo5aPk3HTUFDAdBgNVHQ4EFgQU7S49LSeg/e0onfT44FVbL/rSKnswDgYDVR0PAQH/BAQDAgeAMCMGA1UdEgQcMBqGGGh0dHBzOi8vaWFjYS5leGFtcGxlLmNvbTAVBgNVHSUBAf8ECzAJBgcogYxdBQECMC0GA1UdHwQmMCQwIqAgoB6GHGh0dHBzOi8vaWFjYS5leGFtcGxlLmNvbS9jcmwwCgYIKoZIzj0EAwIDSQAwRgIhAMuSq75BPBXXBWGtIMd57fhRqpKf3Yzl3ldDdoQsK2xEAiEA/dmWLMLiJPV3UzmQS5MUHtn611z0VlL/k3YAdaVJ51c=\n-----END CERTIFICATE-----\n" ], "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" }, "portrait": { "type": "string", "conversionType": "base64StringToByteString" }, "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" } } } ] } } } } } ``` **Body Parameters** - `issuerKeyId`: _String_ - The ID of the signing key to use for credential issuance - `credentialConfigurationId`: _String_ - Must be `"org.iso.18013.5.1.mDL"` for mDL credentials - `credentialData`: _JSON_ - The mDL credential data containing all required and optional fields - `authenticationMethod`: _String_ - Authentication method for the issuance session (default: `"PRE_AUTHORIZED"`) - `expiresInSeconds`: _Number_ - Session expiration time in seconds (default: 300) - `x5Chain`: _Array of Strings_ - Array of Strings - The X.509 certificate chain for the Document Signer, starting with the DS certificate. - `mDocNameSpacesDataMappingConfig`: _JSON_ - The mapping configuration for the mDoc name spaces data. This allows us to use non-primitive data types in the mDoc claims. --- **Example Response** The issuance endpoint will return a credential offer URL that can be used to complete the issuance process: ```json openid-credential-offer://waltid.enterprise.test.waltid.cloud/?credential_offer_uri=https%3A%2F%2Fwaltid.enterprise.test.waltid.cloud%2Fv1%2Fwaltid.tenant2.issuer-mdl%2Fissuer-service-api%2Fopenid4vc%2Fdraft13%2Fcredential-offer%3Fid%3D38672f8b-bed0-4e29-b6db-c0e2580a7fa0 ``` ## Step 4: Receive the Credential Offer Now you can receive the credential offer via the wallet service API. See [Receive Credential Offer](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/legacy-draft13/mdl-oid4vci.md) for more details. 🎉 Congratulations, you've issued a mDL using OID4VCI! 🎉 -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/services/issuer-service/configurations/overview RAW: https://docs.walt.id/enterprise-stack/services/issuer-service/configurations/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview The issuer, similar to other services in the Enterprise Stack, maintains a collection of configurations. These configurations include **internal configs such as signing keys for access tokens and the base URL**, which are used solely within the Enterprise Stack. Additionally, the issuer holds configurations that is both internally used and **externally shared, such as supported credential types and issuer display information**. This external information is made available through the **OpenID issuer metadata endpoint**, which can be accessed by wallets and other external applications involved in the credential exchange process. - [General](https://docs.walt.id/enterprise-stack/services/issuer-service/configurations/general-config.md) - Get and update all configurations at once. - [OpenID Metadata Display](https://docs.walt.id/enterprise-stack/services/issuer-service/configurations/openid-metadata/issuer-display.md) - Create, update, and delete openid metadata issuer display information. -------------------------------------------------------------------------------- PAGE: General Config URL: https://docs.walt.id/enterprise-stack/services/issuer-service/configurations/general-config RAW: https://docs.walt.id/enterprise-stack/services/issuer-service/configurations/general-config.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # General Config The issuer, similar to other services in the Enterprise Stack, maintains a collection of configurations. These configurations include **internal configs such as signing keys for access tokens and the base URL**, which are used solely within the Enterprise Stack. Additionally, the issuer holds configurations that is both internally used and **externally shared, such as supported credential types and issuer display information**. This external information is made available through the **OpenID issuer metadata endpoint**, which can be accessed by wallets and other external applications involved in the credential exchange process. ## Get Issuer Config **Option: CURL** Endpoint: `/v1/{target}/issuer-service-api/configuration/view` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%2F%20Issuer%20Service/get_v1__target__issuer_service_api_configuration_view) **Example Request** ```bash curl -X 'GET' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/issuer-service-api/configuration/view' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + issuer service from which to receive the configuration(`{organizationID}.{tenantID}.{issuerServiceID}`), e.g. `waltid.tenant1.issuer1` **Response** - `201` - Service created successfully. ```json { "dependencies": [], "traversable": true, "_id": "waltid.tenant1.issuer1", "baseUrl": "http://waltid.enterprise.localhost:3000", "kms": "waltid.tenant1.kms1", "tokenKeyId": "waltid.tenant1.kms1.key1", "parent": "waltid.tenant1", "supportedCredentialTypes": { "identity_credential_vc+sd-jwt": { "format": "vc+sd-jwt", "vct": "{vctBaseURL}/identity_credential", "cryptographic_binding_methods_supported": [ "jwk" ], "credential_signing_alg_values_supported": [ "ES256" ], "sdJwtVcTypeMetadata": { "name": "Identity Credential", "description": "The Identity Verifiable Credential", "vct": "{vctBaseURL}/identity_credential" } }, "OpenBadgeCredential_jwt_vc_json": { "format": "jwt_vc_json", "cryptographic_binding_methods_supported": [ "did" ], "credential_signing_alg_values_supported": [ "ES256" ], "credential_definition": { "type": [ "VerifiableCredential", "OpenBadgeCredential" ] } } }, "displayConfigurations": [ { "name": "walt.id Enterprise Issuer Service", "locale": "en-US", "logo": { "uri": "http://cdn.walt.id/issuer/logo.png", "alt_text": "logo specific text goes here" } } ] } ``` - `dependencies`: _List_ - List of service dependencies. - `id`: _ID_ - Unique ID of this service. - `baseUrl`: _String_ - This URL will be included in the generated OIDC4VC offer, allowing the wallet to know how to reach the issuer. It should reflect your organization's base URL. The general format for this URL is `https://{orgID}.yourEnterpriseStackUrl.com`. For example, if your organization is named **myorg** and the Enterprise Stack is hosted at the domain **enterprise-stack.com**, your base URL would be: https://myorg.enterprise-stack.com. - `kms`: - _resourceIdentifier_ - The [KMS service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) used for signing access tokens. The exact key is specified in via the `tokenKeyId` field. - `tokenKeyId`: - _resourceIdentifier_ - A key in the connected [KMS service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) used to sign the access token, which is used by the wallet to get the credential from the credential endpoint - `supportedCredentialTypes`: _Object_ - A map of credentials the issuer supports. This list will be used to generate the [issuer metadata](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-credential-issuer-metadata). - `displayConfigurations` - issuer display metadata configs. Each object contains specific display information for different languages. To learn more about the display config in general go [here](https://docs.walt.id/enterprise-stack/services/issuer-service/configurations/openid-metadata/issuer-display.md) ## Update Issuer Config **Option: CURL** Endpoint: `/v1/{target}/issuer-service-api/configuration/update` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%2F%20Issuer%20Service/put_v1__target__issuer_service_api_configuration_update) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/issuer-service-api/configuration/update' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "dependencies": [], "traversable": true, "_id": "waltid.tenant1.issuer1", "supportedCredentialTypes": { "identity_credential_vc+sd-jwt": { "format": "vc+sd-jwt", "vct": "{vctBaseURL}/identity_credential", "cryptographic_binding_methods_supported": [ "jwk" ], "credential_signing_alg_values_supported": [ "ES256" ], "sdJwtVcTypeMetadata": { "name": "Identity Credential", "description": "The Identity Verifiable Credential", "vct": "{vctBaseURL}/identity_credential" } }, "OpenBadgeCredential_jwt_vc_json": { "format": "jwt_vc_json", "cryptographic_binding_methods_supported": [ "did" ], "credential_signing_alg_values_supported": [ "ES256" ], "credential_definition": { "type": [ "VerifiableCredential", "OpenBadgeCredential" ] } } }, "tokenKeyId": "waltid.tenant1.kms1.key1", "kms": "waltid.tenant1.kms1", "baseUrl": "http://waltid.enterprise.localhost:3000", "parent": "waltid.tenant1", "displayConfigurations": [ { "name": "walt.id Enterprise Issuer Service", "locale": "en-US", "logo": { "uri": "http://cdn.walt.id/issuer/logo.png", "alt_text": "logo specific text goes here" } } ] }' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + issuer service in which to update the configuration(`{organizationID}.{tenantID}.{issuerServiceID}`), e.g. `waltid.tenant1.issuer1` **Body Parameters** - `dependencies`: _List_ - List of service dependencies. - `traversable`: _Boolean_ - Must be `true`. - `id`: _ID_ - Unique ID of this service. - `supportedCredentialTypes`: _Object_ - A map of credentials the issuer supports. This list will be used to generate the [issuer metadata](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-credential-issuer-metadata). - `baseUrl`: _String_ - This URL will be included in the generated OIDC4VC offer, allowing the wallet to know how to reach the issuer. It should reflect your organization's base URL. The general format for this URL is `https://{orgID}.yourEnterpriseStackUrl.com`. For example, if your organization is named **myorg** and the Enterprise Stack is hosted at the domain **enterprise-stack.com**, your base URL would be: https://myorg.enterprise-stack.com. - `kms`: - _resourceIdentifier_ - A [KMS service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) connect with the Issuer service. It should be setup under the same tenant. - `tokenKeyId`: - _resourceIdentifier_ - A key in the connected [KMS service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) used to sign the access token, which is used to get the credential from the credential endpoint - `displayConfigurations` (optional) - an optional list of objects, where each object contains specific display information for the issuer for different languages. Including the local option is not mandatory; you can simply provide one object if localization is not required. To learn more about the display config in general go [here](https://docs.walt.id/enterprise-stack/services/issuer-service/configurations/openid-metadata/issuer-display.md), for object property details see below. ## Configuration Details ### Display Config Object ```json { "name": "walt.id Enterprise Issuer Service", "locale": "en-US", "logo": { "uri": "http://cdn.walt.id/issuer/logo.png", "alt_text": "Logo of walt.id Enterprise Issuer Service" } } ``` - `name` (optional) _String_: String value of a display name for the Credential Issuer. - `locale` (optional) _String_: String value that identifies the language of this object represented as a language tag taken from values defined in BCP47 [RFC5646](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#RFC5646). There MUST be only one object for each language identifier. Example values are: `en-US`, `de-DE`, or `fr-FR`. - `logo` (optional) _Object_: Object with information about the logo of the Credential Issuer. - `uri` _String_: String value that contains a URI where the Wallet can obtain the logo of the Credential Issuer. - `alt_text` (optional) _String_: String value of the alternative text for the logo image. ### Credential Types #### W3C JWT & SD-JWT Credentials To specify the support of a W3C credential the object will look as follows: ```json "OpenBadgeCredential_jwt_vc_json": { "format": "jwt_vc_json", "cryptographic_binding_methods_supported": [ "did" ], "credential_signing_alg_values_supported": [ "ES256" ], "credential_definition": { "type": [ "VerifiableCredential", "OpenBadgeCredential" ] } } ``` The key will have the following structure: `[CustomCredentialType]_jwt_vc_json`, e.g. `OpenBadgeCredential_jwt_vc_json` Inside the object: - `format` - will be `jwt_vc_json` for W3C JWT & SD-JWT credentials - `cryptographic_binding_methods_supported` - will be `did` - `credential_signing_alg_values_supported` - will be `ES256` - `credential_definition` - `type` - specifies a list of credential types. E.g. `[VerifiableCredential, MyCustomCredential]` 1. First Entry: Your list must always start with `VerifiableCredential`. 2. Subsequent Entries: After `VerifiableCredential`, you have two options: - You can add your custom type, such as `CustomCredential`. - If your credential is based on another credential (for example, `VerifiableAttestation`), first list all the credentials it builds upon, and then add your custom type at the end. #### SD-JWT VC Credentials To specify the support of a SD-JWT VC credential the object will look as follows: ```json "identity_credential_vc+sd-jwt": { "format": "vc+sd-jwt", "vct": "{vctBaseURL}/identity_credential", "cryptographic_binding_methods_supported": [ "jwk" ], "credential_signing_alg_values_supported": [ "ES256" ], "sdJwtVcTypeMetadata": { "name": "Identity Credential", "description": "The Identity Verifiable Credential", "vct": "{vctBaseURL}/identity_credential" } } ``` The key will have the following structure: `[custom_credential_type]_vc+sd-jwt`, e.g. `identity_credential_vc+sd-jwt` Inside the object: - `format` - will be `vc+sd-jwt` for SD-JWT VC credentials. - `vct` - The Verifiable Credential Type URL. You can either: - Use the auto-generated format: `{vctBaseURL}/[custom_credential_type]` (e.g. `{vctBaseURL}/identity_credential`). The `vctBaseURL` will be replaced during issuance by the issuer API. Or you may choose to **use your own external VCT URL**: Provide a fully qualified URL pointing to your own VCT metadata document (e.g. `https://schema.example.com/vct/identity_credential`). This enables you to use shared VCTs in ecosystem setups, where multiple issuers reference the same schema repository. The credential data will be validated against the schema defined in your VCT during issuance. - `cryptographic_binding_methods_supported` - will be `jwk` - `credential_signing_alg_values_supported` - will be `ES256` - `sdJwtVcTypeMetadata` (optional) - Metadata for the credential type. If omitted, the issuer API will synthesize metadata from your configuration. The document may contain either the `schema` or `schema_uri` properties: - `name` - Name of the credential - `description` - Description of the credential - `vct` - Holding the same value as the `vct` above. **Note:** **Using External VCT URLs**: When configuring your own VCT URL, ensure it points to a valid VCT metadata document that conforms to the SD-JWT VC specification. The document must be accessible and contain a valid JSON schema. During credential issuance, the credential data will be validated against this schema. #### ISO mDocs To specify the support of a mDoc credential the object will look as follows (example for mDL credentials): ```json "org.iso.18013.5.1.mDL": { "format": "mso_mdoc", "cryptographic_binding_methods_supported": [ "cose_key" ], "credential_signing_alg_values_supported": [ "ES256" ], "credential_definition": { "type": [ "org.iso.18013.5.1.mDL" ] }, "doctype": "org.iso.18013.5.1.mDL" } ``` -------------------------------------------------------------------------------- PAGE: Issuer Display URL: https://docs.walt.id/enterprise-stack/services/issuer-service/configurations/openid-metadata/issuer-display RAW: https://docs.walt.id/enterprise-stack/services/issuer-service/configurations/openid-metadata/issuer-display.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Issuer Display The issuer metadata display options enable the issuer service to expose details such as issuer name and logo in a standardized way with other services such as digital wallets. These services can then use this information to display a human-readable issuer name and logo to the end-user during credential exchange. Learn more about the display options in the OID4VCI standard [here](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-credential-issuer-metadata:~:text=display%3A%20OPTIONAL.%20Array%20of%20objects%2C%20where%20each%20object%20contains%20display,String%20value%20of%20the%20alternative%20text%20for%20the%20logo%20image). The display info is exposed via the public endpoints `/.well-known/openid-credential-issuer`, `/.well-known/oauth-authorization-server`, `/.well-known/openid-configuration` ## Example of Display Options Here is an example of how display options can be provided in the issuer service. ```json [ { "name": "walt.id Enterprise Issuer Service", "locale": "en-US", "logo": { "uri": "http://cdn.walt.id/issuer/logo.png" } } ] ``` This allows the issuer to specify localized names and logos, ensuring that users in different regions can view the information in their preferred language and format. ## Get Display Options **Option: CURL** Endpoint: `/v1/{target}/issuer-service-api/configuration/openid-metadata/display/view` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Issuer%20Service/get_v1__target__issuer_service_api_configuration_openid_metadata_display_view) **Example Request** ```bash curl -X 'GET' \ 'http://waltid.enterprise.localhost:8000/v1/{target}/issuer-service-api/configuration/openid-metadata/display/view' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + issuer from which to retrieve the issuer display info (`{organizationID}.{tenantID}.[NewIssuerServiceID]`), e.g. `waltid.tenant1.issuer1` **Response** - `200` - Config retrieved successfully. ```json [ { "name": "walt.id Enterprise Issuer Service", "locale": "en-US", "logo": { "uri": "http://cdn.walt.id/issuer/logo.png", "alt_text": "logo specific text goes here" } } ] ``` - `401` - Invalid authentication ## Update Display Options **Option: CURL** Endpoint: `/v1/{target}/issuer-service-api/configuration/openid-metadata/display/update` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Issuer%20Service/put_v1__target__issuer_service_api_configuration_openid_metadata_display_update) **Example Request** ```bash curl -X 'PUT' \ 'http://waltid.enterprise.localhost:8000/v1/{target}/issuer-service-api/configuration/openid-metadata/display/update' \ -H 'accept: */*' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -d '[ { "name": "walt.id Enterprise Issuer Service", "locale": "en-US", "logo": { "uri": "http://cdn.walt.id/issuer/logo.png" } }, { "name": "walt.id Enterprise Issuer Service", "locale": "en-GB", "logo": { "uri": "http://cdn.walt.id/issuer/logo.png" } } ]' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + issuer in which to update the issuer display info (`{organizationID}.{tenantID}.[NewIssuerServiceID]`), e.g. `waltid.tenant1.issuer1` **Body** ```json [ { "name": "walt.id Enterprise Issuer Service", "locale": "en-US", "logo": { "uri": "http://cdn.walt.id/issuer/logo.png" } }, { "name": "walt.id Enterprise Issuer Service", "locale": "en-GB", "logo": { "uri": "http://cdn.walt.id/issuer/logo.png" } } ] ``` **Body Parameters** The body is structured as a list of objects, with each object offering specific display information for different languages. Providing the `local` option is optional, so it is enough to specify just one object if no localization is needed. **Display Config Object** ```json { "name": "walt.id Enterprise Issuer Service", "locale": "en-US", "logo": { "uri": "http://cdn.walt.id/issuer/logo.png", "alt_text": "Logo of walt.id Enterprise Issuer Service" } } ``` - `name` (optional) _String_: String value of a display name for the Credential Issuer. - `locale` (optional) _String_: String value that identifies the language of this object represented as a language tag taken from values defined in BCP47 [RFC5646](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#RFC5646). There MUST be only one object for each language identifier. Example values are: `en-US`, `de-DE`, or `fr-FR`. - `logo` (optional) _Object_: Object with information about the logo of the Credential Issuer. - `uri` _String_: String value that contains a URI where the Wallet can obtain the logo of the Credential Issuer. - `alt_text` (optional) _String_: String value of the alternative text for the logo image. --- **Response** - `200` - Config updated successfully. ```json [ { "name": "walt.id Enterprise Issuer Service", "locale": "en-US", "logo": { "uri": "http://cdn.walt.id/issuer/logo.png" } }, { "name": "walt.id Enterprise Issuer Service", "locale": "en-GB", "logo": { "uri": "http://cdn.walt.id/issuer/logo.png" } } ] ``` - `401` - Invalid authentication ## Delete Display Options **Option: CURL** Endpoint: `/v1/{target}/issuer-service-api/configuration/openid-metadata/display/delete` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Issuer%20Service/delete_v1__target__issuer_service_api_configuration_openid_metadata_display_delete) **Example Request** ```bash curl -X 'DELETE' \ 'http://waltid.enterprise.localhost:8000/v1/{target}/issuer-service-api/configuration/openid-metadata/display/delete' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + issuer in which to delete the issuer display info (`{organizationID}.{tenantID}.[NewIssuerServiceID]`), e.g. `waltid.tenant1.issuer1` --- **Response** - `200` - Config deleted successfully. - `401` - Invalid authentication -------------------------------------------------------------------------------- PAGE: Credential Display URL: https://docs.walt.id/enterprise-stack/services/issuer-service/configurations/openid-metadata/credential-display RAW: https://docs.walt.id/enterprise-stack/services/issuer-service/configurations/openid-metadata/credential-display.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Credential Display ## Overview The Display Metadata feature in the Enterprise Stack enhances credential branding and localization by allowing issuers to define rich, user-friendly visual and textual representations of credentials. These representations are published in the issuer-metadata and can be automatically embedded into issued credentials upon request. This enables seamless support for localized credential rendering, visual customization, and branded digital credentials across wallets and verifiers. ## Configuration The configuration of credential display options is done through [the issuer service config](https://docs.walt.id/enterprise-stack/services/issuer-service/configurations/general-config.md). The issuer service config allows you to define the display options for credentials, including the logo, name, and other metadata that can be used by wallets and verifiers to present the credentials in a user-friendly manner. ## Example of Credential Display Options Here is an example of how credential display options can be provided in the issuer service. ```json { "supportedCredentialTypes": { "UniversityDegree_jwt_vc_json": { "format": "jwt_vc_json", "credential_definition": { "type": [ "VerifiableCredential", "UniversityDegree" ] }, "display": [ { "name": "University Credential", "locale": "en-US", "logo": { "url": "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" : { "url": "https://example.com/background.png", "alt_text": "Background" }, "secondary_image" : { "url": "https://example.com/secondary.png", "alt_text": "Secondary" } }, { "name": "Diplôme Universitaire", "locale": "fr-FR", "logo": { "url": "https://university.example.edu/public/logo-fr.png", "alt_text": "un logo carré d'une université" }, "description": "Ce diplôme atteste de l'obtention d'un diplôme universitaire.", "background_color": "#008000", "text_color": "#FFFFFF", "background_image" : { "url": "https://example.com/background.png", "alt_text": "arrière-plan" }, "secondary_image" : { "url": "https://example.com/Secondaire.png", "alt_text": "Secondaire" } } ] } } } ``` This can be configured in the [issuer service](https://docs.walt.id/enterprise-stack/services/issuer-service/configurations/general-config.md). -------------------------------------------------------------------------------- PAGE: Data Functions URL: https://docs.walt.id/enterprise-stack/services/issuer-service/data-functions RAW: https://docs.walt.id/enterprise-stack/services/issuer-service/data-functions.md STACK: Enterprise Stack (commercial) — 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/enterprise-stack/services/issuer-service/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 subject did. #### 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": { "url": "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" : { "url" : "https://university.example.edu/public/background.png", "alt_text" : "a background image of a university" }, "secondary_image" : { "url" : "https://university.example.edu/public/secondary.png", "alt_text" : "a secondary image of a university" } } ] } ```
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": { "url": "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": { "url": "https://university.example.edu/public/background.png", "alt_text": "a background image of a university" }, "secondary_image": { "url": "https://university.example.edu/public/secondary.png", "alt_text": "a secondary 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:ssZZ` -------------------------------------------------------------------------------- PAGE: Full API Reference URL: https://docs.walt.id/enterprise-stack/services/issuer-service/full-api-reference RAW: https://docs.walt.id/enterprise-stack/services/issuer-service/full-api-reference.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Full API Reference Find it [here](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Issuer%20Service) -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/services/verifier2-service/overview RAW: https://docs.walt.id/enterprise-stack/services/verifier2-service/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview **Verifier2 Service** enables organizations to verify digital identity credentials. 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/openid4vp.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, please see the [Verifier Service documentation](https://docs.walt.id/enterprise-stack/services/verifier-service/overview.md). If possible, please use the verifier2 service instead. ## 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), 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 | --- ## Features - [Credential Verification](https://docs.walt.id/enterprise-stack/services/verifier2-service/credential-verification/vc-oid4vp.md) - Request and verify various digital credential types and formats ([W3C](https://docs.walt.id/enterprise-stack/services/verifier2-service/credential-verification/vc-oid4vp.md), [SD-JWT](https://docs.walt.id/enterprise-stack/services/verifier2-service/credential-verification/sd-jwt-vc-oid4vp.md), [mDL](https://docs.walt.id/enterprise-stack/services/verifier2-service/credential-verification/mdl-oid4vp.md)) via OID4VP V1. - [Policies](https://docs.walt.id/enterprise-stack/services/verifier2-service/policies/overview.md) - 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/enterprise-stack/services/verifier2-service/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. - **Session data retention & auto-purge** - Presentation sessions can contain PII (holder identifiers, disclosed attributes, device metadata). The Enterprise Stack can automatically delete expired verifier sessions so you do not have to script manual cleanup. Configure retention windows, purge cadence, dry-run mode, and deletion limits via the [Enterprise Stack → Setup → Data Retention](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/data-retention.md) file by setting options such as `maxVerifierSessionAge` and `schedule`. --- ## Getting Started - [Setup](https://docs.walt.id/enterprise-stack/services/verifier2-service/setup.md) – Configure a verifier service (set default signing keys, certificate chains, and URL hosts during initial setup to simplify future verification sessions) - [Verify W3C credentials](https://docs.walt.id/enterprise-stack/services/verifier2-service/credential-verification/vc-oid4vp.md) – Walkthrough on how to verify a W3C credential - [Verify SD-JWT VC credentials](https://docs.walt.id/enterprise-stack/services/verifier2-service/credential-verification/sd-jwt-vc-oid4vp.md) – Walkthrough on how to verify an SD-JWT VC credential - [Verify mDL/mDOC credentials](https://docs.walt.id/enterprise-stack/services/verifier2-service/credential-verification/mdl-oid4vp.md) – Walkthrough on how to verify an mDL/mDOC credential - [Verify credentials via Digital Credentials API](https://docs.walt.id/enterprise-stack/services/verifier2-service/credential-verification/dc-api.md) – Walkthrough on how to verify credentials using the DC API -------------------------------------------------------------------------------- PAGE: Setup URL: https://docs.walt.id/enterprise-stack/services/verifier2-service/setup RAW: https://docs.walt.id/enterprise-stack/services/verifier2-service/setup.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Setup We will setup a Verifier service inside of a tenant. If you don't have a tenant yet, you can learn how to create one [here](https://docs.walt.id/enterprise-stack/administration/tenants/create.md). **Option: CURL** Endpoint: `/v1/{target}/resource-api/services/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Resource/post_v1__target__resource_api_services_create) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "verifier2", "baseUrl": "http://org.enterprise.localhost:3000", "clientId": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiU05JZ1ItTHZzSEUtNkhUUGxYYUpQVDVubHRZMFpqZlA0VkpTWEwzQ2ZkMCIsIngiOiJIVWM1ZHY1eFREV3VMVmZQcFdvVXBWTjZ1QTNJNzBsVzVnNEhoMG5VaDQ0In0", "clientMetadata": { "client_name": "walt.id Enterprise Verifier", "logo_uri": "https://cdn.walt.id/verifier/logo.png" } }' ``` **Body** ```json { "type": "verifier2", "baseUrl": "http://org.enterprise.localhost:3000", "clientId": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiU05JZ1ItTHZzSEUtNkhUUGxYYUpQVDVubHRZMFpqZlA0VkpTWEwzQ2ZkMCIsIngiOiJIVWM1ZHY1eFREV3VMVmZQcFdvVXBWTjZ1QTNJNzBsVzVnNEhoMG5VaDQ0In0", "clientMetadata": { "client_name": "walt.id Enterprise Verifier", "logo_uri": "https://cdn.walt.id/verifier/logo.png" } } ``` Further metadata configuration options can be found in the official specification [here](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html#name-new-parameters) **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant in which to create the new Verifier service and the service's ID (`{organizationID}.{tenantID}.[NewVerifierServiceID]`), e.g. `waltid.tenant1.verifier1` **Body Parameters** - `type`: _serviceType_ - Specifies the type of service to create. In our case `verifier2` - `baseUrl`: _String_ - This URL will be included in the generated OIDC4VC offer, allowing the wallet to know how to reach the verifier. It should reflect your organization's base URL. The general format for this URL is `https://{orgID}.yourEnterpriseStackUrl.com`. For example, if your organization is named **myorg** and the Enterprise Stack is hosted at the domain **enterprise-stack.com**, your base URL would be: https://myorg.enterprise-stack.com. - `clientId`: _String_ (required for mdoc verification, optional for other credential types) - The identifier the verifier presents to wallets during credential exchange. Supports multiple formats (defined in OID4VP 1.0, §5.9.3): - **Plain string** (e.g. `"verifier2"`) — Used as-is in authorization requests. Suitable for development and use cases where the verifier does not need a verifiable identity. - **DID** (e.g. `"did:jwk:..."`) — A decentralized identifier that the wallet can resolve to verify the verifier's identity. - **`x509_san_dns:`** (e.g. `"x509_san_dns:verifier.example.com"`) — Client identity 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` parameters. - **`x509_hash:`** — Client identity 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. - `clientMetadata` (optional): _Object_ - Verifier metadata config. Can be used to edit e.g. externally exposed human-readable verifier name and logo. Defaults to `{"client_name": "Verifier"}`. Learn more in general [here](https://docs.walt.id/enterprise-stack/services/verifier2-service/configurations/openid-metadata.md) or expand below to see object property options.

Expand to learn more about metadata config object properties **Metadata Config Object** ```json "metadataConfig": { "client_name": "walt.id Enterprise Verifier", "logo_uri": "https://cdn.walt.id/verifier/logo.png" } ``` - `client_name` (optional) _String_: Human-readable string name of the verifier. - `logo_uri` (optional) _String_: URL string that references a logo for the verifier.
Further metadata configuration options can be found in the official specification [here](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html#name-new-parameters) **Note:** **Configuration Tip:** The `key`, `x5c`, and `urlHost` parameters set service-wide defaults that apply to all verification sessions. These can be overridden per-session during verification session creation if needed. - `urlHost` (optional): _String_ - **Service-wide default** base URL used to construct the QR code link or deep link for cross-device flows. Applied to all verification sessions unless overridden per-session. Defaults to `"openid4vp://authorize"` for standard OID4VP flows. For conformance testing with a browser-based authorization endpoint, use a full HTTPS URL. Not applicable for DC API flows (derived from request body instead). - `key` (optional): _Object_ - **Service-wide default** signing key for authorization request JWTs. Applied to all verification sessions unless overridden per-session. Must be a serialized key in the format `{"type":"jwk","jwk":{...}}`. When `signedRequest: true` or `encryptedResponse: true` is set during verification session creation, a signing key is **required** — either configured here as a default or included in the verification session setup body. - `x5c` (optional): _Array of Strings_ - **Service-wide default** X.509 certificate chain for the verifier's signing key. Applied to all verification sessions unless overridden per-session. Must be an ordered chain in DER-encoded, Base64-encoded format where the first certificate corresponds 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. Also explicitly required for signed Annex C (ISO 18013-7) requests. --- **Response Codes** - `201` - Service created successfully. -------------------------------------------------------------------------------- PAGE: Verifying Verifiable Credentials (W3C) via OID4VP URL: https://docs.walt.id/enterprise-stack/services/verifier2-service/credential-verification/vc-oid4vp RAW: https://docs.walt.id/enterprise-stack/services/verifier2-service/credential-verification/vc-oid4vp.md STACK: Enterprise Stack (commercial) — 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 Enterprise Verifier2 Service and the OID4VP protocol. The verification process sends a credential request to a holder's wallet, receives the 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. ## Prerequisites Before you begin, ensure you have: - **Enterprise Verifier2 Service setup** — Follow the [Setup](https://docs.walt.id/enterprise-stack/services/verifier2-service/setup.md) guide - **Valid authentication token** — Obtain a JWT token with appropriate permissions for your organization and tenant - **A test wallet with credentials** — Use the [walt.id Wallet](https://docs.walt.id/enterprise-stack/services/wallet-service/overview.md) for testing, or any OID4VP-compatible wallet **Note:** **Enterprise Architecture:** The Enterprise Verifier2 Service runs as a dedicated microservice within your organization's infrastructure. All requests are scoped to `{organizationID}.{tenantID}.{verifierServiceID}` for multi-tenancy isolation. --- ## Example 1: Basic Verification This example requests a single W3C Verifiable Credential (UniversityDegree) and verifies its cryptographic signature using the default signature policy. **Option: CURL** **Endpoint:** `POST /v1/{target}/verifier2-service-api/verification-session/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Verifier2%20Service%20-%20Verification%20Sessions/post_v1__target__verifier2_service_api_verification_session_create) ##### Example Request ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/verifier2-service-api/verification-session/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "flow_type": "cross_device", "core_flow": { "dcql_query": { "credentials": [ { "id": "example_university_degree_jwt_vc", "format": "jwt_vc_json", "meta": { "type_values": [ [ "VerifiableCredential", "UniversityDegree" ] ] } } ] } } }' ``` **Path Parameters** - **orgID**: _String_ (required) - Your organization's Base URL. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.waltid.dev` when using the sandbox environment. - **target**: _String_ (required) - The target resource identifier specifying the organization, tenant, and verifier service with which to execute the credential verification. Format: `{organizationID}.{tenantID}.{verifierServiceID}`, e.g. `waltid.tenant1.verifier1` **Header Parameters** - **Authorization**: _String_ (required) - Bearer token obtained from your Enterprise authentication. Format: `Bearer {token}`. **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 `"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/enterprise-stack/services/verifier2-service/policies/overview.md) for the complete list. - **vp_policies**: _Array_ (optional) - Policies applied to the entire presentation (not shown in this example) - **signed_request**: _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. - **encrypted_response**: _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 during [verifier2 service creation](https://docs.walt.id/enterprise-stack/services/verifier2-service/setup.md#body-parameters). Must be a serialized key in the format `{"type":"jwk","jwk":{...}}`. Required when `signed_request: true` or `encrypted_response: 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 during [verifier2 service creation](https://docs.walt.id/enterprise-stack/services/verifier2-service/setup.md#body-parameters). 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. - **credential_ids**: _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. - **expiration_duration**: _String_ (optional) - ISO-8601 duration specifying how long the verification session remains valid. Defaults to 10 minutes (`PT10M`). Example: `"PT1H"` for 1 hour, `"PT30M"` for 30 minutes. - **expiration_date**: _String_ (optional) - ISO-8601 datetime specifying the exact time when the session expires. Takes precedence over `expiration_duration` if both are set. Example: `"2026-05-21T15:00:00Z"`. Session expires if left unused (no presentation pushed). Once a presentation is received, the session is no longer eligible for expiry. - **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. - The service constructs: Request URI: `{urlPrefix}/{target}/verifier2-service-api/{sessionId}/request` and Response URI: `{urlPrefix}/{target}/verifier2-service-api/{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. Can be set as a service-wide default during [verifier2 service creation](https://docs.walt.id/enterprise-stack/services/verifier2-service/setup.md#body-parameters) and overridden per-session here. - 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) - **successRedirectUrl**: _String_ - URL to redirect the user after successful verification - **errorRedirectUrl**: _String_ - URL to redirect the user if verification fails **Note:** **Service-Level Defaults:** The `key`, `x5c`, and `url_host` parameters can be configured as service-wide defaults during [verifier2 service creation](https://docs.walt.id/enterprise-stack/services/verifier2-service/setup.md#body-parameters). Request-level parameters provided here override these defaults for that specific session only. --- ##### Example Response The response contains the session ID and authorization URLs to present to the user. ```json { "sessionId": "ed593e34-9e86-4144-87c1-de6d5ae9beb4", "bootstrapAuthorizationRequestUrl": "openid4vp://authorize?request_uri=https%3A%2F%2F{orgID}.enterprise-sandbox.waltid.dev%2Fv1%2Fwaltid.tenant1.verifier1%2Fverifier2-service-api%2Fed593e34-9e86-4144-87c1-de6d5ae9beb4%2Frequest", "fullAuthorizationRequestUrl": "openid4vp://authorize?response_type=vp_token&state=a65f02bd-c7f0-4094-932f-555ddce1b32f&response_mode=direct_post&nonce=f9b57ec3-c852-49ad-9537-2e1820f23ad9&response_uri=https%3A%2F%2F{orgID}.enterprise-sandbox.waltid.dev%2Fv1%2Fwaltid.tenant1.verifier1%2Fverifier2-service-api%2Fed593e34-9e86-4144-87c1-de6d5ae9beb4%2Fresponse&dcql_query=%7B%22credentials%22%3A%5B%7B%22id%22%3A%22example_university_degree_jwt_vc%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%22Verifier%22%7D", "creationTarget": "waltid.tenant1.verifier1.ed593e34-9e86-4144-87c1-de6d5ae9beb4" } ``` **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). - **creationTarget**: _String_ - The full resource path including the session ID. Use this as the `{creationTarget}` path parameter when querying session info. --- ## 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 `successRedirectUrl` or `errorRedirectUrl` 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 /v1/{creationTarget}/verifier2-service-api/verification-session/info` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Verifier2%20Service%20-%20Verification%20Sessions/get_v1__target__verifier2_service_api_verification_session_info) ##### Example Request ```bash curl -X 'GET' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{creationTarget}/verifier2-service-api/verification-session/info' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' ``` **Path Parameters** - **orgID**: _String_ (required) - Your organization's Base URL. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.waltid.dev` when using the sandbox environment. - **creationTarget**: _String_ (required) - The target creation target returned when creating the verification session. It is a concatenation of the target resource and the session ID (e.g., `org.tenant.verifier.ff4f0c86-56ce-4e4a-a137-dd5569049ff3`). **Header Parameters** - **Authorization**: _String_ (required) - Bearer token obtained from your Enterprise authentication. Format: `Bearer {token}`. --- ##### Example Response The response contains the complete session information including setup, status, policy results, and presented credentials. ```json { "_id": "waltid.tenant1.verifier1.ed593e34-9e86-4144-87c1-de6d5ae9beb4", "session": { "id": "ed593e34-9e86-4144-87c1-de6d5ae9beb4", "setup": { "flow_type": "cross_device", "core_flow": { "dcql_query": { "credentials": [ { "id": "example_university_degree_jwt_vc", "format": "jwt_vc_json", "multiple": false, "meta": { "type_values": [ [ "VerifiableCredential", "UniversityDegree" ] ], "format": "jwt_vc_json" }, "require_cryptographic_holder_binding": true } ] }, "signed_request": false, "encrypted_response": false, "policies": {} }, "url_config": {} }, "creationDate": "2026-05-20T11:36:36.742929808Z", "expirationDate": "2026-05-20T11:47:15.747252763Z", "retentionDate": "2036-05-20T11:36:36.742929808Z", "status": "SUCCESSFUL", "attempted": true, "reattemptable": true, "bootstrapAuthorizationRequest": { "response_type": "vp_token", "request_uri": "https://waltid.testing-0-20-0.enterprise.test.waltid.cloud/v1/waltid.tenant1.verifier1/verifier2-service-api/ed593e34-9e86-4144-87c1-de6d5ae9beb4/request" }, "bootstrapAuthorizationRequestUrl": "openid4vp://authorize?request_uri=https%3A%2F%2Fwaltid.testing-0-20-0.enterprise.test.waltid.cloud%2Fv1%2Fwaltid.tenant1.verifier1%2Fverifier2-service-api%2Fed593e34-9e86-4144-87c1-de6d5ae9beb4%2Frequest", "authorizationRequest": { "response_type": "vp_token", "state": "a65f02bd-c7f0-4094-932f-555ddce1b32f", "response_mode": "direct_post", "nonce": "f9b57ec3-c852-49ad-9537-2e1820f23ad9", "response_uri": "https://waltid.testing-0-20-0.enterprise.test.waltid.cloud/v1/waltid.tenant1.verifier1/verifier2-service-api/ed593e34-9e86-4144-87c1-de6d5ae9beb4/response", "dcql_query": { "credentials": [ { "id": "example_university_degree_jwt_vc", "format": "jwt_vc_json", "multiple": false, "meta": { "type_values": [ [ "VerifiableCredential", "UniversityDegree" ] ], "format": "jwt_vc_json" }, "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": "Verifier" } }, "authorizationRequestUrl": "openid4vp://authorize?response_type=vp_token&state=a65f02bd-c7f0-4094-932f-555ddce1b32f&response_mode=direct_post&nonce=f9b57ec3-c852-49ad-9537-2e1820f23ad9&response_uri=https%3A%2F%2Fwaltid.testing-0-20-0.enterprise.test.waltid.cloud%2Fv1%2Fwaltid.tenant1.verifier1%2Fverifier2-service-api%2Fed593e34-9e86-4144-87c1-de6d5ae9beb4%2Fresponse&dcql_query=%7B%22credentials%22%3A%5B%7B%22id%22%3A%22example_university_degree_jwt_vc%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%22Verifier%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": { "example_university_degree_jwt_vc": { "jwt_vc_json/audience-check": { "policy_executed": { "policy": "jwt_vc_json/audience-check", "id": "jwt_vc_json/audience-check", "description": "Check if presentation audience matches expected audience for session" }, "success": true, "results": { "presentation_audience": "" }, "errors": [], "execution_time": "PT0.010967155S" }, "jwt_vc_json/nonce-check": { "policy_executed": { "policy": "jwt_vc_json/nonce-check", "id": "jwt_vc_json/nonce-check", "description": "Check if presentation nonce matches expected nonce for session" }, "success": true, "results": {}, "errors": [], "execution_time": "PT0.011009056S" }, "jwt_vc_json/envelope_signature": { "policy_executed": { "policy": "jwt_vc_json/envelope_signature", "id": "jwt_vc_json/envelope_signature", "description": "Verify signature using holders public key" }, "success": true, "results": { "issuer": "did:key:zDnaeau98xnt9awckNpEq34Cw2LbfpEiggJc43uwMh8uAa8B3", "holder_key_jwk": { "kty": "EC", "crv": "P-256", "x": "m4r2Sf0jYK3OXLPrdrwC2OSSkwCBUVNJH-kpIlzmBzo", "y": "Uqa4uWu4rgrm3Cj9wugwYIig5ZS9DkLKxKnGk8sYr1o" }, "verified_vp_jwt_content": { "sub": "did:key:zDnaeau98xnt9awckNpEq34Cw2LbfpEiggJc43uwMh8uAa8B3", "nbf": 1779276975, "iat": 1779277035, "jti": "urn:uuid:7da432ef-bc20-42dc-94e5-f5c0bd702711", "iss": "did:key:zDnaeau98xnt9awckNpEq34Cw2LbfpEiggJc43uwMh8uAa8B3", "nonce": "f9b57ec3-c852-49ad-9537-2e1820f23ad9", "aud": "", "vp": { "@context": [ "https://www.w3.org/2018/credentials/v1" ], "type": [ "VerifiablePresentation" ], "id": "urn:uuid:7da432ef-bc20-42dc-94e5-f5c0bd702711", "holder": "did:key:zDnaeau98xnt9awckNpEq34Cw2LbfpEiggJc43uwMh8uAa8B3", "verifiableCredential": [ "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaWFVcE5VelZpYTFwV1NXeHVZMlp4WDB4bVgxTjFlRW95U25SUk5VaDJZWG8zZEZkUWJrRnFWVlZrY3lJc0luZ2lPaUpHV21SMmQwTTRZVWRvVW5keGVsZHdkR1ZxTUU1YVozUjNXVUZKTVZONVJtY3hiVXRFUlZSUFpuRkZJbjAjaUpNUzVia1pWSWxuY2ZxX0xmX1N1eEoySnRRNUh2YXo3dFdQbkFqVVVkcyIsInR5cCI6IkpXVCIsImFsZyI6IkVkRFNBIn0.eyJpc3MiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaWFVcE5VelZpYTFwV1NXeHVZMlp4WDB4bVgxTjFlRW95U25SUk5VaDJZWG8zZEZkUWJrRnFWVlZrY3lJc0luZ2lPaUpHV21SMmQwTTRZVWRvVW5keGVsZHdkR1ZxTUU1YVozUjNXVUZKTVZONVJtY3hiVXRFUlZSUFpuRkZJbjAiLCJzdWIiOiJkaWQ6a2V5OnpEbmFlYXU5OHhudDlhd2NrTnBFcTM0Q3cyTGJmcEVpZ2dKYzQzdXdNaDh1QWE4QjMiLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL2V4YW1wbGVzL3YxIl0sImlkIjoidXJuOnV1aWQ6NzE4OTU5MjYtZjRiYy00OGNhLTk3MmQtMjExODM4MjgzMmJkIiwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIlVuaXZlcnNpdHlEZWdyZWUiXSwiaXNzdWVyIjp7ImlkIjoiZGlkOmp3azpleUpyZEhraU9pSlBTMUFpTENKamNuWWlPaUpGWkRJMU5URTVJaXdpYTJsa0lqb2lhVXBOVXpWaWExcFdTV3h1WTJaeFgweG1YMU4xZUVveVNuUlJOVWgyWVhvM2RGZFFia0ZxVlZWa2N5SXNJbmdpT2lKR1dtUjJkME00WVVkb1VuZHhlbGR3ZEdWcU1FNWFaM1IzV1VGSk1WTjVSbWN4YlV0RVJWUlBabkZGSW4wIn0sImlzc3VhbmNlRGF0ZSI6IjIwMjYtMDUtMjBUMTE6Mjc6NDAuNzkyMTAzOTEzWiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmtleTp6RG5hZWF1OTh4bnQ5YXdja05wRXEzNEN3MkxiZnBFaWdnSmM0M3V3TWg4dUFhOEIzIiwiZGVncmVlIjp7InR5cGUiOiJCYWNoZWxvckRlZ3JlZSIsIm5hbWUiOiJCYWNoZWxvciBvZiBTY2llbmNlIGFuZCBBcnRzIn19LCJleHBpcmF0aW9uRGF0ZSI6IjIwMjctMDUtMjBUMTE6Mjc6NDAuNzkyMTM3NjE0WiJ9LCJqdGkiOiJ1cm46dXVpZDo3MTg5NTkyNi1mNGJjLTQ4Y2EtOTcyZC0yMTE4MzgyODMyYmQiLCJleHAiOjE4MTA4MTI0NjAsImlhdCI6MTc3OTI3NjQ2MCwibmJmIjoxNzc5Mjc2NDYwfQ._oWIQARZvoTP71K2JxbnDpUaA9tfBxMBM6e3kjlATffqwpV5_rHgmYEYgZNrQ1aK7idlXCbsyxtHiUrVgR54Ag" ] } } }, "errors": [], "execution_time": "PT0.030289930S" } } }, "vc_policies": [ { "policy": { "policy": "signature", "id": "signature" }, "success": true, "result": { "verification_result": true, "signed_credential": "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaWFVcE5VelZpYTFwV1NXeHVZMlp4WDB4bVgxTjFlRW95U25SUk5VaDJZWG8zZEZkUWJrRnFWVlZrY3lJc0luZ2lPaUpHV21SMmQwTTRZVWRvVW5keGVsZHdkR1ZxTUU1YVozUjNXVUZKTVZONVJtY3hiVXRFUlZSUFpuRkZJbjAjaUpNUzVia1pWSWxuY2ZxX0xmX1N1eEoySnRRNUh2YXo3dFdQbkFqVVVkcyIsInR5cCI6IkpXVCIsImFsZyI6IkVkRFNBIn0.eyJpc3MiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaWFVcE5VelZpYTFwV1NXeHVZMlp4WDB4bVgxTjFlRW95U25SUk5VaDJZWG8zZEZkUWJrRnFWVlZrY3lJc0luZ2lPaUpHV21SMmQwTTRZVWRvVW5keGVsZHdkR1ZxTUU1YVozUjNXVUZKTVZONVJtY3hiVXRFUlZSUFpuRkZJbjAiLCJzdWIiOiJkaWQ6a2V5OnpEbmFlYXU5OHhudDlhd2NrTnBFcTM0Q3cyTGJmcEVpZ2dKYzQzdXdNaDh1QWE4QjMiLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL2V4YW1wbGVzL3YxIl0sImlkIjoidXJuOnV1aWQ6NzE4OTU5MjYtZjRiYy00OGNhLTk3MmQtMjExODM4MjgzMmJkIiwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIlVuaXZlcnNpdHlEZWdyZWUiXSwiaXNzdWVyIjp7ImlkIjoiZGlkOmp3azpleUpyZEhraU9pSlBTMUFpTENKamNuWWlPaUpGWkRJMU5URTVJaXdpYTJsa0lqb2lhVXBOVXpWaWExcFdTV3h1WTJaeFgweG1YMU4xZUVveVNuUlJOVWgyWVhvM2RGZFFia0ZxVlZWa2N5SXNJbmdpT2lKR1dtUjJkME00WVVkb1VuZHhlbGR3ZEdWcU1FNWFaM1IzV1VGSk1WTjVSbWN4YlV0RVJWUlBabkZGSW4wIn0sImlzc3VhbmNlRGF0ZSI6IjIwMjYtMDUtMjBUMTE6Mjc6NDAuNzkyMTAzOTEzWiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmtleTp6RG5hZWF1OTh4bnQ5YXdja05wRXEzNEN3MkxiZnBFaWdnSmM0M3V3TWg4dUFhOEIzIiwiZGVncmVlIjp7InR5cGUiOiJCYWNoZWxvckRlZ3JlZSIsIm5hbWUiOiJCYWNoZWxvciBvZiBTY2llbmNlIGFuZCBBcnRzIn19LCJleHBpcmF0aW9uRGF0ZSI6IjIwMjctMDUtMjBUMTE6Mjc6NDAuNzkyMTM3NjE0WiJ9LCJqdGkiOiJ1cm46dXVpZDo3MTg5NTkyNi1mNGJjLTQ4Y2EtOTcyZC0yMTE4MzgyODMyYmQiLCJleHAiOjE4MTA4MTI0NjAsImlhdCI6MTc3OTI3NjQ2MCwibmJmIjoxNzc5Mjc2NDYwfQ._oWIQARZvoTP71K2JxbnDpUaA9tfBxMBM6e3kjlATffqwpV5_rHgmYEYgZNrQ1aK7idlXCbsyxtHiUrVgR54Ag", "credential_signature": { "type": "signature-jwt", "signature": "_oWIQARZvoTP71K2JxbnDpUaA9tfBxMBM6e3kjlATffqwpV5_rHgmYEYgZNrQ1aK7idlXCbsyxtHiUrVgR54Ag", "jwtHeader": { "kid": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiaUpNUzVia1pWSWxuY2ZxX0xmX1N1eEoySnRRNUh2YXo3dFdQbkFqVVVkcyIsIngiOiJGWmR2d0M4YUdoUndxeldwdGVqME5aZ3R3WUFJMVN5RmcxbUtERVRPZnFFIn0#iJMS5bkZVIlncfq_Lf_SuxJ2JtQ5Hvaz7tWPnAjUUds", "typ": "JWT", "alg": "EdDSA" } }, "verified_data": { "iss": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiaUpNUzVia1pWSWxuY2ZxX0xmX1N1eEoySnRRNUh2YXo3dFdQbkFqVVVkcyIsIngiOiJGWmR2d0M4YUdoUndxeldwdGVqME5aZ3R3WUFJMVN5RmcxbUtERVRPZnFFIn0", "sub": "did:key:zDnaeau98xnt9awckNpEq34Cw2LbfpEiggJc43uwMh8uAa8B3", "vc": { "@context": [ "https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1" ], "id": "urn:uuid:71895926-f4bc-48ca-972d-2118382832bd", "type": [ "VerifiableCredential", "UniversityDegree" ], "issuer": { "id": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiaUpNUzVia1pWSWxuY2ZxX0xmX1N1eEoySnRRNUh2YXo3dFdQbkFqVVVkcyIsIngiOiJGWmR2d0M4YUdoUndxeldwdGVqME5aZ3R3WUFJMVN5RmcxbUtERVRPZnFFIn0" }, "issuanceDate": "2026-05-20T11:27:40.792103913Z", "credentialSubject": { "id": "did:key:zDnaeau98xnt9awckNpEq34Cw2LbfpEiggJc43uwMh8uAa8B3", "degree": { "type": "BachelorDegree", "name": "Bachelor of Science and Arts" } }, "expirationDate": "2027-05-20T11:27:40.792137614Z" }, "jti": "urn:uuid:71895926-f4bc-48ca-972d-2118382832bd", "exp": 1810812460, "iat": 1779276460, "nbf": 1779276460 }, "successful_issuer_public_key": { "kty": "OKP", "crv": "Ed25519", "kid": "iJMS5bkZVIlncfq_Lf_SuxJ2JtQ5Hvaz7tWPnAjUUds", "x": "FZdvwC8aGhRwqzWptej0NZgtwYAI1SyFg1mKDETOfqE" }, "successful_issuer_public_key_id": "iJMS5bkZVIlncfq_Lf_SuxJ2JtQ5Hvaz7tWPnAjUUds" } } ], "specific_vc_policies": {}, "overallSuccess": true }, "presentation_validation_results": { "example_university_degree_jwt_vc": { "jwt_vc_json/audience-check": { "policy_executed": { "policy": "jwt_vc_json/audience-check", "id": "jwt_vc_json/audience-check", "description": "Check if presentation audience matches expected audience for session" }, "success": true, "results": { "presentation_audience": "" }, "errors": [], "execution_time": "PT0.010967155S" }, "jwt_vc_json/nonce-check": { "policy_executed": { "policy": "jwt_vc_json/nonce-check", "id": "jwt_vc_json/nonce-check", "description": "Check if presentation nonce matches expected nonce for session" }, "success": true, "results": {}, "errors": [], "execution_time": "PT0.011009056S" }, "jwt_vc_json/envelope_signature": { "policy_executed": { "policy": "jwt_vc_json/envelope_signature", "id": "jwt_vc_json/envelope_signature", "description": "Verify signature using holders public key" }, "success": true, "results": { "issuer": "did:key:zDnaeau98xnt9awckNpEq34Cw2LbfpEiggJc43uwMh8uAa8B3", "holder_key_jwk": { "kty": "EC", "crv": "P-256", "x": "m4r2Sf0jYK3OXLPrdrwC2OSSkwCBUVNJH-kpIlzmBzo", "y": "Uqa4uWu4rgrm3Cj9wugwYIig5ZS9DkLKxKnGk8sYr1o" }, "verified_vp_jwt_content": { "sub": "did:key:zDnaeau98xnt9awckNpEq34Cw2LbfpEiggJc43uwMh8uAa8B3", "nbf": 1779276975, "iat": 1779277035, "jti": "urn:uuid:7da432ef-bc20-42dc-94e5-f5c0bd702711", "iss": "did:key:zDnaeau98xnt9awckNpEq34Cw2LbfpEiggJc43uwMh8uAa8B3", "nonce": "f9b57ec3-c852-49ad-9537-2e1820f23ad9", "aud": "", "vp": { "@context": [ "https://www.w3.org/2018/credentials/v1" ], "type": [ "VerifiablePresentation" ], "id": "urn:uuid:7da432ef-bc20-42dc-94e5-f5c0bd702711", "holder": "did:key:zDnaeau98xnt9awckNpEq34Cw2LbfpEiggJc43uwMh8uAa8B3", "verifiableCredential": [ "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaWFVcE5VelZpYTFwV1NXeHVZMlp4WDB4bVgxTjFlRW95U25SUk5VaDJZWG8zZEZkUWJrRnFWVlZrY3lJc0luZ2lPaUpHV21SMmQwTTRZVWRvVW5keGVsZHdkR1ZxTUU1YVozUjNXVUZKTVZONVJtY3hiVXRFUlZSUFpuRkZJbjAjaUpNUzVia1pWSWxuY2ZxX0xmX1N1eEoySnRRNUh2YXo3dFdQbkFqVVVkcyIsInR5cCI6IkpXVCIsImFsZyI6IkVkRFNBIn0.eyJpc3MiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaWFVcE5VelZpYTFwV1NXeHVZMlp4WDB4bVgxTjFlRW95U25SUk5VaDJZWG8zZEZkUWJrRnFWVlZrY3lJc0luZ2lPaUpHV21SMmQwTTRZVWRvVW5keGVsZHdkR1ZxTUU1YVozUjNXVUZKTVZONVJtY3hiVXRFUlZSUFpuRkZJbjAiLCJzdWIiOiJkaWQ6a2V5OnpEbmFlYXU5OHhudDlhd2NrTnBFcTM0Q3cyTGJmcEVpZ2dKYzQzdXdNaDh1QWE4QjMiLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL2V4YW1wbGVzL3YxIl0sImlkIjoidXJuOnV1aWQ6NzE4OTU5MjYtZjRiYy00OGNhLTk3MmQtMjExODM4MjgzMmJkIiwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIlVuaXZlcnNpdHlEZWdyZWUiXSwiaXNzdWVyIjp7ImlkIjoiZGlkOmp3azpleUpyZEhraU9pSlBTMUFpTENKamNuWWlPaUpGWkRJMU5URTVJaXdpYTJsa0lqb2lhVXBOVXpWaWExcFdTV3h1WTJaeFgweG1YMU4xZUVveVNuUlJOVWgyWVhvM2RGZFFia0ZxVlZWa2N5SXNJbmdpT2lKR1dtUjJkME00WVVkb1VuZHhlbGR3ZEdWcU1FNWFaM1IzV1VGSk1WTjVSbWN4YlV0RVJWUlBabkZGSW4wIn0sImlzc3VhbmNlRGF0ZSI6IjIwMjYtMDUtMjBUMTE6Mjc6NDAuNzkyMTAzOTEzWiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmtleTp6RG5hZWF1OTh4bnQ5YXdja05wRXEzNEN3MkxiZnBFaWdnSmM0M3V3TWg4dUFhOEIzIiwiZGVncmVlIjp7InR5cGUiOiJCYWNoZWxvckRlZ3JlZSIsIm5hbWUiOiJCYWNoZWxvciBvZiBTY2llbmNlIGFuZCBBcnRzIn19LCJleHBpcmF0aW9uRGF0ZSI6IjIwMjctMDUtMjBUMTE6Mjc6NDAuNzkyMTM3NjE0WiJ9LCJqdGkiOiJ1cm46dXVpZDo3MTg5NTkyNi1mNGJjLTQ4Y2EtOTcyZC0yMTE4MzgyODMyYmQiLCJleHAiOjE4MTA4MTI0NjAsImlhdCI6MTc3OTI3NjQ2MCwibmJmIjoxNzc5Mjc2NDYwfQ._oWIQARZvoTP71K2JxbnDpUaA9tfBxMBM6e3kjlATffqwpV5_rHgmYEYgZNrQ1aK7idlXCbsyxtHiUrVgR54Ag" ] } } }, "errors": [], "execution_time": "PT0.030289930S" } } }, "presented_raw_data": { "vpToken": { "example_university_degree_jwt_vc": [ "eyJraWQiOiJkaWQ6a2V5OnpEbmFlYXU5OHhudDlhd2NrTnBFcTM0Q3cyTGJmcEVpZ2dKYzQzdXdNaDh1QWE4QjMjekRuYWVhdTk4eG50OWF3Y2tOcEVxMzRDdzJMYmZwRWlnZ0pjNDN1d01oOHVBYThCMyIsInR5cCI6IkpXVCIsImFsZyI6IkVTMjU2In0.eyJzdWIiOiJkaWQ6a2V5OnpEbmFlYXU5OHhudDlhd2NrTnBFcTM0Q3cyTGJmcEVpZ2dKYzQzdXdNaDh1QWE4QjMiLCJuYmYiOjE3NzkyNzY5NzUsImlhdCI6MTc3OTI3NzAzNSwianRpIjoidXJuOnV1aWQ6N2RhNDMyZWYtYmMyMC00MmRjLTk0ZTUtZjVjMGJkNzAyNzExIiwiaXNzIjoiZGlkOmtleTp6RG5hZWF1OTh4bnQ5YXdja05wRXEzNEN3MkxiZnBFaWdnSmM0M3V3TWg4dUFhOEIzIiwibm9uY2UiOiJmOWI1N2VjMy1jODUyLTQ5YWQtOTUzNy0yZTE4MjBmMjNhZDkiLCJhdWQiOiIiLCJ2cCI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVQcmVzZW50YXRpb24iXSwiaWQiOiJ1cm46dXVpZDo3ZGE0MzJlZi1iYzIwLTQyZGMtOTRlNS1mNWMwYmQ3MDI3MTEiLCJob2xkZXIiOiJkaWQ6a2V5OnpEbmFlYXU5OHhudDlhd2NrTnBFcTM0Q3cyTGJmcEVpZ2dKYzQzdXdNaDh1QWE4QjMiLCJjbmYiOm51bGwsInZlcmlmaWFibGVDcmVkZW50aWFsIjpbImV5SnJhV1FpT2lKa2FXUTZhbmRyT21WNVNuSmtTR3RwVDJsS1VGTXhRV2xNUTBwcVkyNVphVTlwU2taYVJFa3hUbFJGTlVscGQybGhNbXhyU1dwdmFXRlZjRTVWZWxacFlURndWMU5YZUhWWk1scDRXREI0YlZneFRqRmxSVzk1VTI1U1VrNVZhREpaV0c4elpFWmtVV0pyUm5GV1ZsWnJZM2xKYzBsdVoybFBhVXBIVjIxU01tUXdUVFJaVldSdlZXNWtlR1ZzWkhka1IxWnhUVVUxWVZvelVqTlhWVVpLVFZaT05WSnRZM2hpVlhSRlVsWlNVRnB1UmtaSmJqQWphVXBOVXpWaWExcFdTV3h1WTJaeFgweG1YMU4xZUVveVNuUlJOVWgyWVhvM2RGZFFia0ZxVlZWa2N5SXNJblI1Y0NJNklrcFhWQ0lzSW1Gc1p5STZJa1ZrUkZOQkluMC5leUpwYzNNaU9pSmthV1E2YW5kck9tVjVTbkprU0d0cFQybEtVRk14UVdsTVEwcHFZMjVaYVU5cFNrWmFSRWt4VGxSRk5VbHBkMmxoTW14clNXcHZhV0ZWY0U1VmVsWnBZVEZ3VjFOWGVIVlpNbHA0V0RCNGJWZ3hUakZsUlc5NVUyNVNVazVWYURKWldHOHpaRVprVVdKclJuRldWbFpyWTNsSmMwbHVaMmxQYVVwSFYyMVNNbVF3VFRSWlZXUnZWVzVrZUdWc1pIZGtSMVp4VFVVMVlWb3pVak5YVlVaS1RWWk9OVkp0WTNoaVZYUkZVbFpTVUZwdVJrWkpiakFpTENKemRXSWlPaUprYVdRNmEyVjVPbnBFYm1GbFlYVTVPSGh1ZERsaGQyTnJUbkJGY1RNMFEzY3lUR0ptY0VWcFoyZEtZelF6ZFhkTmFEaDFRV0U0UWpNaUxDSjJZeUk2ZXlKQVkyOXVkR1Y0ZENJNld5Sm9kSFJ3Y3pvdkwzZDNkeTUzTXk1dmNtY3ZNakF4T0M5amNtVmtaVzUwYVdGc2N5OTJNU0lzSW1oMGRIQnpPaTh2ZDNkM0xuY3pMbTl5Wnk4eU1ERTRMMk55WldSbGJuUnBZV3h6TDJWNFlXMXdiR1Z6TDNZeElsMHNJbWxrSWpvaWRYSnVPblYxYVdRNk56RTRPVFU1TWpZdFpqUmlZeTAwT0dOaExUazNNbVF0TWpFeE9ETTRNamd6TW1Ka0lpd2lkSGx3WlNJNld5SldaWEpwWm1saFlteGxRM0psWkdWdWRHbGhiQ0lzSWxWdWFYWmxjbk5wZEhsRVpXZHlaV1VpWFN3aWFYTnpkV1Z5SWpwN0ltbGtJam9pWkdsa09tcDNhenBsZVVweVpFaHJhVTlwU2xCVE1VRnBURU5LYW1OdVdXbFBhVXBHV2tSSk1VNVVSVFZKYVhkcFlUSnNhMGxxYjJsaFZYQk9WWHBXYVdFeGNGZFRWM2gxV1RKYWVGZ3dlRzFZTVU0eFpVVnZlVk51VWxKT1ZXZ3lXVmh2TTJSR1pGRmlhMFp4VmxaV2EyTjVTWE5KYm1kcFQybEtSMWR0VWpKa01FMDBXVlZrYjFWdVpIaGxiR1IzWkVkV2NVMUZOV0ZhTTFJelYxVkdTazFXVGpWU2JXTjRZbFYwUlZKV1VsQmFia1pHU1c0d0luMHNJbWx6YzNWaGJtTmxSR0YwWlNJNklqSXdNall0TURVdE1qQlVNVEU2TWpjNk5EQXVOemt5TVRBek9URXpXaUlzSW1OeVpXUmxiblJwWVd4VGRXSnFaV04wSWpwN0ltbGtJam9pWkdsa09tdGxlVHA2Ukc1aFpXRjFPVGg0Ym5RNVlYZGphMDV3UlhFek5FTjNNa3hpWm5CRmFXZG5TbU0wTTNWM1RXZzRkVUZoT0VJeklpd2laR1ZuY21WbElqcDdJblI1Y0dVaU9pSkNZV05vWld4dmNrUmxaM0psWlNJc0ltNWhiV1VpT2lKQ1lXTm9aV3h2Y2lCdlppQlRZMmxsYm1ObElHRnVaQ0JCY25SekluMTlMQ0psZUhCcGNtRjBhVzl1UkdGMFpTSTZJakl3TWpjdE1EVXRNakJVTVRFNk1qYzZOREF1TnpreU1UTTNOakUwV2lKOUxDSnFkR2tpT2lKMWNtNDZkWFZwWkRvM01UZzVOVGt5TmkxbU5HSmpMVFE0WTJFdE9UY3laQzB5TVRFNE16Z3lPRE15WW1RaUxDSmxlSEFpT2pFNE1UQTRNVEkwTmpBc0ltbGhkQ0k2TVRjM09USTNOalEyTUN3aWJtSm1Jam94TnpjNU1qYzJORFl3ZlEuX29XSVFBUlp2b1RQNzFLMkp4Ym5EcFVhQTl0ZkJ4TUJNNmUza2psQVRmZnF3cFY1X3JIZ21ZRVlnWk5yUTFhSzdpZGxYQ2JzeXh0SGlVclZnUjU0QWciXX19.D6w2TPAx92_FGTH64K7ANrir6aJRRvyhicT1q5fJVuIy8M5EVjDWDM3sOibTHgDcmr3-EM-fv2WmTiY7YUpU2Q" ] }, "state": "a65f02bd-c7f0-4094-932f-555ddce1b32f" }, "presented_presentations": { "example_university_degree_jwt_vc": { "type": "jwt_vc_json", "format": "jwt_vc_json", "jwt": "eyJraWQiOiJkaWQ6a2V5OnpEbmFlYXU5OHhudDlhd2NrTnBFcTM0Q3cyTGJmcEVpZ2dKYzQzdXdNaDh1QWE4QjMjekRuYWVhdTk4eG50OWF3Y2tOcEVxMzRDdzJMYmZwRWlnZ0pjNDN1d01oOHVBYThCMyIsInR5cCI6IkpXVCIsImFsZyI6IkVTMjU2In0.eyJzdWIiOiJkaWQ6a2V5OnpEbmFlYXU5OHhudDlhd2NrTnBFcTM0Q3cyTGJmcEVpZ2dKYzQzdXdNaDh1QWE4QjMiLCJuYmYiOjE3NzkyNzY5NzUsImlhdCI6MTc3OTI3NzAzNSwianRpIjoidXJuOnV1aWQ6N2RhNDMyZWYtYmMyMC00MmRjLTk0ZTUtZjVjMGJkNzAyNzExIiwiaXNzIjoiZGlkOmtleTp6RG5hZWF1OTh4bnQ5YXdja05wRXEzNEN3MkxiZnBFaWdnSmM0M3V3TWg4dUFhOEIzIiwibm9uY2UiOiJmOWI1N2VjMy1jODUyLTQ5YWQtOTUzNy0yZTE4MjBmMjNhZDkiLCJhdWQiOiIiLCJ2cCI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVQcmVzZW50YXRpb24iXSwiaWQiOiJ1cm46dXVpZDo3ZGE0MzJlZi1iYzIwLTQyZGMtOTRlNS1mNWMwYmQ3MDI3MTEiLCJob2xkZXIiOiJkaWQ6a2V5OnpEbmFlYXU5OHhudDlhd2NrTnBFcTM0Q3cyTGJmcEVpZ2dKYzQzdXdNaDh1QWE4QjMiLCJjbmYiOm51bGwsInZlcmlmaWFibGVDcmVkZW50aWFsIjpbImV5SnJhV1FpT2lKa2FXUTZhbmRyT21WNVNuSmtTR3RwVDJsS1VGTXhRV2xNUTBwcVkyNVphVTlwU2taYVJFa3hUbFJGTlVscGQybGhNbXhyU1dwdmFXRlZjRTVWZWxacFlURndWMU5YZUhWWk1scDRXREI0YlZneFRqRmxSVzk1VTI1U1VrNVZhREpaV0c4elpFWmtVV0pyUm5GV1ZsWnJZM2xKYzBsdVoybFBhVXBIVjIxU01tUXdUVFJaVldSdlZXNWtlR1ZzWkhka1IxWnhUVVUxWVZvelVqTlhWVVpLVFZaT05WSnRZM2hpVlhSRlVsWlNVRnB1UmtaSmJqQWphVXBOVXpWaWExcFdTV3h1WTJaeFgweG1YMU4xZUVveVNuUlJOVWgyWVhvM2RGZFFia0ZxVlZWa2N5SXNJblI1Y0NJNklrcFhWQ0lzSW1Gc1p5STZJa1ZrUkZOQkluMC5leUpwYzNNaU9pSmthV1E2YW5kck9tVjVTbkprU0d0cFQybEtVRk14UVdsTVEwcHFZMjVaYVU5cFNrWmFSRWt4VGxSRk5VbHBkMmxoTW14clNXcHZhV0ZWY0U1VmVsWnBZVEZ3VjFOWGVIVlpNbHA0V0RCNGJWZ3hUakZsUlc5NVUyNVNVazVWYURKWldHOHpaRVprVVdKclJuRldWbFpyWTNsSmMwbHVaMmxQYVVwSFYyMVNNbVF3VFRSWlZXUnZWVzVrZUdWc1pIZGtSMVp4VFVVMVlWb3pVak5YVlVaS1RWWk9OVkp0WTNoaVZYUkZVbFpTVUZwdVJrWkpiakFpTENKemRXSWlPaUprYVdRNmEyVjVPbnBFYm1GbFlYVTVPSGh1ZERsaGQyTnJUbkJGY1RNMFEzY3lUR0ptY0VWcFoyZEtZelF6ZFhkTmFEaDFRV0U0UWpNaUxDSjJZeUk2ZXlKQVkyOXVkR1Y0ZENJNld5Sm9kSFJ3Y3pvdkwzZDNkeTUzTXk1dmNtY3ZNakF4T0M5amNtVmtaVzUwYVdGc2N5OTJNU0lzSW1oMGRIQnpPaTh2ZDNkM0xuY3pMbTl5Wnk4eU1ERTRMMk55WldSbGJuUnBZV3h6TDJWNFlXMXdiR1Z6TDNZeElsMHNJbWxrSWpvaWRYSnVPblYxYVdRNk56RTRPVFU1TWpZdFpqUmlZeTAwT0dOaExUazNNbVF0TWpFeE9ETTRNamd6TW1Ka0lpd2lkSGx3WlNJNld5SldaWEpwWm1saFlteGxRM0psWkdWdWRHbGhiQ0lzSWxWdWFYWmxjbk5wZEhsRVpXZHlaV1VpWFN3aWFYTnpkV1Z5SWpwN0ltbGtJam9pWkdsa09tcDNhenBsZVVweVpFaHJhVTlwU2xCVE1VRnBURU5LYW1OdVdXbFBhVXBHV2tSSk1VNVVSVFZKYVhkcFlUSnNhMGxxYjJsaFZYQk9WWHBXYVdFeGNGZFRWM2gxV1RKYWVGZ3dlRzFZTVU0eFpVVnZlVk51VWxKT1ZXZ3lXVmh2TTJSR1pGRmlhMFp4VmxaV2EyTjVTWE5KYm1kcFQybEtSMWR0VWpKa01FMDBXVlZrYjFWdVpIaGxiR1IzWkVkV2NVMUZOV0ZhTTFJelYxVkdTazFXVGpWU2JXTjRZbFYwUlZKV1VsQmFia1pHU1c0d0luMHNJbWx6YzNWaGJtTmxSR0YwWlNJNklqSXdNall0TURVdE1qQlVNVEU2TWpjNk5EQXVOemt5TVRBek9URXpXaUlzSW1OeVpXUmxiblJwWVd4VGRXSnFaV04wSWpwN0ltbGtJam9pWkdsa09tdGxlVHA2Ukc1aFpXRjFPVGg0Ym5RNVlYZGphMDV3UlhFek5FTjNNa3hpWm5CRmFXZG5TbU0wTTNWM1RXZzRkVUZoT0VJeklpd2laR1ZuY21WbElqcDdJblI1Y0dVaU9pSkNZV05vWld4dmNrUmxaM0psWlNJc0ltNWhiV1VpT2lKQ1lXTm9aV3h2Y2lCdlppQlRZMmxsYm1ObElHRnVaQ0JCY25SekluMTlMQ0psZUhCcGNtRjBhVzl1UkdGMFpTSTZJakl3TWpjdE1EVXRNakJVTVRFNk1qYzZOREF1TnpreU1UTTNOakUwV2lKOUxDSnFkR2tpT2lKMWNtNDZkWFZwWkRvM01UZzVOVGt5TmkxbU5HSmpMVFE0WTJFdE9UY3laQzB5TVRFNE16Z3lPRE15WW1RaUxDSmxlSEFpT2pFNE1UQTRNVEkwTmpBc0ltbGhkQ0k2TVRjM09USTNOalEyTUN3aWJtSm1Jam94TnpjNU1qYzJORFl3ZlEuX29XSVFBUlp2b1RQNzFLMkp4Ym5EcFVhQTl0ZkJ4TUJNNmUza2psQVRmZnF3cFY1X3JIZ21ZRVlnWk5yUTFhSzdpZGxYQ2JzeXh0SGlVclZnUjU0QWciXX19.D6w2TPAx92_FGTH64K7ANrir6aJRRvyhicT1q5fJVuIy8M5EVjDWDM3sOibTHgDcmr3-EM-fv2WmTiY7YUpU2Q", "payload": { "sub": "did:key:zDnaeau98xnt9awckNpEq34Cw2LbfpEiggJc43uwMh8uAa8B3", "nbf": 1779276975, "iat": 1779277035, "jti": "urn:uuid:7da432ef-bc20-42dc-94e5-f5c0bd702711", "iss": "did:key:zDnaeau98xnt9awckNpEq34Cw2LbfpEiggJc43uwMh8uAa8B3", "nonce": "f9b57ec3-c852-49ad-9537-2e1820f23ad9", "aud": "", "vp": { "@context": [ "https://www.w3.org/2018/credentials/v1" ], "type": [ "VerifiablePresentation" ], "id": "urn:uuid:7da432ef-bc20-42dc-94e5-f5c0bd702711", "holder": "did:key:zDnaeau98xnt9awckNpEq34Cw2LbfpEiggJc43uwMh8uAa8B3", "verifiableCredential": [ "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaWFVcE5VelZpYTFwV1NXeHVZMlp4WDB4bVgxTjFlRW95U25SUk5VaDJZWG8zZEZkUWJrRnFWVlZrY3lJc0luZ2lPaUpHV21SMmQwTTRZVWRvVW5keGVsZHdkR1ZxTUU1YVozUjNXVUZKTVZONVJtY3hiVXRFUlZSUFpuRkZJbjAjaUpNUzVia1pWSWxuY2ZxX0xmX1N1eEoySnRRNUh2YXo3dFdQbkFqVVVkcyIsInR5cCI6IkpXVCIsImFsZyI6IkVkRFNBIn0.eyJpc3MiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaWFVcE5VelZpYTFwV1NXeHVZMlp4WDB4bVgxTjFlRW95U25SUk5VaDJZWG8zZEZkUWJrRnFWVlZrY3lJc0luZ2lPaUpHV21SMmQwTTRZVWRvVW5keGVsZHdkR1ZxTUU1YVozUjNXVUZKTVZONVJtY3hiVXRFUlZSUFpuRkZJbjAiLCJzdWIiOiJkaWQ6a2V5OnpEbmFlYXU5OHhudDlhd2NrTnBFcTM0Q3cyTGJmcEVpZ2dKYzQzdXdNaDh1QWE4QjMiLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL2V4YW1wbGVzL3YxIl0sImlkIjoidXJuOnV1aWQ6NzE4OTU5MjYtZjRiYy00OGNhLTk3MmQtMjExODM4MjgzMmJkIiwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIlVuaXZlcnNpdHlEZWdyZWUiXSwiaXNzdWVyIjp7ImlkIjoiZGlkOmp3azpleUpyZEhraU9pSlBTMUFpTENKamNuWWlPaUpGWkRJMU5URTVJaXdpYTJsa0lqb2lhVXBOVXpWaWExcFdTV3h1WTJaeFgweG1YMU4xZUVveVNuUlJOVWgyWVhvM2RGZFFia0ZxVlZWa2N5SXNJbmdpT2lKR1dtUjJkME00WVVkb1VuZHhlbGR3ZEdWcU1FNWFaM1IzV1VGSk1WTjVSbWN4YlV0RVJWUlBabkZGSW4wIn0sImlzc3VhbmNlRGF0ZSI6IjIwMjYtMDUtMjBUMTE6Mjc6NDAuNzkyMTAzOTEzWiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmtleTp6RG5hZWF1OTh4bnQ5YXdja05wRXEzNEN3MkxiZnBFaWdnSmM0M3V3TWg4dUFhOEIzIiwiZGVncmVlIjp7InR5cGUiOiJCYWNoZWxvckRlZ3JlZSIsIm5hbWUiOiJCYWNoZWxvciBvZiBTY2llbmNlIGFuZCBBcnRzIn19LCJleHBpcmF0aW9uRGF0ZSI6IjIwMjctMDUtMjBUMTE6Mjc6NDAuNzkyMTM3NjE0WiJ9LCJqdGkiOiJ1cm46dXVpZDo3MTg5NTkyNi1mNGJjLTQ4Y2EtOTcyZC0yMTE4MzgyODMyYmQiLCJleHAiOjE4MTA4MTI0NjAsImlhdCI6MTc3OTI3NjQ2MCwibmJmIjoxNzc5Mjc2NDYwfQ._oWIQARZvoTP71K2JxbnDpUaA9tfBxMBM6e3kjlATffqwpV5_rHgmYEYgZNrQ1aK7idlXCbsyxtHiUrVgR54Ag" ] } }, "issuer": "did:key:zDnaeau98xnt9awckNpEq34Cw2LbfpEiggJc43uwMh8uAa8B3", "audience": "", "nonce": "f9b57ec3-c852-49ad-9537-2e1820f23ad9", "vp": { "@context": [ "https://www.w3.org/2018/credentials/v1" ], "type": [ "VerifiablePresentation" ], "id": "urn:uuid:7da432ef-bc20-42dc-94e5-f5c0bd702711", "holder": "did:key:zDnaeau98xnt9awckNpEq34Cw2LbfpEiggJc43uwMh8uAa8B3", "verifiableCredential": [ "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaWFVcE5VelZpYTFwV1NXeHVZMlp4WDB4bVgxTjFlRW95U25SUk5VaDJZWG8zZEZkUWJrRnFWVlZrY3lJc0luZ2lPaUpHV21SMmQwTTRZVWRvVW5keGVsZHdkR1ZxTUU1YVozUjNXVUZKTVZONVJtY3hiVXRFUlZSUFpuRkZJbjAjaUpNUzVia1pWSWxuY2ZxX0xmX1N1eEoySnRRNUh2YXo3dFdQbkFqVVVkcyIsInR5cCI6IkpXVCIsImFsZyI6IkVkRFNBIn0.eyJpc3MiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaWFVcE5VelZpYTFwV1NXeHVZMlp4WDB4bVgxTjFlRW95U25SUk5VaDJZWG8zZEZkUWJrRnFWVlZrY3lJc0luZ2lPaUpHV21SMmQwTTRZVWRvVW5keGVsZHdkR1ZxTUU1YVozUjNXVUZKTVZONVJtY3hiVXRFUlZSUFpuRkZJbjAiLCJzdWIiOiJkaWQ6a2V5OnpEbmFlYXU5OHhudDlhd2NrTnBFcTM0Q3cyTGJmcEVpZ2dKYzQzdXdNaDh1QWE4QjMiLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL2V4YW1wbGVzL3YxIl0sImlkIjoidXJuOnV1aWQ6NzE4OTU5MjYtZjRiYy00OGNhLTk3MmQtMjExODM4MjgzMmJkIiwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIlVuaXZlcnNpdHlEZWdyZWUiXSwiaXNzdWVyIjp7ImlkIjoiZGlkOmp3azpleUpyZEhraU9pSlBTMUFpTENKamNuWWlPaUpGWkRJMU5URTVJaXdpYTJsa0lqb2lhVXBOVXpWaWExcFdTV3h1WTJaeFgweG1YMU4xZUVveVNuUlJOVWgyWVhvM2RGZFFia0ZxVlZWa2N5SXNJbmdpT2lKR1dtUjJkME00WVVkb1VuZHhlbGR3ZEdWcU1FNWFaM1IzV1VGSk1WTjVSbWN4YlV0RVJWUlBabkZGSW4wIn0sImlzc3VhbmNlRGF0ZSI6IjIwMjYtMDUtMjBUMTE6Mjc6NDAuNzkyMTAzOTEzWiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmtleTp6RG5hZWF1OTh4bnQ5YXdja05wRXEzNEN3MkxiZnBFaWdnSmM0M3V3TWg4dUFhOEIzIiwiZGVncmVlIjp7InR5cGUiOiJCYWNoZWxvckRlZ3JlZSIsIm5hbWUiOiJCYWNoZWxvciBvZiBTY2llbmNlIGFuZCBBcnRzIn19LCJleHBpcmF0aW9uRGF0ZSI6IjIwMjctMDUtMjBUMTE6Mjc6NDAuNzkyMTM3NjE0WiJ9LCJqdGkiOiJ1cm46dXVpZDo3MTg5NTkyNi1mNGJjLTQ4Y2EtOTcyZC0yMTE4MzgyODMyYmQiLCJleHAiOjE4MTA4MTI0NjAsImlhdCI6MTc3OTI3NjQ2MCwibmJmIjoxNzc5Mjc2NDYwfQ._oWIQARZvoTP71K2JxbnDpUaA9tfBxMBM6e3kjlATffqwpV5_rHgmYEYgZNrQ1aK7idlXCbsyxtHiUrVgR54Ag" ] }, "credentials": [ { "type": "vc-w3c_1_1", "disclosables": {}, "credentialData": { "@context": [ "https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1" ], "id": "urn:uuid:71895926-f4bc-48ca-972d-2118382832bd", "type": [ "VerifiableCredential", "UniversityDegree" ], "issuer": { "id": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiaUpNUzVia1pWSWxuY2ZxX0xmX1N1eEoySnRRNUh2YXo3dFdQbkFqVVVkcyIsIngiOiJGWmR2d0M4YUdoUndxeldwdGVqME5aZ3R3WUFJMVN5RmcxbUtERVRPZnFFIn0" }, "issuanceDate": "2026-05-20T11:27:40.792103913Z", "credentialSubject": { "id": "did:key:zDnaeau98xnt9awckNpEq34Cw2LbfpEiggJc43uwMh8uAa8B3", "degree": { "type": "BachelorDegree", "name": "Bachelor of Science and Arts" } }, "expirationDate": "2027-05-20T11:27:40.792137614Z" }, "issuer": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiaUpNUzVia1pWSWxuY2ZxX0xmX1N1eEoySnRRNUh2YXo3dFdQbkFqVVVkcyIsIngiOiJGWmR2d0M4YUdoUndxeldwdGVqME5aZ3R3WUFJMVN5RmcxbUtERVRPZnFFIn0", "subject": "did:key:zDnaeau98xnt9awckNpEq34Cw2LbfpEiggJc43uwMh8uAa8B3", "signature": { "type": "signature-jwt", "signature": "_oWIQARZvoTP71K2JxbnDpUaA9tfBxMBM6e3kjlATffqwpV5_rHgmYEYgZNrQ1aK7idlXCbsyxtHiUrVgR54Ag", "jwtHeader": { "kid": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiaUpNUzVia1pWSWxuY2ZxX0xmX1N1eEoySnRRNUh2YXo3dFdQbkFqVVVkcyIsIngiOiJGWmR2d0M4YUdoUndxeldwdGVqME5aZ3R3WUFJMVN5RmcxbUtERVRPZnFFIn0#iJMS5bkZVIlncfq_Lf_SuxJ2JtQ5Hvaz7tWPnAjUUds", "typ": "JWT", "alg": "EdDSA" } }, "signed": "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaWFVcE5VelZpYTFwV1NXeHVZMlp4WDB4bVgxTjFlRW95U25SUk5VaDJZWG8zZEZkUWJrRnFWVlZrY3lJc0luZ2lPaUpHV21SMmQwTTRZVWRvVW5keGVsZHdkR1ZxTUU1YVozUjNXVUZKTVZONVJtY3hiVXRFUlZSUFpuRkZJbjAjaUpNUzVia1pWSWxuY2ZxX0xmX1N1eEoySnRRNUh2YXo3dFdQbkFqVVVkcyIsInR5cCI6IkpXVCIsImFsZyI6IkVkRFNBIn0.eyJpc3MiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaWFVcE5VelZpYTFwV1NXeHVZMlp4WDB4bVgxTjFlRW95U25SUk5VaDJZWG8zZEZkUWJrRnFWVlZrY3lJc0luZ2lPaUpHV21SMmQwTTRZVWRvVW5keGVsZHdkR1ZxTUU1YVozUjNXVUZKTVZONVJtY3hiVXRFUlZSUFpuRkZJbjAiLCJzdWIiOiJkaWQ6a2V5OnpEbmFlYXU5OHhudDlhd2NrTnBFcTM0Q3cyTGJmcEVpZ2dKYzQzdXdNaDh1QWE4QjMiLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL2V4YW1wbGVzL3YxIl0sImlkIjoidXJuOnV1aWQ6NzE4OTU5MjYtZjRiYy00OGNhLTk3MmQtMjExODM4MjgzMmJkIiwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIlVuaXZlcnNpdHlEZWdyZWUiXSwiaXNzdWVyIjp7ImlkIjoiZGlkOmp3azpleUpyZEhraU9pSlBTMUFpTENKamNuWWlPaUpGWkRJMU5URTVJaXdpYTJsa0lqb2lhVXBOVXpWaWExcFdTV3h1WTJaeFgweG1YMU4xZUVveVNuUlJOVWgyWVhvM2RGZFFia0ZxVlZWa2N5SXNJbmdpT2lKR1dtUjJkME00WVVkb1VuZHhlbGR3ZEdWcU1FNWFaM1IzV1VGSk1WTjVSbWN4YlV0RVJWUlBabkZGSW4wIn0sImlzc3VhbmNlRGF0ZSI6IjIwMjYtMDUtMjBUMTE6Mjc6NDAuNzkyMTAzOTEzWiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmtleTp6RG5hZWF1OTh4bnQ5YXdja05wRXEzNEN3MkxiZnBFaWdnSmM0M3V3TWg4dUFhOEIzIiwiZGVncmVlIjp7InR5cGUiOiJCYWNoZWxvckRlZ3JlZSIsIm5hbWUiOiJCYWNoZWxvciBvZiBTY2llbmNlIGFuZCBBcnRzIn19LCJleHBpcmF0aW9uRGF0ZSI6IjIwMjctMDUtMjBUMTE6Mjc6NDAuNzkyMTM3NjE0WiJ9LCJqdGkiOiJ1cm46dXVpZDo3MTg5NTkyNi1mNGJjLTQ4Y2EtOTcyZC0yMTE4MzgyODMyYmQiLCJleHAiOjE4MTA4MTI0NjAsImlhdCI6MTc3OTI3NjQ2MCwibmJmIjoxNzc5Mjc2NDYwfQ._oWIQARZvoTP71K2JxbnDpUaA9tfBxMBM6e3kjlATffqwpV5_rHgmYEYgZNrQ1aK7idlXCbsyxtHiUrVgR54Ag", "format": "jwt_vc_json" } ] } }, "presented_credentials": { "example_university_degree_jwt_vc": [ { "type": "vc-w3c_1_1", "disclosables": {}, "credentialData": { "@context": [ "https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1" ], "id": "urn:uuid:71895926-f4bc-48ca-972d-2118382832bd", "type": [ "VerifiableCredential", "UniversityDegree" ], "issuer": { "id": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiaUpNUzVia1pWSWxuY2ZxX0xmX1N1eEoySnRRNUh2YXo3dFdQbkFqVVVkcyIsIngiOiJGWmR2d0M4YUdoUndxeldwdGVqME5aZ3R3WUFJMVN5RmcxbUtERVRPZnFFIn0" }, "issuanceDate": "2026-05-20T11:27:40.792103913Z", "credentialSubject": { "id": "did:key:zDnaeau98xnt9awckNpEq34Cw2LbfpEiggJc43uwMh8uAa8B3", "degree": { "type": "BachelorDegree", "name": "Bachelor of Science and Arts" } }, "expirationDate": "2027-05-20T11:27:40.792137614Z" }, "issuer": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiaUpNUzVia1pWSWxuY2ZxX0xmX1N1eEoySnRRNUh2YXo3dFdQbkFqVVVkcyIsIngiOiJGWmR2d0M4YUdoUndxeldwdGVqME5aZ3R3WUFJMVN5RmcxbUtERVRPZnFFIn0", "subject": "did:key:zDnaeau98xnt9awckNpEq34Cw2LbfpEiggJc43uwMh8uAa8B3", "signature": { "type": "signature-jwt", "signature": "_oWIQARZvoTP71K2JxbnDpUaA9tfBxMBM6e3kjlATffqwpV5_rHgmYEYgZNrQ1aK7idlXCbsyxtHiUrVgR54Ag", "jwtHeader": { "kid": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiaUpNUzVia1pWSWxuY2ZxX0xmX1N1eEoySnRRNUh2YXo3dFdQbkFqVVVkcyIsIngiOiJGWmR2d0M4YUdoUndxeldwdGVqME5aZ3R3WUFJMVN5RmcxbUtERVRPZnFFIn0#iJMS5bkZVIlncfq_Lf_SuxJ2JtQ5Hvaz7tWPnAjUUds", "typ": "JWT", "alg": "EdDSA" } }, "signed": "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaWFVcE5VelZpYTFwV1NXeHVZMlp4WDB4bVgxTjFlRW95U25SUk5VaDJZWG8zZEZkUWJrRnFWVlZrY3lJc0luZ2lPaUpHV21SMmQwTTRZVWRvVW5keGVsZHdkR1ZxTUU1YVozUjNXVUZKTVZONVJtY3hiVXRFUlZSUFpuRkZJbjAjaUpNUzVia1pWSWxuY2ZxX0xmX1N1eEoySnRRNUh2YXo3dFdQbkFqVVVkcyIsInR5cCI6IkpXVCIsImFsZyI6IkVkRFNBIn0.eyJpc3MiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaWFVcE5VelZpYTFwV1NXeHVZMlp4WDB4bVgxTjFlRW95U25SUk5VaDJZWG8zZEZkUWJrRnFWVlZrY3lJc0luZ2lPaUpHV21SMmQwTTRZVWRvVW5keGVsZHdkR1ZxTUU1YVozUjNXVUZKTVZONVJtY3hiVXRFUlZSUFpuRkZJbjAiLCJzdWIiOiJkaWQ6a2V5OnpEbmFlYXU5OHhudDlhd2NrTnBFcTM0Q3cyTGJmcEVpZ2dKYzQzdXdNaDh1QWE4QjMiLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL2V4YW1wbGVzL3YxIl0sImlkIjoidXJuOnV1aWQ6NzE4OTU5MjYtZjRiYy00OGNhLTk3MmQtMjExODM4MjgzMmJkIiwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIlVuaXZlcnNpdHlEZWdyZWUiXSwiaXNzdWVyIjp7ImlkIjoiZGlkOmp3azpleUpyZEhraU9pSlBTMUFpTENKamNuWWlPaUpGWkRJMU5URTVJaXdpYTJsa0lqb2lhVXBOVXpWaWExcFdTV3h1WTJaeFgweG1YMU4xZUVveVNuUlJOVWgyWVhvM2RGZFFia0ZxVlZWa2N5SXNJbmdpT2lKR1dtUjJkME00WVVkb1VuZHhlbGR3ZEdWcU1FNWFaM1IzV1VGSk1WTjVSbWN4YlV0RVJWUlBabkZGSW4wIn0sImlzc3VhbmNlRGF0ZSI6IjIwMjYtMDUtMjBUMTE6Mjc6NDAuNzkyMTAzOTEzWiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmtleTp6RG5hZWF1OTh4bnQ5YXdja05wRXEzNEN3MkxiZnBFaWdnSmM0M3V3TWg4dUFhOEIzIiwiZGVncmVlIjp7InR5cGUiOiJCYWNoZWxvckRlZ3JlZSIsIm5hbWUiOiJCYWNoZWxvciBvZiBTY2llbmNlIGFuZCBBcnRzIn19LCJleHBpcmF0aW9uRGF0ZSI6IjIwMjctMDUtMjBUMTE6Mjc6NDAuNzkyMTM3NjE0WiJ9LCJqdGkiOiJ1cm46dXVpZDo3MTg5NTkyNi1mNGJjLTQ4Y2EtOTcyZC0yMTE4MzgyODMyYmQiLCJleHAiOjE4MTA4MTI0NjAsImlhdCI6MTc3OTI3NjQ2MCwibmJmIjoxNzc5Mjc2NDYwfQ._oWIQARZvoTP71K2JxbnDpUaA9tfBxMBM6e3kjlATffqwpV5_rHgmYEYgZNrQ1aK7idlXCbsyxtHiUrVgR54Ag", "format": "jwt_vc_json" } ] } }, "timestamp": 1779276996743, "parent": "waltid.tenant1.verifier1" } ``` **Key Response Fields:** - **id**: _String_ - The verification session ID - **setup**: _Object_ - The complete session configuration that was used to create this verification session - **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 - **presented_credentials**: _Object_ (optional) - The parsed credentials presented by the user, organized by credential ID **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/enterprise-stack/services/verifier2-service/callback-and-sse.md) for details (Enterprise implementation may differ). --- ## 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 /v1/{target}/verifier2-service-api/verification-session/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Verifier2%20Service%20-%20Verification%20Sessions/post_v1__target__verifier2_service_api_verification_session_create) ##### Example Request ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/verifier2-service-api/verification-session/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -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 `policy_results` 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 | --- ## 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 `signed_request: 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 during [verifier2 service creation](https://docs.walt.id/enterprise-stack/services/verifier2-service/setup.md#body-parameters) 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 `encrypted_response: true` in the request body. **Key Configuration Required:** Encrypted responses require a key (`key`) for decrypting the VP token response. Configure it during [verifier2 service creation](https://docs.walt.id/enterprise-stack/services/verifier2-service/setup.md#body-parameters) 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/enterprise-stack/services/verifier2-service/policies/overview.md) for the complete list and configuration options. --- ## Next Steps - **Apply custom policies** — See [Policies](https://docs.walt.id/enterprise-stack/services/verifier2-service/policies/overview.md) for validation rules beyond signature verification - **Verify other credential types** — Try [SD-JWT VC](https://docs.walt.id/enterprise-stack/services/verifier2-service/credential-verification/sd-jwt-vc-oid4vp.md) or [mDL](https://docs.walt.id/enterprise-stack/services/verifier2-service/credential-verification/mdl-oid4vp.md) - **Use DC API** — Try the [Digital Credentials API](https://docs.walt.id/enterprise-stack/services/verifier2-service/credential-verification/dc-api.md) for browser-native credential exchange -------------------------------------------------------------------------------- PAGE: Verifying SD-JWT VC Credentials via OID4VP URL: https://docs.walt.id/enterprise-stack/services/verifier2-service/credential-verification/sd-jwt-vc-oid4vp RAW: https://docs.walt.id/enterprise-stack/services/verifier2-service/credential-verification/sd-jwt-vc-oid4vp.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Verifying SD-JWT VC credentials via OID4VP This guide shows you how to verify SD-JWT VC credentials using the Enterprise Verifier2 Service 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. ## Prerequisites Before you begin, ensure you have: - **Enterprise Verifier2 Service setup** — Follow the [Setup](https://docs.walt.id/enterprise-stack/services/verifier2-service/setup.md) guide - **Valid authentication token** — Required for all API calls to the Enterprise Stack - **A test wallet with credentials** — Use the [walt.id Wallet](https://docs.walt.id/enterprise-stack/services/wallet-service/overview.md) for testing, or any OID4VP-compatible wallet **Note:** **Enterprise Architecture:** The Enterprise Verifier2 Service runs as a dedicated microservice within your organization's infrastructure. All requests are scoped to `{organizationID}.{tenantID}.{verifierServiceID}` for multi-tenancy isolation. --- ## Example 1: Basic Verification This example requests an SD-JWT identity credential and verifies its signature. **Option: CURL** **Endpoint:** `POST /v1/{target}/verifier2-service-api/verification-session/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Verifier2%20Service%20-%20Verification%20Sessions/post_v1__target__verifier2_service_api_verification_session_create) ##### Example Request ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/verifier2-service-api/verification-session/create' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -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://{orgID}.enterprise-sandbox.waltid.dev/v1/waltid.issuer/issuer-service-api/openid4vc/draft13/identity_credential" ] } } ] } } }' ``` **Path Parameters** - **orgID**: _String_ (required) - Your organization ID. When performing operations within an organization, use the organization's Base URL. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.waltid.dev` when using the sandbox environment. - **target**: _String_ (required) - The resource identifier indicating the organization + tenant + verifier service (`{organizationID}.{tenantID}.{verifierServiceID}`). Example: `waltid.tenant1.verifier1` **Header Parameters** - **Authorization**: _String_ (required) - Bearer token for Enterprise Stack authentication. Format: `Bearer {token}`. **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 `"dc+sd-jwt"`. - **meta.vct_values**: _Array_ - List of credential types. Example: `["https://{orgID}.enterprise-sandbox.waltid.dev/v1/waltid.issuer/issuer-service-api/openid4vc/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/enterprise-stack/services/verifier2-service/policies/overview.md) for the complete list. - **vp_policies**: _Array_ (optional) - Policies applied to the entire presentation - **signed_request**: _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. - **encrypted_response**: _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 during [verifier2 service creation](https://docs.walt.id/enterprise-stack/services/verifier2-service/setup.md#body-parameters). Must be a serialized key in the format `{"type":"jwk","jwk":{...}}`. Required when `signed_request: true` or `encrypted_response: 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 during [verifier2 service creation](https://docs.walt.id/enterprise-stack/services/verifier2-service/setup.md#body-parameters). 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. - **credential_ids**: _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. - **expiration_duration**: _String_ (optional) - ISO-8601 duration specifying how long the verification session remains valid. Defaults to 10 minutes (`PT10M`). Example: `"PT1H"` for 1 hour, `"PT30M"` for 30 minutes. - **expiration_date**: _String_ (optional) - ISO-8601 datetime specifying the exact time when the session expires. Takes precedence over `expiration_duration` if both are set. Example: `"2026-05-21T15:00:00Z"`. Session expires if left unused (no presentation pushed). Once a presentation is received, the session is no longer eligible for expiry. - **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. - The service constructs: Request URI: `{urlPrefix}/{target}/verifier2-service-api/{sessionId}/request` and Response URI: `{urlPrefix}/{target}/verifier2-service-api/{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. Can be set as a service-wide default during [verifier2 service creation](https://docs.walt.id/enterprise-stack/services/verifier2-service/setup.md#body-parameters) and overridden per-session here. - 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:** **Service-Level Defaults:** The `key`, `x5c`, and `url_host` parameters can be configured as service-wide defaults during [verifier2 service creation](https://docs.walt.id/enterprise-stack/services/verifier2-service/setup.md#body-parameters). Request-level parameters provided here override these defaults for that specific session only. --- ##### Example Response ```json { "sessionId": "ff4f0c86-56ce-4e4a-a137-dd5569049ff3", "bootstrapAuthorizationRequestUrl": "openid4vp://authorize?client_id=did%3Ajwk%3AeyJr...", "fullAuthorizationRequestUrl": "openid4vp://authorize?response_type=vp_token&client_id=did%3Ajwk%3AeyJr...", "creationTarget": "org.tenant.verifier.ff4f0c86-56ce-4e4a-a137-dd5569049ff3" } ``` **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). - **creationTarget**: _String_ - The full resource path including the session ID. Used for querying the session status. ## 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 ### 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 /v1/{creationTarget}/verifier2-service-api/verification-session/info` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Verifier2%20Service%20-%20Verification%20Sessions/get_v1__target__verifier2_service_api_verification_session__sessionId__info) ##### Example Request ```bash curl -X 'GET' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{creationTarget}/verifier2-service-api/verification-session/info' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' ``` **Path Parameters** - **orgID**: _String_ (required) - Your organization ID for the base URL. - **creationTarget**: _String_ (required) - The `creationTarget` value returned when creating the verification session. It is a concatenation of the target resource and the session ID. **Header Parameters** - **Authorization**: _String_ (required) - Bearer token for Enterprise Stack authentication. Format: `Bearer {token}`. --- ##### Example Response ```json { "_id": "waltid.tenant1.verifier1.fc1e70cc-04b3-4c17-a454-cf03826ee520", "session": { "id": "fc1e70cc-04b3-4c17-a454-cf03826ee520", "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, "claims": [ { "path": [ "given_name" ] }, { "path": [ "family_name" ] }, { "path": [ "address", "street_address" ] } ] } ] }, "signed_request": false, "encrypted_response": false, "policies": {} }, "url_config": {} }, "creationDate": "2026-05-20T12:26:38.286031245Z", "expirationDate": "2026-05-20T12:36:52.697195591Z", "retentionDate": "2036-05-20T12:26:38.286031245Z", "status": "SUCCESSFUL", "attempted": true, "reattemptable": true, "bootstrapAuthorizationRequest": { "response_type": "vp_token", "request_uri": "https://waltid.testing-0-20-0.enterprise.test.waltid.cloud/v1/waltid.tenant1.verifier1/verifier2-service-api/fc1e70cc-04b3-4c17-a454-cf03826ee520/request" }, "bootstrapAuthorizationRequestUrl": "openid4vp://authorize?request_uri=https%3A%2F%2Fwaltid.testing-0-20-0.enterprise.test.waltid.cloud%2Fv1%2Fwaltid.tenant1.verifier1%2Fverifier2-service-api%2Ffc1e70cc-04b3-4c17-a454-cf03826ee520%2Frequest", "authorizationRequest": { "response_type": "vp_token", "state": "295283f9-a5fa-4040-afc3-8af30bdb8e54", "response_mode": "direct_post", "nonce": "a63dca7c-1557-46ac-9972-cea6696ebf28", "response_uri": "https://waltid.testing-0-20-0.enterprise.test.waltid.cloud/v1/waltid.tenant1.verifier1/verifier2-service-api/fc1e70cc-04b3-4c17-a454-cf03826ee520/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, "claims": [ { "path": [ "given_name" ] }, { "path": [ "family_name" ] }, { "path": [ "address", "street_address" ] } ] } ] }, "client_metadata": { "vp_formats_supported": { "mso_mdoc": { "issuerauth_alg_values": [ -7, -9, -50 ], "deviceauth_alg_values": [ -7, -9, -50, -65537 ] } }, "client_name": "Verifier" } }, "authorizationRequestUrl": "openid4vp://authorize?response_type=vp_token&state=295283f9-a5fa-4040-afc3-8af30bdb8e54&response_mode=direct_post&nonce=a63dca7c-1557-46ac-9972-cea6696ebf28&response_uri=https%3A%2F%2Fwaltid.testing-0-20-0.enterprise.test.waltid.cloud%2Fv1%2Fwaltid.tenant1.verifier1%2Fverifier2-service-api%2Ffc1e70cc-04b3-4c17-a454-cf03826ee520%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%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%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%22Verifier%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": { "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": {}, "errors": [], "execution_time": "PT0.000023300S" }, "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": "EC", "crv": "P-256", "kid": "61BF1dMVfrdRMGIEO4ZOSZX39a0stj0_d8h4CkATQRU", "x": "m4r2Sf0jYK3OXLPrdrwC2OSSkwCBUVNJH-kpIlzmBzo", "y": "Uqa4uWu4rgrm3Cj9wugwYIig5ZS9DkLKxKnGk8sYr1o" }, "verified_kb_jwt_content": { "nonce": "a63dca7c-1557-46ac-9972-cea6696ebf28", "iat": 1779280012, "sd_hash": "Xt4VeNQBksR-pQwtrYe8xLhabbdQeKdUJtN-0ELnGR4" } }, "errors": [], "execution_time": "PT0.001774609S" }, "dc+sd-jwt/nonce-check": { "policy_executed": { "policy": "dc+sd-jwt/nonce-check", "id": "dc+sd-jwt/nonce-check", "description": "Check if presentation nonce matches expected nonce for session" }, "success": true, "results": {}, "errors": [], "execution_time": "PT0.000009700S" }, "dc+sd-jwt/sd_hash-check": { "policy_executed": { "policy": "dc+sd-jwt/sd_hash-check", "id": "dc+sd-jwt/sd_hash-check", "description": "Verify SD-JWT Key Binding by recalculating SD Hashes" }, "success": true, "results": { "presentation_sdhash": "Xt4VeNQBksR-pQwtrYe8xLhabbdQeKdUJtN-0ELnGR4", "recalculated_sdhash": "Xt4VeNQBksR-pQwtrYe8xLhabbdQeKdUJtN-0ELnGR4" }, "errors": [], "execution_time": "PT0.000107600S" } } }, "vc_policies": [ { "policy": { "policy": "signature", "id": "signature" }, "success": true, "result": { "verification_result": true, "signed_credential": "eyJraWQiOiJkaWQ6a2V5Ono2TWtqb1JocTFqU05KZExpcnVTWHJGRnhhZ3FyenRaYVhIcUhHVVRLSmJjTnl3cCN6Nk1ram9SaHExalNOSmRMaXJ1U1hyRkZ4YWdxcnp0WmFYSHFIR1VUS0piY055d3AiLCJ0eXAiOiJkYytzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJnaXZlbl9uYW1lIjoiSm9obiIsImZhbWlseV9uYW1lIjoiRG9lIiwiZW1haWwiOiJqb2huZG9lQGV4YW1wbGUuY29tIiwicGhvbmVfbnVtYmVyIjoiKzEtMjAyLTU1NS0wMTAxIiwiYWRkcmVzcyI6eyJzdHJlZXRfYWRkcmVzcyI6IjEyMyBNYWluIFN0IiwibG9jYWxpdHkiOiJBbnl0b3duIiwicmVnaW9uIjoiQW55c3RhdGUiLCJjb3VudHJ5IjoiVVMifSwiaXNfb3Zlcl8xOCI6dHJ1ZSwiaXNfb3Zlcl8yMSI6dHJ1ZSwiaXNfb3Zlcl82NSI6dHJ1ZSwiaWQiOiJ1cm46dXVpZDpkOGNmZWJkYS05NjAxLTRkNzEtODllYy05ZTFhZWJmOTRhZDkiLCJpYXQiOjE3NzkyNzg1MTAsIm5iZiI6MTc3OTI3ODUxMCwiZXhwIjoxODEwODE0NTEwLCJfc2RfYWxnIjoic2hhLTI1NiIsImlzcyI6ImRpZDprZXk6ejZNa2pvUmhxMWpTTkpkTGlydVNYckZGeGFncXJ6dFphWEhxSEdVVEtKYmNOeXdwIiwiY25mIjp7Imp3ayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiNjFCRjFkTVZmcmRSTUdJRU80Wk9TWlgzOWEwc3RqMF9kOGg0Q2tBVFFSVSIsIngiOiJtNHIyU2YwallLM09YTFByZHJ3QzJPU1Nrd0NCVVZOSkgta3BJbHptQnpvIiwieSI6IlVxYTR1V3U0cmdybTNDajl3dWd3WUlpZzVaUzlEa0xLeEtuR2s4c1lyMW8ifX0sInZjdCI6Imh0dHBzOi8vaXNzdWVyLmRlbW8ud2FsdC5pZC9kcmFmdDEzL2lkZW50aXR5X2NyZWRlbnRpYWwiLCJfc2QiOlsiXzc5U2IzMk5BSUZ5MjJDQjNHdTlncVJXc3hCMHJOdnZLWFJ4ZEFmLUZYTSJdfQ.wbfTBi1yW6RbcOwhPRxVYrwcOZBYrktAUWgqqkQpmY0cZH6Ckki3qHLtBx51lasoHUIdIAgqWLvr5CMuy3YfDA", "credential_signature": { "type": "signature-sd_jwt", "signature": "wbfTBi1yW6RbcOwhPRxVYrwcOZBYrktAUWgqqkQpmY0cZH6Ckki3qHLtBx51lasoHUIdIAgqWLvr5CMuy3YfDA", "jwtHeader": { "kid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp#z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp", "typ": "dc+sd-jwt", "alg": "EdDSA" } }, "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, "id": "urn:uuid:d8cfebda-9601-4d71-89ec-9e1aebf94ad9", "iat": 1779278510, "nbf": 1779278510, "exp": 1810814510, "_sd_alg": "sha-256", "iss": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp", "cnf": { "jwk": { "kty": "EC", "crv": "P-256", "kid": "61BF1dMVfrdRMGIEO4ZOSZX39a0stj0_d8h4CkATQRU", "x": "m4r2Sf0jYK3OXLPrdrwC2OSSkwCBUVNJH-kpIlzmBzo", "y": "Uqa4uWu4rgrm3Cj9wugwYIig5ZS9DkLKxKnGk8sYr1o" } }, "vct": "https://issuer.demo.walt.id/draft13/identity_credential", "_sd": [ "_79Sb32NAIFy22CB3Gu9gqRWsxB0rNvvKXRxdAf-FXM" ] }, "successful_issuer_public_key": { "kty": "OKP", "crv": "Ed25519", "x": "T3T4-u1Xz3vAV2JwPNxWfs4pik_JLiArz_WTCvrCFUM" }, "successful_issuer_public_key_id": "Vzx7l5fh56F3Pf9aR3DECU5BwfrY6ZJe05aiWYWzan8" } } ], "specific_vc_policies": {}, "overallSuccess": true }, "presentation_validation_results": { "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": {}, "errors": [], "execution_time": "PT0.000023300S" }, "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": "EC", "crv": "P-256", "kid": "61BF1dMVfrdRMGIEO4ZOSZX39a0stj0_d8h4CkATQRU", "x": "m4r2Sf0jYK3OXLPrdrwC2OSSkwCBUVNJH-kpIlzmBzo", "y": "Uqa4uWu4rgrm3Cj9wugwYIig5ZS9DkLKxKnGk8sYr1o" }, "verified_kb_jwt_content": { "nonce": "a63dca7c-1557-46ac-9972-cea6696ebf28", "iat": 1779280012, "sd_hash": "Xt4VeNQBksR-pQwtrYe8xLhabbdQeKdUJtN-0ELnGR4" } }, "errors": [], "execution_time": "PT0.001774609S" }, "dc+sd-jwt/nonce-check": { "policy_executed": { "policy": "dc+sd-jwt/nonce-check", "id": "dc+sd-jwt/nonce-check", "description": "Check if presentation nonce matches expected nonce for session" }, "success": true, "results": {}, "errors": [], "execution_time": "PT0.000009700S" }, "dc+sd-jwt/sd_hash-check": { "policy_executed": { "policy": "dc+sd-jwt/sd_hash-check", "id": "dc+sd-jwt/sd_hash-check", "description": "Verify SD-JWT Key Binding by recalculating SD Hashes" }, "success": true, "results": { "presentation_sdhash": "Xt4VeNQBksR-pQwtrYe8xLhabbdQeKdUJtN-0ELnGR4", "recalculated_sdhash": "Xt4VeNQBksR-pQwtrYe8xLhabbdQeKdUJtN-0ELnGR4" }, "errors": [], "execution_time": "PT0.000107600S" } } }, "presented_raw_data": { "vpToken": { "credential_1": [ "eyJraWQiOiJkaWQ6a2V5Ono2TWtqb1JocTFqU05KZExpcnVTWHJGRnhhZ3FyenRaYVhIcUhHVVRLSmJjTnl3cCN6Nk1ram9SaHExalNOSmRMaXJ1U1hyRkZ4YWdxcnp0WmFYSHFIR1VUS0piY055d3AiLCJ0eXAiOiJkYytzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJnaXZlbl9uYW1lIjoiSm9obiIsImZhbWlseV9uYW1lIjoiRG9lIiwiZW1haWwiOiJqb2huZG9lQGV4YW1wbGUuY29tIiwicGhvbmVfbnVtYmVyIjoiKzEtMjAyLTU1NS0wMTAxIiwiYWRkcmVzcyI6eyJzdHJlZXRfYWRkcmVzcyI6IjEyMyBNYWluIFN0IiwibG9jYWxpdHkiOiJBbnl0b3duIiwicmVnaW9uIjoiQW55c3RhdGUiLCJjb3VudHJ5IjoiVVMifSwiaXNfb3Zlcl8xOCI6dHJ1ZSwiaXNfb3Zlcl8yMSI6dHJ1ZSwiaXNfb3Zlcl82NSI6dHJ1ZSwiaWQiOiJ1cm46dXVpZDpkOGNmZWJkYS05NjAxLTRkNzEtODllYy05ZTFhZWJmOTRhZDkiLCJpYXQiOjE3NzkyNzg1MTAsIm5iZiI6MTc3OTI3ODUxMCwiZXhwIjoxODEwODE0NTEwLCJfc2RfYWxnIjoic2hhLTI1NiIsImlzcyI6ImRpZDprZXk6ejZNa2pvUmhxMWpTTkpkTGlydVNYckZGeGFncXJ6dFphWEhxSEdVVEtKYmNOeXdwIiwiY25mIjp7Imp3ayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiNjFCRjFkTVZmcmRSTUdJRU80Wk9TWlgzOWEwc3RqMF9kOGg0Q2tBVFFSVSIsIngiOiJtNHIyU2YwallLM09YTFByZHJ3QzJPU1Nrd0NCVVZOSkgta3BJbHptQnpvIiwieSI6IlVxYTR1V3U0cmdybTNDajl3dWd3WUlpZzVaUzlEa0xLeEtuR2s4c1lyMW8ifX0sInZjdCI6Imh0dHBzOi8vaXNzdWVyLmRlbW8ud2FsdC5pZC9kcmFmdDEzL2lkZW50aXR5X2NyZWRlbnRpYWwiLCJfc2QiOlsiXzc5U2IzMk5BSUZ5MjJDQjNHdTlncVJXc3hCMHJOdnZLWFJ4ZEFmLUZYTSJdfQ.wbfTBi1yW6RbcOwhPRxVYrwcOZBYrktAUWgqqkQpmY0cZH6Ckki3qHLtBx51lasoHUIdIAgqWLvr5CMuy3YfDA~eyJ0eXAiOiJrYitqd3QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOm51bGwsIm5vbmNlIjoiYTYzZGNhN2MtMTU1Ny00NmFjLTk5NzItY2VhNjY5NmViZjI4IiwiaWF0IjoxNzc5MjgwMDEyLCJzZF9oYXNoIjoiWHQ0VmVOUUJrc1ItcFF3dHJZZTh4TGhhYmJkUWVLZFVKdE4tMEVMbkdSNCJ9.BEWOb3oQNcMWNyyAVlM9MZeKBn7S-xQawumUEcGNZa_3PYcw9XKOwo0EEOb02m7zv7iv3ew9xOm5P0_42zQrRQ" ] }, "state": "295283f9-a5fa-4040-afc3-8af30bdb8e54" }, "presented_presentations": { "credential_1": { "type": "dc+sd-jwt", "format": "dc+sd-jwt", "sdJwt": "eyJraWQiOiJkaWQ6a2V5Ono2TWtqb1JocTFqU05KZExpcnVTWHJGRnhhZ3FyenRaYVhIcUhHVVRLSmJjTnl3cCN6Nk1ram9SaHExalNOSmRMaXJ1U1hyRkZ4YWdxcnp0WmFYSHFIR1VUS0piY055d3AiLCJ0eXAiOiJkYytzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJnaXZlbl9uYW1lIjoiSm9obiIsImZhbWlseV9uYW1lIjoiRG9lIiwiZW1haWwiOiJqb2huZG9lQGV4YW1wbGUuY29tIiwicGhvbmVfbnVtYmVyIjoiKzEtMjAyLTU1NS0wMTAxIiwiYWRkcmVzcyI6eyJzdHJlZXRfYWRkcmVzcyI6IjEyMyBNYWluIFN0IiwibG9jYWxpdHkiOiJBbnl0b3duIiwicmVnaW9uIjoiQW55c3RhdGUiLCJjb3VudHJ5IjoiVVMifSwiaXNfb3Zlcl8xOCI6dHJ1ZSwiaXNfb3Zlcl8yMSI6dHJ1ZSwiaXNfb3Zlcl82NSI6dHJ1ZSwiaWQiOiJ1cm46dXVpZDpkOGNmZWJkYS05NjAxLTRkNzEtODllYy05ZTFhZWJmOTRhZDkiLCJpYXQiOjE3NzkyNzg1MTAsIm5iZiI6MTc3OTI3ODUxMCwiZXhwIjoxODEwODE0NTEwLCJfc2RfYWxnIjoic2hhLTI1NiIsImlzcyI6ImRpZDprZXk6ejZNa2pvUmhxMWpTTkpkTGlydVNYckZGeGFncXJ6dFphWEhxSEdVVEtKYmNOeXdwIiwiY25mIjp7Imp3ayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiNjFCRjFkTVZmcmRSTUdJRU80Wk9TWlgzOWEwc3RqMF9kOGg0Q2tBVFFSVSIsIngiOiJtNHIyU2YwallLM09YTFByZHJ3QzJPU1Nrd0NCVVZOSkgta3BJbHptQnpvIiwieSI6IlVxYTR1V3U0cmdybTNDajl3dWd3WUlpZzVaUzlEa0xLeEtuR2s4c1lyMW8ifX0sInZjdCI6Imh0dHBzOi8vaXNzdWVyLmRlbW8ud2FsdC5pZC9kcmFmdDEzL2lkZW50aXR5X2NyZWRlbnRpYWwiLCJfc2QiOlsiXzc5U2IzMk5BSUZ5MjJDQjNHdTlncVJXc3hCMHJOdnZLWFJ4ZEFmLUZYTSJdfQ.wbfTBi1yW6RbcOwhPRxVYrwcOZBYrktAUWgqqkQpmY0cZH6Ckki3qHLtBx51lasoHUIdIAgqWLvr5CMuy3YfDA", "disclosures": [], "keyBindingJwt": "eyJ0eXAiOiJrYitqd3QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOm51bGwsIm5vbmNlIjoiYTYzZGNhN2MtMTU1Ny00NmFjLTk5NzItY2VhNjY5NmViZjI4IiwiaWF0IjoxNzc5MjgwMDEyLCJzZF9oYXNoIjoiWHQ0VmVOUUJrc1ItcFF3dHJZZTh4TGhhYmJkUWVLZFVKdE4tMEVMbkdSNCJ9.BEWOb3oQNcMWNyyAVlM9MZeKBn7S-xQawumUEcGNZa_3PYcw9XKOwo0EEOb02m7zv7iv3ew9xOm5P0_42zQrRQ", "credential": { "dmtype": "sdjwtvcdm", "disclosables": { "_sd": [ "_79Sb32NAIFy22CB3Gu9gqRWsxB0rNvvKXRxdAf-FXM" ] }, "signedWithDisclosures": "eyJraWQiOiJkaWQ6a2V5Ono2TWtqb1JocTFqU05KZExpcnVTWHJGRnhhZ3FyenRaYVhIcUhHVVRLSmJjTnl3cCN6Nk1ram9SaHExalNOSmRMaXJ1U1hyRkZ4YWdxcnp0WmFYSHFIR1VUS0piY055d3AiLCJ0eXAiOiJkYytzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJnaXZlbl9uYW1lIjoiSm9obiIsImZhbWlseV9uYW1lIjoiRG9lIiwiZW1haWwiOiJqb2huZG9lQGV4YW1wbGUuY29tIiwicGhvbmVfbnVtYmVyIjoiKzEtMjAyLTU1NS0wMTAxIiwiYWRkcmVzcyI6eyJzdHJlZXRfYWRkcmVzcyI6IjEyMyBNYWluIFN0IiwibG9jYWxpdHkiOiJBbnl0b3duIiwicmVnaW9uIjoiQW55c3RhdGUiLCJjb3VudHJ5IjoiVVMifSwiaXNfb3Zlcl8xOCI6dHJ1ZSwiaXNfb3Zlcl8yMSI6dHJ1ZSwiaXNfb3Zlcl82NSI6dHJ1ZSwiaWQiOiJ1cm46dXVpZDpkOGNmZWJkYS05NjAxLTRkNzEtODllYy05ZTFhZWJmOTRhZDkiLCJpYXQiOjE3NzkyNzg1MTAsIm5iZiI6MTc3OTI3ODUxMCwiZXhwIjoxODEwODE0NTEwLCJfc2RfYWxnIjoic2hhLTI1NiIsImlzcyI6ImRpZDprZXk6ejZNa2pvUmhxMWpTTkpkTGlydVNYckZGeGFncXJ6dFphWEhxSEdVVEtKYmNOeXdwIiwiY25mIjp7Imp3ayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiNjFCRjFkTVZmcmRSTUdJRU80Wk9TWlgzOWEwc3RqMF9kOGg0Q2tBVFFSVSIsIngiOiJtNHIyU2YwallLM09YTFByZHJ3QzJPU1Nrd0NCVVZOSkgta3BJbHptQnpvIiwieSI6IlVxYTR1V3U0cmdybTNDajl3dWd3WUlpZzVaUzlEa0xLeEtuR2s4c1lyMW8ifX0sInZjdCI6Imh0dHBzOi8vaXNzdWVyLmRlbW8ud2FsdC5pZC9kcmFmdDEzL2lkZW50aXR5X2NyZWRlbnRpYWwiLCJfc2QiOlsiXzc5U2IzMk5BSUZ5MjJDQjNHdTlncVJXc3hCMHJOdnZLWFJ4ZEFmLUZYTSJdfQ.wbfTBi1yW6RbcOwhPRxVYrwcOZBYrktAUWgqqkQpmY0cZH6Ckki3qHLtBx51lasoHUIdIAgqWLvr5CMuy3YfDA~", "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, "id": "urn:uuid:d8cfebda-9601-4d71-89ec-9e1aebf94ad9", "iat": 1779278510, "nbf": 1779278510, "exp": 1810814510, "_sd_alg": "sha-256", "iss": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp", "cnf": { "jwk": { "kty": "EC", "crv": "P-256", "kid": "61BF1dMVfrdRMGIEO4ZOSZX39a0stj0_d8h4CkATQRU", "x": "m4r2Sf0jYK3OXLPrdrwC2OSSkwCBUVNJH-kpIlzmBzo", "y": "Uqa4uWu4rgrm3Cj9wugwYIig5ZS9DkLKxKnGk8sYr1o" } }, "vct": "https://issuer.demo.walt.id/draft13/identity_credential" }, "originalCredentialData": { "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:d8cfebda-9601-4d71-89ec-9e1aebf94ad9", "iat": 1779278510, "nbf": 1779278510, "exp": 1810814510, "_sd_alg": "sha-256", "iss": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp", "cnf": { "jwk": { "kty": "EC", "crv": "P-256", "kid": "61BF1dMVfrdRMGIEO4ZOSZX39a0stj0_d8h4CkATQRU", "x": "m4r2Sf0jYK3OXLPrdrwC2OSSkwCBUVNJH-kpIlzmBzo", "y": "Uqa4uWu4rgrm3Cj9wugwYIig5ZS9DkLKxKnGk8sYr1o" } }, "vct": "https://issuer.demo.walt.id/draft13/identity_credential", "_sd": [ "_79Sb32NAIFy22CB3Gu9gqRWsxB0rNvvKXRxdAf-FXM" ] }, "issuer": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp", "signature": { "type": "signature-sd_jwt", "signature": "wbfTBi1yW6RbcOwhPRxVYrwcOZBYrktAUWgqqkQpmY0cZH6Ckki3qHLtBx51lasoHUIdIAgqWLvr5CMuy3YfDA", "jwtHeader": { "kid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp#z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp", "typ": "dc+sd-jwt", "alg": "EdDSA" } }, "signed": "eyJraWQiOiJkaWQ6a2V5Ono2TWtqb1JocTFqU05KZExpcnVTWHJGRnhhZ3FyenRaYVhIcUhHVVRLSmJjTnl3cCN6Nk1ram9SaHExalNOSmRMaXJ1U1hyRkZ4YWdxcnp0WmFYSHFIR1VUS0piY055d3AiLCJ0eXAiOiJkYytzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJnaXZlbl9uYW1lIjoiSm9obiIsImZhbWlseV9uYW1lIjoiRG9lIiwiZW1haWwiOiJqb2huZG9lQGV4YW1wbGUuY29tIiwicGhvbmVfbnVtYmVyIjoiKzEtMjAyLTU1NS0wMTAxIiwiYWRkcmVzcyI6eyJzdHJlZXRfYWRkcmVzcyI6IjEyMyBNYWluIFN0IiwibG9jYWxpdHkiOiJBbnl0b3duIiwicmVnaW9uIjoiQW55c3RhdGUiLCJjb3VudHJ5IjoiVVMifSwiaXNfb3Zlcl8xOCI6dHJ1ZSwiaXNfb3Zlcl8yMSI6dHJ1ZSwiaXNfb3Zlcl82NSI6dHJ1ZSwiaWQiOiJ1cm46dXVpZDpkOGNmZWJkYS05NjAxLTRkNzEtODllYy05ZTFhZWJmOTRhZDkiLCJpYXQiOjE3NzkyNzg1MTAsIm5iZiI6MTc3OTI3ODUxMCwiZXhwIjoxODEwODE0NTEwLCJfc2RfYWxnIjoic2hhLTI1NiIsImlzcyI6ImRpZDprZXk6ejZNa2pvUmhxMWpTTkpkTGlydVNYckZGeGFncXJ6dFphWEhxSEdVVEtKYmNOeXdwIiwiY25mIjp7Imp3ayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiNjFCRjFkTVZmcmRSTUdJRU80Wk9TWlgzOWEwc3RqMF9kOGg0Q2tBVFFSVSIsIngiOiJtNHIyU2YwallLM09YTFByZHJ3QzJPU1Nrd0NCVVZOSkgta3BJbHptQnpvIiwieSI6IlVxYTR1V3U0cmdybTNDajl3dWd3WUlpZzVaUzlEa0xLeEtuR2s4c1lyMW8ifX0sInZjdCI6Imh0dHBzOi8vaXNzdWVyLmRlbW8ud2FsdC5pZC9kcmFmdDEzL2lkZW50aXR5X2NyZWRlbnRpYWwiLCJfc2QiOlsiXzc5U2IzMk5BSUZ5MjJDQjNHdTlncVJXc3hCMHJOdnZLWFJ4ZEFmLUZYTSJdfQ.wbfTBi1yW6RbcOwhPRxVYrwcOZBYrktAUWgqqkQpmY0cZH6Ckki3qHLtBx51lasoHUIdIAgqWLvr5CMuy3YfDA", "format": "dc+sd-jwt" }, "nonce": "a63dca7c-1557-46ac-9972-cea6696ebf28", "sdHash": "Xt4VeNQBksR-pQwtrYe8xLhabbdQeKdUJtN-0ELnGR4", "presentationStringHashable": "eyJraWQiOiJkaWQ6a2V5Ono2TWtqb1JocTFqU05KZExpcnVTWHJGRnhhZ3FyenRaYVhIcUhHVVRLSmJjTnl3cCN6Nk1ram9SaHExalNOSmRMaXJ1U1hyRkZ4YWdxcnp0WmFYSHFIR1VUS0piY055d3AiLCJ0eXAiOiJkYytzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJnaXZlbl9uYW1lIjoiSm9obiIsImZhbWlseV9uYW1lIjoiRG9lIiwiZW1haWwiOiJqb2huZG9lQGV4YW1wbGUuY29tIiwicGhvbmVfbnVtYmVyIjoiKzEtMjAyLTU1NS0wMTAxIiwiYWRkcmVzcyI6eyJzdHJlZXRfYWRkcmVzcyI6IjEyMyBNYWluIFN0IiwibG9jYWxpdHkiOiJBbnl0b3duIiwicmVnaW9uIjoiQW55c3RhdGUiLCJjb3VudHJ5IjoiVVMifSwiaXNfb3Zlcl8xOCI6dHJ1ZSwiaXNfb3Zlcl8yMSI6dHJ1ZSwiaXNfb3Zlcl82NSI6dHJ1ZSwiaWQiOiJ1cm46dXVpZDpkOGNmZWJkYS05NjAxLTRkNzEtODllYy05ZTFhZWJmOTRhZDkiLCJpYXQiOjE3NzkyNzg1MTAsIm5iZiI6MTc3OTI3ODUxMCwiZXhwIjoxODEwODE0NTEwLCJfc2RfYWxnIjoic2hhLTI1NiIsImlzcyI6ImRpZDprZXk6ejZNa2pvUmhxMWpTTkpkTGlydVNYckZGeGFncXJ6dFphWEhxSEdVVEtKYmNOeXdwIiwiY25mIjp7Imp3ayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiNjFCRjFkTVZmcmRSTUdJRU80Wk9TWlgzOWEwc3RqMF9kOGg0Q2tBVFFSVSIsIngiOiJtNHIyU2YwallLM09YTFByZHJ3QzJPU1Nrd0NCVVZOSkgta3BJbHptQnpvIiwieSI6IlVxYTR1V3U0cmdybTNDajl3dWd3WUlpZzVaUzlEa0xLeEtuR2s4c1lyMW8ifX0sInZjdCI6Imh0dHBzOi8vaXNzdWVyLmRlbW8ud2FsdC5pZC9kcmFmdDEzL2lkZW50aXR5X2NyZWRlbnRpYWwiLCJfc2QiOlsiXzc5U2IzMk5BSUZ5MjJDQjNHdTlncVJXc3hCMHJOdnZLWFJ4ZEFmLUZYTSJdfQ.wbfTBi1yW6RbcOwhPRxVYrwcOZBYrktAUWgqqkQpmY0cZH6Ckki3qHLtBx51lasoHUIdIAgqWLvr5CMuy3YfDA~" } }, "presented_credentials": { "credential_1": [ { "type": "vc-sd_jwt", "dmtype": "sdjwtvcdm", "disclosables": { "_sd": [ "_79Sb32NAIFy22CB3Gu9gqRWsxB0rNvvKXRxdAf-FXM" ] }, "signedWithDisclosures": "eyJraWQiOiJkaWQ6a2V5Ono2TWtqb1JocTFqU05KZExpcnVTWHJGRnhhZ3FyenRaYVhIcUhHVVRLSmJjTnl3cCN6Nk1ram9SaHExalNOSmRMaXJ1U1hyRkZ4YWdxcnp0WmFYSHFIR1VUS0piY055d3AiLCJ0eXAiOiJkYytzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJnaXZlbl9uYW1lIjoiSm9obiIsImZhbWlseV9uYW1lIjoiRG9lIiwiZW1haWwiOiJqb2huZG9lQGV4YW1wbGUuY29tIiwicGhvbmVfbnVtYmVyIjoiKzEtMjAyLTU1NS0wMTAxIiwiYWRkcmVzcyI6eyJzdHJlZXRfYWRkcmVzcyI6IjEyMyBNYWluIFN0IiwibG9jYWxpdHkiOiJBbnl0b3duIiwicmVnaW9uIjoiQW55c3RhdGUiLCJjb3VudHJ5IjoiVVMifSwiaXNfb3Zlcl8xOCI6dHJ1ZSwiaXNfb3Zlcl8yMSI6dHJ1ZSwiaXNfb3Zlcl82NSI6dHJ1ZSwiaWQiOiJ1cm46dXVpZDpkOGNmZWJkYS05NjAxLTRkNzEtODllYy05ZTFhZWJmOTRhZDkiLCJpYXQiOjE3NzkyNzg1MTAsIm5iZiI6MTc3OTI3ODUxMCwiZXhwIjoxODEwODE0NTEwLCJfc2RfYWxnIjoic2hhLTI1NiIsImlzcyI6ImRpZDprZXk6ejZNa2pvUmhxMWpTTkpkTGlydVNYckZGeGFncXJ6dFphWEhxSEdVVEtKYmNOeXdwIiwiY25mIjp7Imp3ayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiNjFCRjFkTVZmcmRSTUdJRU80Wk9TWlgzOWEwc3RqMF9kOGg0Q2tBVFFSVSIsIngiOiJtNHIyU2YwallLM09YTFByZHJ3QzJPU1Nrd0NCVVZOSkgta3BJbHptQnpvIiwieSI6IlVxYTR1V3U0cmdybTNDajl3dWd3WUlpZzVaUzlEa0xLeEtuR2s4c1lyMW8ifX0sInZjdCI6Imh0dHBzOi8vaXNzdWVyLmRlbW8ud2FsdC5pZC9kcmFmdDEzL2lkZW50aXR5X2NyZWRlbnRpYWwiLCJfc2QiOlsiXzc5U2IzMk5BSUZ5MjJDQjNHdTlncVJXc3hCMHJOdnZLWFJ4ZEFmLUZYTSJdfQ.wbfTBi1yW6RbcOwhPRxVYrwcOZBYrktAUWgqqkQpmY0cZH6Ckki3qHLtBx51lasoHUIdIAgqWLvr5CMuy3YfDA~", "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, "id": "urn:uuid:d8cfebda-9601-4d71-89ec-9e1aebf94ad9", "iat": 1779278510, "nbf": 1779278510, "exp": 1810814510, "_sd_alg": "sha-256", "iss": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp", "cnf": { "jwk": { "kty": "EC", "crv": "P-256", "kid": "61BF1dMVfrdRMGIEO4ZOSZX39a0stj0_d8h4CkATQRU", "x": "m4r2Sf0jYK3OXLPrdrwC2OSSkwCBUVNJH-kpIlzmBzo", "y": "Uqa4uWu4rgrm3Cj9wugwYIig5ZS9DkLKxKnGk8sYr1o" } }, "vct": "https://issuer.demo.walt.id/draft13/identity_credential" }, "originalCredentialData": { "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:d8cfebda-9601-4d71-89ec-9e1aebf94ad9", "iat": 1779278510, "nbf": 1779278510, "exp": 1810814510, "_sd_alg": "sha-256", "iss": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp", "cnf": { "jwk": { "kty": "EC", "crv": "P-256", "kid": "61BF1dMVfrdRMGIEO4ZOSZX39a0stj0_d8h4CkATQRU", "x": "m4r2Sf0jYK3OXLPrdrwC2OSSkwCBUVNJH-kpIlzmBzo", "y": "Uqa4uWu4rgrm3Cj9wugwYIig5ZS9DkLKxKnGk8sYr1o" } }, "vct": "https://issuer.demo.walt.id/draft13/identity_credential", "_sd": [ "_79Sb32NAIFy22CB3Gu9gqRWsxB0rNvvKXRxdAf-FXM" ] }, "issuer": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp", "signature": { "type": "signature-sd_jwt", "signature": "wbfTBi1yW6RbcOwhPRxVYrwcOZBYrktAUWgqqkQpmY0cZH6Ckki3qHLtBx51lasoHUIdIAgqWLvr5CMuy3YfDA", "jwtHeader": { "kid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp#z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp", "typ": "dc+sd-jwt", "alg": "EdDSA" } }, "signed": "eyJraWQiOiJkaWQ6a2V5Ono2TWtqb1JocTFqU05KZExpcnVTWHJGRnhhZ3FyenRaYVhIcUhHVVRLSmJjTnl3cCN6Nk1ram9SaHExalNOSmRMaXJ1U1hyRkZ4YWdxcnp0WmFYSHFIR1VUS0piY055d3AiLCJ0eXAiOiJkYytzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJnaXZlbl9uYW1lIjoiSm9obiIsImZhbWlseV9uYW1lIjoiRG9lIiwiZW1haWwiOiJqb2huZG9lQGV4YW1wbGUuY29tIiwicGhvbmVfbnVtYmVyIjoiKzEtMjAyLTU1NS0wMTAxIiwiYWRkcmVzcyI6eyJzdHJlZXRfYWRkcmVzcyI6IjEyMyBNYWluIFN0IiwibG9jYWxpdHkiOiJBbnl0b3duIiwicmVnaW9uIjoiQW55c3RhdGUiLCJjb3VudHJ5IjoiVVMifSwiaXNfb3Zlcl8xOCI6dHJ1ZSwiaXNfb3Zlcl8yMSI6dHJ1ZSwiaXNfb3Zlcl82NSI6dHJ1ZSwiaWQiOiJ1cm46dXVpZDpkOGNmZWJkYS05NjAxLTRkNzEtODllYy05ZTFhZWJmOTRhZDkiLCJpYXQiOjE3NzkyNzg1MTAsIm5iZiI6MTc3OTI3ODUxMCwiZXhwIjoxODEwODE0NTEwLCJfc2RfYWxnIjoic2hhLTI1NiIsImlzcyI6ImRpZDprZXk6ejZNa2pvUmhxMWpTTkpkTGlydVNYckZGeGFncXJ6dFphWEhxSEdVVEtKYmNOeXdwIiwiY25mIjp7Imp3ayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiNjFCRjFkTVZmcmRSTUdJRU80Wk9TWlgzOWEwc3RqMF9kOGg0Q2tBVFFSVSIsIngiOiJtNHIyU2YwallLM09YTFByZHJ3QzJPU1Nrd0NCVVZOSkgta3BJbHptQnpvIiwieSI6IlVxYTR1V3U0cmdybTNDajl3dWd3WUlpZzVaUzlEa0xLeEtuR2s4c1lyMW8ifX0sInZjdCI6Imh0dHBzOi8vaXNzdWVyLmRlbW8ud2FsdC5pZC9kcmFmdDEzL2lkZW50aXR5X2NyZWRlbnRpYWwiLCJfc2QiOlsiXzc5U2IzMk5BSUZ5MjJDQjNHdTlncVJXc3hCMHJOdnZLWFJ4ZEFmLUZYTSJdfQ.wbfTBi1yW6RbcOwhPRxVYrwcOZBYrktAUWgqqkQpmY0cZH6Ckki3qHLtBx51lasoHUIdIAgqWLvr5CMuy3YfDA", "format": "dc+sd-jwt" } ] } }, "timestamp": 1779279998286, "parent": "waltid.tenant1.verifier1" } ``` **Key Response Fields:** - **id**: _String_ - The verification session ID - **setup**: _Object_ - The complete session configuration used to create this verification session - **creationDate**: _String_ - ISO 8601 timestamp when the session was created - **expirationDate**: _String_ - ISO 8601 timestamp when the session expires (default: 10 minutes after creation) - **status**: _String_ - The current status of the verification session. Possible values: - `"ACTIVE"` — Session was created and is active - `"IN_USE"` — AuthorizationRequest was requested by wallet - `"PROCESSING_FLOW"` — Received presentation is being processed - `"EXPIRED"` — Verification request expired - `"SUCCESSFUL"` — Verification completed successfully (all policies passed) - `"FAILED"` — Verification was unsuccessful - **attempted**: _Boolean_ - Whether the wallet has attempted to respond - **reattemptable**: _Boolean_ - Whether the user can retry this verification session if it failed - **policy_results**: _Object_ (optional) - Detailed results for all policies applied to the verification: - **vp_policies**: _Object_ - Results for presentation-level policies - **vc_policies**: _Array_ - Results for credential-level policies. Each item contains `policy`, `success`, and `result`. - **overallSuccess**: _Boolean_ - Whether all policies passed - **presented_credentials**: _Object_ (optional) - The credentials presented by the user, organized by credential ID **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/enterprise-stack/services/verifier2-service/callback-and-sse.md) for details. ## Example 2: Requesting Specific Claims SD-JWT VCs support selective disclosure. You can request specific claims using the `claims` array: **Option: CURL** **Endpoint:** `POST /v1/{target}/verifier2-service-api/verification-session/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Verifier2%20Service%20-%20Verification%20Sessions/post_v1__target__verifier2_service_api_verification_session_create) ##### Example Request ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/verifier2-service-api/verification-session/create' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -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://{orgID}.enterprise-sandbox.waltid.dev/v1/waltid.issuer/issuer-service-api/openid4vc/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. ## 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 | ## 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 `signed_request: 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 during [verifier2 service creation](https://docs.walt.id/enterprise-stack/services/verifier2-service/setup.md#body-parameters) 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 `encrypted_response: true` in the request body. **Key Configuration Required:** Encrypted responses require a key (`key`) for decrypting the VP token response. Configure it during [verifier2 service creation](https://docs.walt.id/enterprise-stack/services/verifier2-service/setup.md#body-parameters) or provide it in the verification request body. ### 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/enterprise-stack/services/verifier2-service/policies/overview.md) for all available policies. ### Transaction Data Authorization Bind credential presentations to specific transactions via transaction data like payment authorization or account access. **Use Cases:** - Payment authorization (amount, currency, payee) - Account access requests (account ID, scope) - Custom transaction types via configuration **Supported Formats:** `dc+sd-jwt` and `mso_mdoc` 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. For mdoc credentials, the wallet embeds transaction data hashes in the DeviceSigned namespaces and the verifier evaluates the `mso_mdoc/transaction-data-hash-check` policy. **Example Request:** ```json { "flow_type": "cross_device", "core_flow": { "dcql_query": { "credentials": [ { "id": "pid", "format": "dc+sd-jwt", "meta": { "vct_values": [ "https://{orgID}.enterprise-sandbox.waltid.dev/v1/waltid.issuer/issuer-service-api/openid4vc/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" } ] } } ``` #### Transaction Data Parameters Each entry in the `transactionData` array includes: **Common Parameters (Required for all types):** - **type**: _String_ (required) — The transaction data type identifier. Must match a type defined in your [transaction-data-profiles configuration](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/transaction-data-profiles.md). Built-in types: - `"org.waltid.transaction-data.payment-authorization"` — For payment authorizations - `"org.waltid.transaction-data.account-access"` — For account access requests - **credential_ids**: _Array of Strings_ (required) — References the `id` fields from your DCQL `credentials` array. Specifies which credential(s) this transaction data applies to. Example: `["pid"]` if your credential query has `"id": "pid"`. - **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:** The remaining parameters depend on the `type` field and are defined by your transaction data profile configuration: - **For `payment-authorization` type:** - **amount**: _String_ — Payment amount (e.g., `"42.00"`) - **currency**: _String_ — Currency code (e.g., `"EUR"`) - **payee**: _String_ — Payee identifier or name (e.g., `"ACME Corp"`) - **For `account-access` type:** - **account_identifier**: _String_ — Account identifier - **access_scope**: _String_ — Scope of access being requested See [Transaction Data Profiles](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/transaction-data-profiles.md) for how to define custom types and fields. **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 transaction data profiles. Built-in profiles include `payment-authorization` and `account-access`. Custom types can be added through the [`transaction-data-profiles` configuration](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/transaction-data-profiles.md). - 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 To see which transaction data types your verifier service recognizes: ```bash curl -X 'GET' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/verifier2-service-api/transaction-data-profiles' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' ``` This endpoint requires `CREATE_VERIFIER_SESSION` permission. See [Transaction Data Profiles](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/transaction-data-profiles.md) to add custom types. ## Next Steps - **Apply custom policies** — See [Policies](https://docs.walt.id/enterprise-stack/services/verifier2-service/policies/overview.md) for validation rules beyond signature verification - **Set up notifications** — Use [Callbacks & SSE](https://docs.walt.id/enterprise-stack/services/verifier2-service/callback-and-sse.md) instead of polling for session status - **Verify other credential types** — Try [W3C VC](https://docs.walt.id/enterprise-stack/services/verifier2-service/credential-verification/vc-oid4vp.md) or [mDL](https://docs.walt.id/enterprise-stack/services/verifier2-service/credential-verification/mdl-oid4vp.md) - **Use DC API** — Try the [Digital Credentials API](https://docs.walt.id/enterprise-stack/services/verifier2-service/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/enterprise-stack/services/verifier2-service/credential-verification/mdl-oid4vp RAW: https://docs.walt.id/enterprise-stack/services/verifier2-service/credential-verification/mdl-oid4vp.md STACK: Enterprise Stack (commercial) — 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 Enterprise Stack 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. ## Prerequisites Before you begin, ensure you have: - **Enterprise Verifier2 Service setup** — Follow the [Setup](https://docs.walt.id/enterprise-stack/services/verifier2-service/setup.md) guide - **Valid authentication token** — Required for all API calls to the Enterprise Stack - **A test wallet with credentials** — Use the [walt.id Wallet](https://docs.walt.id/enterprise-stack/services/wallet-service/overview.md) for testing, or any OID4VP-compatible wallet **Note:** **Enterprise Architecture:** The Enterprise Verifier2 Service runs as a dedicated microservice within your organization's infrastructure. All requests are scoped to `{organizationID}.{tenantID}.{verifierServiceID}` for multi-tenancy isolation. **Error:** **mDL Verification Requirement:** Verifying mDL credentials requires a `clientId` to be configured during [verifier2 service setup](https://docs.walt.id/enterprise-stack/services/verifier2-service/setup.md#body-parameters). If you created your service without setting a `clientId`, you'll need to recreate it with one before mDL verification will work. --- ## 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 /v1/{target}/verifier2-service-api/verification-session/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20|%20Verifier2%20Service%20-%20Verification%20Sessions/post_v1__target__verifier2_service_api_verification_session_create) ##### Example Request ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/verifier2-service-api/verification-session/create' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -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" } ] } } }' ``` **Path Parameters** - **orgID**: _String_ (required) - Your organization ID (e.g., `test` for `test.enterprise-sandbox.waltid.dev`) - **target**: _String_ (required) - The verifier service target path (e.g., `waltid.verifier1`) **Header Parameters** - **Authorization**: _String_ (required) - Bearer token for Enterprise Stack authentication. Format: `Bearer {token}`. Obtain from your Enterprise Stack authentication flow. **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/enterprise-stack/services/verifier2-service/policies/overview.md) for the complete list. - **vp_policies**: _Array_ (optional) - Policies applied to the entire presentation (not shown in this example) - **signed_request**: _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. - **encrypted_response**: _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 during [verifier2 service creation](https://docs.walt.id/enterprise-stack/services/verifier2-service/setup.md#body-parameters). Must be a serialized key in the format `{"type":"jwk","jwk":{...}}`. Required when `signed_request: true` or `encrypted_response: 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 during [verifier2 service creation](https://docs.walt.id/enterprise-stack/services/verifier2-service/setup.md#body-parameters). 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. - **credential_ids**: _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. - **expiration_duration**: _String_ (optional) - ISO-8601 duration specifying how long the verification session remains valid. Defaults to 10 minutes (`PT10M`). Example: `"PT1H"` for 1 hour, `"PT30M"` for 30 minutes. - **expiration_date**: _String_ (optional) - ISO-8601 datetime specifying the exact time when the session expires. Takes precedence over `expiration_duration` if both are set. Example: `"2026-05-21T15:00:00Z"`. Session expires if left unused (no presentation pushed). Once a presentation is received, the session is no longer eligible for expiry. - **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. - The service constructs: Request URI: `{urlPrefix}/{target}/verifier2-service-api/{sessionId}/request` and Response URI: `{urlPrefix}/{target}/verifier2-service-api/{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. Can be set as a service-wide default during [verifier2 service creation](https://docs.walt.id/enterprise-stack/services/verifier2-service/setup.md#body-parameters) and overridden per-session here. - 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:** **Service-Level Defaults:** The `key`, `x5c`, and `url_host` parameters can be configured as service-wide defaults during [verifier2 service creation](https://docs.walt.id/enterprise-stack/services/verifier2-service/setup.md#body-parameters). Request-level parameters provided here override these defaults for that specific session only. --- ##### Example Response The response contains the session ID and authorization URLs to present to the user. ```json { "sessionId": "b68503d9-432c-49e7-97f8-5a1a7029c13a", "bootstrapAuthorizationRequestUrl": "openid4vp://authorize?client_id=did%3Ajwk%3AeyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiU05JZ1ItTHZzSEUtNkhUUGxYYUpQVDVubHRZMFpqZlA0VkpTWEwzQ2ZkMCIsIngiOiJIVWM1ZHY1eFREV3VMVmZQcFdvVXBWTjZ1QTNJNzBsVzVnNEhoMG5VaDQ0In0&request_uri=https%3A%2F%2Fwaltid.testing-0-20-0.enterprise.test.waltid.cloud%2Fv1%2Fwaltid.tenant1.verifier3%2Fverifier2-service-api%2Fb68503d9-432c-49e7-97f8-5a1a7029c13a%2Frequest", "fullAuthorizationRequestUrl": "openid4vp://authorize?response_type=vp_token&client_id=did%3Ajwk%3AeyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiU05JZ1ItTHZzSEUtNkhUUGxYYUpQVDVubHRZMFpqZlA0VkpTWEwzQ2ZkMCIsIngiOiJIVWM1ZHY1eFREV3VMVmZQcFdvVXBWTjZ1QTNJNzBsVzVnNEhoMG5VaDQ0In0&state=57ce75d0-c42b-4779-988f-65ca3a07a060&response_mode=direct_post&nonce=01ebdfac-948b-4e49-b2a6-b959a039d860&response_uri=https%3A%2F%2Fwaltid.testing-0-20-0.enterprise.test.waltid.cloud%2Fv1%2Fwaltid.tenant1.verifier3%2Fverifier2-service-api%2Fb68503d9-432c-49e7-97f8-5a1a7029c13a%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%22Verifier%22%7D", "creationTarget": "waltid.tenant1.verifier3.b68503d9-432c-49e7-97f8-5a1a7029c13a" } ``` - **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). - **creationTarget**: _String_ - The full resource path including the session ID. Use this as the `{creationTarget}` path parameter when querying session info. ## 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 /v1/{target}/verifier2-service-api/verification-session/{sessionId}/info` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20|%20Verifier2%20Service%20-%20Verification%20Sessions/get_v1__target__verifier2_service_api_verification_session__sessionId__info) ##### Example Request ```bash curl -X 'GET' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/verifier2-service-api/verification-session/{sessionId}/info' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' ``` **Path Parameters** - **orgID**: _String_ (required) - Your organization ID (e.g., `test` for `test.enterprise-sandbox.waltid.dev`) - **target**: _String_ (required) - The verifier service target path (e.g., `waltid.verifier1`) - **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. **Header Parameters** - **Authorization**: _String_ (required) - Bearer token for Enterprise Stack authentication. Format: `Bearer {token}`. --- ##### Example Response The response contains the complete session information including setup, status, policy results, and presented credentials. ```json { "_id": "waltid.tenant1.verifier3.b68503d9-432c-49e7-97f8-5a1a7029c13a", "session": { "id": "b68503d9-432c-49e7-97f8-5a1a7029c13a", "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-20T12:53:33.892550815Z", "expirationDate": "2026-05-20T13:03:52.467486314Z", "retentionDate": "2036-05-20T12:53:33.892550815Z", "status": "SUCCESSFUL", "attempted": true, "reattemptable": true, "bootstrapAuthorizationRequest": { "response_type": "vp_token", "client_id": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiU05JZ1ItTHZzSEUtNkhUUGxYYUpQVDVubHRZMFpqZlA0VkpTWEwzQ2ZkMCIsIngiOiJIVWM1ZHY1eFREV3VMVmZQcFdvVXBWTjZ1QTNJNzBsVzVnNEhoMG5VaDQ0In0", "request_uri": "https://waltid.testing-0-20-0.enterprise.test.waltid.cloud/v1/waltid.tenant1.verifier3/verifier2-service-api/b68503d9-432c-49e7-97f8-5a1a7029c13a/request" }, "bootstrapAuthorizationRequestUrl": "openid4vp://authorize?client_id=did%3Ajwk%3AeyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiU05JZ1ItTHZzSEUtNkhUUGxYYUpQVDVubHRZMFpqZlA0VkpTWEwzQ2ZkMCIsIngiOiJIVWM1ZHY1eFREV3VMVmZQcFdvVXBWTjZ1QTNJNzBsVzVnNEhoMG5VaDQ0In0&request_uri=https%3A%2F%2Fwaltid.testing-0-20-0.enterprise.test.waltid.cloud%2Fv1%2Fwaltid.tenant1.verifier3%2Fverifier2-service-api%2Fb68503d9-432c-49e7-97f8-5a1a7029c13a%2Frequest", "authorizationRequest": { "response_type": "vp_token", "client_id": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiU05JZ1ItTHZzSEUtNkhUUGxYYUpQVDVubHRZMFpqZlA0VkpTWEwzQ2ZkMCIsIngiOiJIVWM1ZHY1eFREV3VMVmZQcFdvVXBWTjZ1QTNJNzBsVzVnNEhoMG5VaDQ0In0", "state": "57ce75d0-c42b-4779-988f-65ca3a07a060", "response_mode": "direct_post", "nonce": "01ebdfac-948b-4e49-b2a6-b959a039d860", "response_uri": "https://waltid.testing-0-20-0.enterprise.test.waltid.cloud/v1/waltid.tenant1.verifier3/verifier2-service-api/b68503d9-432c-49e7-97f8-5a1a7029c13a/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": "Verifier" } }, "authorizationRequestUrl": "openid4vp://authorize?response_type=vp_token&client_id=did%3Ajwk%3AeyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiU05JZ1ItTHZzSEUtNkhUUGxYYUpQVDVubHRZMFpqZlA0VkpTWEwzQ2ZkMCIsIngiOiJIVWM1ZHY1eFREV3VMVmZQcFdvVXBWTjZ1QTNJNzBsVzVnNEhoMG5VaDQ0In0&state=57ce75d0-c42b-4779-988f-65ca3a07a060&response_mode=direct_post&nonce=01ebdfac-948b-4e49-b2a6-b959a039d860&response_uri=https%3A%2F%2Fwaltid.testing-0-20-0.enterprise.test.waltid.cloud%2Fv1%2Fwaltid.tenant1.verifier3%2Fverifier2-service-api%2Fb68503d9-432c-49e7-97f8-5a1a7029c13a%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%22Verifier%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": "EC", "crv": "P-256", "x": "m4r2Sf0jYK3OXLPrdrwC2OSSkwCBUVNJH-kpIlzmBzo", "y": "Uqa4uWu4rgrm3Cj9wugwYIig5ZS9DkLKxKnGk8sYr1o" }, "device_auth_bytes_hex": "d8185868847444657669636541757468656e7469636174696f6e83f6f682714f70656e494434565048616e646f7665725820e85d5ae0c4a98f58e23408479ebe1b09b396e774e51ba9fd3848116113ee0e98756f72672e69736f2e31383031332e352e312e6d444cd81841a0" }, "errors": [], "execution_time": "PT0.078628917S" }, "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.003564846S" }, "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": [ "30820208308201ada00302010202140e8f24af5f78b7ab2db6de8a2f761c9ccb5c6986300a06082a8648ce3d0403023024310b30090603550406130255533115301306035504030c0c4578616d706c652049414341301e170d3235303532393037313833395a170d3236303832393037313833395a3022310b30090603550406130255533113301106035504030c0a4578616d706c652044533059301306072a8648ce3d020106082a8648ce3d030107034200041962a974bde33e83c9e702a0480fa3c52da3829f9950313ab0841b781f3abc5d177c40c07f7aff155c895ee6150b2553f7914203fee6949988d7fc6e332819faa381be3081bb301f0603551d230418301680148c2311b1fa254de2b90ece8ca8e5a3e4dc74d414301d0603551d0e04160414ed2e3d2d27a0fded289df4f8e0555b2ffad22a7b300e0603551d0f0101ff04040302078030230603551d12041c301a861868747470733a2f2f696163612e6578616d706c652e636f6d30150603551d250101ff040b3009060728818c5d050102302d0603551d1f042630243022a020a01e861c68747470733a2f2f696163612e6578616d706c652e636f6d2f63726c300a06082a8648ce3d0403020349003046022100cb92abbe413c15d70561ad20c779edf851aa929fdd8ce5de574376842c2b6c44022100fdd9962cc2e224f5775339904b93141ed9fad75cf45652ff93760075a549e757" ], "signer_jwk": { "kty": "EC", "crv": "P-256", "x": "GWKpdL3jPoPJ5wKgSA-jxS2jgp-ZUDE6sIQbeB86vF0", "y": "F3xAwH96_xVciV7mFQslU_eRQgP-5pSZiNf8bjMoGfo" }, "signer_pem": "-----BEGIN CERTIFICATE-----\nMIICCDCCAa2gAwIBAgIUDo8kr194t6sttt6KL3YcnMtcaYYwCgYIKoZIzj0EAwIw\r\nJDELMAkGA1UEBhMCVVMxFTATBgNVBAMMDEV4YW1wbGUgSUFDQTAeFw0yNTA1Mjkw\r\nNzE4MzlaFw0yNjA4MjkwNzE4MzlaMCIxCzAJBgNVBAYTAlVTMRMwEQYDVQQDDApF\r\neGFtcGxlIERTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEGWKpdL3jPoPJ5wKg\r\nSA+jxS2jgp+ZUDE6sIQbeB86vF0XfEDAf3r/FVyJXuYVCyVT95FCA/7mlJmI1/xu\r\nMygZ+qOBvjCBuzAfBgNVHSMEGDAWgBSMIxGx+iVN4rkOzoyo5aPk3HTUFDAdBgNV\r\nHQ4EFgQU7S49LSeg/e0onfT44FVbL/rSKnswDgYDVR0PAQH/BAQDAgeAMCMGA1Ud\r\nEgQcMBqGGGh0dHBzOi8vaWFjYS5leGFtcGxlLmNvbTAVBgNVHSUBAf8ECzAJBgco\r\ngYxdBQECMC0GA1UdHwQmMCQwIqAgoB6GHGh0dHBzOi8vaWFjYS5leGFtcGxlLmNv\r\nbS9jcmwwCgYIKoZIzj0EAwIDSQAwRgIhAMuSq75BPBXXBWGtIMd57fhRqpKf3Yzl\r\n3ldDdoQsK2xEAiEA/dmWLMLiJPV3UzmQS5MUHtn611z0VlL/k3YAdaVJ51c=\n-----END CERTIFICATE-----" }, "errors": [], "execution_time": "PT0.078830219S" }, "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": "e14217e08f91b89c3bbe3ee466778e98", "serialized_hex": "a4686469676573744944006672616e646f6d50e14217e08f91b89c3bbe3ee466778e9871656c656d656e744964656e7469666965726b66616d696c795f6e616d656c656c656d656e7456616c756563446f65" }, { "id": "given_name", "digest_id": 1, "value": "John", "value_type": "CborString", "random_hex": "fbee28e15ccb9cdbca72ee8b5bf94ff2", "serialized_hex": "a4686469676573744944016672616e646f6d50fbee28e15ccb9cdbca72ee8b5bf94ff271656c656d656e744964656e7469666965726a676976656e5f6e616d656c656c656d656e7456616c7565644a6f686e" }, { "id": "birth_date", "digest_id": 2, "value": "1986-03-22", "value_type": "CborString", "random_hex": "fd7b099a5883278e8b3351ec2089afbe", "serialized_hex": "a4686469676573744944026672616e646f6d50fd7b099a5883278e8b3351ec2089afbe71656c656d656e744964656e7469666965726a62697274685f646174656c656c656d656e7456616c75656a313938362d30332d3232" } ] }, "matching_digest": { "org.iso.18013.5.1": [ "family_name", "given_name", "birth_date" ] } }, "errors": [], "execution_time": "PT0.040422222S" }, "mso_mdoc/mso": { "policy_executed": { "policy": "mso_mdoc/mso", "id": "mso_mdoc/mso", "description": "Verify MSO" }, "success": true, "results": { "signed": "2026-05-20T12:31:10.361778536Z", "valid_from": "2027-05-20T12:31:10.361778836Z" }, "errors": [], "execution_time": "PT0.009410621S" } } }, "vc_policies": [ { "policy": { "policy": "signature", "id": "signature" }, "success": true, "result": { "verification_result": true, "signed_credential": "o2d2ZXJzaW9uYzEuMGlkb2N1bWVudHOBo2dkb2NUeXBldW9yZy5pc28uMTgwMTMuNS4xLm1ETGxpc3N1ZXJTaWduZWSiam5hbWVTcGFjZXOhcW9yZy5pc28uMTgwMTMuNS4xg9gYWFKkaGRpZ2VzdElEAGZyYW5kb21Q4UIX4I-RuJw7vj7kZneOmHFlbGVtZW50SWRlbnRpZmllcmtmYW1pbHlfbmFtZWxlbGVtZW50VmFsdWVjRG9l2BhYUqRoZGlnZXN0SUQBZnJhbmRvbVD77ijhXMuc28py7otb-U_ycWVsZW1lbnRJZGVudGlmaWVyamdpdmVuX25hbWVsZWxlbWVudFZhbHVlZEpvaG7YGFhYpGhkaWdlc3RJRAJmcmFuZG9tUP17CZpYgyeOizNR7CCJr75xZWxlbWVudElkZW50aWZpZXJqYmlydGhfZGF0ZWxlbGVtZW50VmFsdWVqMTk4Ni0wMy0yMmppc3N1ZXJBdXRohEOhASahGCFZAgwwggIIMIIBraADAgECAhQOjySvX3i3qy223oovdhycy1xphjAKBggqhkjOPQQDAjAkMQswCQYDVQQGEwJVUzEVMBMGA1UEAwwMRXhhbXBsZSBJQUNBMB4XDTI1MDUyOTA3MTgzOVoXDTI2MDgyOTA3MTgzOVowIjELMAkGA1UEBhMCVVMxEzARBgNVBAMMCkV4YW1wbGUgRFMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQZYql0veM-g8nnAqBID6PFLaOCn5lQMTqwhBt4Hzq8XRd8QMB_ev8VXIle5hULJVP3kUID_uaUmYjX_G4zKBn6o4G-MIG7MB8GA1UdIwQYMBaAFIwjEbH6JU3iuQ7OjKjlo-TcdNQUMB0GA1UdDgQWBBTtLj0tJ6D97Sid9PjgVVsv-tIqezAOBgNVHQ8BAf8EBAMCB4AwIwYDVR0SBBwwGoYYaHR0cHM6Ly9pYWNhLmV4YW1wbGUuY29tMBUGA1UdJQEB_wQLMAkGByiBjF0FAQIwLQYDVR0fBCYwJDAioCCgHoYcaHR0cHM6Ly9pYWNhLmV4YW1wbGUuY29tL2NybDAKBggqhkjOPQQDAgNJADBGAiEAy5KrvkE8FdcFYa0gx3nt-FGqkp_djOXeV0N2hCwrbEQCIQD92ZYswuIk9XdTOZBLkxQe2frXXPRWUv-TdgB1pUnnV1kC29gYWQLWpmd2ZXJzaW9uYzEuMG9kaWdlc3RBbGdvcml0aG1nU0hBLTI1Nmx2YWx1ZURpZ2VzdHOhcW9yZy5pc28uMTgwMTMuNS4xqwBYINkmb8SGjrc7AGjP6gkyvJ1pNbdZ6I-xwFA8f9PL2guiAVggg2tS4UeThS0khoqyZTViFHzWiuuRR1I4aUCEswwJ6tcCWCBhf1K133IForrOHf43caHLjpNTlAJVxHYT_SzmJ4eK1gNYINMP97ZPmf-QLX72eHRlgwN0evBd5GvLr-6cDYvT7L09BFggb0V8kEZVt9-_wzIBLAlhrytSmm7ajzC58n1yVf4uE7QFWCCBdiI7t2bTX4feg_2GwpgMysLgiravmc_awwdpD2Z0cQZYID7NylJKtQhbBuKye2pL1k5iFX6X38JHL-9AiGa0559RB1ggG_IJ-IWsyVPi4JWkVf2p2PVHkga6jjaA5gQA4SLQCb0IWCBA_JctXYaTi9Z1eH11APUEX4fVq0RlvySyY75Vx5bvaglYIFCx9IEHFGrffrbP97PsM-dwi8K5ZMqolCg-oGrF-mlwClggBo2e11n0snkAvTs49Yu5McIu_BQ4hYgQTiDXTTCkvsltZGV2aWNlS2V5SW5mb6FpZGV2aWNlS2V5pAECIAEhWCCbivZJ_SNgrc5cs-t2vALY5JKTAIFRU0kf6SkiXOYHOiJYIFKmuLlruK4K5two_cLoMGCIoOWUvQ5CysSpxpPLGK9aZ2RvY1R5cGV1b3JnLmlzby4xODAxMy41LjEubURMbHZhbGlkaXR5SW5mb6Nmc2lnbmVkwHgeMjAyNi0wNS0yMFQxMjozMToxMC4zNjE3Nzg1MzZaaXZhbGlkRnJvbcB4HjIwMjYtMDUtMjBUMTI6MzE6MTAuMzYxNzc4NzM2Wmp2YWxpZFVudGlswHgeMjAyNy0wNS0yMFQxMjozMToxMC4zNjE3Nzg4MzZaWEBpgFeAPp3GYOhzUo9aUpOyQfv6dQONcrJEVd6N_AvHOyoQyKDXrpYXST9Djj01iPAe5aR29TI7sWIkUiYwZxwBbGRldmljZVNpZ25lZKJqbmFtZVNwYWNlc9gYQaBqZGV2aWNlQXV0aKFvZGV2aWNlU2lnbmF0dXJlhEOhASag9lhALzj9UuVdvDr5rmcY8bFNCc6l6Fg3vJ6sKNbQI_9lCtYptsdvt1UlRwsdjjq9zCzEBHKk7_qiPWG2gqk7hFSV6WZzdGF0dXMA", "credential_signature": { "type": "signature-cose", "signerKey": { "type": "jwk", "jwk": { "kty": "EC", "crv": "P-256", "x": "GWKpdL3jPoPJ5wKgSA-jxS2jgp-ZUDE6sIQbeB86vF0", "y": "F3xAwH96_xVciV7mFQslU_eRQgP-5pSZiNf8bjMoGfo" } }, "x5cList": [ "MIICCDCCAa2gAwIBAgIUDo8kr194t6sttt6KL3YcnMtcaYYwCgYIKoZIzj0EAwIwJDELMAkGA1UEBhMCVVMxFTATBgNVBAMMDEV4YW1wbGUgSUFDQTAeFw0yNTA1MjkwNzE4MzlaFw0yNjA4MjkwNzE4MzlaMCIxCzAJBgNVBAYTAlVTMRMwEQYDVQQDDApFeGFtcGxlIERTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEGWKpdL3jPoPJ5wKgSA+jxS2jgp+ZUDE6sIQbeB86vF0XfEDAf3r/FVyJXuYVCyVT95FCA/7mlJmI1/xuMygZ+qOBvjCBuzAfBgNVHSMEGDAWgBSMIxGx+iVN4rkOzoyo5aPk3HTUFDAdBgNVHQ4EFgQU7S49LSeg/e0onfT44FVbL/rSKnswDgYDVR0PAQH/BAQDAgeAMCMGA1UdEgQcMBqGGGh0dHBzOi8vaWFjYS5leGFtcGxlLmNvbTAVBgNVHSUBAf8ECzAJBgcogYxdBQECMC0GA1UdHwQmMCQwIqAgoB6GHGh0dHBzOi8vaWFjYS5leGFtcGxlLmNvbS9jcmwwCgYIKoZIzj0EAwIDSQAwRgIhAMuSq75BPBXXBWGtIMd57fhRqpKf3Yzl3ldDdoQsK2xEAiEA/dmWLMLiJPV3UzmQS5MUHtn611z0VlL/k3YAdaVJ51c=" ] }, "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": "GWKpdL3jPoPJ5wKgSA-jxS2jgp-ZUDE6sIQbeB86vF0", "y": "F3xAwH96_xVciV7mFQslU_eRQgP-5pSZiNf8bjMoGfo" }, "successful_issuer_public_key_id": "pX99OZjL2iNqM7OMkE1r1rYyuAObvPntewcDHdc2bMM" } } ], "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": "EC", "crv": "P-256", "x": "m4r2Sf0jYK3OXLPrdrwC2OSSkwCBUVNJH-kpIlzmBzo", "y": "Uqa4uWu4rgrm3Cj9wugwYIig5ZS9DkLKxKnGk8sYr1o" }, "device_auth_bytes_hex": "d8185868847444657669636541757468656e7469636174696f6e83f6f682714f70656e494434565048616e646f7665725820e85d5ae0c4a98f58e23408479ebe1b09b396e774e51ba9fd3848116113ee0e98756f72672e69736f2e31383031332e352e312e6d444cd81841a0" }, "errors": [], "execution_time": "PT0.078628917S" }, "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.003564846S" }, "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": [ "30820208308201ada00302010202140e8f24af5f78b7ab2db6de8a2f761c9ccb5c6986300a06082a8648ce3d0403023024310b30090603550406130255533115301306035504030c0c4578616d706c652049414341301e170d3235303532393037313833395a170d3236303832393037313833395a3022310b30090603550406130255533113301106035504030c0a4578616d706c652044533059301306072a8648ce3d020106082a8648ce3d030107034200041962a974bde33e83c9e702a0480fa3c52da3829f9950313ab0841b781f3abc5d177c40c07f7aff155c895ee6150b2553f7914203fee6949988d7fc6e332819faa381be3081bb301f0603551d230418301680148c2311b1fa254de2b90ece8ca8e5a3e4dc74d414301d0603551d0e04160414ed2e3d2d27a0fded289df4f8e0555b2ffad22a7b300e0603551d0f0101ff04040302078030230603551d12041c301a861868747470733a2f2f696163612e6578616d706c652e636f6d30150603551d250101ff040b3009060728818c5d050102302d0603551d1f042630243022a020a01e861c68747470733a2f2f696163612e6578616d706c652e636f6d2f63726c300a06082a8648ce3d0403020349003046022100cb92abbe413c15d70561ad20c779edf851aa929fdd8ce5de574376842c2b6c44022100fdd9962cc2e224f5775339904b93141ed9fad75cf45652ff93760075a549e757" ], "signer_jwk": { "kty": "EC", "crv": "P-256", "x": "GWKpdL3jPoPJ5wKgSA-jxS2jgp-ZUDE6sIQbeB86vF0", "y": "F3xAwH96_xVciV7mFQslU_eRQgP-5pSZiNf8bjMoGfo" }, "signer_pem": "-----BEGIN CERTIFICATE-----\nMIICCDCCAa2gAwIBAgIUDo8kr194t6sttt6KL3YcnMtcaYYwCgYIKoZIzj0EAwIw\r\nJDELMAkGA1UEBhMCVVMxFTATBgNVBAMMDEV4YW1wbGUgSUFDQTAeFw0yNTA1Mjkw\r\nNzE4MzlaFw0yNjA4MjkwNzE4MzlaMCIxCzAJBgNVBAYTAlVTMRMwEQYDVQQDDApF\r\neGFtcGxlIERTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEGWKpdL3jPoPJ5wKg\r\nSA+jxS2jgp+ZUDE6sIQbeB86vF0XfEDAf3r/FVyJXuYVCyVT95FCA/7mlJmI1/xu\r\nMygZ+qOBvjCBuzAfBgNVHSMEGDAWgBSMIxGx+iVN4rkOzoyo5aPk3HTUFDAdBgNV\r\nHQ4EFgQU7S49LSeg/e0onfT44FVbL/rSKnswDgYDVR0PAQH/BAQDAgeAMCMGA1Ud\r\nEgQcMBqGGGh0dHBzOi8vaWFjYS5leGFtcGxlLmNvbTAVBgNVHSUBAf8ECzAJBgco\r\ngYxdBQECMC0GA1UdHwQmMCQwIqAgoB6GHGh0dHBzOi8vaWFjYS5leGFtcGxlLmNv\r\nbS9jcmwwCgYIKoZIzj0EAwIDSQAwRgIhAMuSq75BPBXXBWGtIMd57fhRqpKf3Yzl\r\n3ldDdoQsK2xEAiEA/dmWLMLiJPV3UzmQS5MUHtn611z0VlL/k3YAdaVJ51c=\n-----END CERTIFICATE-----" }, "errors": [], "execution_time": "PT0.078830219S" }, "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": "e14217e08f91b89c3bbe3ee466778e98", "serialized_hex": "a4686469676573744944006672616e646f6d50e14217e08f91b89c3bbe3ee466778e9871656c656d656e744964656e7469666965726b66616d696c795f6e616d656c656c656d656e7456616c756563446f65" }, { "id": "given_name", "digest_id": 1, "value": "John", "value_type": "CborString", "random_hex": "fbee28e15ccb9cdbca72ee8b5bf94ff2", "serialized_hex": "a4686469676573744944016672616e646f6d50fbee28e15ccb9cdbca72ee8b5bf94ff271656c656d656e744964656e7469666965726a676976656e5f6e616d656c656c656d656e7456616c7565644a6f686e" }, { "id": "birth_date", "digest_id": 2, "value": "1986-03-22", "value_type": "CborString", "random_hex": "fd7b099a5883278e8b3351ec2089afbe", "serialized_hex": "a4686469676573744944026672616e646f6d50fd7b099a5883278e8b3351ec2089afbe71656c656d656e744964656e7469666965726a62697274685f646174656c656c656d656e7456616c75656a313938362d30332d3232" } ] }, "matching_digest": { "org.iso.18013.5.1": [ "family_name", "given_name", "birth_date" ] } }, "errors": [], "execution_time": "PT0.040422222S" }, "mso_mdoc/mso": { "policy_executed": { "policy": "mso_mdoc/mso", "id": "mso_mdoc/mso", "description": "Verify MSO" }, "success": true, "results": { "signed": "2026-05-20T12:31:10.361778536Z", "valid_from": "2027-05-20T12:31:10.361778836Z" }, "errors": [], "execution_time": "PT0.009410621S" } } }, "presented_raw_data": { "vpToken": { "credential_1": [ "o2d2ZXJzaW9uYzEuMGlkb2N1bWVudHOBo2dkb2NUeXBldW9yZy5pc28uMTgwMTMuNS4xLm1ETGxpc3N1ZXJTaWduZWSiam5hbWVTcGFjZXOhcW9yZy5pc28uMTgwMTMuNS4xg9gYWFKkaGRpZ2VzdElEAGZyYW5kb21Q4UIX4I-RuJw7vj7kZneOmHFlbGVtZW50SWRlbnRpZmllcmtmYW1pbHlfbmFtZWxlbGVtZW50VmFsdWVjRG9l2BhYUqRoZGlnZXN0SUQBZnJhbmRvbVD77ijhXMuc28py7otb-U_ycWVsZW1lbnRJZGVudGlmaWVyamdpdmVuX25hbWVsZWxlbWVudFZhbHVlZEpvaG7YGFhYpGhkaWdlc3RJRAJmcmFuZG9tUP17CZpYgyeOizNR7CCJr75xZWxlbWVudElkZW50aWZpZXJqYmlydGhfZGF0ZWxlbGVtZW50VmFsdWVqMTk4Ni0wMy0yMmppc3N1ZXJBdXRohEOhASahGCFZAgwwggIIMIIBraADAgECAhQOjySvX3i3qy223oovdhycy1xphjAKBggqhkjOPQQDAjAkMQswCQYDVQQGEwJVUzEVMBMGA1UEAwwMRXhhbXBsZSBJQUNBMB4XDTI1MDUyOTA3MTgzOVoXDTI2MDgyOTA3MTgzOVowIjELMAkGA1UEBhMCVVMxEzARBgNVBAMMCkV4YW1wbGUgRFMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQZYql0veM-g8nnAqBID6PFLaOCn5lQMTqwhBt4Hzq8XRd8QMB_ev8VXIle5hULJVP3kUID_uaUmYjX_G4zKBn6o4G-MIG7MB8GA1UdIwQYMBaAFIwjEbH6JU3iuQ7OjKjlo-TcdNQUMB0GA1UdDgQWBBTtLj0tJ6D97Sid9PjgVVsv-tIqezAOBgNVHQ8BAf8EBAMCB4AwIwYDVR0SBBwwGoYYaHR0cHM6Ly9pYWNhLmV4YW1wbGUuY29tMBUGA1UdJQEB_wQLMAkGByiBjF0FAQIwLQYDVR0fBCYwJDAioCCgHoYcaHR0cHM6Ly9pYWNhLmV4YW1wbGUuY29tL2NybDAKBggqhkjOPQQDAgNJADBGAiEAy5KrvkE8FdcFYa0gx3nt-FGqkp_djOXeV0N2hCwrbEQCIQD92ZYswuIk9XdTOZBLkxQe2frXXPRWUv-TdgB1pUnnV1kC29gYWQLWpmd2ZXJzaW9uYzEuMG9kaWdlc3RBbGdvcml0aG1nU0hBLTI1Nmx2YWx1ZURpZ2VzdHOhcW9yZy5pc28uMTgwMTMuNS4xqwBYINkmb8SGjrc7AGjP6gkyvJ1pNbdZ6I-xwFA8f9PL2guiAVggg2tS4UeThS0khoqyZTViFHzWiuuRR1I4aUCEswwJ6tcCWCBhf1K133IForrOHf43caHLjpNTlAJVxHYT_SzmJ4eK1gNYINMP97ZPmf-QLX72eHRlgwN0evBd5GvLr-6cDYvT7L09BFggb0V8kEZVt9-_wzIBLAlhrytSmm7ajzC58n1yVf4uE7QFWCCBdiI7t2bTX4feg_2GwpgMysLgiravmc_awwdpD2Z0cQZYID7NylJKtQhbBuKye2pL1k5iFX6X38JHL-9AiGa0559RB1ggG_IJ-IWsyVPi4JWkVf2p2PVHkga6jjaA5gQA4SLQCb0IWCBA_JctXYaTi9Z1eH11APUEX4fVq0RlvySyY75Vx5bvaglYIFCx9IEHFGrffrbP97PsM-dwi8K5ZMqolCg-oGrF-mlwClggBo2e11n0snkAvTs49Yu5McIu_BQ4hYgQTiDXTTCkvsltZGV2aWNlS2V5SW5mb6FpZGV2aWNlS2V5pAECIAEhWCCbivZJ_SNgrc5cs-t2vALY5JKTAIFRU0kf6SkiXOYHOiJYIFKmuLlruK4K5two_cLoMGCIoOWUvQ5CysSpxpPLGK9aZ2RvY1R5cGV1b3JnLmlzby4xODAxMy41LjEubURMbHZhbGlkaXR5SW5mb6Nmc2lnbmVkwHgeMjAyNi0wNS0yMFQxMjozMToxMC4zNjE3Nzg1MzZaaXZhbGlkRnJvbcB4HjIwMjYtMDUtMjBUMTI6MzE6MTAuMzYxNzc4NzM2Wmp2YWxpZFVudGlswHgeMjAyNy0wNS0yMFQxMjozMToxMC4zNjE3Nzg4MzZaWEBpgFeAPp3GYOhzUo9aUpOyQfv6dQONcrJEVd6N_AvHOyoQyKDXrpYXST9Djj01iPAe5aR29TI7sWIkUiYwZxwBbGRldmljZVNpZ25lZKJqbmFtZVNwYWNlc9gYQaBqZGV2aWNlQXV0aKFvZGV2aWNlU2lnbmF0dXJlhEOhASag9lhALzj9UuVdvDr5rmcY8bFNCc6l6Fg3vJ6sKNbQI_9lCtYptsdvt1UlRwsdjjq9zCzEBHKk7_qiPWG2gqk7hFSV6WZzdGF0dXMA" ] }, "state": "57ce75d0-c42b-4779-988f-65ca3a07a060" }, "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": "o2d2ZXJzaW9uYzEuMGlkb2N1bWVudHOBo2dkb2NUeXBldW9yZy5pc28uMTgwMTMuNS4xLm1ETGxpc3N1ZXJTaWduZWSiam5hbWVTcGFjZXOhcW9yZy5pc28uMTgwMTMuNS4xg9gYWFKkaGRpZ2VzdElEAGZyYW5kb21Q4UIX4I-RuJw7vj7kZneOmHFlbGVtZW50SWRlbnRpZmllcmtmYW1pbHlfbmFtZWxlbGVtZW50VmFsdWVjRG9l2BhYUqRoZGlnZXN0SUQBZnJhbmRvbVD77ijhXMuc28py7otb-U_ycWVsZW1lbnRJZGVudGlmaWVyamdpdmVuX25hbWVsZWxlbWVudFZhbHVlZEpvaG7YGFhYpGhkaWdlc3RJRAJmcmFuZG9tUP17CZpYgyeOizNR7CCJr75xZWxlbWVudElkZW50aWZpZXJqYmlydGhfZGF0ZWxlbGVtZW50VmFsdWVqMTk4Ni0wMy0yMmppc3N1ZXJBdXRohEOhASahGCFZAgwwggIIMIIBraADAgECAhQOjySvX3i3qy223oovdhycy1xphjAKBggqhkjOPQQDAjAkMQswCQYDVQQGEwJVUzEVMBMGA1UEAwwMRXhhbXBsZSBJQUNBMB4XDTI1MDUyOTA3MTgzOVoXDTI2MDgyOTA3MTgzOVowIjELMAkGA1UEBhMCVVMxEzARBgNVBAMMCkV4YW1wbGUgRFMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQZYql0veM-g8nnAqBID6PFLaOCn5lQMTqwhBt4Hzq8XRd8QMB_ev8VXIle5hULJVP3kUID_uaUmYjX_G4zKBn6o4G-MIG7MB8GA1UdIwQYMBaAFIwjEbH6JU3iuQ7OjKjlo-TcdNQUMB0GA1UdDgQWBBTtLj0tJ6D97Sid9PjgVVsv-tIqezAOBgNVHQ8BAf8EBAMCB4AwIwYDVR0SBBwwGoYYaHR0cHM6Ly9pYWNhLmV4YW1wbGUuY29tMBUGA1UdJQEB_wQLMAkGByiBjF0FAQIwLQYDVR0fBCYwJDAioCCgHoYcaHR0cHM6Ly9pYWNhLmV4YW1wbGUuY29tL2NybDAKBggqhkjOPQQDAgNJADBGAiEAy5KrvkE8FdcFYa0gx3nt-FGqkp_djOXeV0N2hCwrbEQCIQD92ZYswuIk9XdTOZBLkxQe2frXXPRWUv-TdgB1pUnnV1kC29gYWQLWpmd2ZXJzaW9uYzEuMG9kaWdlc3RBbGdvcml0aG1nU0hBLTI1Nmx2YWx1ZURpZ2VzdHOhcW9yZy5pc28uMTgwMTMuNS4xqwBYINkmb8SGjrc7AGjP6gkyvJ1pNbdZ6I-xwFA8f9PL2guiAVggg2tS4UeThS0khoqyZTViFHzWiuuRR1I4aUCEswwJ6tcCWCBhf1K133IForrOHf43caHLjpNTlAJVxHYT_SzmJ4eK1gNYINMP97ZPmf-QLX72eHRlgwN0evBd5GvLr-6cDYvT7L09BFggb0V8kEZVt9-_wzIBLAlhrytSmm7ajzC58n1yVf4uE7QFWCCBdiI7t2bTX4feg_2GwpgMysLgiravmc_awwdpD2Z0cQZYID7NylJKtQhbBuKye2pL1k5iFX6X38JHL-9AiGa0559RB1ggG_IJ-IWsyVPi4JWkVf2p2PVHkga6jjaA5gQA4SLQCb0IWCBA_JctXYaTi9Z1eH11APUEX4fVq0RlvySyY75Vx5bvaglYIFCx9IEHFGrffrbP97PsM-dwi8K5ZMqolCg-oGrF-mlwClggBo2e11n0snkAvTs49Yu5McIu_BQ4hYgQTiDXTTCkvsltZGV2aWNlS2V5SW5mb6FpZGV2aWNlS2V5pAECIAEhWCCbivZJ_SNgrc5cs-t2vALY5JKTAIFRU0kf6SkiXOYHOiJYIFKmuLlruK4K5two_cLoMGCIoOWUvQ5CysSpxpPLGK9aZ2RvY1R5cGV1b3JnLmlzby4xODAxMy41LjEubURMbHZhbGlkaXR5SW5mb6Nmc2lnbmVkwHgeMjAyNi0wNS0yMFQxMjozMToxMC4zNjE3Nzg1MzZaaXZhbGlkRnJvbcB4HjIwMjYtMDUtMjBUMTI6MzE6MTAuMzYxNzc4NzM2Wmp2YWxpZFVudGlswHgeMjAyNy0wNS0yMFQxMjozMToxMC4zNjE3Nzg4MzZaWEBpgFeAPp3GYOhzUo9aUpOyQfv6dQONcrJEVd6N_AvHOyoQyKDXrpYXST9Djj01iPAe5aR29TI7sWIkUiYwZxwBbGRldmljZVNpZ25lZKJqbmFtZVNwYWNlc9gYQaBqZGV2aWNlQXV0aKFvZGV2aWNlU2lnbmF0dXJlhEOhASag9lhALzj9UuVdvDr5rmcY8bFNCc6l6Fg3vJ6sKNbQI_9lCtYptsdvt1UlRwsdjjq9zCzEBHKk7_qiPWG2gqk7hFSV6WZzdGF0dXMA", "docType": "org.iso.18013.5.1.mDL", "signature": { "type": "signature-cose", "signerKey": { "type": "jwk", "jwk": { "kty": "EC", "crv": "P-256", "x": "GWKpdL3jPoPJ5wKgSA-jxS2jgp-ZUDE6sIQbeB86vF0", "y": "F3xAwH96_xVciV7mFQslU_eRQgP-5pSZiNf8bjMoGfo" } }, "x5cList": [ "MIICCDCCAa2gAwIBAgIUDo8kr194t6sttt6KL3YcnMtcaYYwCgYIKoZIzj0EAwIwJDELMAkGA1UEBhMCVVMxFTATBgNVBAMMDEV4YW1wbGUgSUFDQTAeFw0yNTA1MjkwNzE4MzlaFw0yNjA4MjkwNzE4MzlaMCIxCzAJBgNVBAYTAlVTMRMwEQYDVQQDDApFeGFtcGxlIERTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEGWKpdL3jPoPJ5wKgSA+jxS2jgp+ZUDE6sIQbeB86vF0XfEDAf3r/FVyJXuYVCyVT95FCA/7mlJmI1/xuMygZ+qOBvjCBuzAfBgNVHSMEGDAWgBSMIxGx+iVN4rkOzoyo5aPk3HTUFDAdBgNVHQ4EFgQU7S49LSeg/e0onfT44FVbL/rSKnswDgYDVR0PAQH/BAQDAgeAMCMGA1UdEgQcMBqGGGh0dHBzOi8vaWFjYS5leGFtcGxlLmNvbTAVBgNVHSUBAf8ECzAJBgcogYxdBQECMC0GA1UdHwQmMCQwIqAgoB6GHGh0dHBzOi8vaWFjYS5leGFtcGxlLmNvbS9jcmwwCgYIKoZIzj0EAwIDSQAwRgIhAMuSq75BPBXXBWGtIMd57fhRqpKf3Yzl3ldDdoQsK2xEAiEA/dmWLMLiJPV3UzmQS5MUHtn611z0VlL/k3YAdaVJ51c=" ] }, "format": "mso_mdoc", "mso": { "version": "1.0", "digestAlgorithm": "SHA-256", "valueDigests": { "org.iso.18013.5.1": { ... }, "deviceKeyInfo": { "deviceKey": { ... } }, "docType": "org.iso.18013.5.1.mDL", "validityInfo": { "signed": "2026-05-20T12:31:10.361778536Z", "validFrom": "2026-05-20T12:31:10.361778736Z", "validUntil": "2027-05-20T12:31:10.361778836Z" } } } } }, "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": "o2d2ZXJzaW9uYzEuMGlkb2N1bWVudHOBo2dkb2NUeXBldW9yZy5pc28uMTgwMTMuNS4xLm1ETGxpc3N1ZXJTaWduZWSiam5hbWVTcGFjZXOhcW9yZy5pc28uMTgwMTMuNS4xg9gYWFKkaGRpZ2VzdElEAGZyYW5kb21Q4UIX4I-RuJw7vj7kZneOmHFlbGVtZW50SWRlbnRpZmllcmtmYW1pbHlfbmFtZWxlbGVtZW50VmFsdWVjRG9l2BhYUqRoZGlnZXN0SUQBZnJhbmRvbVD77ijhXMuc28py7otb-U_ycWVsZW1lbnRJZGVudGlmaWVyamdpdmVuX25hbWVsZWxlbWVudFZhbHVlZEpvaG7YGFhYpGhkaWdlc3RJRAJmcmFuZG9tUP17CZpYgyeOizNR7CCJr75xZWxlbWVudElkZW50aWZpZXJqYmlydGhfZGF0ZWxlbGVtZW50VmFsdWVqMTk4Ni0wMy0yMmppc3N1ZXJBdXRohEOhASahGCFZAgwwggIIMIIBraADAgECAhQOjySvX3i3qy223oovdhycy1xphjAKBggqhkjOPQQDAjAkMQswCQYDVQQGEwJVUzEVMBMGA1UEAwwMRXhhbXBsZSBJQUNBMB4XDTI1MDUyOTA3MTgzOVoXDTI2MDgyOTA3MTgzOVowIjELMAkGA1UEBhMCVVMxEzARBgNVBAMMCkV4YW1wbGUgRFMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQZYql0veM-g8nnAqBID6PFLaOCn5lQMTqwhBt4Hzq8XRd8QMB_ev8VXIle5hULJVP3kUID_uaUmYjX_G4zKBn6o4G-MIG7MB8GA1UdIwQYMBaAFIwjEbH6JU3iuQ7OjKjlo-TcdNQUMB0GA1UdDgQWBBTtLj0tJ6D97Sid9PjgVVsv-tIqezAOBgNVHQ8BAf8EBAMCB4AwIwYDVR0SBBwwGoYYaHR0cHM6Ly9pYWNhLmV4YW1wbGUuY29tMBUGA1UdJQEB_wQLMAkGByiBjF0FAQIwLQYDVR0fBCYwJDAioCCgHoYcaHR0cHM6Ly9pYWNhLmV4YW1wbGUuY29tL2NybDAKBggqhkjOPQQDAgNJADBGAiEAy5KrvkE8FdcFYa0gx3nt-FGqkp_djOXeV0N2hCwrbEQCIQD92ZYswuIk9XdTOZBLkxQe2frXXPRWUv-TdgB1pUnnV1kC29gYWQLWpmd2ZXJzaW9uYzEuMG9kaWdlc3RBbGdvcml0aG1nU0hBLTI1Nmx2YWx1ZURpZ2VzdHOhcW9yZy5pc28uMTgwMTMuNS4xqwBYINkmb8SGjrc7AGjP6gkyvJ1pNbdZ6I-xwFA8f9PL2guiAVggg2tS4UeThS0khoqyZTViFHzWiuuRR1I4aUCEswwJ6tcCWCBhf1K133IForrOHf43caHLjpNTlAJVxHYT_SzmJ4eK1gNYINMP97ZPmf-QLX72eHRlgwN0evBd5GvLr-6cDYvT7L09BFggb0V8kEZVt9-_wzIBLAlhrytSmm7ajzC58n1yVf4uE7QFWCCBdiI7t2bTX4feg_2GwpgMysLgiravmc_awwdpD2Z0cQZYID7NylJKtQhbBuKye2pL1k5iFX6X38JHL-9AiGa0559RB1ggG_IJ-IWsyVPi4JWkVf2p2PVHkga6jjaA5gQA4SLQCb0IWCBA_JctXYaTi9Z1eH11APUEX4fVq0RlvySyY75Vx5bvaglYIFCx9IEHFGrffrbP97PsM-dwi8K5ZMqolCg-oGrF-mlwClggBo2e11n0snkAvTs49Yu5McIu_BQ4hYgQTiDXTTCkvsltZGV2aWNlS2V5SW5mb6FpZGV2aWNlS2V5pAECIAEhWCCbivZJ_SNgrc5cs-t2vALY5JKTAIFRU0kf6SkiXOYHOiJYIFKmuLlruK4K5two_cLoMGCIoOWUvQ5CysSpxpPLGK9aZ2RvY1R5cGV1b3JnLmlzby4xODAxMy41LjEubURMbHZhbGlkaXR5SW5mb6Nmc2lnbmVkwHgeMjAyNi0wNS0yMFQxMjozMToxMC4zNjE3Nzg1MzZaaXZhbGlkRnJvbcB4HjIwMjYtMDUtMjBUMTI6MzE6MTAuMzYxNzc4NzM2Wmp2YWxpZFVudGlswHgeMjAyNy0wNS0yMFQxMjozMToxMC4zNjE3Nzg4MzZaWEBpgFeAPp3GYOhzUo9aUpOyQfv6dQONcrJEVd6N_AvHOyoQyKDXrpYXST9Djj01iPAe5aR29TI7sWIkUiYwZxwBbGRldmljZVNpZ25lZKJqbmFtZVNwYWNlc9gYQaBqZGV2aWNlQXV0aKFvZGV2aWNlU2lnbmF0dXJlhEOhASag9lhALzj9UuVdvDr5rmcY8bFNCc6l6Fg3vJ6sKNbQI_9lCtYptsdvt1UlRwsdjjq9zCzEBHKk7_qiPWG2gqk7hFSV6WZzdGF0dXMA", "docType": "org.iso.18013.5.1.mDL", "signature": { "type": "signature-cose", "signerKey": { "type": "jwk", "jwk": { "kty": "EC", "crv": "P-256", "x": "GWKpdL3jPoPJ5wKgSA-jxS2jgp-ZUDE6sIQbeB86vF0", "y": "F3xAwH96_xVciV7mFQslU_eRQgP-5pSZiNf8bjMoGfo" } }, "x5cList": [ "MIICCDCCAa2gAwIBAgIUDo8kr194t6sttt6KL3YcnMtcaYYwCgYIKoZIzj0EAwIwJDELMAkGA1UEBhMCVVMxFTATBgNVBAMMDEV4YW1wbGUgSUFDQTAeFw0yNTA1MjkwNzE4MzlaFw0yNjA4MjkwNzE4MzlaMCIxCzAJBgNVBAYTAlVTMRMwEQYDVQQDDApFeGFtcGxlIERTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEGWKpdL3jPoPJ5wKgSA+jxS2jgp+ZUDE6sIQbeB86vF0XfEDAf3r/FVyJXuYVCyVT95FCA/7mlJmI1/xuMygZ+qOBvjCBuzAfBgNVHSMEGDAWgBSMIxGx+iVN4rkOzoyo5aPk3HTUFDAdBgNVHQ4EFgQU7S49LSeg/e0onfT44FVbL/rSKnswDgYDVR0PAQH/BAQDAgeAMCMGA1UdEgQcMBqGGGh0dHBzOi8vaWFjYS5leGFtcGxlLmNvbTAVBgNVHSUBAf8ECzAJBgcogYxdBQECMC0GA1UdHwQmMCQwIqAgoB6GHGh0dHBzOi8vaWFjYS5leGFtcGxlLmNvbS9jcmwwCgYIKoZIzj0EAwIDSQAwRgIhAMuSq75BPBXXBWGtIMd57fhRqpKf3Yzl3ldDdoQsK2xEAiEA/dmWLMLiJPV3UzmQS5MUHtn611z0VlL/k3YAdaVJ51c=" ] }, "format": "mso_mdoc", "mso": { "version": "1.0", "digestAlgorithm": "SHA-256", "valueDigests": { "org.iso.18013.5.1": { ... }, "deviceKeyInfo": { "deviceKey": { ... } }, "docType": "org.iso.18013.5.1.mDL", "validityInfo": { "signed": "2026-05-20T12:31:10.361778536Z", "validFrom": "2026-05-20T12:31:10.361778736Z", "validUntil": "2027-05-20T12:31:10.361778836Z" } } } ] } }, "timestamp": 1779281613892, "parent": "waltid.tenant1.verifier3" } ``` **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 - **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) - **vc_policies**: _Array_ - Results for credential-level policies (signature, expiry, schema, etc.). Each item contains `policy`, `success`, and `result`. - **overallSuccess**: _Boolean_ (computed) - Whether all policies passed - **presented_credentials**: _Object_ (optional) - The parsed credentials presented by the user, organized by credential ID **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 | --- ## 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/enterprise-stack/services/verifier2-service/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/enterprise-stack/services/verifier2-service/policies/available-policies.md#vical) for detailed parameter descriptions and configuration examples. ### 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 `signed_request: 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 during [verifier2 service creation](https://docs.walt.id/enterprise-stack/services/verifier2-service/setup.md#body-parameters) 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 `encrypted_response: true` in the request body. **Key Configuration Required:** Encrypted responses require a key (`key`) for decrypting the VP token response. Configure it during [verifier2 service creation](https://docs.walt.id/enterprise-stack/services/verifier2-service/setup.md#body-parameters) or provide it in the verification request body. ### Transaction Data Authorization Bind credential presentations to specific transactions via transaction data like payment authorization or account access. **Use Cases:** - Payment authorization (amount, currency, payee) - Account access requests (account ID, scope) - Custom transaction types via configuration **Supported Formats:** `dc+sd-jwt` and `mso_mdoc` For mDL credentials, the wallet embeds transaction data hashes in the DeviceSigned namespaces corresponding to the transaction data type. The verifier evaluates the `mso_mdoc/transaction-data-hash-check` policy 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" } ] } } ``` #### Transaction Data Parameters Each entry in the `transactionData` array includes: **Common Parameters (Required for all types):** - **type**: _String_ (required) — The transaction data type identifier. Must match a type defined in your [transaction-data-profiles configuration](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/transaction-data-profiles.md). Built-in types: - `"org.waltid.transaction-data.payment-authorization"` — For payment authorizations - `"org.waltid.transaction-data.account-access"` — For account access requests - **credential_ids**: _Array of Strings_ (required) — References the `id` fields from your DCQL `credentials` array. Specifies which credential(s) this transaction data applies to. Example: `["mdl"]` if your credential query has `"id": "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:** The remaining parameters depend on the `type` field and are defined by your transaction data profile configuration: - **For `payment-authorization` type:** - **amount**: _String_ — Payment amount (e.g., `"42.00"`) - **currency**: _String_ — Currency code (e.g., `"EUR"`) - **payee**: _String_ — Payee identifier or name (e.g., `"ACME Corp"`) - **For `account-access` type:** - **account_identifier**: _String_ — Account identifier - **access_scope**: _String_ — Scope of access being requested See [Transaction Data Profiles](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/transaction-data-profiles.md) for how to define custom types and fields. **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 transaction data profiles. Built-in profiles include `payment-authorization` and `account-access`. Custom types can be added through the [`transaction-data-profiles` configuration](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/transaction-data-profiles.md). - 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 To see which transaction data types your verifier service recognizes: ```bash curl -X 'GET' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/verifier2-service-api/transaction-data-profiles' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' ``` This endpoint requires `CREATE_VERIFIER_SESSION` permission. See [Transaction Data Profiles](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/transaction-data-profiles.md) to add custom types. --- ## Next Steps - **Apply custom policies** — See [Policies](https://docs.walt.id/enterprise-stack/services/verifier2-service/policies/overview.md) for validation rules beyond signature verification - **Set up notifications** — Use callbacks or SSE instead of polling for session status - **Verify other credential types** — Try [W3C VC](https://docs.walt.id/enterprise-stack/services/verifier2-service/credential-verification/vc-oid4vp.md) or [SD-JWT VC](https://docs.walt.id/enterprise-stack/services/verifier2-service/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/enterprise-stack/services/verifier2-service/credential-verification/dc-api RAW: https://docs.walt.id/enterprise-stack/services/verifier2-service/credential-verification/dc-api.md STACK: Enterprise Stack (commercial) — 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 an 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 Service setup** — Follow the [Setup](https://docs.walt.id/enterprise-stack/services/verifier2-service/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 wallet** — Use a 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 - **Authentication token** — Bearer token from your Enterprise Stack authentication flow ## Understanding DC API Protocols The Verifier2 API supports two DC API protocols: - Over ISO 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. **Option: CURL** **Endpoint:** `POST /v1/{target}/verifier2-service-api/verification-session/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Verifier2%20Service%20-%20Verification%20Sessions/post_v1__target__verifier2_service_api_verification_session_create) ##### Example Request ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/verifier2-service-api/verification-session/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -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": ["https://{orgID}.enterprise-sandbox.waltid.dev"] }' ``` **Path Parameters** - **orgID**: _String_ - Your organization ID (e.g., `test` for `test.enterprise-sandbox.waltid.dev`) - **target**: _String_ - The verifier service target path (e.g., `waltid.verifier1`) **Header Parameters** - **Authorization**: _String_ - Bearer token obtained from your Enterprise Stack authentication flow. Format: `Bearer {token}`. **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/enterprise-stack/services/verifier2-service/policies/overview.md) for the complete list. - **vp_policies**: _Array_ (optional) - Policies applied to the entire presentation (not shown in this example) - **expiration_duration**: _String_ (optional) - ISO-8601 duration specifying how long the verification session remains valid. Defaults to 10 minutes (`PT10M`). Example: `"PT1H"` for 1 hour, `"PT30M"` for 30 minutes. - **expiration_date**: _String_ (optional) - ISO-8601 datetime specifying the exact time when the session expires. Takes precedence over `expiration_duration` if both are set. Example: `"2026-05-21T15:00:00Z"`. Session expires if left unused (no presentation pushed). Once a presentation is received, the session is no longer eligible for expiry. - **expectedOrigins**: _Array_ (required for dc_api) - Expected origins for the DC API verification **Note:** Many parameters have defaults configured in the verifier service. Request-level parameters override these defaults for that specific session only. --- ##### Example Response ```json { "sessionId": "ff4f0c86-56ce-4e4a-a137-dd5569049ff3", "bootstrapAuthorizationRequestUrl": "openid4vp://authorize?client_id=...", "fullAuthorizationRequestUrl": "openid4vp://authorize?response_type=vp_token&client_id=...", "creationTarget": "org.tenant.verifier.ff4f0c86-56ce-4e4a-a137-dd5569049ff3" } ``` - **sessionId**: _String_ - The unique ID of this verification session - **bootstrapAuthorizationRequestUrl**: _String_ - Shortened URL using `request_uri` by reference - **fullAuthorizationRequestUrl**: _String_ - Complete URL with all parameters embedded - **creationTarget**: _String_ - The target path for this verification session in the Enterprise Stack ### Step 2: Generating the DC API Request Take the sessionId from Step 1 and make the following request to get the DC API request URL. **Option: CURL** **Endpoint:** `GET /v1/{target}/verifier2-service-api/verification-session/{sessionId}/request` ##### Example Request ```bash curl -X 'GET' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/verifier2-service-api/verification-session/{sessionId}/request' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' ``` **Path Parameters** - **orgID**: _String_ - Your organization ID (e.g., `test` for `test.enterprise-sandbox.waltid.dev`) - **target**: _String_ - The verifier service target path (e.g., `waltid.tenant1.verifier1`) - **sessionId**: _String_ - The unique ID of the verification session returned from Step 1 **Header Parameters** - **Authorization**: _String_ - Bearer token obtained from your Enterprise Stack authentication flow. Format: `Bearer {token}`. --- ##### Example Response This returns the DC API request URL which can be used with the Browser API. ```json { "digital": { "requests": [ { "protocol": "openid4vp-v1-signed", "data": { "request": "eyJ0eXAiOiJvYXV0aC1hdXRoei1yZXErand0IiwiaWF0IjoxNzc5MjkwNjc0LCJhbGciOiJFUzI1NiIsImtpZCI6Il9uZC1UMllSWUxTbXVLa0pabFJJNjQxenJDSUpMVHBpSGVxTXdYdXZkdWcifQ.eyJyZXNwb25zZV90eXBlIjoidnBfdG9rZW4iLCJjbGllbnRfaWQiOiJ4NTA5X2hhc2g6YWJjLXh5ei1iYXNlNjR1cmwtc2hhMjU2LWhhc2gtb2YtZGVyLXg1MDktbGVhZiIsInJlc3BvbnNlX21vZGUiOiJkY19hcGkuand0Iiwibm9uY2UiOiI1MjU1NTE2NS0xMTE5LTQzYjQtOGQ4Zi02ZjNiZmJlNDVmMmIiLCJkY3FsX3F1ZXJ5Ijp7ImNyZWRlbnRpYWxzIjpbeyJpZCI6Im15X21kbCIsImZvcm1hdCI6Im1zb19tZG9jIiwibWV0YSI6eyJkb2N0eXBlX3ZhbHVlIjoib3JnLmlzby4xODAxMy41LjEubURMIn0sImNsYWltcyI6W3sicGF0aCI6WyJvcmcuaXNvLjE4MDEzLjUuMSIsImZhbWlseV9uYW1lIl19LHsicGF0aCI6WyJvcmcuaXNvLjE4MDEzLjUuMSIsImdpdmVuX25hbWUiXX0seyJwYXRoIjpbIm9yZy5pc28uMTgwMTMuNS4xIiwiYWdlX292ZXJfMjEiXX1dfV19LCJjbGllbnRfbWV0YWRhdGEiOnsiandrcyI6eyJrZXlzIjpbeyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoibFpWUGNWWkdQTnpZSXNlNEZXd19JWGo0dDRwWTNwYjNmakpzUEQ3VTNldyIsIngiOiJBQ090dXFXRUVXN0RHLWNsX3o0RGVNTTNia2FUUWlBUC1KcHJrOVRMOXVNIiwieSI6IkhDbjA5ZERMRUZXQXdMZUF3UTBFR2FNaTJCU0I2TEhFTzZNS0Zlb1VPcWciLCJhbGciOiJFQ0RILUVTIiwidXNlIjoiZW5jIn1dfSwidnBfZm9ybWF0c19zdXBwb3J0ZWQiOnsibXNvX21kb2MiOnt9fSwiZW5jcnlwdGVkX3Jlc3BvbnNlX2VuY192YWx1ZXNfc3VwcG9ydGVkIjpbIkExMjhHQ00iXSwiY2xpZW50X25hbWUiOiJWZXJpZmllciJ9LCJleHBlY3RlZF9vcmlnaW5zIjpbImh0dHBzOi8ve29yZ0lEfS5lbnRlcnByaXNlLXNhbmRib3gud2FsdGlkLmRldiJdfQ.oHHikcmtwWZ-V7thIl2p0bclsrQDyNHZBmeLmC8Y_8L9wxIbFJu513Zq97xQUIJjYgy76MZ2qveuTENJCwg3Ew" } } ] } } ``` ### Step 3: Handling the Response Once the wallet presents the credential via the DC API, you'll receive a response containing the verifiable presentation. Pass this to the verifier endpoint to verify the credential and run configured policies. **Option: CURL** **Endpoint:** `POST /v1/{target}/verifier2-service-api/verification-session/{sessionId}/response` ##### Example Request ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/verifier2-service-api/verification-session/{sessionId}/response' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "protocol": "openid4vp-v1-signed", "data": { "vp_token": { "my_mdl": ["o2d2ZXJzaW9uYzEuMGlkb2N1bWVudH..."] } } }' ``` ## 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 /v1/{target}/verifier2-service-api/verification-session/{sessionId}/info` ##### Example Request ```bash curl -X 'GET' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/verifier2-service-api/verification-session/{sessionId}/info' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' ``` **Path Parameters** - **orgID**: _String_ - Your organization ID - **target**: _String_ - The verification session service target path (includes the session ID) - **sessionId**: _String_ - The unique ID of the verification session --- ##### Example Response The response contains complete session information including setup, status, policy results, and presented credentials. **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/enterprise-stack/services/verifier2-service/configurations/callback-and-sse) for details. ## Additional Resources 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/enterprise-stack/services/verifier2-service/policies/overview.md) for validation rules beyond signature verification - **Set up notifications** — Use [Callbacks & SSE](https://docs.walt.id/enterprise-stack/services/verifier2-service/configurations/callback-and-sse) instead of polling for session status - **Verify other credential types** — Try [W3C VC](https://docs.walt.id/enterprise-stack/services/verifier2-service/credential-verification/vc-oid4vp.md) or [SD-JWT VC](https://docs.walt.id/enterprise-stack/services/verifier2-service/credential-verification/sd-jwt-vc-oid4vp.md) -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/services/verifier2-service/policies/overview RAW: https://docs.walt.id/enterprise-stack/services/verifier2-service/policies/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Policies Verifier 2 introduces a fully parallelized validation process: - [Credential (VC) validation](https://docs.walt.id/enterprise-stack/services/verifier2-service/policies/available-policies.md#available-policies-vc) - [Presentation (VP) validation](https://docs.walt.id/enterprise-stack/services/verifier2-service/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/enterprise-stack/services/verifier2-service/policies/configuration.md). -------------------------------------------------------------------------------- PAGE: Policy Configuration Levels URL: https://docs.walt.id/enterprise-stack/services/verifier2-service/policies/configuration RAW: https://docs.walt.id/enterprise-stack/services/verifier2-service/policies/configuration.md STACK: Enterprise Stack (commercial) — 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/enterprise-stack/services/verifier2-service/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/enterprise-stack/services/verifier2-service/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/enterprise-stack/services/verifier2-service/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/enterprise-stack/services/verifier2-service/policies/available-policies RAW: https://docs.walt.id/enterprise-stack/services/verifier2-service/policies/available-policies.md STACK: Enterprise Stack (commercial) — 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 with Enterprise VICAL Service (Recommended) ```json { "policy": "vical", "vicalUrl": "https://enterprise.example.com/v1/org1.tenant1.vical-service/vical-service-api/latest", "enableDocumentTypeValidation": true, "enableTrustedChainRoot": true, "enableSystemTrustAnchors": false, "enableRevocation": false } ``` **Note:** **Enterprise VICAL Service:** The walt.id [VICAL Service](https://docs.walt.id/enterprise-stack/services/vical-service/overview.md) provides managed VICAL endpoints for the Enterprise Stack. Use the `/v1/{target}/vical-service-api/latest` endpoint as your `vicalUrl`, where `{target}` is your VICAL service path (e.g., `org1.tenant1.vical-service`). --- ### `etsi-trust-list` Validates the issuer's certificate against an ETSI Trust List (TSL or LoTE). **Use case:** Verify that the credential was issued by a trusted entity registered in an official ETSI trust list, such as an EU member state TSL or the EWC pilot trust list. For full configuration details and resolution modes, see [Trust Registry Service — Verifier Integration](https://docs.walt.id/enterprise-stack/services/trust-registry-service/verifier-integration.md). **Example (linked Trust Registry Service):** ```json { "policy": "etsi-trust-list", "expectedEntityType": "PID_PROVIDER" } ``` **Example (inline trust list):** ```json { "policy": "etsi-trust-list", "trustLists": [ "https://ewc-consortium.github.io/ewc-trust-list/EWC-TL" ], "expectedEntityType": "PID_PROVIDER" } ``` --- ### `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: Overview URL: https://docs.walt.id/enterprise-stack/services/verifier2-service/configurations/overview RAW: https://docs.walt.id/enterprise-stack/services/verifier2-service/configurations/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview The verifier, similar to other services in the Enterprise Stack, maintains a collection of configurations. These configurations include **internal configs such as the base URL**, which are used solely within the Enterprise Stack. Additionally, the verifier holds configurations that is both internally used and **externally shared, such as verifier display information**. This external information is made available through the **OpenID configuration endpoint**, which can be accessed by wallets and other external applications involved in the credential exchange process. - [OpenID Metadata](https://docs.walt.id/enterprise-stack/services/verifier2-service/configurations/openid-metadata.md) - Create, update, and delete openid metadata verifier information. -------------------------------------------------------------------------------- PAGE: OpenID Metadata URL: https://docs.walt.id/enterprise-stack/services/verifier2-service/configurations/openid-metadata RAW: https://docs.walt.id/enterprise-stack/services/verifier2-service/configurations/openid-metadata.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # OpenID Metadata Via the openid-metadata config, the verifier service can be configured to expose details such as verifier name and logo in a standardized way with other services such as digital wallets. These services can then use this information to display a human-readable verifier name and logo to the end-user during credential exchange. Learn more about the `client_name` and `logo_uri` info exposure in the OAuth 2.0 standard [here](https://www.rfc-editor.org/rfc/rfc7591.html#section-2:~:text=code%22%20response%20type.-,client_name%0A%20%20%20%20%20%20Human%2Dreadable%20string%20name%20of%20the%20client%20to%20be%20presented,this%20field%20MAY%20be%20internationalized%2C%20as%20described%20in%0A%20%20%20%20%20%20Section%202.2.,-scope%0A%20%20%20%20%20%20String%20containing). The configured info will be exposed via the public endpoint `/.well-known/openid-configuration`. The Verifier API 2 supports RFC 7591 internationalized client metadata fields, allowing you to provide localized versions of client information for different languages. This enables wallets to display verifier information in the user's preferred language. ### Supported Internationalized Fields The following fields support internationalization using BCP 47 language tags: - `client_name` → `client_name#language-tag` (e.g., `client_name#fr-FR`) - `logo_uri` → `logo_uri#language-tag` (e.g., `logo_uri#de`) - `tos_uri` → `tos_uri#language-tag` (Terms of Service URI) - `policy_uri` → `policy_uri#language-tag` (Privacy Policy URI) - `client_uri` → `client_uri#language-tag` (Client URI) Language tags follow BCP 47 format: - **Simple language**: `en`, `fr`, `de`, `es` - **Language with region**: `en-US`, `fr-FR`, `de-DE`, `es-ES` - **Language with script**: `zh-Hans`, `zh-Hant` **Best Practices:** - Always provide a base/default value (non-tagged field) as a fallback - Use consistent language tags throughout your configuration - Only add language variants you actually need The new OpenID4VP 1.0 specification introduces additional objects to the client metadata object. These are: - `jwks`: OPTIONAL. A JSON Web Key Set, as defined in [RFC7591](https://www.rfc-editor.org/info/rfc7591), that contains one or more public keys, such as those used by the Wallet as an input to a key agreement that may be used for encryption of the Authorization Response see [Section 8.3](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html#response_encryption), or where the Wallet will require the public key of the Verifier to generate a Verifiable Presentation. This allows the Verifier to pass ephemeral keys specific to this Authorization Request. Public keys included in this parameter MUST NOT be used to verify the signature of signed Authorization Requests. Each JWK in the set MUST have a kid (Key ID) parameter that uniquely identifies the key within the context of the request. - `encrypted_response_enc_values_supported`: OPTIONAL. Non-empty array of strings, where each string is a JWE [RFC7516](https://www.rfc-editor.org/info/rfc7516) enc algorithm that can be used as the content encryption algorithm for encrypting the Response. When a response_mode requiring encryption of the Response (such as dc_api.jwt or direct_post.jwt) is specified, this MUST be present for anything other than the default single value of A128GCM. Otherwise, this SHOULD be absent. - `vp_formats_supported`: REQUIRED when not available to the Wallet via another mechanism. As defined in [Section 11.1](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html#client_metadata_parameters). ## Get OpenID Metadata **Option: CURL** Endpoint: `/v1/{target}/verifier-service-api/configuration/openid-metadata/view` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Verifier/get_v1__target__verifier_service_api_configuration_openid_metadata_view) **Example Request** ```bash curl -X 'GET' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/verifier-service-api/configuration/openid-metadata/view' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' ``` **Path Parameters** - `orgID`: When performing operations within an organization, use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + verifier service (`{organizationID}.{tenantID}.{verifierServiceID}`), e.g. `waltid.tenant1.verifier1` **Example Response** - `200` - metadata retrieved successfully. See [Response Structure](#response-structure) below for details. - `401` - Invalid authentication ## Update OpenID Metadata **Option: CURL** Endpoint: `/v1/{target}/verifier-service-api/configuration/openid-metadata/update` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Verifier/get_v1__target__verifier_service_api_configuration_openid_metadata_view) **Example Request** ```bash curl -X 'PUT' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/verifier-service-api/configuration/openid-metadata/update' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "baseUrl": "http://org.enterprise.localhost:3000", "clientId": "did:jwk:...", "clientMetadata": { "client_name": "walt.id Enterprise Verifier", "client_name#fr-FR": "Vérificateur Enterprise walt.id", "logo_uri": "https://cdn.walt.id/verifier/logo.png" } }' ``` **Path Parameters** - `orgID`: When performing operations within an organization, use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + verifier service (`{organizationID}.{tenantID}.{verifierServiceID}`), e.g. `waltid.tenant1.verifier1` **Body Parameters** All fields in `clientMetadata` are optional. See [Response Structure](#response-structure) for the complete structure. **Example Response** - `200` - metadata updated successfully. Returns the same structure as [Get OpenID Metadata](#get-openid-metadata). - `401` - Invalid authentication ## Response Structure The response contains the following structure: ```json { "baseUrl": "http://org.enterprise.localhost:3000", "clientId": "did:jwk:...", "clientMetadata": { "client_name": "walt.id Enterprise Verifier", "client_name#fr-FR": "Vérificateur Enterprise walt.id", "client_name#de": "walt.id Enterprise Prüfer", "logo_uri": "https://cdn.walt.id/verifier/logo.png", "logo_uri#fr-FR": "https://cdn.walt.id/verifier/logo-fr.png", "tos_uri": "https://verifier.example.com/terms", "policy_uri": "https://verifier.example.com/privacy", "client_uri": "https://verifier.example.com", "jwks": { "keys": [...] }, "encrypted_response_enc_values_supported": ["A128GCM"], "vp_formats_supported": ["vp_jwt"] } } ``` **Response Fields** - `baseUrl` _String_: The base URL of the verifier. - `clientId` _String_: The client ID of the verifier. - `clientMetadata` _Object_: The client metadata object containing: - **Base fields** (provide fallback values): - `client_name` _String_: Human-readable name of the verifier. - `logo_uri` _String_: URL referencing the verifier logo. - `tos_uri` _String_ (optional): Terms of Service URI. - `policy_uri` _String_ (optional): Privacy Policy URI. - `client_uri` _String_ (optional): Client URI. - **Internationalized fields** (optional, use `#language-tag` suffix): - `client_name#language-tag` _String_: Localized client name (e.g., `client_name#fr-FR`, `client_name#de`). - `logo_uri#language-tag` _String_: Localized logo URI. - `tos_uri#language-tag` _String_: Localized Terms of Service URI. - `policy_uri#language-tag` _String_: Localized Privacy Policy URI. - `client_uri#language-tag` _String_: Localized Client URI. - **OpenID4VP fields**: - `jwks` _Object_ (optional): JSON Web Key Set for encryption/verification. - `encrypted_response_enc_values_supported` _Array_ (optional): Supported encryption algorithms (e.g., `["A128GCM"]`). - `vp_formats_supported` _Array_ (optional): Supported VP formats (e.g., `["vp_jwt"]`). **Note:** When using internationalized fields, always provide a base/default value (non-tagged field) as a fallback. Wallets will select the appropriate language variant based on user preferences or Accept-Language headers. ## References - [RFC 7591 - OAuth 2.0 Dynamic Client Registration Protocol](https://www.rfc-editor.org/rfc/rfc7591.txt) - Section 2.1 (Language Tags) - [BCP 47 - Tags for Identifying Languages](https://www.rfc-editor.org/rfc/rfc5646.txt) - [OpenID4VP 1.0 Specification](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html#name-verifier-metadata-client-me) -------------------------------------------------------------------------------- PAGE: Callback and SSE URL: https://docs.walt.id/enterprise-stack/services/verifier2-service/callback-and-sse RAW: https://docs.walt.id/enterprise-stack/services/verifier2-service/callback-and-sse.md STACK: Enterprise Stack (commercial) — 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/enterprise-stack/services/verifier2-service/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: Full API Reference URL: https://docs.walt.id/enterprise-stack/services/verifier2-service/full-api-reference RAW: https://docs.walt.id/enterprise-stack/services/verifier2-service/full-api-reference.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Full API Reference Find it [here](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Verifier2%20Service%20-%20Service%20Management) -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/services/wallet-service/overview RAW: https://docs.walt.id/enterprise-stack/services/wallet-service/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview walt.id's Wallet Service enables organizations and wallet providers to create digital wallets for a variety of use cases. It supports consumer wallets for end users as well as organizational wallets for companies. ## 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 and V1.0) & OID4VP (Draft 14, 20 and V1.0)](https://docs.walt.id/concepts/data-exchange-protocols.md) , [ISO/IEC 18013-7](https://docs.walt.id/concepts/data-exchange-protocols.md) | | **Signing Algorithms:** | ed25519, secp256k1, secp256r1, RSA | --- ## Wallet API v2 vs. v1 A single Wallet Service instance exposes **two API versions on the same wallet resource** — you choose per request via the URL path, and both share the same wallet, keys, DIDs, and credential stores: ```text /v1/{target}/wallet-service-api/... → original wallet API /v2/{target}/wallet-service-api/... → Wallet API v2 supporting the OID4VCI & VP v1 protcol versions (recommended for new integrations) ``` | | **v1** (`/v1`) | **v2** (`/v2`) | |---|---|---| | **OID4VCI** | Draft 11, 13 | 1.0 | | **OID4VP** | Draft 14, 20 | 1.0 | | **Presentation query** | Presentation Exchange | DCQL | | **Isolated (step-by-step) flows** | Yes | Yes | Both versions issue and present the same credential formats (SD-JWT VC, W3C VC, mDL). For new projects, use **v2** — it implements the finalized OID4VCI/OID4VP 1.0 specifications with DCQL. The v1 endpoints remain available for existing integrations; the older per-format guides are linked under [Legacy](#getting-started) below. **Note:** The v2 endpoints are a distinct route set on the wallet service — the wallet itself (its keys, DIDs, and stored credentials) is shared across both versions. --- ## Core Features ### KMS & Identifiers - **Holder Keys** - Storage is configured via the [KMS Service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md). Keys may reside in the Enterprise database for non-production setups or be delegated to providers such as AWS KMS, HashiCorp Vault or Oracle KMS. - **DIDs** - Create and store DIDs with the [DID Service](https://docs.walt.id/enterprise-stack/services/did-service/overview.md) and [DID Store](https://docs.walt.id/enterprise-stack/services/did-store-service/overview.md). ### Credential Storage - Persist received credentials using the [Credential Store Service](https://docs.walt.id/enterprise-stack/services/credential-store-service/overview.md). ### Credential Exchange - Supports OID4VCI/VP flows and ISO/IEC 18013‑7 for receiving and presenting credentials. - Matches stored credentials against Presentation Definitions or DCQL requests. - Automatically proves key and DID ownership during exchanges. - Supports [attestation-based client authentication](https://docs.walt.id/enterprise-stack/services/client-attestation-service/overview.md) when the issuer’s authorization server metadata advertises `attest_jwt_client_auth`, so wallets can prove identity before credentials are issued (see [wallet integration](https://docs.walt.id/enterprise-stack/services/client-attestation-service/wallet-integration.md)). --- ## Getting Started - [Setup](https://docs.walt.id/enterprise-stack/services/wallet-service/setup.md) — How to set up a wallet. **Receive, manage & present credentials (OID4VCI / OID4VP 1.0)** - [Accept credentials via OID4VCI 1.0](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/overview.md) — Receive and store W3C, SD-JWT VC, and mDL credentials. - [Manage credentials](https://docs.walt.id/enterprise-stack/services/wallet-service/management.md) — List and retrieve the keys, DIDs, and credentials stored in a wallet. - [Share credentials via OID4VP 1.0](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/overview.md) — Present stored credentials to any OID4VP verifier using DCQL. **Legacy (OID4VCI Draft 13 / OID4VP Draft 20)** - Receive: [W3C](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/legacy-draft13/vc-oid4vci.md) · [SD-JWT VC](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/legacy-draft13/sd-jwt-vc-oid4vci.md) · [mDL/mDOC](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/legacy-draft13/mdl-oid4vci.md) - Present: [W3C](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/legacy-draft20/vc-oid4vp.md) · [SD-JWT VC](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/legacy-draft20/sd-jwt-vc-oid4vp.md) · [mDL/mDOC](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/legacy-draft20/mdl-oid4vp.md) -------------------------------------------------------------------------------- PAGE: Setup URL: https://docs.walt.id/enterprise-stack/services/wallet-service/setup RAW: https://docs.walt.id/enterprise-stack/services/wallet-service/setup.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Setup **There are two ways to set up a new wallet (wallet-service):** - [**Wizard-based setup**](#creating-a-wallet-with-dependencies-wizard): Use the Wallet Service wizard to automatically create a wallet instance and configure the necessary dependency services (e.g., KMS, DID Store, Credential Store). This setup not only enables core wallet functionality—such as receiving and sharing credentials—but also ensures that credentials, keys, and DIDs are associated and stored for the wallet. - [**Manual setup**](#creating-a-wallet-without-dependencies): Create a standalone wallet service without any dependency services to enable core wallet functionality, such as receiving credentials via OID4VC and presenting them via OID4VP. Optionally, you can provide a static key and DID to associate with the wallet — ideal if the wallet only needs to have a single key and DID. With that there is no requirement for a DID store or KMS. If needed, you can also manually attach services like KMS, DID service, DID store, or a credential store to support multiple keys and DIDs, and to persist (store) received credentials. **Note:** Each Wallet Service instance corresponds to a single wallet identified by a unique ID, known as a **target** ( e.g., `waltid.tenant1.wallet1`). ## Creating a Wallet with Dependencies (Wizard)
**The Role of Dependency Services** Dependency services ensure that the wallet goes beyond basic, stateless functionality—such as receiving credentials via OID4VC without storing them, or presenting credentials via OID4VP by supplying the raw credential instead of a stored reference. These services provide the necessary infrastructure for persistence and full-featured wallet capabilities: - [**KMS Service**](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) – Provides the wallet with keys needed to receive credentials and to sign credential presentations. - [**DID Service**](https://docs.walt.id/enterprise-stack/services/did-service/overview.md) – Enables the wallet to create one or more decentralized identifiers (DIDs). - [**DID Store**](https://docs.walt.id/enterprise-stack/services/did-store-service/overview.md) – Persists the DID Documents of the created DIDs. - [**Credential Store**](https://docs.walt.id/enterprise-stack/services/credential-store-service/overview.md) – Stores received credentials so they can be viewed, managed, and later retrieved for presentation. - [**Client Attestation Service**](https://docs.walt.id/enterprise-stack/services/client-attestation-service/overview.md) _(optional)_ – Enables the wallet to authenticate itself when an issuer’s authorization server metadata lists `attest_jwt_client_auth` (issuer `clientAuthenticationConfig` includes `client-attestation`). See [wallet integration](https://docs.walt.id/enterprise-stack/services/client-attestation-service/wallet-integration.md). ### Create New Service Dependencies In this example, we will create a wallet and initialize all dependency services like KMS, DID Service, DID Store Service, and Credential Store Service. If you already have created some of these services, please refer to the [Link Existing Service Dependencies Example](#link-existing-service-dependencies). **Option: CURL** Endpoint: `/v1/{target}/wallet-service-api/init-wallet` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Wallet%20Service/post_v1__target__wallet_service_api_init_wallet) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/wallet-service-api/init-wallet' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "createKms": true, "kmsName": "my-custom-kms-name", "createKeyInKms": { "backend": "jwk", "keyType": "Ed25519" }, "createDidStore": true, "didStoreName": "my-custom-did-store-name", "createDidService": true, "didServiceName": "my-custom-did-service-name", "createDidWithDidService": "jwk", "createCredentialStore": true, "credentialStoreName": "my-custom-credential-store-name" }' ``` **Body** ```json { "createKms": true, "kmsName": "my-custom-kms-name", "createKeyInKms": { "backend": "jwk", "keyType": "Ed25519" }, "createDidStore": true, "didStoreName": "my-custom-did-store-name", "createDidService": true, "didServiceName": "my-custom-did-service-name", "createDidWithDidService": "jwk", "createCredentialStore": true, "credentialStoreName": "my-custom-credential-store-name" } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant in which to create the new wallet ( wallet service) and the wallet's ID (`{organizationID}.{tenantID}.[walletID]`), e.g. `waltid.tenant1.wallet1` **Body Parameters** - `createKms` _Boolean_ - Specifies whether a new [KMS service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) should be created. - `kmsName` (optional) _String_ - Custom name for the KMS service. This will be the last part of the resource ID. For example, `waltid.tenant1.{kms-name}`. - `createKeyInKms` (optional) _Object_ - Object defining the key to create in the KMS. It follows the same structure as the create key request in the KMS service. Learn more about the different options [here](https://docs.walt.id/enterprise-stack/services/key-management-service/local.md). - `createDidStore` _Boolean_ - Indicates whether a [DID Store Service](https://docs.walt.id/enterprise-stack/services/did-store-service/overview.md) should be created. - `didStoreName` (optional) _String_ - Custom name for the DID Store Service. This will form part of the resource ID. For example, `waltid.tenant1.{did-store-name}`. - `createDidService` _Boolean_ - Specifies if a [DID Service](https://docs.walt.id/enterprise-stack/services/did-service/overview.md) should be created. - `didServiceName` (optional) _String_ - Custom name for the DID Service. This will be included in the resource ID. For example, `waltid.tenant1.{did-service-name}`. - `createDidWithDidService` (optional) _String_ - Method for creating a DID in the DID Service. Supported values: `jwk` or`key` - `createCredentialStore` _Boolean_ - Indicates whether a [Credential Store Service](https://docs.walt.id/enterprise-stack/services/credential-store-service/overview.md) should be created. - `credentialStoreName` (optional) _String_ - Custom name for the Credential Store Service. This will be part of the resource ID. For example, `waltid.tenant1.{credential-store-name}`. --- **Response Codes** - `201` - Wallet created successfully. **Body** ```json { "wallet": { "dependencies": [ "waltid.usertenant.didstore", "waltid.usertenant.kms", "waltid.usertenant.credentialstore" ], "traversable": true, "_id": "waltid.usertenant.wallet3", "configuration": {}, "parent": "waltid.usertenant" }, "createdResources": { "keyId": "waltid.usertenant.kms.wallet_key", "didId": "waltid.usertenant.didstore.wallet_did", "did": "did:key:zDnaeSwMcFNobzqVhKxAGRrZAct37L4o7fMzQXocuKDASmiq3" } } ``` ### Link Existing Service Dependencies In this example, we will create a wallet and link existing dependency services like KMS, DID Service, DID Store Service, and Credential Store Service. **Option: CURL** Endpoint: `/v1/{target}/wallet-service-api/init-wallet` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Wallet%20Service/post_v1__target__wallet_service_api_init_wallet) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/wallet-service-api/init-wallet' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "kms": "waltid.tenant1.my-custom-kms-name-test-1", "createKms": false, "didStore": "waltid.tenant1.my-custom-did-store-name-test-1", "createDidStore": false, "didService": "waltid.tenant1.my-custom-did-service-name-test-1", "createDidService": false, "credentialStore": "waltid.tenant1.my-custom-credential-store-name-test-1", "createCredentialStore": false }' ``` **Body** ```json { "kms": "waltid.tenant1.my-custom-kms-name-test-1", "createKms": false, "didStore": "waltid.tenant1.my-custom-did-store-name-test-1", "createDidStore": false, "didService": "waltid.tenant1.my-custom-did-service-name-test-1", "createDidService": false, "credentialStore": "waltid.tenant1.my-custom-credential-store-name-test-1", "createCredentialStore": false } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant in which to create the new wallet ( wallet service) and the wallet's ID (`{organizationID}.{tenantID}.[walletID]`), e.g. `waltid.tenant1.wallet1` **Body Parameters** - `kms` _Target_ - Resource ID (target) of the KMS to link to the wallet, e.g. `waltid.tenant1.kms1`. - `createKms` _Boolean_ - Specifies whether a new [KMS service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) should be created. - `didStore` _Target_ - Resource ID (target) of the DID Store to link to the wallet, e.g. `waltid.tenant1.did-store1`. - `createDidStore` _Boolean_ - Indicates whether a [DID Store Service](https://docs.walt.id/enterprise-stack/services/did-store-service/overview.md) should be created. - `didService` _Target_ - Resource ID (target) of the DID Service to link to the wallet, e.g. `waltid.tenant1.did-service1`. - `createDidService` _Boolean_ - Specifies if a [DID Service](https://docs.walt.id/enterprise-stack/services/did-service/overview.md) should be created. - `credentialStore` _Target_ - Resource ID (target) of the Credential Store Service to link to the wallet, e.g. `waltid.tenant1.credential-store1`. - `createCredentialStore` _Boolean_ - Indicates whether a [Credential Store Service](https://docs.walt.id/enterprise-stack/services/credential-store-service/overview.md) should be created. resource ID. For example, `waltid.tenant1.{credential-store-name}`. --- **Response Codes** - `201` - Wallet created successfully. **Body** ```json { "wallet": { "dependencies": [ "waltid.usertenant.didstore", "waltid.usertenant.kms", "waltid.usertenant.credentialstore" ], "traversable": true, "_id": "waltid.usertenant.wallet3", "configuration": {}, "parent": "waltid.usertenant" }, "createdResources": { } } ``` - `401` - Invalid authentication ## Creating a Wallet without Dependencies In this example, we will create a wallet without any dependency services (KMS, DID service, DID Store, Credential Store). Optionally, we can provide a static key and DID to be associated with the wallet. With this setup (with static key and DID), the wallet is still capable of receiving and presenting credentials. However, it will not store them, since no credential store is attached. Similarly, the DID document related to the static DID must be stored separately, as there is no DID store connected. Despite this minimal setup (static key and DID), it is still possible to manually attach other services like a credential store or DID store, as described in the [section below](#attach-service-dependencies-to-wallet-after-creation). **Option: CURL** Endpoint: `/v1/{target}/resource-api/services/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%2F%20Resource/post_v1__target__resource_api_services_create) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "wallet", "configuration": { "staticKey": { "type": "jwk", "jwk": { "kty": "OKP", "d": "wZ69WYFIZHYEC9gWsRYCIHsJ4G5qdZeCrCry6szGij4", "crv": "Ed25519", "kid": "I-bh2IYP93feK_dIYA7QaNSHoRlftw53KYpNYPdEpNQ", "x": "sD8DqvgzEjQaex5p4NNp_Qyd8knJAJfZY7Ge8vCmBGY" } }, "staticDid": "did:key:z6MkrKFXimvdRgYnsFeyQq5sUn4vmXJVDHriHJKpgy6oaC6H" } }' ``` **Body** ```json { "type": "wallet", "configuration": { "staticKey": { "type": "jwk", "jwk": { "kty": "OKP", "d": "wZ69WYFIZHYEC9gWsRYCIHsJ4G5qdZeCrCry6szGij4", "crv": "Ed25519", "kid": "I-bh2IYP93feK_dIYA7QaNSHoRlftw53KYpNYPdEpNQ", "x": "sD8DqvgzEjQaex5p4NNp_Qyd8knJAJfZY7Ge8vCmBGY" } }, "staticDid": "did:key:z6MkrKFXimvdRgYnsFeyQq5sUn4vmXJVDHriHJKpgy6oaC6H" } } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant in which to create the new wallet ( wallet service) and the wallet's ID (`{organizationID}.{tenantID}.[walletID]`), e.g. `waltid.tenant1.wallet1` **Body Parameters** - `type` _serviceType_ - Specifies the type of service to create. In our case `wallet` - `configuration` (optional) _Object_ - Object to configure static key & DID. - `staticKey` _Key_ - A key object that specifies the key to be associated with the wallet. Its structure matches the format returned under the key property when creating a key using the [KMS service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md). - `staticDid` _String_ - String of the DID to be associated with the wallet. --- **Response Codes** - `201` - Wallet created successfully. ## Attach Service Dependencies to Wallet After Creation In the example below, we will attach a credential store service to an existing wallet to ensure that received credentials are automatically stored. The same approach can be used to attach other services as needed, such as a KMS service, DID service, or DID store service. **Option: CURL** Endpoint: `/v1/{target}/wallet-service-api/dependencies/add` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Wallet%20Service/post_v1__target__wallet_service_api_dependencies_add) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/wallet-service-api/dependencies/add' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d 'waltid.tenant1.credential-store-1' ``` **Body** ``` waltid.tenant1.credential-store-1 ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + wallet service to which to add an existing Credential Store (`{organizationID}.{tenantID}.{walletServiceID}`), e.g. `waltid.tenant1.wallet1` **Body Parameters** - Specifies the ID of the Credential store that should be linked. --- **Response Codes** - `201` - Unit attached. -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/overview RAW: https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Credential Receiving The **Wallet API v2** receives credentials from an issuer using [OID4VCI 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/enterprise-stack/services/issuer2-service/overview.md) — and the wallet claims the credential and stores it in a linked Credential Store. All endpoints live under `/v2/{target}/wallet-service-api/`, where `{target}` is the wallet's resource path (`{organization}.{tenant}.{wallet-id}`). ## How It Works Receiving a credential runs through the same steps regardless of format: 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 /v2/{target}/wallet-service-api/credentials/receive`) runs every step for you. This is the recommended path and what the per-format guides below use. - **[Custom Flow](https://docs.walt.id/enterprise-stack/services/wallet-service/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. ## Supported Formats Choose the credential type that matches your use case — the flow is identical; only the offer's credential type and the stored `format` differ: - [SD-JWT VC (IETF) via OID4VCI](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/sd-jwt-vc.md) — `format: dc+sd-jwt` - [W3C VC via OID4VCI](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/w3c-vc.md) — `format: jwt_vc_json` - [mDL / mDoc (ISO 18013-5) via OID4VCI](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/mdl.md) — `format: mso_mdoc` Need fine-grained control? See [Custom Flow](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/custom-flow.md). **Note:** Using OID4VCI Draft 13 (the `/v1` API)? See the [legacy receiving guides](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/legacy-draft13/vc-oid4vci.md). -------------------------------------------------------------------------------- PAGE: SD-JWT VC via OID4VCI URL: https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/sd-jwt-vc RAW: https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/sd-jwt-vc.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Receiving SD-JWT VC Credentials via OID4VCI 1.0 This guide shows how to receive an [IETF SD-JWT VC](https://docs.walt.id/concepts/digital-credentials/sd-jwt-vc.md) credential into an Enterprise wallet using the **Wallet API v2**. You take a credential offer — for example one created with [Issuer2](https://docs.walt.id/enterprise-stack/services/issuer2-service/overview.md) — and claim it into the 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. **Note:** This page follows the **pre-authorized code flow**. For the authorization-code grant and for driving each step yourself (custom UIs, deferred issuance), see [Custom Flow](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/custom-flow.md). ## Prerequisites - A **wallet service** with a linked KMS, DID Store, and Credential Store — see [Setup](https://docs.walt.id/enterprise-stack/services/wallet-service/setup.md). The wallet's default key and DID are used unless you override them. - The wallet's **target** — `{organization}.{tenant}.{wallet-id}`, e.g. `waltid.tenant1.wallet`. - An **SD-JWT VC credential offer** (`openid-credential-offer://…`), for example from the [Issuer2](https://docs.walt.id/enterprise-stack/services/issuer2-service/overview.md) service. --- ## Receive the Credential This single call resolves the offer, requests an access token, signs a proof of possession with the wallet's key, fetches the credential, and stores it in the linked Credential Store. **Option: CURL** Endpoint: `POST /v2/{target}/wallet-service-api/credentials/receive` **Example Request** ```bash curl -X POST \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials/receive' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "offerUrl": "openid-credential-offer://?credential_offer_uri=https%3A%2F%2Fissuer.example.org%2Fcredential-offer%3Fid%3Dabc123" }' ``` **Path Parameters** - `orgID` _String_ — Organization host alias to call against, e.g. `test.enterprise-sandbox.waltid.dev` for organization `test`. - `target` _resourceIdentifier_ — The wallet's resource path, `{organization}.{tenant}.{wallet-id}`, e.g. `waltid.tenant1.wallet`. **Body Parameters** - **Credential offer** (required) — provide exactly one of: - `offerUrl` _String_ — OID4VCI credential offer URL. - `offerJson` _Object_ — Raw credential offer as a JSON object, instead of a URL. - `keyId` (optional) _String_ — Full resource path of a key in a linked KMS (`{org}.{tenant}.{kms}.{key}`). Defaults to the first key in the wallet's linked KMS (or the service's configured static key if the KMS has none). - `key` (optional) _Object_ — Inline serialized key (`{ "type": "jwk", "jwk": { … } }`). Takes precedence over `keyId`. - `did` (optional) _String_ — Holder DID. Defaults to the first DID in the wallet's linked DID Store (or the configured static DID). If the issuer uses DID-based proof binding and you pass a custom `keyId`/`key`, also pass the `did` associated with that key — otherwise the wallet's default DID is used for binding instead. - `txCode` (optional) _String_ — Transaction code (PIN), if the pre-authorized offer requires one. - `clientId` (optional) _String_ — OAuth 2.0 `client_id` presented to the issuer's authorization server. Defaults to `wallet-client`. **Example Response** ```json { "credentialIds": [ "6ba7b810-9dad-11d1-80b4-00c04fd430c8" ], "deferredTransactionIds": {} } ``` - `credentialIds` _String[]_ — IDs of the credentials received and stored in the wallet's Credential Store. - `deferredTransactionIds` _Object_ — Map of credential configuration ID → `transaction_id`, present only when the issuer defers issuance. See [deferred credentials](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/custom-flow.md#deferred-issuance). **Response Codes** - `200` — Credential(s) received and stored. - `400` — Invalid request (e.g. neither `offerUrl` nor `offerJson` provided, or the offer could not be resolved). - `401` — Invalid authentication. 🎉 You've received an SD-JWT VC into the wallet. --- ## Confirm It Was Stored List the wallet's credentials to see the one you just received. This returns metadata only. **Option: CURL** Endpoint: `GET /v2/{target}/wallet-service-api/credentials` **Example Request** ```bash curl \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials' \ -H 'Authorization: Bearer {yourToken}' ``` **Path Parameters** - `orgID` _String_ — Organization host alias. - `target` _resourceIdentifier_ — The wallet's resource path, e.g. `waltid.tenant1.wallet`. **Example Response** ```json [ { "id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "format": "dc+sd-jwt", "issuer": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwi...", "addedAt": "2026-07-09T13:24:04.282744Z" } ] ``` - `id` _String_ — The stored credential's ID. Use it with the [management endpoints](https://docs.walt.id/enterprise-stack/services/wallet-service/management.md) to view or delete it. - `format` _String_ — Credential format. For an SD-JWT VC this is `dc+sd-jwt`. - `issuer` / `subject` (optional) _String_ — Issuer and holder identifiers, when present (`subject` is often absent for SD-JWT VC). - `addedAt` _String_ — ISO-8601 timestamp of when the credential was stored. **Response Codes** - `200` — Credential metadata returned. - `401` — Invalid authentication. --- ## Doing It Step by Step The full-flow call above is the fastest path. To show intermediate state in a wallet UI — 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/enterprise-stack/services/wallet-service/credential-receiving/custom-flow.md). --- ## Next Steps - **Present the credential you just received** — [Presenting SD-JWT VC Credentials](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/sd-jwt-vc.md). - **Receive other credential types** — [W3C VC](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/w3c-vc.md) or [mDL](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/mdl.md). - **Manage stored credentials** — [list and retrieve](https://docs.walt.id/enterprise-stack/services/wallet-service/management.md) credentials in the wallet. -------------------------------------------------------------------------------- PAGE: W3C VC via OID4VCI URL: https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/w3c-vc RAW: https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/w3c-vc.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Receiving W3C VC Credentials via OID4VCI 1.0 This guide shows how to receive a [W3C Verifiable Credential](https://docs.walt.id/concepts/digital-credentials/verifiable-credentials-w3c.md) into an Enterprise wallet using the **Wallet API v2**. You take a credential offer — for example one created with [Issuer2](https://docs.walt.id/enterprise-stack/services/issuer2-service/overview.md) — and claim it into the wallet in a single call, then confirm it was stored. **Note:** This page follows the **pre-authorized code flow**. For the authorization-code grant and for driving each step yourself (custom UIs, deferred issuance), see [Custom Flow](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/custom-flow.md). ## Prerequisites - A **wallet service** with a linked KMS, DID Store, and Credential Store — see [Setup](https://docs.walt.id/enterprise-stack/services/wallet-service/setup.md). The wallet's default key and DID are used unless you override them. - The wallet's **target** — `{organization}.{tenant}.{wallet-id}`, e.g. `waltid.tenant1.wallet`. - A **W3C VC credential offer** (`openid-credential-offer://…`), for example from the [Issuer2](https://docs.walt.id/enterprise-stack/services/issuer2-service/overview.md) service. --- ## Receive the Credential This single call resolves the offer, requests an access token, signs a proof of possession with the wallet's key, fetches the credential, and stores it in the linked Credential Store. **Option: CURL** Endpoint: `POST /v2/{target}/wallet-service-api/credentials/receive` **Example Request** ```bash curl -X POST \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials/receive' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "offerUrl": "openid-credential-offer://?credential_offer_uri=https%3A%2F%2Fissuer.example.org%2Fcredential-offer%3Fid%3Dabc123" }' ``` **Path Parameters** - `orgID` _String_ — Organization host alias to call against, e.g. `test.enterprise-sandbox.waltid.dev` for organization `test`. - `target` _resourceIdentifier_ — The wallet's resource path, `{organization}.{tenant}.{wallet-id}`, e.g. `waltid.tenant1.wallet`. **Body Parameters** - **Credential offer** (required) — provide exactly one of: - `offerUrl` _String_ — OID4VCI credential offer URL. - `offerJson` _Object_ — Raw credential offer as a JSON object, instead of a URL. - `keyId` (optional) _String_ — Full resource path of a key in a linked KMS (`{org}.{tenant}.{kms}.{key}`). Defaults to the first key in the wallet's linked KMS (or the service's configured static key if the KMS has none). - `key` (optional) _Object_ — Inline serialized key (`{ "type": "jwk", "jwk": { … } }`). Takes precedence over `keyId`. - `did` (optional) _String_ — Holder DID. Defaults to the first DID in the wallet's linked DID Store (or the configured static DID). If the issuer uses DID-based proof binding and you pass a custom `keyId`/`key`, also pass the `did` associated with that key — otherwise the wallet's default DID is used for binding instead. - `txCode` (optional) _String_ — Transaction code (PIN), if the pre-authorized offer requires one. - `clientId` (optional) _String_ — OAuth 2.0 `client_id` presented to the issuer's authorization server. Defaults to `wallet-client`. **Example Response — `ReceiveCredentialResult`** ```json { "credentialIds": [ "f58ac347-0728-45c6-ba59-bde9d3343aa6" ], "deferredTransactionIds": {} } ``` - `credentialIds` _String[]_ — IDs of the credentials received and stored in the wallet's Credential Store. - `deferredTransactionIds` _Object_ — Map of credential configuration ID → `transaction_id`, present only when the issuer defers issuance. See [deferred credentials](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/custom-flow.md#deferred-issuance). **Response Codes** - `200` — Credential(s) received and stored. - `400` — Invalid request (e.g. neither `offerUrl` nor `offerJson` provided, or the offer could not be resolved). - `401` — Invalid authentication. 🎉 You've received a W3C VC into the wallet. --- ## Confirm It Was Stored List the wallet's credentials to see the one you just received. This returns metadata only. **Option: CURL** Endpoint: `GET /v2/{target}/wallet-service-api/credentials` **Example Request** ```bash curl \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials' \ -H 'Authorization: Bearer {yourToken}' ``` **Path Parameters** - `orgID` _String_ — Organization host alias. - `target` _resourceIdentifier_ — The wallet's resource path, e.g. `waltid.tenant1.wallet`. **Example Response** ```json [ { "id": "f58ac347-0728-45c6-ba59-bde9d3343aa6", "format": "jwt_vc_json", "issuer": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp", "subject": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Ii...", "addedAt": "2026-07-09T13:24:04.282744Z" } ] ``` - `id` _String_ — The stored credential's ID. Use it with the [management endpoints](https://docs.walt.id/enterprise-stack/services/wallet-service/management.md) to view or delete it. - `format` _String_ — Credential format. For a W3C VC this is `jwt_vc_json`. - `issuer` / `subject` (optional) _String_ — Issuer and holder identifiers, when present. - `addedAt` _String_ — ISO-8601 timestamp of when the credential was stored. **Response Codes** - `200` — Credential metadata returned. - `401` — Invalid authentication. --- ## Doing It Step by Step The full-flow call above is the fastest path. To show intermediate state in a wallet UI — 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/enterprise-stack/services/wallet-service/credential-receiving/custom-flow.md). --- ## Next Steps - **Present the credential you just received** — [Presenting W3C VC Credentials](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/w3c-vc.md). - **Receive other credential types** — [SD-JWT VC](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/sd-jwt-vc.md) or [mDL](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/mdl.md). - **Manage stored credentials** — [list and retrieve](https://docs.walt.id/enterprise-stack/services/wallet-service/management.md) credentials in the wallet. -------------------------------------------------------------------------------- PAGE: mDL / mDoc via OID4VCI URL: https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/mdl RAW: https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/mdl.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Receiving mDL / mDoc Credentials via OID4VCI 1.0 This guide shows how to receive an [ISO 18013-5 mDL / mDoc](https://docs.walt.id/concepts/digital-credentials/mdoc-mdl-iso.md) credential into an Enterprise wallet using the **Wallet API v2**. You take a credential offer — for example one created with [Issuer2](https://docs.walt.id/enterprise-stack/services/issuer2-service/overview.md) — and claim it into the 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, used for mobile driving licences and other ISO mDocs. **Note:** This page follows the **pre-authorized code flow**. For the authorization-code grant and for driving each step yourself (custom UIs, deferred issuance), see [Custom Flow](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/custom-flow.md). ## Prerequisites - A **wallet service** with a linked KMS, DID Store, and Credential Store — see [Setup](https://docs.walt.id/enterprise-stack/services/wallet-service/setup.md). The wallet's default key and DID are used unless you override them. - The wallet's **target** — `{organization}.{tenant}.{wallet-id}`, e.g. `waltid.tenant1.wallet`. - An **mDL / mDoc credential offer** (`openid-credential-offer://…`), for example from the [Issuer2](https://docs.walt.id/enterprise-stack/services/issuer2-service/overview.md) service. --- ## Receive the Credential This single call resolves the offer, requests an access token, signs a proof of possession with the wallet's key, fetches the credential, and stores it in the linked Credential Store. **Option: CURL** Endpoint: `POST /v2/{target}/wallet-service-api/credentials/receive` **Example Request** ```bash curl -X POST \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials/receive' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "offerUrl": "openid-credential-offer://?credential_offer_uri=https%3A%2F%2Fissuer.example.org%2Fcredential-offer%3Fid%3Dabc123" }' ``` **Path Parameters** - `orgID` _String_ — Organization host alias to call against, e.g. `test.enterprise-sandbox.waltid.dev` for organization `test`. - `target` _resourceIdentifier_ — The wallet's resource path, `{organization}.{tenant}.{wallet-id}`, e.g. `waltid.tenant1.wallet`. **Body Parameters** - **Credential offer** (required) — provide exactly one of: - `offerUrl` _String_ — OID4VCI credential offer URL. - `offerJson` _Object_ — Raw credential offer as a JSON object, instead of a URL. - `keyId` (optional) _String_ — Full resource path of a key in a linked KMS (`{org}.{tenant}.{kms}.{key}`). Defaults to the first key in the wallet's linked KMS (or the service's configured static key if the KMS has none). - `key` (optional) _Object_ — Inline serialized key (`{ "type": "jwk", "jwk": { … } }`). Takes precedence over `keyId`. - `txCode` (optional) _String_ — Transaction code (PIN), if the pre-authorized offer requires one. - `clientId` (optional) _String_ — OAuth 2.0 `client_id` presented to the issuer's authorization server. Defaults to `wallet-client`. **Example Response — `ReceiveCredentialResult`** ```json { "credentialIds": [ "550e8400-e29b-41d4-a716-446655440000" ], "deferredTransactionIds": {} } ``` - `credentialIds` _String[]_ — IDs of the credentials received and stored in the wallet's Credential Store. - `deferredTransactionIds` _Object_ — Map of credential configuration ID → `transaction_id`, present only when the issuer defers issuance. See [deferred credentials](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/custom-flow.md#deferred-issuance). **Response Codes** - `200` — Credential(s) received and stored. - `400` — Invalid request (e.g. neither `offerUrl` nor `offerJson` provided, or the offer could not be resolved). - `401` — Invalid authentication. 🎉 You've received an mDL / mDoc into the wallet. --- ## Confirm It Was Stored List the wallet's credentials to see the one you just received. This returns metadata only. **Option: CURL** Endpoint: `GET /v2/{target}/wallet-service-api/credentials` **Example Request** ```bash curl \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials' \ -H 'Authorization: Bearer {yourToken}' ``` **Path Parameters** - `orgID` _String_ — Organization host alias. - `target` _resourceIdentifier_ — The wallet's resource path, e.g. `waltid.tenant1.wallet`. **Example Response** ```json [ { "id": "550e8400-e29b-41d4-a716-446655440000", "format": "mso_mdoc", "addedAt": "2026-07-09T13:24:04.282744Z" } ] ``` - `id` _String_ — The stored credential's ID. Use it with the [management endpoints](https://docs.walt.id/enterprise-stack/services/wallet-service/management.md) to view or delete it. - `format` _String_ — Credential format. For an mDL / mDoc this is `mso_mdoc`. - `issuer` / `subject` (optional) _String_ — Issuer and holder identifiers, when present (often absent for mDocs, which bind to keys and certificates rather than DIDs). - `addedAt` _String_ — ISO-8601 timestamp of when the credential was stored. **Response Codes** - `200` — Credential metadata returned. - `401` — Invalid authentication. --- ## Doing It Step by Step The full-flow call above is the fastest path. To show intermediate state in a wallet UI — 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/enterprise-stack/services/wallet-service/credential-receiving/custom-flow.md). --- ## Next Steps - **Present the credential you just received** — [Presenting mDL / mDoc Credentials](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/mdl.md). - **Receive other credential types** — [SD-JWT VC](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/sd-jwt-vc.md) or [W3C VC](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/w3c-vc.md). - **Manage stored credentials** — [list and retrieve](https://docs.walt.id/enterprise-stack/services/wallet-service/management.md) credentials in the wallet. -------------------------------------------------------------------------------- PAGE: Custom Flow URL: https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/custom-flow RAW: https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/custom-flow.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Custom Flow The [full-flow](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/overview.md#two-ways-to-receive) endpoint (`credentials/receive`) runs the whole exchange in one call. The **custom flow** lets you drive each step yourself when you need to: - Build a custom wallet UI with consent screens and PIN prompts - Use the **authorization-code grant** (browser redirect) - Handle **deferred issuance** - Inspect intermediate results, or manage keys/credentials externally All endpoints live under `/v2/{target}/wallet-service-api/`. ## Holder Key & DID Every receive flow signs a **proof of possession** to prove the wallet controls the holder key. You can let the wallet use its defaults, reference stored material, or pass material inline: | Field | Type | Description | |---|---|---| | `keyId` | String | Full resource path of a key in a linked KMS, e.g. `waltid.tenant1.kms.wallet_key`. | | `key` | Object | Inline serialized key (`{ "type": "jwk", "jwk": { … } }`). Takes precedence over `keyId`. | | `did` | String | DID to use as the holder identity, e.g. `did:jwk:…`. Resolved against a linked DID Store or used inline. | All three are **optional** — if omitted, the wallet uses its configured default key and DID. --- ## Pre-Authorized Code Flow Drive the pre-authorized code grant one step at a time: ```text 1. Resolve Offer → 2. Request Token → 2b. Get Nonce → 3. Sign Proof → 4. Fetch Credential ``` For the authorization-code grant (browser redirect, user login), see [Authorization-Code Grant](#authorization-code-grant) below — it reuses Get Nonce, Sign Proof, and Fetch Credential from this flow. ### Step 1 — Resolve Offer Parse the offer URL and resolve the issuer metadata in one call. **Option: CURL** Endpoint: `POST /v2/{target}/wallet-service-api/credentials/receive/resolve-offer` **Example Request** ```bash curl -X POST \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials/receive/resolve-offer' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "offerUrl": "openid-credential-offer://?credential_offer_uri=https%3A%2F%2Fissuer.example.org%2Fcredential-offer%3Fid%3Dabc123" }' ``` **Body Parameters** - **Credential offer** (required) — provide exactly one of: - `offerUrl` _String_ — The OID4VCI offer URL. - `offerJson` _Object_ — Raw credential offer JSON, instead of a URL. **Example Response** ```json { "credentialIssuer": "https://issuer.example.org", "credentialConfigurationIds": ["UniversityDegree_jwt_vc_json"], "grantType": "pre-authorized_code", "txCodeRequired": false, "credentialEndpoint": "https://issuer.example.org/credential", "offeredCredentials": ["UniversityDegree_jwt_vc_json"] } ``` - `grantType` — `pre-authorized_code` or `authorization_code`. - `txCodeRequired` — Whether a transaction code (PIN) is required for the pre-authorized grant. - `credentialEndpoint` — Where to POST the credential request in Step 4. **Response Codes** - `200` — Offer resolved. - `400` — Offer could not be resolved. ### Step 2 — Request Token Exchange a pre-authorized code for an access token. **Note:** `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 /v2/{target}/wallet-service-api/credentials/receive/request-token` **Example Request** ```bash curl -X POST \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials/receive/request-token' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "tokenEndpoint": "https://auth.example.org/token", "preAuthorizedCode": "SplxlOBeZQQYbYS6WxSbIA", "txCode": "493536" }' ``` **Body Parameters** - `tokenEndpoint` _String_ — The authorization server's token endpoint. - `preAuthorizedCode` _String_ — The pre-authorized code from the offer. - `txCode` (optional) _String_ — Transaction code (PIN), if the offer requires one. - `clientId` (optional) _String_ — Defaults to `wallet-client`. - `redirectUri` (optional) _String_ — Defaults to `openid://`. **Example Response** ```json { "accessToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", "expiresIn": 300 } ``` - `accessToken` — Bearer token for the credential request in Step 4. - `cNonce` (optional) — Proof nonce, **if** the issuer returns one in the token response. Under OID4VCI 1.0 the issuer (including Issuer2) instead exposes a dedicated **Nonce Endpoint**, so `cNonce` is usually absent here — fetch the nonce in [Step 2b](#step-2b-get-a-nonce) instead. **Response Codes** - `200` — Token issued. - `400` — Invalid or expired pre-authorized code. **Note:** 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 [Step 2b](#step-2b-get-a-nonce) before signing the proof. ### Step 2b — 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 this — 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/enterprise-stack/services/issuer2-service/overview.md) it is `{credentialIssuer}/nonce` (the `credentialIssuer` from [Step 1](#step-1-resolve-offer)). **Option: CURL** Endpoint: `POST {credentialIssuer}/nonce` — this is the **issuer's** endpoint, not a wallet endpoint. **Example Request** ```bash curl -X POST 'https://issuer.example.org/nonce' ``` **Example Response** ```json { "c_nonce": "wKI4LT5Sadf6a0dyih5b0Ws" } ``` Use the returned `c_nonce` as the `nonce` in [Step 3](#step-3-sign-proof). **Response Codes** - `200` — Nonce issued. ### Step 3 — Sign Proof Sign a proof of possession over the issuer's `c_nonce` to prove control of the holder key. **Option: CURL** Endpoint: `POST /v2/{target}/wallet-service-api/credentials/receive/sign-proof` **Example Request** ```bash curl -X POST \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials/receive/sign-proof' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "issuerUrl": "https://issuer.example.org", "nonce": "wKI4LT5Sadf6a0dyih5b0Ws", "keyId": "waltid.tenant1.kms.wallet_key", "did": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Ii..." }' ``` **Body Parameters** - `issuerUrl` _String_ — The credential issuer identifier (used as the proof `aud`). - `nonce` _String_ — The `c_nonce` from the [Nonce Endpoint](#step-2b-get-a-nonce) (Step 2b), or from the token response if the issuer returned one. - `keyId` / `key` (optional) — Holder key reference or inline key (see [Holder Key & DID](#holder-key-did)). - `did` (optional) _String_ — Holder DID. When set, the proof binds to the DID; otherwise it binds to the raw JWK. **Example Response** ```json { "proofJwt": "eyJ0eXAiOiJvcGVuaWQ0dmNpLXByb29mK2p3dC..." } ``` **Response Codes** - `200` — Proof signed. - `400` — No resolvable holder key. ### Step 4 — Fetch Credential Request the credential from the issuer's credential endpoint using the access token and the signed proof. **Option: CURL** Endpoint: `POST /v2/{target}/wallet-service-api/credentials/receive/fetch-credential` **Example Request** ```bash curl -X POST \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials/receive/fetch-credential' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "credentialEndpoint": "https://issuer.example.org/credential", "accessToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", "credentialConfigurationId": "UniversityDegree_jwt_vc_json", "proofJwt": "eyJ0eXAiOiJvcGVuaWQ0dmNpLXByb29mK2p3dC..." }' ``` **Body Parameters** - `credentialEndpoint` _String_ — The issuer's credential endpoint (from Step 1 or issuer metadata). - `accessToken` _String_ — The access token from Step 2. - `credentialConfigurationId` _String_ — The credential configuration being requested. - `proofJwt` (optional) _String_ — The proof JWT from Step 3. Required when the issuer mandates key-proof. - `clientId` (optional) _String_ — Defaults to `wallet-client`. **Example Response** ```json { "rawCredentials": [ "eyJraWQiOiJkaWQ6a2V5Ono2TWtqb1Jo..." ] } ``` - `rawCredentials` — The issued credential string(s) (JWT, SD-JWT, or base64-encoded mDoc). These are **not** stored automatically; use the [full-flow](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/overview.md#two-ways-to-receive) endpoints if you want the wallet to persist them. **Response Codes** - `200` — Credential(s) fetched. - `400` / `500` — The issuer rejected the credential request (e.g. missing or invalid proof). --- ## Authorization-Code Grant You don't know which grant an offer uses until you resolve it — [Step 1 — Resolve Offer](#step-1-resolve-offer) above returns `grantType`, and `authorization_code` is what routes you here instead of the pre-authorized flow. The authorization-code grant then requires the user to authenticate with the issuer's authorization server via a browser redirect, so it cannot be a single blocking call: ```text Step 1 (above) — Resolve Offer → grantType: "authorization_code" Generate Authorization URL → redirect the user to the issuer ── user authenticates in the browser ── Complete Receive → exchange the code, sign the proof, fetch & store the credential (single call) — or drive it yourself: — Exchange Code → Step 2b (above) — Get Nonce → Step 3 (above) — Sign Proof → Step 4 (above) — Fetch Credential (Get Nonce, Sign Proof, and Fetch Credential are the same steps used by the pre-authorized flow above) ``` ### Generate Authorization URL Resolve the offer and build the authorization URL the user must be redirected to. **Option: CURL** Endpoint: `POST /v2/{target}/wallet-service-api/credentials/receive/authorization-url` **Example Request** ```bash curl -X POST \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials/receive/authorization-url' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "offerUrl": "openid-credential-offer://?credential_offer_uri=https%3A%2F%2Fissuer.example.org%2Fcredential-offer%3Fid%3Dabc123", "redirectUri": "https://your-wallet-app.example.com/callback" }' ``` **Body Parameters** - **Credential offer** (required) — provide exactly one of: - `offerUrl` _String_ — The OID4VCI offer URL. - `offerJson` _Object_ — Raw credential offer JSON, instead of a URL. - `clientId` (optional) _String_ — Defaults to `wallet-client`. - `redirectUri` (optional) _String_ — Where the authorization server redirects the user after authentication. Defaults to `openid://`. - `usePkce` (optional) _Boolean_ — Whether to use PKCE. Defaults to `true`. **Example Response** ```json { "authorizationUrl": "https://auth.example.org/authorize?response_type=code&client_id=wallet-client&redirect_uri=https%3A%2F%2Fyour-wallet-app.example.com%2Fcallback&scope=UniversityDegree_jwt_vc_json&issuer_state=eyJhbGciOiJSU...&code_challenge=abc123&code_challenge_method=S256", "state": "abc123", "codeVerifier": "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM", "credentialConfigurationId": "UniversityDegree_jwt_vc_json" } ``` - `authorizationUrl` — Redirect the user here to begin authentication. - `state` — Returned to your `redirectUri` as a query parameter; use it to correlate the callback. - `codeVerifier` — PKCE code verifier. **Store this securely** — the [Exchange Code](#exchange-code-isolated) step below needs it. - `credentialConfigurationId` — The credential configuration this authorization is for. After redirecting the user, your app receives the authorization code at `redirectUri`: `?code=&state=`. **Response Codes** - `200` — Authorization URL generated. - `400` — Offer could not be resolved, or the issuer has no authorization endpoint. ### Exchange Code (Isolated) Exchange the authorization code obtained above for an access token. This is the isolated alternative to [Complete Receive](#complete-receive-combined-alternative) below — after this, continue with [Step 2b — Get a Nonce](#step-2b-get-a-nonce), [Step 3 — Sign Proof](#step-3-sign-proof), and [Step 4 — Fetch Credential](#step-4-fetch-credential) from the pre-authorized flow above; they're identical for both grant types. **Option: CURL** Endpoint: `POST /v2/{target}/wallet-service-api/credentials/receive/exchange-code` **Example Request** ```bash curl -X POST \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials/receive/exchange-code' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "tokenEndpoint": "https://auth.example.org/token", "code": "SplxlOBeZQQYbYS6WxSbIA", "codeVerifier": "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM" }' ``` **Body Parameters** - `tokenEndpoint` _String_ — The authorization server's token endpoint. - `code` _String_ — The authorization code from the redirect callback. - `codeVerifier` (optional) _String_ — The PKCE code verifier saved from the authorization-url step. - `clientId` (optional) _String_ — Defaults to `wallet-client`. - `redirectUri` (optional) _String_ — Must match the redirect URI used earlier. Defaults to `openid://`. **Example Response** Same structure as [Step 2 — Request Token](#step-2-request-token) in the pre-authorized flow. **Response Codes** - `200` — Token issued. - `400` — Invalid authorization code or PKCE verifier. ### Complete Receive (Combined Alternative) The simpler alternative to driving Exchange Code, Get Nonce, Sign Proof, and Fetch Credential yourself once the user is back with an authorization code: exchange the code for a token, sign the proof, fetch the credential, and store it — in one call. **Option: CURL** Endpoint: `POST /v2/{target}/wallet-service-api/credentials/receive/authorized` **Example Request** ```bash curl -X POST \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials/receive/authorized' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "tokenEndpoint": "https://auth.example.org/token", "code": "SplxlOBeZQQYbYS6WxSbIA", "codeVerifier": "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM", "credentialIssuer": "https://issuer.example.org", "credentialEndpoint": "https://issuer.example.org/credential", "nonceEndpoint": "https://issuer.example.org/nonce", "credentialConfigurationId": "UniversityDegree_jwt_vc_json", "keyId": "waltid.tenant1.kms.wallet_key", "did": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Ii..." }' ``` **Body Parameters** - `tokenEndpoint` _String_ — The authorization server's token endpoint (from its metadata). - `code` _String_ — The authorization code received at your `redirectUri`. - `codeVerifier` (optional) _String_ — The PKCE code verifier saved from Step 1. Required if PKCE was used. - `credentialIssuer` _String_ — The credential issuer identifier. - `credentialEndpoint` _String_ — The issuer's credential endpoint (from its metadata). - `credentialConfigurationId` _String_ — The credential configuration being requested. - `nonceEndpoint` (optional in the request schema, but effectively required for Issuer2) _String_ — The issuer's nonce endpoint — for Issuer2 this is `{credentialIssuer}/nonce`. Under OID4VCI 1.0 the token response typically carries no `c_nonce` (same as [Step 2b](#step-2b-get-a-nonce)), so if you omit `nonceEndpoint` **and** the issuer doesn't return a `c_nonce`, this call fails with a `500`. - `keyId` / `key` (optional) — Holder key reference or inline key. - `did` (optional) _String_ — Holder DID. - `clientId` (optional) _String_ — Defaults to `wallet-client`. - `redirectUri` (optional) _String_ — Must match the `redirectUri` used in Step 1. Defaults to `openid://`. **Example Response** ```json { "credentialIds": [ "f58ac347-0728-45c6-ba59-bde9d3343aa6" ], "deferredTransactionIds": {} } ``` **Response Codes** - `200` — Credential(s) received and stored. - `400` — Invalid authorization code, or the issuer rejected the request. - `500` — The issuer provided no `c_nonce` and no `nonceEndpoint` was supplied to fetch one. --- ## Deferred Issuance If the issuer defers a credential, the full-flow (or `authorized`) response returns its `transaction_id` under `deferredTransactionIds`. Poll the issuer's deferred credential endpoint until the credential becomes available. **Option: CURL** Endpoint: `POST /v2/{target}/wallet-service-api/credentials/receive/deferred` **Example Request** ```bash curl -X POST \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials/receive/deferred' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "deferredCredentialEndpoint": "https://issuer.example.org/credential_deferred", "transactionId": "8xLOxBtZp8", "accessToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." }' ``` **Body Parameters** - `deferredCredentialEndpoint` _String_ — The deferred credential endpoint from the issuer's metadata. - `transactionId` _String_ — The `transaction_id` returned under `deferredTransactionIds`. - `accessToken` _String_ — The access token from the original token response. **Example Response** ```json { "credentialIds": [ "f58ac347-0728-45c6-ba59-bde9d3343aa6" ], "deferredTransactionIds": {} } ``` If the credential is not yet ready, `credentialIds` is empty — retry later. **Response Codes** - `200` — Poll completed (credential stored, or not yet ready). - `400` / `500` — The issuer rejected the deferred request. --- ## Next Steps - **Receive with the full flow** — the per-format guides ([SD-JWT VC](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/sd-jwt-vc.md), [W3C VC](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/w3c-vc.md), [mDL](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/mdl.md)) use the single-call `credentials/receive` endpoint. - **Manage stored credentials** — [list and retrieve](https://docs.walt.id/enterprise-stack/services/wallet-service/management.md) credentials in the wallet. - **Present a credential** — [Credential Presenting](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/overview.md). -------------------------------------------------------------------------------- PAGE: Accept W3C Verifiable Credentials via OID4VCI URL: https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/legacy-draft13/vc-oid4vci RAW: https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/legacy-draft13/vc-oid4vci.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Accept W3C Verifiable Credentials (JWT) via OID4VCI In this guide, we take a look at how you can use the walt.id Enterprise wallet service API to accept a W3C Verifiable Credential from an issuer via OID4VCI. **Note:** Ensure you have [set up a wallet (service)](https://docs.walt.id/enterprise-stack/services/wallet-service/setup.md) and linked the necessary enterprise services (e.g., Credential Store) before proceeding with this guide.
**Note:** This guide refers specifically to W3C Verifiable Credential standard without selective disclosure. It is possible to wrap a W3C Verifiable Credential within an SD-JWT in order to support selective disclosure. To understand more, check out the [Issue W3C Credentials (JWT / SD-JWT) via OID4VC](https://docs.walt.id/enterprise-stack/services/issuer-service/credential-issuance/vc-oid4vc.md) guide. ## 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** ``` openid-credential-offer://issuer.portal.walt.id/?credential_offer= ``` Within the URL, a query parameter `credential_offer` or `credential_offer_uri` provides either the actual offer as a JSON object or a URL that points to the JSON object. **Example of a Credential Offer Object for JWT VCs** ```json { "credential_issuer": "https://issuer.demo.walt.id", "credentials": [ { "format": "jwt_vc_json", "types": [ "VerifiableCredential", "BankId" ], "credential_definition": { "@context": [ "https://www.w3.org/2018/credentials/v1" ], "types": [ "VerifiableCredential", "BankId" ] } } ], "grants": { "authorization_code": { "issuer_state": "" }, "urn:ietf:params:oauth:grant-type:pre-authorized_code": { "pre-authorized_code": "", "user_pin_required": false } } } ``` ## Accepting Credential Offers In the next step, we will provide the credential offer URL to a wallet service endpoint. This will initialize the exchange between wallet and issuer. The response of the call will contain the received credential, which will also be stored in the linked credential store. In case there is no linked the credential store, the credential won't be stored and must be saved otherwise if later retrieval is wanted. **Note:** If there is no credential stored linked with the wallet the received credential won't be persisted.
**Option: CURL** Endpoint: `/v1/{target}/wallet-service-api/credentials/receive` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Wallet%20Service/post_v1__target__wallet_service_api_credentials_receive) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/wallet-service-api/credentials/receive' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "offerUrl": "openid-credential-offer://issuer.example.org/issue/?credential_offer_uri=https%3A%2F%2Fissuer.example.org%2FcredentialOffer%3Fabc123xyz789", "keyReference": "example.kms.key1", "didReference": "example.didstore.did1" }' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + wallet which should be used to receive the credential (`{organizationID}.{tenantID}.[walletID]`), e.g. `waltid.tenant1.wallet1` **Body** ```json { "offerUrl": "openid-credential-offer://issuer.example.org/issue/?credential_offer_uri=https%3A%2F%2Fissuer.example.org%2FcredentialOffer%3Fabc123xyz789", "keyReference": "example.kms.key1", "didReference": "example.didstore.did1" } ``` **Body Parameters** - `offerUrl` _String_ - An OID4VCI offer URL. - `keyReference` (optional) _String_ - The resource ID (target) of the key to which the received credential should be bound. Key can be stored in a linked KMS store, provided as a static key as described in [the setup section](https://docs.walt.id/enterprise-stack/services/wallet-service/setup.md), or just a raw Key object. See example by expanding. - `didReference` (optional) _String_ - The DID reference of a DID stored in a linked DID Store. - `key` (optional) _Object_ - A key object or reference key object, when using an external KMS, to which the credential should be bound to.

Click to expand and see full key example
**Key Object** ```json { "type": "jwk", "jwk": { "kty": "OKP", "d": "ywmoRVTD9fexMtGW0lKE3o9_0ulfzGXr9xHGL0lPhhA", "crv": "Ed25519", "kid": "IQO7DILxtagpTLXkuHkRkJURb2GqcUIwXYZAcGwW1AU", "x": "JOsiIE7ME9UZ8y2H-P5RSuYAUiIfs1ywtdBjMGN7I5s" } } ``` This key object can also be of the types `tse`, `aws`, or `oci-rest-api` when using external KMS providers. In this case, instead of providing the raw key, an object containing reference information that allows the walt.id Enterprise Stack to access keys stored externally is provided. Learn more about different keys [here](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md).
- `did` (optional) _String_ - DID as a string, e.g. `did:key:z6MktjQeU9dZp8VfK8M9N8Y2u5wV6JzZ2v6s6Q1P7pRkFJ8H` - `metadata` (optional) _Object_ - Arbitrary JSON object that will be stored together with the credential if there is a credential store linked with the wallet. - `runPolicies` (optional) _Boolean_ - If set to `true`, the wallet service will run any [holder policies](https://docs.walt.id/enterprise-stack/services/holder-policy-store-service/overview.md) that are attached to the wallet service before storing the credential. If set to `false`, no holder policies will be run. --- **Response Codes** - `201` - Credential received successfully. **Body** ```json { "offeredCredential": { "format": "jwt_vc_json", "credential_definition": { "type": [ "VerifiableCredential", "OpenBadgeCredential" ] }, "cryptographic_binding_methods_supported": [ "did" ], "customParameters": {} }, "credentialResponse": { "format": "jwt_vc_json", "credential": "eyJraWQiOiJkaWQ6a2V5Ono2TWtqb1JocTFqU05KZExpcnVTWHJGRnhhZ3FyenRaYVhIcUhHVVRLSmJjTnl3cCN6Nk1ram9SaHExalNOSmRMaXJ1U1hyRkZ4YWdxcnp0WmFYSHFIR1VUS0piY055d3AiLCJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtqb1JocTFqU05KZExpcnVTWHJGRnhhZ3FyenRaYVhIcUhHVVRLSmJjTnl3cCIsInN1YiI6IiIsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIiwiaHR0cHM6Ly9wdXJsLmltc2dsb2JhbC5vcmcvc3BlYy9vYi92M3AwL2NvbnRleHQuanNvbiJdLCJpZCI6InVybjp1dWlkOjIyNGJjYmM0LTc0MzYtNDg1NC04ZWYxLWNjNmUzYjgyMzZiZCIsInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJPcGVuQmFkZ2VDcmVkZW50aWFsIl0sIm5hbWUiOiJKRkYgeCB2Yy1lZHUgUGx1Z2Zlc3QgMyBJbnRlcm9wZXJhYmlsaXR5IiwiaXNzdWVyIjp7InR5cGUiOlsiUHJvZmlsZSJdLCJuYW1lIjoiSm9icyBmb3IgdGhlIEZ1dHVyZSAoSkZGKSIsInVybCI6Imh0dHBzOi8vd3d3LmpmZi5vcmcvIiwiaW1hZ2UiOiJodHRwczovL3czYy1jY2cuZ2l0aHViLmlvL3ZjLWVkL3BsdWdmZXN0LTEtMjAyMi9pbWFnZXMvSkZGX0xvZ29Mb2NrdXAucG5nIiwiaWQiOiJkaWQ6a2V5Ono2TWtqb1JocTFqU05KZExpcnVTWHJGRnhhZ3FyenRaYVhIcUhHVVRLSmJjTnl3cCJ9LCJjcmVkZW50aWFsU3ViamVjdCI6eyJ0eXBlIjpbIkFjaGlldmVtZW50U3ViamVjdCJdLCJhY2hpZXZlbWVudCI6eyJpZCI6InVybjp1dWlkOmFjMjU0YmQ1LThmYWQtNGJiMS05ZDI5LWVmZDkzODUzNjkyNiIsInR5cGUiOlsiQWNoaWV2ZW1lbnQiXSwibmFtZSI6IkpGRiB4IHZjLWVkdSBQbHVnRmVzdCAzIEludGVyb3BlcmFiaWxpdHkiLCJkZXNjcmlwdGlvbiI6IlRoaXMgd2FsbGV0IHN1cHBvcnRzIHRoZSB1c2Ugb2YgVzNDIFZlcmlmaWFibGUgQ3JlZGVudGlhbHMgYW5kIGhhcyBkZW1vbnN0cmF0ZWQgaW50ZXJvcGVyYWJpbGl0eSBkdXJpbmcgdGhlIHByZXNlbnRhdGlvbiByZXF1ZXN0IHdvcmtmbG93IGR1cmluZyBKRkYgeCBWQy1FRFUgUGx1Z2Zlc3QgMy4iLCJjcml0ZXJpYSI6eyJ0eXBlIjoiQ3JpdGVyaWEiLCJuYXJyYXRpdmUiOiJXYWxsZXQgc29sdXRpb25zIHByb3ZpZGVycyBlYXJuZWQgdGhpcyBiYWRnZSBieSBkZW1vbnN0cmF0aW5nIGludGVyb3BlcmFiaWxpdHkgZHVyaW5nIHRoZSBwcmVzZW50YXRpb24gcmVxdWVzdCB3b3JrZmxvdy4gVGhpcyBpbmNsdWRlcyBzdWNjZXNzZnVsbHkgcmVjZWl2aW5nIGEgcHJlc2VudGF0aW9uIHJlcXVlc3QsIGFsbG93aW5nIHRoZSBob2xkZXIgdG8gc2VsZWN0IGF0IGxlYXN0IHR3byB0eXBlcyBvZiB2ZXJpZmlhYmxlIGNyZWRlbnRpYWxzIHRvIGNyZWF0ZSBhIHZlcmlmaWFibGUgcHJlc2VudGF0aW9uLCByZXR1cm5pbmcgdGhlIHByZXNlbnRhdGlvbiB0byB0aGUgcmVxdWVzdG9yLCBhbmQgcGFzc2luZyB2ZXJpZmljYXRpb24gb2YgdGhlIHByZXNlbnRhdGlvbiBhbmQgdGhlIGluY2x1ZGVkIGNyZWRlbnRpYWxzLiJ9LCJpbWFnZSI6eyJpZCI6Imh0dHBzOi8vdzNjLWNjZy5naXRodWIuaW8vdmMtZWQvcGx1Z2Zlc3QtMy0yMDIzL2ltYWdlcy9KRkYtVkMtRURVLVBMVUdGRVNUMy1iYWRnZS1pbWFnZS5wbmciLCJ0eXBlIjoiSW1hZ2UifX19LCJpc3N1YW5jZURhdGUiOiIyMDI1LTA0LTAzVDA3OjM0OjEzLjUyMjQzMjc1M1oiLCJleHBpcmF0aW9uRGF0ZSI6IjIwMjYtMDQtMDNUMDc6MzQ6MTMuNTIyNTM1MjUzWiJ9LCJqdGkiOiJ1cm46dXVpZDoyMjRiY2JjNC03NDM2LTQ4NTQtOGVmMS1jYzZlM2I4MjM2YmQiLCJleHAiOjE3NzUyMDE2NTMsImlhdCI6MTc0MzY2NTY1MywibmJmIjoxNzQzNjY1NjUzfQ.eu7Owii99m9DGT_L9PPpdOxcjxpBN1CMsPoTvVtMD0WTsatn5c8MCIrr3NFM74CTbVrhe-NAOFseyC9QrFGdDw", "customParameters": {} } } ``` -------------------------------------------------------------------------------- PAGE: Accept SD-JWT Verifiable Credentials via OID4VCI URL: https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/legacy-draft13/sd-jwt-vc-oid4vci RAW: https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/legacy-draft13/sd-jwt-vc-oid4vci.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Accept SD-JWT Verifiable Credentials via OID4VCI In this guide, we take a look at how you can use the walt.id Enterprise wallet service API to accept an SD-JWT Verifiable Credential from an issuer via OID4VCI. **Note:** Ensure you have [set up a wallet (service)](https://docs.walt.id/enterprise-stack/services/wallet-service/setup.md) and linked the necessary enterprise services (e.g., Credential Store) before proceeding with this guide. ## 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** ``` openid-credential-offer://issuer.portal.walt.id/?credential_offer= ``` **Example of a Credential Offer Object for SD-JWT VCs** ```json { "credential_issuer": "https://issuer.demo.walt.id", "credential_configuration_ids": [ "identity_credential_vc+sd-jwt" ] "grants": { "authorization_code": { "issuer_state": "" }, "urn:ietf:params:oauth:grant-type:pre-authorized_code": { "pre-authorized_code": "", "user_pin_required": false } } } ``` ## Accepting SD-JWT Credential Offers SD-JWT credentials have built-in selective disclosure capabilities, allowing the holder to choose which claims to share when presenting the credential. **Option: CURL** Endpoint: `/v1/{target}/wallet-service-api/credentials/receive` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Wallet%20Service/post_v1__target__wallet_service_api_credentials_receive) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/wallet-service-api/credentials/receive' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "offerUrl": "openid-credential-offer://issuer.example.org/issue/?credential_offer_uri=https%3A%2F%2Fissuer.example.org%2FcredentialOffer%3Fabc123xyz789", "keyReference": "example.kms.key1", "didReference": "example.didstore.did1" }' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + wallet which should be used to receive the credential (`{organizationID}.{tenantID}.[walletID]`), e.g. `waltid.tenant1.wallet1` **Body Parameters** - `offerUrl` _String_ - An OID4VCI offer URL. - `keyReference` (optional) _String_ - The resource ID (target) of the key to which the received credential should be bound. - `didReference` (optional) _String_ - The DID reference of a DID stored in a linked DID Store. - `key` (optional) _Object_ - A key object or reference key object for external KMS. - `did` (optional) _String_ - DID as a string. - `metadata` (optional) _Object_ - Arbitrary JSON object that will be stored together with the credential. - `runPolicies` (optional) _Boolean_ - If set to `true`, the wallet service will run any holder policies. --- **Response Codes** - `201` - SD-JWT Credential received successfully. **Body** ```json { "offeredCredential": { "format": "vc+sd-jwt", "vct": "http://org.enterprise.localhost:3000/v1/org.tenant.issuer2/issuer-service-api/openid4vc/draft13/identity_credential", "credential_definition": {}, "cryptographic_binding_methods_supported": [ "jwk" ] }, "credentialResponse": { "format": "vc+sd-jwt", "credential": "eyJraWQiOiJkaWQ6a2V5OnptWWc5YmdLbVJpQ3FUVGQ5TUExdWZWRTl0ZnpVcHR3UXA0R01SeHB0WHF1Sld3NFVqNWRCUVY3QlBQQm5oSEd6RGF3UlJxSmltdnM3c1FZMUVOWTJ3SEdaU2RBN3BQQWhvYThqV0pYR3JaWHNVSFdMI3ptWWc5YmdLbVJpQ3FUVGQ5TUExdWZWRTl0ZnpVcHR3UXA0R01SeHB0WHF1Sld3NFVqNWRCUVY3QlBQQm5oSEd6RGF3UlJxSmltdnM3c1FZMUVOWTJ3SEdaU2RBN3BQQWhvYThqV0pYR3JaWHNVSFdMIiwidHlwIjoidmMrc2Qtand0IiwiYWxnIjoiRWREU0EifQ.eyJnaXZlbl9uYW1lIjoiSm9obiIsImZhbWlseV9uYW1lIjoiRG9lIiwiZW1haWwiOiJqb2huZG9lQGV4YW1wbGUuY29tIiwicGhvbmVfbnVtYmVyIjoiKzEtMjAyLTU1NS0wMTAxIiwiYWRkcmVzcyI6eyJzdHJlZXRfYWRkcmVzcyI6IjEyMyBNYWluIFN0IiwibG9jYWxpdHkiOiJBbnl0b3duIiwicmVnaW9uIjoiQW55c3RhdGUiLCJjb3VudHJ5IjoiVVMifSwiaXNfb3Zlcl8xOCI6dHJ1ZSwiaXNfb3Zlcl8yMSI6dHJ1ZSwiaXNfb3Zlcl82NSI6dHJ1ZSwiaWQiOiJ1cm46dXVpZDplMzUxY2VhZi0zYWIwLTRkYTQtYjY2NC0zNmFjNjk0YzI0NWEiLCJpYXQiOjE3NjEzMTgzODksIm5iZiI6MTc2MTMxODM4OSwiZXhwIjoxNzkyODU0Mzg5LCJfc2RfYWxnIjoic2hhLTI1NiIsImlzcyI6ImRpZDprZXk6em1ZZzliZ0ttUmlDcVRUZDlNQTF1ZlZFOXRmelVwdHdRcDRHTVJ4cHRYcXVKV3c0VWo1ZEJRVjdCUFBCbmhIR3pEYXdSUnFKaW12czdzUVkxRU5ZMndIR1pTZEE3cFBBaG9hOGpXSlhHclpYc1VIV0wiLCJjbmYiOnsia2lkIjoiZGlkOmp3azpleUpyZEhraU9pSlBTMUFpTENKamNuWWlPaUpGWkRJMU5URTVJaXdpYTJsa0lqb2lVVFExVWtvNVdIcGxiM0pqVTFaamVpMVdaRmhQV0VSMmJFaDNkMkpaZDJWWlREQjRkR0ZrZWs5Vk9DSXNJbmdpT2lKTVVXVTRhVzlqUlhkWWIzRkNVSFI1VWt0NVFteEpPR3BUTkVoeGFUZG1Sa2s1UjFwRFVDMW1VbDlySW4wIn0sInZjdCI6Imh0dHA6Ly9vcmcuZW50ZXJwcmlzZS5sb2NhbGhvc3Q6MzAwMC92MS9vcmcudGVuYW50Lmlzc3VlcjIvaXNzdWVyLXNlcnZpY2UtYXBpL29wZW5pZDR2Yy9kcmFmdDEzL2lkZW50aXR5X2NyZWRlbnRpYWwiLCJkaXNwbGF5IjpbXSwiX3NkIjpbIi1WUFBOUEphMkdTbWo5WHlBZXRzb1laWkYtYkRyRHFEb2lkUWl4WTQwcEkiXX0.7DItRsC1Pg6dcV5GFbyiFQpB3k3FnKF0bUlBmuaEgZEsWNRehBTR2Scfj3mHkFs11JIUOHpxewJEvBfr-5C_Aw~WyJOODdkTTNJZnJhdF9PeW45UVFSZC1BIiwiYmlydGhkYXRlIiwiMTk0MC0wMS0wMSJd~", } } ``` -------------------------------------------------------------------------------- PAGE: Accept mDL/mDOC Credentials via OID4VCI URL: https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/legacy-draft13/mdl-oid4vci RAW: https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/legacy-draft13/mdl-oid4vci.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Accept mDL/mDOC Credentials via OID4VCI In this guide, we take a look at how you can use the walt.id Enterprise wallet service API to accept an mDL (mobile Driving License) or mDOC credential from an issuer via OID4VCI. **Note:** Ensure you have [set up a wallet (service)](https://docs.walt.id/enterprise-stack/services/wallet-service/setup.md) and linked the necessary enterprise services (e.g., Credential Store) before proceeding with this guide.
**Note:** Our implementation for mDL/mDOC credentials currently only supports binding to secp256r1 keys. ## 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** ``` openid-credential-offer://issuer.portal.walt.id/?credential_offer= ``` **Example of a Credential Offer Object for mDL/mDOC** ```json { "credential_issuer": "https://issuer.demo.walt.id", "credential_configuration_ids": [ "org.iso.18013.5.1.mDL" ], "grants": { "authorization_code": { "issuer_state": "" }, "urn:ietf:params:oauth:grant-type:pre-authorized_code": { "pre-authorized_code": "", "user_pin_required": false } } } ``` ## Accepting mDL/mDOC Credential Offers mDL/mDOC credentials follow the ISO 18013-5 standard and provide structured data for identity documents like driving licenses. **Option: CURL** Endpoint: `/v1/{target}/wallet-service-api/credentials/receive` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Wallet%20Service/post_v1__target__wallet_service_api_credentials_receive) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/wallet-service-api/credentials/receive' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "offerUrl": "openid-credential-offer://issuer.example.org/issue/?credential_offer_uri=https%3A%2F%2Fissuer.example.org%2FcredentialOffer%3Fabc123xyz789", "keyReference": "example.kms.key1", }' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + wallet which should be used to receive the credential (`{organizationID}.{tenantID}.[walletID]`), e.g. `waltid.tenant1.wallet1` **Body Parameters** - `offerUrl` _String_ - An OID4VCI offer URL. - `keyReference` (optional) _String_ - The resource ID (target) of the key to which the received credential should be bound. Beware that the key must be a Secp256r1 key. - `key` (optional) _Object_ - A key object or reference key object for external KMS. Beware that the key must be a Secp256r1 key. - `metadata` (optional) _Object_ - Arbitrary JSON object that will be stored together with the credential. - `runPolicies` (optional) _Boolean_ - If set to `true`, the wallet service will run any holder policies. --- **Response Codes** - `201` - mDL/mDOC Credential received successfully. **Body** ```json { "offeredCredential": { "format": "mso_mdoc", "doctype": "org.iso.18013.5.1.mDL", "credential_definition": { "type": [ "org.iso.18013.5.1.mDL" ] }, "cryptographic_binding_methods_supported": [ "cose_key" ] }, "credentialResponse": { "format": "mdoc", "credential": "a267646f6354797065756f72672e69736f2e31383031332e352e312e6d444c6c6973737565725369676e6564a26a6e616d65537061636573a1716f72672e69736f2e31383031332e352e318bd8185852a4686469676573744944006672616e646f6d504c695c9b82008c5431330feae284019d71656c656d656.....", } } ``` -------------------------------------------------------------------------------- PAGE: Wallet Management URL: https://docs.walt.id/enterprise-stack/services/wallet-service/management RAW: https://docs.walt.id/enterprise-stack/services/wallet-service/management.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Wallet Management The **wallet management v2** endpoints provide read access to the resources stored in a wallet — keys, DIDs, and credentials. **Note:** Ensure you have [set up a wallet service](https://docs.walt.id/enterprise-stack/services/wallet-service/setup.md) with linked KMS, DID Store, and Credential Store services before proceeding. --- ## List Keys Retrieve all keys available in the wallet's linked KMS services. **Option: CURL** Endpoint: `GET /v2/{target}/wallet-service-api/keys` **Example Request** ```bash curl -X GET \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/keys' \ -H 'Authorization: Bearer {yourToken}' ``` **Response** ```json [ { "keyId": "waltid.tenant1.kms.key1", "keyType": "Ed25519" }, { "keyId": "waltid.tenant1.kms.wallet_key", "keyType": "secp256r1" } ] ``` - `keyId` _String_ — The key's identifier — the dotted KMS resource path. - `keyType` _String_ — walt.id key-type name, e.g. `Ed25519`, `secp256r1`, `secp256k1`, `RSA`. - `algorithm` (optional) _String_ — Signing algorithm, when the KMS reports one; omitted otherwise. --- ## List DIDs Retrieve all DIDs stored in the wallet's linked DID Store. **Option: CURL** Endpoint: `GET /v2/{target}/wallet-service-api/dids` **Example Request** ```bash curl -X GET \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/dids' \ -H 'Authorization: Bearer {yourToken}' ``` **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:z6Mkfriq1MqLBoPWecGoDLjguo1sB9brj6wT3qZ5BxkKpuP6#z6Mkfriq1MqLBoPWecGoDLjguo1sB9brj6wT3qZ5BxkKpuP6", "type": "JsonWebKey2020", "controller": "did:key:z6Mkfriq1MqLBoPWecGoDLjguo1sB9brj6wT3qZ5BxkKpuP6", "publicKeyJwk": { "kty": "OKP", "crv": "Ed25519", "x": "..." } } ], "authentication": ["did:key:z6Mkfriq1MqLBoPWecGoDLjguo1sB9brj6wT3qZ5BxkKpuP6#z6Mkfriq1MqLBoPWecGoDLjguo1sB9brj6wT3qZ5BxkKpuP6"], "assertionMethod": ["did:key:z6Mkfriq1MqLBoPWecGoDLjguo1sB9brj6wT3qZ5BxkKpuP6#z6Mkfriq1MqLBoPWecGoDLjguo1sB9brj6wT3qZ5BxkKpuP6"] } } ] ``` - `did` _String_ — The DID string. - `document` _Object_ — The full DID document as a JSON object. --- ## List Credentials Retrieve metadata for all credentials stored in the wallet's linked Credential Store. This endpoint returns lightweight metadata without the full credential payload. **Option: CURL** Endpoint: `GET /v2/{target}/wallet-service-api/credentials` **Example Request** ```bash curl -X GET \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials' \ -H 'Authorization: Bearer {yourToken}' ``` **Response** ```json [ { "id": "550e8400-e29b-41d4-a716-446655440000", "format": "dc+sd-jwt", "issuer": "did:web:issuer.example.org", "subject": "did:key:z6Mkfriq1MqLBoPWecGoDLjguo1sB9brj6wT3qZ5BxkKpuP6", "label": "Identity Credential", "addedAt": "2024-01-15T10:30:00Z" }, { "id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "format": "jwt_vc_json", "issuer": "did:web:university.example.org", "subject": "did:key:z6Mkfriq1MqLBoPWecGoDLjguo1sB9brj6wT3qZ5BxkKpuP6", "label": "University Diploma", "addedAt": "2024-02-20T14:45:00Z" } ] ``` - `id` _String_ — Wallet-assigned credential identifier (UUID). - `format` _String_ — Credential format (`dc+sd-jwt`, `jwt_vc_json`, `mso_mdoc`, etc.). - `issuer` (optional) _String_ — The credential issuer's identifier. - `subject` (optional) _String_ — The credential subject's identifier. - `label` (optional) _String_ — Human-readable label (from credential display or user-assigned). - `addedAt` _String_ — Timestamp when the credential was added to the wallet. --- ## Get Credential Retrieve a specific credential by its ID, including the full credential payload. **Option: CURL** Endpoint: `GET /v2/{target}/wallet-service-api/credentials/{credentialId}` **Example Request** ```bash curl -X GET \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials/{credentialId}' \ -H 'Authorization: Bearer {yourToken}' ``` **Path Parameters** - `orgID` _String_ — Organization host alias. - `target` _resourceIdentifier_ — The wallet's resource path, e.g. `waltid.tenant1.wallet`. - `credentialId` _String_ — The stored credential's ID, e.g. `550e8400-e29b-41d4-a716-446655440000` — from [List Credentials](#list-credentials) or the receive response's `credentialIds`. **Response** The wrapper is always `{ id, credential, label?, addedAt }`. The `credential` object's shape depends on the credential format — pick the matching tab: **Option: SD-JWT VC** `format: dc+sd-jwt` · `type: vc-sd_jwt` — adds `disclosures`, `disclosables`, `originalCredentialData`, `signedWithDisclosures`. ```json { "id": "a2f705c5-7ec9-4113-8091-00982fffccd4", "credential": { "type": "vc-sd_jwt", "format": "dc+sd-jwt", "issuer": "https://issuer.example.org/.../openid4vci", "credentialData": { "given_name": "John", "family_name": "Doe", "birthdate": "1990-01-15", "vct": "https://issuer.example.org/.well-known/vct/.../identity_credential", "cnf": { "jwk": { "kty": "OKP", "crv": "Ed25519", "x": "Jfch6RO..." } } }, "disclosures": [ { "name": "birthdate", "value": "1990-01-15", "salt": "XWinP-V3uXnRMfgAZDtbjw", "encoded": "WyJYV2luUC1WM3VYblJNZmdBWkR0Ymp3IiwiYmlydGhkYXRlIiwiMTk5MC0wMS0xNSJd" } ], "signed": "eyJraWQiOiJHMzdKaUdyM3B0bEQ...", "signature": { "type": "signature-sd_jwt", "jwtHeader": { "kid": "G37JiGr3ptlD-YVW2APHZ7i4N43-vcIQZNFEuXzjwZw", "typ": "dc+sd-jwt", "alg": "EdDSA" } } }, "addedAt": "2026-07-13T09:40:30Z" } ``` **Option: W3C VC** `format: jwt_vc_json` · `type: vc-w3c_2` — adds `issuer`, `subject`, `disclosables`. ```json { "id": "f7d50cd6-87b3-476f-a47c-b8c18133fede", "credential": { "type": "vc-w3c_2", "format": "jwt_vc_json", "issuer": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp", "subject": "did:jwk:eyJrdHkiOiJPS1Ai...", "disclosables": {}, "credentialData": { "@context": ["https://www.w3.org/ns/credentials/v2"], "type": ["VerifiableCredential", "UniversityDegree"], "issuer": { "id": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp" }, "credentialSubject": { "id": "did:jwk:eyJrdHkiOiJPS1Ai...", "degree": { "type": "BachelorDegree", "name": "Bachelor of Science and Arts" } }, "validFrom": "2026-07-09T14:34:49Z", "validUntil": "2027-07-09T14:34:49Z" }, "signed": "eyJraWQiOiJkaWQ6a2V5Ono2TWtqb1Jo...", "signature": { "type": "signature-jwt", "jwtHeader": { "kid": "did:key:z6MkjoRhq1j...#z6MkjoRhq1j...", "typ": "vc+jwt", "alg": "EdDSA" } } }, "addedAt": "2026-07-13T09:45:10Z" } ``` **Option: mDoc** `format: mso_mdoc` · `type: vc-mdocs` — adds `docType` and `mso` instead of `issuer`/`subject`. ```json { "id": "3b686a30-0504-4284-bcd1-fab711de4143", "credential": { "type": "vc-mdocs", "format": "mso_mdoc", "docType": "org.iso.18013.5.1.mDL", "credentialData": { "docType": "org.iso.18013.5.1.mDL", "org.iso.18013.5.1": { "family_name": "Doe", "given_name": "John", "birth_date": "1990-01-01", "document_number": "DL123456789", "issuing_country": "US" } }, "signed": "ompuYW1lU3BhY2Vzo...", "signature": { "type": "signature-cose", "signerKey": { "type": "jwk", "jwk": { "kty": "EC", "crv": "P-256", "x": "dUpq6W...", "y": "urKlUB..." } }, "x5cList": ["MIICITCCAcag...", "MIIBxTCCAWug..."] }, "mso": { "version": "1.0", "digestAlgorithm": "SHA-256", "valueDigests": { "org.iso.18013.5.1": { "0": [98, 125, 80, -38, 31, 65, 93, -76, "..."], "1": [26, -99, -30, -107, -118, 2, 47, 44, "..."] } }, "deviceKeyInfo": { "deviceKey": { "kty": 1, "crv": 6, "x": [37, -9, 33, -23, "..."] } }, "docType": "org.iso.18013.5.1.mDL", "validityInfo": { "signed": "2026-07-09T14:35:52Z", "validFrom": "2026-07-09T14:35:52Z", "validUntil": "2027-07-09T14:35:52Z" } } }, "addedAt": "2026-07-13T09:45:10Z" } ``` - `id` _String_ — Wallet-assigned credential identifier (UUID). - `credential` _Object_ — The fully-parsed credential (shape varies by format — see tabs above). - `credential.type` _String_ — Parsed-kind discriminator: `vc-sd_jwt`, `vc-w3c_2`, or `vc-mdocs`. - `credential.format` _String_ — Wire format: `dc+sd-jwt`, `jwt_vc_json`, `mso_mdoc`, … - `credential.credentialData` _Object_ — The decoded credential claims/payload. - `credential.signed` _String_ — The original encoded credential string (compact SD-JWT / JWT; base64url-CBOR for mDoc). There is **no `rawCredential` field**. - `credential.signature` _Object_ — Parsed signature metadata (`type`, plus JWT header or COSE signer key). - `label` (optional) _String_ — Human-readable label; omitted when not set. - `addedAt` _String_ — Timestamp when the credential was added to the wallet. **Response Codes** - `200` — Credential found and returned. - `404` — Credential not found. --- ## Endpoint Summary | Endpoint | Method | Description | |---|---|---| | `/v2/{target}/wallet-service-api/keys` | GET | List all keys in the wallet | | `/v2/{target}/wallet-service-api/dids` | GET | List all DIDs in the wallet | | `/v2/{target}/wallet-service-api/credentials` | GET | List credential metadata | | `/v2/{target}/wallet-service-api/credentials/{credentialId}` | GET | Get a specific credential | --- ## Related Documentation - [Wallet Setup](https://docs.walt.id/enterprise-stack/services/wallet-service/setup.md) — Configure a wallet service with linked stores - [Accept Credentials via OID4VCI](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/overview.md) — Receive credentials into the wallet - [Share Credentials via OID4VP](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/overview.md) — Present credentials from the wallet - [KMS Service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) — Key management configuration - [DID Store Service](https://docs.walt.id/enterprise-stack/services/did-store-service/overview.md) — DID storage configuration - [Credential Store Service](https://docs.walt.id/enterprise-stack/services/credential-store-service/overview.md) — Credential storage configuration -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/overview RAW: https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/overview.md STACK: Enterprise Stack (commercial) — 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/enterprise-stack/services/verifier2-service/overview.md) — and the wallet finds matching credentials, builds a presentation, and submits it. All endpoints live under `/v2/{target}/wallet-service-api/`, where `{target}` is the wallet's resource path (`{organization}.{tenant}.{wallet-id}`). ## 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 /v2/{target}/wallet-service-api/credentials/present`) auto-matches credentials from the wallet's Credential Stores, signs, and submits. This is what the per-format guides below use. - **[Custom Flow](https://docs.walt.id/enterprise-stack/services/wallet-service/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. ## 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: - **SD-JWT VC** — the verifier's requested paths select which **selectively-disclosable** claims are revealed. Claims the issuer marked selectively-disclosable that the verifier does not request stay hidden; a claim that isn't selectively-disclosable is always present. - **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. ## Supported Formats Choose the credential type that matches your use case — the flow is identical; the verifier's DCQL query determines what is matched and disclosed: - [SD-JWT VC (IETF) via OID4VP](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/sd-jwt-vc.md) - [W3C VC via OID4VP](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/w3c-vc.md) - [mDL / mDoc (ISO 18013-5) via OID4VP](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/mdl.md) Need fine-grained control? See [Custom Flow](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/custom-flow.md). **Note:** Using OID4VP Draft 20 (the `/v1` API with Presentation Exchange)? See the [legacy presenting guides](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/legacy-draft20/vc-oid4vp.md). -------------------------------------------------------------------------------- PAGE: SD-JWT VC via OID4VP URL: https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/sd-jwt-vc RAW: https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/sd-jwt-vc.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Presenting SD-JWT VC Credentials via OID4VP 1.0 This guide shows how to present an [IETF SD-JWT VC](https://docs.walt.id/concepts/digital-credentials/sd-jwt-vc.md) stored in an Enterprise wallet to a verifier using the **Wallet API v2**. You hand the wallet a presentation request — for example one created with [Verifier2](https://docs.walt.id/enterprise-stack/services/verifier2-service/overview.md) — and it matches, selectively discloses, signs, and submits in a single call. **Note:** For SD-JWT VC, the wallet reveals **only the claims the verifier requests** in its DCQL `claims` array — see [Selective Disclosure](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/overview.md#selective-disclosure). This page follows the **single-call full flow**; for previews and consent screens, see [Custom Flow](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/custom-flow.md). ## Prerequisites - A **wallet service** with a linked KMS, DID Store, and Credential Store — see [Setup](https://docs.walt.id/enterprise-stack/services/wallet-service/setup.md). - The wallet holds an **SD-JWT VC** — see [Receiving SD-JWT VC Credentials](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/sd-jwt-vc.md). - A **presentation request** from a verifier (an `openid4vp://` URL). --- ## Present the Credential This single call resolves the request, DCQL-matches credentials from the wallet's stores, selectively discloses the requested claims, signs the presentation, and submits it to the verifier. **Option: CURL** Endpoint: `POST /v2/{target}/wallet-service-api/credentials/present` **Example Request** ```bash curl -X POST \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials/present' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "requestUrl": "openid4vp://authorize?client_id=did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5In0&request_uri=https://verifier.example.org/request/abc123" }' ``` **Path Parameters** - `orgID` _String_ — Organization host alias. - `target` _resourceIdentifier_ — The wallet's resource path, e.g. `waltid.tenant1.wallet`. **Body Parameters — `PresentCredentialRequest`** - **Presentation request** (required) — provide exactly one of: - `requestUrl` _String_ — The OID4VP authorization request URL from the verifier. - `requestObject` _Object_ — The authorization request as an already-fetched JSON object, instead of a URL. - `keyId` (optional) _String_ — ID of the key to sign the presentation with. Defaults to the first key in the wallet's linked KMS (or the service's configured static key if the KMS has none). - `did` (optional) _String_ — DID to present as. Defaults to the first DID in the wallet's linked DID Store (or the configured static DID). - `runPolicies` (optional) _Boolean_ — When `true`, applies configured holder policies before presenting and blocks if a policy denies it. Omitted or `false` skips policy enforcement. **Example Response — `WalletPresentResult`** ```json { "transmission_success": true, "verifier_response": { "status": "received", "message": "Presentation received and is being processed." } } ``` - `transmission_success` _Boolean_ — Whether the wallet successfully transmitted the presentation to the verifier's response endpoint. - `verifier_response` _Object_ — The raw response returned by the verifier. For a `direct_post` verifier (e.g. Verifier2) this is `{ "status": "received", "message": "…" }`; some verifiers instead return a `redirect_uri`. - `redirect_to` _String_ (optional) — Present only when the verifier returns a redirect URL to send the user to; absent for `direct_post`. - `get_url` / `form_post_html` _String_ (optional) — Populated instead of `redirect_to` for the redirect (`get_url`) or `form_post` (`form_post_html`) response modes. **Response Codes** - `200` — Presentation built and submitted. - `400` — The presentation request could not be resolved (e.g. an unfetchable `request_uri`). **Note:** When the request resolves but **no stored credential satisfies the DCQL query**, the call still returns **`200`** with `"transmission_success": false` and the verifier's DCQL-fulfillment error inside `verifier_response` — it does **not** return `400`. - `401` — Invalid authentication. 🎉 You've presented an SD-JWT VC to the verifier, disclosing only the requested claims. --- ## Doing It Step by Step The full-flow call above matches, selectively discloses, signs, and submits in one request. To preview which credentials and claims will be shared before presenting, drive the isolated endpoints yourself: `resolve-request` → `match-credentials-from-store` → `present` Each step and the isolated (inline) variants are documented in [Custom Flow](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/custom-flow.md). --- ## Next Steps - **Present other credential types** — [W3C VC](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/w3c-vc.md) or [mDL](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/mdl.md). - **Receive more credentials** — [Credential Receiving](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/overview.md). - **Manage stored credentials** — [list and retrieve](https://docs.walt.id/enterprise-stack/services/wallet-service/management.md) credentials in the wallet. -------------------------------------------------------------------------------- PAGE: W3C VC via OID4VP URL: https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/w3c-vc RAW: https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/w3c-vc.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Presenting W3C VC Credentials via OID4VP 1.0 This guide shows how to present a [W3C Verifiable Credential](https://docs.walt.id/concepts/digital-credentials/verifiable-credentials-w3c.md) stored in an Enterprise wallet to a verifier using the **Wallet API v2**. You hand the wallet a presentation request — for example one created with [Verifier2](https://docs.walt.id/enterprise-stack/services/verifier2-service/overview.md) — and it matches, signs, and submits in a single call. **Note:** This page follows the **single-call full flow**. To preview matches, show a consent screen, or present credentials that aren't stored in the wallet, see [Custom Flow](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/custom-flow.md). ## Prerequisites - A **wallet service** with a linked KMS, DID Store, and Credential Store — see [Setup](https://docs.walt.id/enterprise-stack/services/wallet-service/setup.md). - The wallet holds a **W3C VC** — see [Receiving W3C VC Credentials](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/w3c-vc.md). - A **presentation request** from a verifier (an `openid4vp://` URL). --- ## Present the Credential This single call resolves the request, DCQL-matches credentials from the wallet's stores, signs the presentation with the wallet's key, and submits it to the verifier. **Option: CURL** Endpoint: `POST /v2/{target}/wallet-service-api/credentials/present` **Example Request** ```bash curl -X POST \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials/present' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "requestUrl": "openid4vp://authorize?client_id=did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5In0&request_uri=https://verifier.example.org/request/abc123" }' ``` **Path Parameters** - `orgID` _String_ — Organization host alias. - `target` _resourceIdentifier_ — The wallet's resource path, e.g. `waltid.tenant1.wallet`. **Body Parameters — `PresentCredentialRequest`** - **Presentation request** (required) — provide exactly one of: - `requestUrl` _String_ — The OID4VP authorization request URL from the verifier. - `requestObject` _Object_ — The authorization request as an already-fetched JSON object, instead of a URL. - `keyId` (optional) _String_ — ID of the key to sign the presentation with. Defaults to the first key in the wallet's linked KMS (or the service's configured static key if the KMS has none). - `did` (optional) _String_ — DID to present as. Defaults to the first DID in the wallet's linked DID Store (or the configured static DID). - `runPolicies` (optional) _Boolean_ — When `true`, applies configured holder policies before presenting and blocks if a policy denies it. Omitted or `false` skips policy enforcement. **Example Response — `WalletPresentResult`** ```json { "transmission_success": true, "verifier_response": { "status": "received", "message": "Presentation received and is being processed." } } ``` - `transmission_success` _Boolean_ — Whether the wallet successfully transmitted the presentation to the verifier's response endpoint. - `verifier_response` _Object_ — The raw response returned by the verifier. For a `direct_post` verifier (e.g. Verifier2) this is `{ "status": "received", "message": "…" }`; some verifiers instead return a `redirect_uri`. - `redirect_to` _String_ (optional) — Present only when the verifier returns a redirect URL to send the user to; absent for `direct_post`. - `get_url` / `form_post_html` _String_ (optional) — Populated instead of `redirect_to` for the redirect (`get_url`) or `form_post` (`form_post_html`) response modes. **Response Codes** - `200` — Presentation built and submitted. - `400` — The presentation request could not be resolved (e.g. an unfetchable `request_uri`). **Note:** When the request resolves but **no stored credential satisfies the DCQL query**, the call still returns **`200`** with `"transmission_success": false` and the verifier's DCQL-fulfillment error inside `verifier_response` — it does **not** return `400`. - `401` — Invalid authentication. 🎉 You've presented a W3C VC to the verifier. --- ## Doing It Step by Step The full-flow call above matches, signs, and submits in one request. To preview which credentials match before presenting, show a consent screen, or present credentials supplied inline, drive the isolated endpoints yourself: `resolve-request` → `match-credentials-from-store` → `present` Each step and the isolated (inline) variants are documented in [Custom Flow](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/custom-flow.md). --- ## Next Steps - **Present other credential types** — [SD-JWT VC](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/sd-jwt-vc.md) or [mDL](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/mdl.md). - **Receive more credentials** — [Credential Receiving](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/overview.md). - **Manage stored credentials** — [list and retrieve](https://docs.walt.id/enterprise-stack/services/wallet-service/management.md) credentials in the wallet. -------------------------------------------------------------------------------- PAGE: mDL / mDoc via OID4VP URL: https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/mdl RAW: https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/mdl.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Presenting mDL / mDoc Credentials via OID4VP 1.0 This guide shows how to present an [ISO 18013-5 mDL / mDoc](https://docs.walt.id/concepts/digital-credentials/mdoc-mdl-iso.md) stored in an Enterprise wallet to a verifier using the **Wallet API v2**. You hand the wallet a presentation request — for example one created with [Verifier2](https://docs.walt.id/enterprise-stack/services/verifier2-service/overview.md) — and it matches, discloses the requested data elements, signs, and submits in a single call. **Note:** For mDL, the wallet discloses **exactly the data elements the verifier requests** in its DCQL `claims` array (by `[namespace, element]` path) — see [Selective Disclosure](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/overview.md#selective-disclosure). This page follows the **single-call full flow**; for previews and consent screens, see [Custom Flow](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/custom-flow.md). ## Prerequisites - A **wallet service** with a linked KMS and Credential Store — see [Setup](https://docs.walt.id/enterprise-stack/services/wallet-service/setup.md). - The wallet holds an **mDL / mDoc** — see [Receiving mDL / mDoc Credentials](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/mdl.md). - A **presentation request** from a verifier (an `openid4vp://` URL). --- ## Present the Credential This single call resolves the request, DCQL-matches the mDoc from the wallet's stores, discloses the requested data elements, signs the presentation, and submits it to the verifier. **Option: CURL** Endpoint: `POST /v2/{target}/wallet-service-api/credentials/present` **Example Request** ```bash curl -X POST \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials/present' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "requestUrl": "openid4vp://authorize?client_id=x509_san_dns%3Averifier.example.org&request_uri=https://verifier.example.org/request/abc123" }' ``` **Path Parameters** - `orgID` _String_ — Organization host alias. - `target` _resourceIdentifier_ — The wallet's resource path, e.g. `waltid.tenant1.wallet`. **Body Parameters — `PresentCredentialRequest`** - **Presentation request** (required) — provide exactly one of: - `requestUrl` _String_ — The OID4VP authorization request URL from the verifier. - `requestObject` _Object_ — The authorization request as an already-fetched JSON object, instead of a URL. - `keyId` (optional) _String_ — ID of the key to sign the presentation with. Defaults to the first key in the wallet's linked KMS (or the service's configured static key if the KMS has none). - `did` (optional) _String_ — DID to present as. Defaults to the first DID in the wallet's linked DID Store (or the configured static DID). mDocs typically bind to the raw key rather than a DID. - `runPolicies` (optional) _Boolean_ — When `true`, applies configured holder policies before presenting and blocks if a policy denies it. Omitted or `false` skips policy enforcement. **Example Response — `WalletPresentResult`** ```json { "transmission_success": true, "verifier_response": { "status": "received", "message": "Presentation received and is being processed." } } ``` - `transmission_success` _Boolean_ — Whether the wallet successfully transmitted the presentation to the verifier's response endpoint. - `verifier_response` _Object_ — The raw response returned by the verifier. For a `direct_post` verifier (e.g. Verifier2) this is `{ "status": "received", "message": "…" }`; some verifiers instead return a `redirect_uri`. - `redirect_to` _String_ (optional) — Present only when the verifier returns a redirect URL to send the user to; absent for `direct_post`. - `get_url` / `form_post_html` _String_ (optional) — Populated instead of `redirect_to` for the redirect (`get_url`) or `form_post` (`form_post_html`) response modes. **Response Codes** - `200` — Presentation built and submitted. - `400` — The presentation request could not be resolved (e.g. an unfetchable `request_uri`). **Note:** When the request resolves but **no stored mDoc satisfies the DCQL query**, the call still returns **`200`** with `"transmission_success": false` and the verifier's DCQL-fulfillment error inside `verifier_response` — it does **not** return `400`. - `401` — Invalid authentication. 🎉 You've presented an mDL / mDoc to the verifier, disclosing only the requested data elements. --- ## Doing It Step by Step The full-flow call above matches, discloses, signs, and submits in one request. To preview which data elements will be shared before presenting, drive the isolated endpoints yourself: `resolve-request` → `match-credentials-from-store` → `present` Each step and the isolated (inline) variants are documented in [Custom Flow](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/custom-flow.md). --- ## Next Steps - **Present other credential types** — [SD-JWT VC](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/sd-jwt-vc.md) or [W3C VC](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/w3c-vc.md). - **Receive more credentials** — [Credential Receiving](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/overview.md). - **Manage stored credentials** — [list and retrieve](https://docs.walt.id/enterprise-stack/services/wallet-service/management.md) credentials in the wallet. -------------------------------------------------------------------------------- PAGE: Custom Flow URL: https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/custom-flow RAW: https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/custom-flow.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Custom Flow The [full-flow](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/overview.md#two-ways-to-present) endpoint (`credentials/present`) matches, signs, and submits in one call. The **custom flow** lets you drive each step yourself when you need to: - Preview which credentials (and claims) will be shared, and show a consent screen - Let the user choose between multiple matching credentials - Present credentials that aren't stored in the wallet (supplied inline) - Inspect intermediate results All endpoints live under `/v2/{target}/wallet-service-api/`. ### Flow Overview ```text 1. Resolve VP Request → parse request URL → nonce, clientId, responseUri 2. Match Credentials → DCQL match (from store or inline) → matched credential IDs ── show user consent screen ── 3. Present Credentials → sign and submit → WalletPresentResult ``` --- ## Step 1 — Resolve VP Request Parse the verifier's authorization request to extract its metadata. **Option: CURL** Endpoint: `POST /v2/{target}/wallet-service-api/credentials/present/resolve-request` **Example Request** ```bash curl -X POST \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials/present/resolve-request' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "requestUrl": "openid4vp://authorize?client_id=did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5In0&request_uri=https://verifier.example.org/request/abc123" }' ``` **Body Parameters** - **Presentation request** (required) — provide exactly one of: - `requestUrl` _String_ — The OID4VP authorization request URL. - `requestObject` _Object_ — The authorization request as JSON, instead of a URL. **Example Response** ```json { "nonce": "n-0S6_WzA2Mj", "clientId": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5In0", "responseUri": "https://verifier.example.org/response", "hasRequestUri": true } ``` - `nonce` — The verifier's nonce for replay protection. - `clientId` — The verifier's client identifier (typically a DID). - `responseUri` — The URL the presentation is submitted to. - `hasRequestUri` — Whether the request was fetched from a `request_uri`. **Response Codes** - `200` — Request resolved. - `400` — The request URL/object is malformed and cannot be parsed. - `500` — The `request_uri` could not be fetched (e.g. the verifier's request endpoint returned 404). **Note:** `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. --- ## Step 2a — Match Credentials from Store DCQL-match against the wallet's own stored credentials. This is the recommended matching step when credentials live in the wallet's Credential Store. **Option: CURL** Endpoint: `POST /v2/{target}/wallet-service-api/credentials/present/match-credentials-from-store` **Example Request** ```bash curl -X POST \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials/present/match-credentials-from-store' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "dcqlQuery": { "credentials": [ { "id": "identity_credential", "format": "dc+sd-jwt", "meta": { "vct_values": ["https://issuer.example.org/identity_credential"] }, "claims": [ { "path": ["given_name"] }, { "path": ["family_name"] } ] } ] } }' ``` **Body Parameters** - `dcqlQuery` _Object_ — The DCQL query from the verifier's authorization request. See [DCQL Query Structure](#dcql-query-structure). **Example Response** ```json { "matchedQueryIds": ["identity_credential"], "matchCount": 1, "matchedCredentialIds": { "identity_credential": ["0"] } } ``` - `matchedQueryIds` — DCQL query IDs for which at least one credential matched. - `matchCount` — Total number of matches across all query IDs. - `matchedCredentialIds` — For each matched query ID, the identifiers of the matching credentials. **warning:** For the **from-store** variant these identifiers are **positional indices** (`"0"`, `"1"`, …) into the wallet's matched-credential set for this call — **not** the stored credential UUIDs. They currently cannot be used directly to fetch or select a specific stored credential. (The inline [match-credentials](#step-2b--match-inline-credentials) variant instead echoes back the `id` you supplied.) If you need to present a specific stored credential, drive the [full flow](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/overview.md#two-ways-to-present) with a DCQL query narrow enough to match only that credential. **Response Codes** - `200` — Matching completed. - `400` — Invalid DCQL query. --- ## Step 2b — Match Inline Credentials DCQL-match against credentials you supply inline — useful to preview matches for credentials that aren't stored in the wallet. **Option: CURL** Endpoint: `POST /v2/{target}/wallet-service-api/credentials/present/match-credentials` **Example Request** ```bash curl -X POST \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials/present/match-credentials' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "dcqlQuery": { "credentials": [ { "id": "identity_credential", "format": "dc+sd-jwt", "meta": { "vct_values": ["https://issuer.example.org/identity_credential"] }, "claims": [ { "path": ["given_name"] } ] } ] }, "credentials": [ { "id": "cred-1", "credential": { "type": "vc-sd_jwt", "format": "dc+sd-jwt", "credentialData": { "given_name": "John", "family_name": "Doe", "vct": "https://issuer.example.org/identity_credential", "cnf": { "jwk": { "kty": "OKP", "crv": "Ed25519", "x": "…" } } }, "disclosures": [ { "salt": "…", "name": "birthdate", "value": "1990-01-15", "location": ["birthdate"], "encoded": "…" } ], "signed": "eyJraWQiOiJHMzdKaUdyM3B0bEQ…", "signature": { "type": "signature-sd_jwt", "jwtHeader": { "typ": "dc+sd-jwt", "alg": "EdDSA" } } } } ] }' ``` **Body Parameters** - `dcqlQuery` _Object_ — The DCQL query from the verifier's authorization request. - `credentials` _Array_ — Credentials to match against, each with an `id` (a caller-assigned label) and a `credential` object. The `credential`'s shape depends on the credential format (its `type` field is `vc-sd_jwt`, `vc-w3c_2`, or `vc_mdocs`) — it is **not** a `{ format, rawCredential }` pair. The easiest way to obtain it is to copy the `credential` field from the wallet's `GET /v2/{target}/wallet-service-api/credentials/{credentialId}` response (abbreviated with `…` above). **Example Response** Same structure as [Step 2a](#step-2a--match-credentials-from-store). **Response Codes** - `200` — Matching completed. - `400` — Invalid DCQL query or credentials. --- ## Step 3 — Present After the user consents, complete the presentation. Use the [full-flow endpoint](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/overview.md#two-ways-to-present) (`credentials/present`) to present from the wallet's stores, or the isolated endpoint below to present credentials supplied inline. ### Present Inline Credentials Present credentials that are supplied inline rather than matched from the wallet's stores. **Option: CURL** Endpoint: `POST /v2/{target}/wallet-service-api/credentials/present/isolated` **Example Request** ```bash curl -X POST \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{org}.{tenant}.{wallet-id}/wallet-service-api/credentials/present/isolated' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "requestUrl": "openid4vp://authorize?client_id=did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5In0&request_uri=https://verifier.example.org/request/abc123", "credentials": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "credential": { "type": "vc-sd_jwt", "format": "dc+sd-jwt", "credentialData": { "given_name": "John", "family_name": "Doe", "vct": "https://issuer.example.org/identity_credential", "cnf": { "jwk": { "kty": "OKP", "crv": "Ed25519", "x": "…" } } }, "disclosures": [ { "salt": "…", "name": "birthdate", "value": "1990-01-15", "location": ["birthdate"], "encoded": "…" } ], "signed": "eyJraWQiOiJHMzdKaUdyM3B0bEQ…", "signature": { "type": "signature-sd_jwt", "jwtHeader": { "typ": "dc+sd-jwt", "alg": "EdDSA" } } } } ] }' ``` **Body Parameters** - **Presentation request** (required) — provide exactly one of: - `requestUrl` _String_ — The OID4VP authorization request URL. - `requestObject` _Object_ — The authorization request as JSON, instead of a URL. - `credentials` _Array_ — The credentials to present, each with an `id` (a caller-assigned label) and a `credential` object. The `credential`'s shape depends on the credential format (its `type` field is `vc-sd_jwt`, `vc-w3c_2`, or `vc_mdocs`) — it is **not** a `{ format, rawCredential }` pair. Copy it from the `credential` field of the wallet's `GET /v2/{target}/wallet-service-api/credentials/{credentialId}` response (abbreviated with `…` above). - `keyId` (optional) _String_ — ID of the key to sign with. Defaults to the first key in the wallet's linked KMS (or the service's configured static key if the KMS has none). - `did` (optional) _String_ — DID to present as. Defaults to the first DID in the wallet's linked DID Store (or the configured static DID). **Example Response** ```json { "transmission_success": true, "verifier_response": { "status": "received", "message": "Presentation received and is being processed." } } ``` `WalletPresentResult` fields are the same as the [full-flow response](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/w3c-vc.md#present-the-credential) — `transmission_success`, `verifier_response`, and (only when the verifier redirects) `redirect_to` / `get_url` / `form_post_html`. **Response Codes** - `200` — Presentation built and submitted. If no supplied credential satisfied the DCQL query, this still returns `200` with `"transmission_success": false` and the error in `verifier_response`. - `400` — The presentation request could not be resolved. --- ## Next Steps - **Present with the full flow** — the per-format guides ([SD-JWT VC](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/sd-jwt-vc.md), [W3C VC](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/w3c-vc.md), [mDL](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/mdl.md)) use the single-call `credentials/present` endpoint. - **Receive a credential** — [Credential Receiving](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/overview.md). -------------------------------------------------------------------------------- PAGE: Present W3C Verifiable Credentials via OID4VP URL: https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/legacy-draft20/vc-oid4vp RAW: https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/legacy-draft20/vc-oid4vp.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Present W3C Verifiable Credentials via OID4VP **Note:** To follow this guide, you need a wallet (wallet-service) configured with an additional service like the credential store for storing received credentials. For more information on different setups, please go [here](../1.setup.md).
**Note:** This guide works for both the Verifier Service and the Verifier2 Service.
You will notice examples for Presentation Definitions, which are used in the draft OpenID4VP specifications implemented by the Verifier Service.
The Verifier2 Service uses the new OpenID4VP 1.0 specification, which does not support Presentation Definitions. Instead, it supports DCQL (Digital Credential Query Language).
**Note:** This guide refers specifically to W3C Verifiable Credential standard without selective disclosure. It is possible to wrap a W3C Verifiable Credential within an SD-JWT in order to support selective disclosure. To understand more, check out the [Issue W3C Credentials (JWT / SD-JWT) via OID4VC](https://docs.walt.id/enterprise-stack/services/issuer-service/credential-issuance/vc-oid4vc.md) guide.
There are several methods by which a user (wallet) can present a credential to a verifier, and all are supported by the wallet API: 1. User may receive a direct link, which opens the wallet and prompts credential sharing. 2. A website may display a QR code for the user to scan and share the credential. 3. The user may manually input a credential presentation request URL string into a text field. Irrespective of the chosen method, these are just different ways of receiving, parsing and fulfilling a credential presenting request. ## Credential Authorization Request URL The authorization URL is a standardised method, as per the OID4VP specification, to communicate the needed credentials and claims from verifier to the wallet. **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= ``` **Option: Presentation Definition** #### The Presentation Definition for W3C Verifiable Credentials The presentation definition specifies the criteria for the wallet to know what credentials and claims should be requested from the user and shared with the verifier. **Example for W3C Verifiable Credentials** ```json { "input_descriptors": [ { "id": "VerifiableId", "format": { "vc+sd-jwt": { "alg": [ "EdDSA" ] } }, "constraints": { "fields": [ { "path": [ "$.type" ], "filter": { "type": "string", "pattern": "VerifiableId" } } ] } } ] } ``` The definition specifies that one credential is requested in the W3C Verifiable Credential format, specifically with the algorithm "EdDSA" and it must be of type "VerifiableId". **Option: DCQL** #### DCQL (Data Credential Query Language) for W3C Verifiable Credential DCQL is a newer approach in OpenID4VP that provides a more flexible and expressive way to specify credential requirements. **Example DCQL Query for W3C Verifiable Credential** ```json { "dcql_query": { "credentials": [ { "id": "pid", "format": "dc+sd-jwt", "meta": { "vct_values": [ "http://org.enterprise.localhost:3000/v1/org.tenant.issuer2/issuer-service-api/openid4vc/draft13/identity_credential" ] }, "claims": [ { "path": [ "given_name" ] }, { "path": [ "family_name" ] }, { "path": [ "address", "street_address" ] } ] } ] } ``` ## Steps To Fulfill Presentation Request To fulfill the presentation request received by the verifier, we will need to perform the following steps: 1. Find credentials matching the presentation definition in the linked credential store of the wallet. 2. Present matched credentials ## 1. Find Credentials To Present To locate the credentials that align with the dcql query provided by the verifier, we will utilize the credential store API. **Option: CURL** Endpoint: `/v1/{target}/credential-store-service-api/credentials/query/document/schema` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Credential%20Store%20Service/post_v1__target__credential_store_service_api_credentials_query_document_schema) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/credential-store-service-api/credentials/query/document/schema' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '[ { "id": "pid", "format": "dc+sd-jwt", "meta": { "vct_values": [ "http://org.enterprise.localhost:3000/v1/org.tenant.issuer2/issuer-service-api/openid4vc/draft13/identity_credential" ] }, "claims": [ { "path": [ "given_name" ] }, { "path": [ "family_name" ] }, { "path": [ "address", "street_address" ] } ] } ]' ``` --- **Response Codes** - `200` - Matching credentials ## 2. Present Matched Credentials **Option: CURL** Endpoint: `/v1/{target}/wallet-service-api/credentials/present` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Wallet%20Service/post_v1__target__wallet_service_api_present) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/wallet-service-api/credentials/present' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "requestUrl": "openid4vp://authorize?client_id=did%3Ajwk%3AeyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiU05JZ1ItTHZzSEUtNkhUUGxYYUpQVDVubHRZMFpqZlA0VkpTWEwzQ2ZkMCIsIngiOiJIVWM1ZHY1eFREV3VMVmZQcFdvVXBWTjZ1QTNJNzBsVzVnNEhoMG5VaDQ0In0&request_uri=http%3A%2F%2Forg.enterprise.localhost%3A3000%2Fv1%2Forg.tenant.verifier%2Fverifier2-service-api%2Fbb3f89d1-8bc9-4d04-9418-12bc70bd76e9%2Frequest", "keyReference": "example.kms.key1", "didReference": "example.didstore.did1", "credentials": [ { "credential": "waltid.tenant22.credential-store-wallet1.3d629d4b-4111-461d-9233-b2224fd64486", "disclosures": [ "$.dateOfBirth", ] } ] }' ``` **Body Parameters** - `requestUrl` _String_ - An OID4VCP Authorization URL. - `credentials` (optional) _Array_ - You can optionally provide a list of credential IDs from the linked credential store that you want to present. If you do not provide a list, the wallet will attempt to find matching credentials based on the dcql query specified in the request URL. - `keyReference` (optional) _String_ - The resource ID (target) of the key which owns the credential. - `didReference` (optional) _String_ - The DID reference of a DID stored in a linked DID Store. --- **Response Codes** - `200` - Presentation was accepted --- -------------------------------------------------------------------------------- PAGE: Present SD-JWT Verifiable Credentials via OID4VP URL: https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/legacy-draft20/sd-jwt-vc-oid4vp RAW: https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/legacy-draft20/sd-jwt-vc-oid4vp.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Present SD-JWT Verifiable Credentials via OID4VP **Note:** To follow this guide, you need a wallet (wallet-service) configured with an additional service like the credential store for storing received credentials. For more information on different setups, please go [here](../1.setup.md).
**Note:** This guide works for both the Verifier Service and the Verifier2 Service.
You will notice examples for Presentation Definitions, which are used in the draft OpenID4VP specifications implemented by the Verifier Service.
The Verifier2 Service uses the new OpenID4VP 1.0 specification, which does not support Presentation Definitions. Instead, it supports DCQL (Digital Credential Query Language).
There are several methods by which a user (wallet) can present a credential to a verifier, and all are supported by the wallet API: 1. User may receive a direct link, which opens the wallet and prompts credential sharing. 2. A website may display a QR code for the user to scan and share the credential. 3. The user may manually input a credential presentation request URL string into a text field. ## Credential Authorization Request URL The authorization URL is a standardised method, as per the OID4VP specification, to communicate the needed credentials and claims from verifier to the wallet. **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= ``` **Option: Presentation Definition** #### The Presentation Definition for SD-JWT VCs The presentation definition specifies the criteria for the wallet to know what credentials and claims should be requested from the user and shared with the verifier. **Example for SD-JWT VCs** ```json { "input_descriptors": [ { "id": "VerifiableId", "format": { "vc+sd-jwt": { "alg": [ "EdDSA" ] } }, "constraints": { "fields": [ { "path": [ "$.type" ], "filter": { "type": "string", "pattern": "VerifiableId" } } ] } } ] } ``` The definition specifies that one credential is requested in the SD-JWT format, specifically with the algorithm "EdDSA" and it must be of type "VerifiableId". **Option: DCQL** #### DCQL (Data Credential Query Language) for SD-JWT VCs DCQL is a newer approach in OpenID4VP that provides a more flexible and expressive way to specify credential requirements. **Example DCQL Query for SD-JWT VCs** ```json { "dcql_query": { "credentials": [ { "id": "pid", "format": "dc+sd-jwt", "meta": { "vct_values": [ "http://org.enterprise.localhost:3000/v1/org.tenant.issuer2/issuer-service-api/openid4vc/draft13/identity_credential" ] }, "claims": [ { "path": [ "given_name" ] }, { "path": [ "family_name" ] }, { "path": [ "address", "street_address" ] } ] } ] } ``` DCQL provides enhanced support for selective disclosure features inherent in SD-JWT credentials, allowing verifiers to specify which claims should be selectively disclosed for the purposed of the presentation. ## Steps To Fulfill Presentation Request To fulfill the presentation request received by the verifier, we will need to perform the following steps: 1. Find credentials matching the presentation definition in the linked credential store of the wallet. 2. Present matched credentials ## 1. Find Credentials To Present To locate the credentials that align with the dcql query provided by the verifier, we will utilize the credential store API. **Option: CURL** Endpoint: `/v1/{target}/credential-store-service-api/credentials/query/document/schema` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Credential%20Store%20Service/post_v1__target__credential_store_service_api_credentials_query_document_schema) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/credential-store-service-api/credentials/query/document/schema' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '[ { "id": "pid", "format": "dc+sd-jwt", "meta": { "vct_values": [ "http://org.enterprise.localhost:3000/v1/org.tenant.issuer2/issuer-service-api/openid4vc/draft13/identity_credential" ] }, "claims": [ { "path": [ "given_name" ] }, { "path": [ "family_name" ] }, { "path": [ "address", "street_address" ] } ] } ]' ``` --- **Response Codes** - `200` - Matching credentials ## 2. Present Matched Credentials **Option: CURL** Endpoint: `/v1/{target}/wallet-service-api/credentials/present` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Wallet%20Service/post_v1__target__wallet_service_api_present) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/wallet-service-api/credentials/present' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "requestUrl": "openid4vp://authorize?client_id=did%3Ajwk%3AeyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiU05JZ1ItTHZzSEUtNkhUUGxYYUpQVDVubHRZMFpqZlA0VkpTWEwzQ2ZkMCIsIngiOiJIVWM1ZHY1eFREV3VMVmZQcFdvVXBWTjZ1QTNJNzBsVzVnNEhoMG5VaDQ0In0&request_uri=http%3A%2F%2Forg.enterprise.localhost%3A3000%2Fv1%2Forg.tenant.verifier%2Fverifier2-service-api%2Fbb3f89d1-8bc9-4d04-9418-12bc70bd76e9%2Frequest", "keyReference": "example.kms.key1", "didReference": "example.didstore.did1", "credentials": [ { "credential": "waltid.tenant22.credential-store-wallet1.3d629d4b-4111-461d-9233-b2224fd64486", "disclosures": [ "$.dateOfBirth", ] } ] }' ``` **Body Parameters** - `requestUrl` _String_ - An OID4VCP Authorization URL. - `credentials` (optional) _Array_ - You can optionally provide a list of credential IDs from the linked credential store that you want to present. If you do not provide a list, the wallet will attempt to find matching credentials based on the dcql query specified in the request URL. For SD-JWT credentials, the selective disclosure mechanism is built into the credential format itself. The wallet will automatically handle the selective disclosure of claims based on the dcql query requirements. If you prefer, you can also explicitly specify which fields to disclose next to the credential ID under the "disclosures" field in the credential object. - `keyReference` (optional) _String_ - The resource ID (target) of the key which owns the credential. - `didReference` (optional) _String_ - The DID reference of a DID stored in a linked DID Store. --- **Response Codes** - `200` - Presentation was accepted --- When the `present` endpoint is executed for SD-JWT credentials, the Wallet will create a Verifiable Presentation that contains the selectively disclosed claims from the SD-JWT credential. The selective disclosure mechanism ensures that only the claims specified in the presentation definition (or explicitly provided in the disclosures array) are shared with the verifier, while maintaining the cryptographic integrity of the credential. -------------------------------------------------------------------------------- PAGE: Present mDL Verifiable Credentials via OID4VP URL: https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/legacy-draft20/mdl-oid4vp RAW: https://docs.walt.id/enterprise-stack/services/wallet-service/credential-presenting/legacy-draft20/mdl-oid4vp.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Present mDL and mDoc Verifiable Credentials via OID4VP **Note:** To follow this guide, you need a wallet (wallet-service) configured with an additional service like the credential store for storing received credentials. For more information on different setups, please go [here](../1.setup.md).
**Note:** This guide works for both the Verifier Service and the Verifier2 Service.
You will notice examples for Presentation Definitions, which are used in the draft OpenID4VP specifications implemented by the Verifier Service.
The Verifier2 Service uses the new OpenID4VP 1.0 specification, which does not support Presentation Definitions. Instead, it supports DCQL (Digital Credential Query Language).
There are several methods by which a user (wallet) can present a credential to a verifier, and all are supported by the wallet API: 1. User may receive a direct link, which opens the wallet and prompts credential sharing. 2. A website may display a QR code for the user to scan and share the credential. 3. The user may manually input a credential presentation request URL string into a text field. ## Credential Authorization Request URL The authorization URL is a standardised method, as per the OID4VP specification, to communicate the needed credentials and claims from verifier to the wallet. **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= ``` **Option: Presentation Definition** #### The Presentation Definition for mDL and mDocs The presentation definition specifies the criteria for the wallet to know what credentials and claims should be requested from the user and shared with the verifier. **Example for mDL** ```json { "input_descriptors": [ { "id": "org.iso.18013.5.1.mDL", "format": { "mso_mdoc": { "alg": [ "ES256" ] } }, "constraints": { "fields": [ { "path": [ "$['org.iso.18013.5.1']['age_over_18']" ], "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" } } ] } ``` The definition specifies that one credential is requested which is of the mdl format (Mdocs uses namespaces, which formally represent the document type which in this case is org.iso.18013.5.1.mDL) and requests specifically whether the holder is over 18 years old and the mDL is valid. **Option: DCQL** #### DCQL (Data Credential Query Language) for mDL and mDocs DCQL is a newer approach in OpenID4VP that provides a more flexible and expressive way to specify credential requirements. **Example DCQL Query for PhotoID** ```json { "dcql_query": { "credentials": [ { "id": "my_photoid", "format": "mso_mdoc", "meta": { "doctype_value": "org.iso.23220.photoid.1" }, "claims": [ { "path": [ "org.iso.18013.5.1", "family_name_unicode" ] }, { "path": [ "org.iso.18013.5.1", "given_name_unicode" ] }, { "path": [ "org.iso.18013.5.1", "issuing_authority_unicode" ] }, { "path": [ "org.iso.18013.5.1", "resident_postal_code" ], "values": [ 1180, 1190, 1200, 1210 ] }, { "path": [ "org.iso.18013.5.1", "issuing_country" ], "values": [ "AT" ] } ] } ] } ``` ## Steps To Fulfill Presentation Request To fulfill the presentation request received by the verifier, we will need to perform the following steps: 1. Find credentials matching the presentation definition in the linked credential store of the wallet. 2. Present matched credentials ## 1. Find Credentials To Present To locate the credentials that align with the dcql query provided by the verifier, we will utilize the credential store API. **Option: CURL** Endpoint: `/v1/{target}/credential-store-service-api/credentials/query/document/schema` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Credential%20Store%20Service/post_v1__target__credential_store_service_api_credentials_query_document_schema) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/credential-store-service-api/credentials/query/document/schema' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '[ { "id": "my_photoid", "format": "mso_mdoc", "meta": { "doctype_value": "org.iso.23220.photoid.1" }, "claims": [ { "path": [ "org.iso.18013.5.1", "family_name_unicode" ] }, { "path": [ "org.iso.18013.5.1", "given_name_unicode" ] }, { "path": [ "org.iso.18013.5.1", "issuing_authority_unicode" ] }, { "path": [ "org.iso.18013.5.1", "resident_postal_code" ], "values": [ 1180, 1190, 1200, 1210 ] }, { "path": [ "org.iso.18013.5.1", "issuing_country" ], "values": [ "AT" ] } ] } ]' ``` --- **Response Codes** - `200` - Matching credentials ## 2. Present Matched Credentials **Option: CURL** Endpoint: `/v1/{target}/wallet-service-api/credentials/present` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Wallet%20Service/post_v1__target__wallet_service_api_present) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/wallet-service-api/credentials/present' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "requestUrl": "openid4vp://authorize?client_id=did%3Ajwk%3AeyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiU05JZ1ItTHZzSEUtNkhUUGxYYUpQVDVubHRZMFpqZlA0VkpTWEwzQ2ZkMCIsIngiOiJIVWM1ZHY1eFREV3VMVmZQcFdvVXBWTjZ1QTNJNzBsVzVnNEhoMG5VaDQ0In0&request_uri=http%3A%2F%2Forg.enterprise.localhost%3A3000%2Fv1%2Forg.tenant.verifier%2Fverifier2-service-api%2Fbb3f89d1-8bc9-4d04-9418-12bc70bd76e9%2Frequest", "keyReference": "example.kms.key1", "didReference": "example.didstore.did1", "credentials": [ { "credential": "waltid.tenant22.credential-store-wallet1.3d629d4b-4111-461d-9233-b2224fd64486", } ] }' ``` **Body Parameters** - `requestUrl` _String_ - An OID4VCP Authorization URL. - `credentials` (optional) _Array_ - You can optionally provide a list of credential IDs from the linked credential store that you want to present. If you do not provide a list, the wallet will attempt to find matching credentials based on the dcql query specified in the request URL. For Mdocs credentials, the selective disclosure mechanism is built into the credential format itself. The wallet will automatically handle the selective disclosure of claims based on the dcql query requirements. If you prefer, you can also explicitly specify which fields to disclose next to the credential ID under the "disclosures" field in the credential object. - `keyReference` (optional) _String_ - The resource ID (target) of the key which owns the credential. - `didReference` (optional) _String_ - The DID reference of a DID stored in a linked DID Store. --- **Response Codes** - `200` - Presentation was accepted --- When the `present` endpoint is executed for mDL and mDoc credentials, the Wallet will create a Verifiable Presentation that contains the selectively disclosed claims from the mDL or mDoc credential. The selective disclosure mechanism ensures that only the claims specified in the presentation definition (or explicitly provided in the disclosures array) are shared with the verifier, while maintaining the cryptographic integrity of the credential. -------------------------------------------------------------------------------- PAGE: Intro Silent Issuance & Verification URL: https://docs.walt.id/enterprise-stack/services/wallet-service/silent-issuance-verification/overview RAW: https://docs.walt.id/enterprise-stack/services/wallet-service/silent-issuance-verification/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Intro Silent Issuance & Verification The wallet service is designed with flexibility in mind — both in terms of the types of wallets it supports (e.g., organizational or personal) and how credential exchanges are handled. **While most implementations involve user-facing flows (like scanning a QR code or clicking a link), the Wallet API also supports fully programmatic interactions.** This allows developers to implement **silent issuance and verification** — credential exchanges that happen in the background, without requiring real-time user input. Because the **Wallet API doesn’t enforce assumptions about how it’s used**, you’re free to integrate it into any architecture: from mobile apps to enterprise middleware and custom orchestration layers. The goal of this implementation is to consider credential exchange flows with a UX-first approach, especially when considering the long term scalability of the wallet interactions as users start collecting more and more credentials. Having to accept or reject each credential explicitly, or having to scroll through many valid credentials for a presentation request and selecting the correct ones with the right disclosures is a highly frictional experience that the average user will not tolerate. ## What Are Silent Flows? Silent flows allow credentials to be issued or verified without immediate user interaction. Instead of asking the user to scan a QR code or manually accept an offer by clicking a link, the process happens **asynchronously and programmatically** — in the background. There are two supported flows: - **Silent Issuance** – Credentials are pushed directly into a wallet. - **Silent Verification** – Credential data is shared automatically. This is done simply by pushing the openid credential offer or presentation request URL to the wallet service API. ## Common Concerns & How To Address Them ### Challenge 1: Spam or unwanted credentials? Use: - **Trust lists** to block untrusted issuers - Metadata flags like `"pending": true` to show credentials in a review UI ([metadata body parameter in receive credential endpoint](../2.credential-receiving/5.legacy-draft13/1.vc-oid4vci.md#accepting-credential-offers)) ### Challenge 2: What if the user doesn’t want the credential? Users can accept/reject **pending credentials** at a later time in the wallet UI. ### Challenge 3: Does this violate consent-first data sharing? Silent verification respects consent through: - Local **holder policies** for setting granular sharing rules - Build first-time prompts to guide configuration ### Challenge 4: How can verifiers ensure correct data? - Use Presentation Definitions (DIF) - Digital Credentials Query Language (DCQL) – regex-like filtering for credentials ### Challenge 5: How do I deliver OpenID links to the wallet? Use the REST APIs in the enterprise stack. You can: - Call the wallet service directly via API - Add a communication layer (e.g., internal relay) - For decentralized delivery, expose the endpoint via the DIDDoc -------------------------------------------------------------------------------- PAGE: Full API Reference URL: https://docs.walt.id/enterprise-stack/services/wallet-service/full-api-reference RAW: https://docs.walt.id/enterprise-stack/services/wallet-service/full-api-reference.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Full API Reference Find it [here](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Wallet%20Service) -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/services/credential-status-service/overview RAW: https://docs.walt.id/enterprise-stack/services/credential-status-service/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview The Credential Status service enables users to create, update, and manage status credentials. Status credentials can have different purposes depending on the use-case. The most common purpose is revocation. Currently, the API supports the following credential status standards: - [Bitstring Status List v1.0](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) (only [Direct Trust](#trust-and-signing-models) model) You can refer to the table below on which standards are supported by which credential format
Format RevocationList2020 (w3c) StatusList2021 (w3c) BitstringStatusList (w3c) TokenStatusList - draft#15 (ietf, mdoc)
JWT CWT
JWT
SD-JWT
IETF SD-JWT VC
(x509 / did)
ISO mDOC
✓ - supported
✗ - not supported
‐ - not available **Note:** For an overview on the credential lifecycle concept, please refer to the [credential lifecycle section](../../../community-stack/1.concepts/6.credential-lifecycle/credential-status-and-revocation.md). #### Trust and Signing Models We only support the Direct Trust model for the TokenStatusList standard. * **Same Entity** (Direct Trust) - the entity issuing the credential also issues the Status List * Re-uses the same keys (JWK/DID/x5c) resolution method used for the Referenced Token (credential) * **Delegated Entity** (X.509 Chain) - status management is outsourced to a different entity (Status Issuer) * Uses X.509 certificates * The Status Issuer's certificate contains the specific _id-kp-oauthStatusSigning_ to prove it is authorized to sign status lists ## Get Started - [Setup](https://docs.walt.id/enterprise-stack/services/credential-status-service/setup.md) - How to set up a credential status service - [Manage](https://docs.walt.id/enterprise-stack/services/credential-status-service/manage.md) - How to create, use and manage status credentials. -------------------------------------------------------------------------------- PAGE: Setup URL: https://docs.walt.id/enterprise-stack/services/credential-status-service/setup RAW: https://docs.walt.id/enterprise-stack/services/credential-status-service/setup.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Setup We will setup a Credential Status service inside of a tenant. If you don't have a tenant yet, you can learn how to create one [here](https://docs.walt.id/enterprise-stack/administration/tenants/create.md). Currently, status credentials can be stored and made available via five types of external services: 1. [AWS S3 Bucket](https://aws.amazon.com/s3/) 2. [Azure Blob Storage](https://azure.microsoft.com/en-us/products/storage/blobs) 3. [Google Cloud Storage](https://cloud.google.com/storage) 4. [S3-compatible Storage](https://www.cloudflare.com/en-gb/developer-platform/solutions/s3-compatible-object-storage/) 4. In-Memory We support three ways to authenticate with each of these supported services: 1. [Managed Identity](#via-managed-identity) Recommended for AWS and Azure. 2. [Config](#via-config) Recommended for GCP and S3-compatible storage. 3. [Registry](#via-registry) Recommended for quick testing and local development. Its also important to note how you can [configure the size of your status list](#capacity-configuration). ## via Managed Identity The Credential Status service can be configured to use a managed identity for authentication. This is useful when you want to use a service principal for authentication. This is the recommended way to configure the Credential Status service. **Info:** It is currently only supported for AWS and Azure. For GCP and S3-compatible storage, we recommend you to use the `configRef` method below.
**Option: CURL** Endpoint: `/v1/{target}/resource-api/services/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%2F%20Resource/post_v1__target__resource_api_services_create) **Option: AWS** Configuration options vary based on the bucket's hosting environment and its access method. **Option: AWS domain** **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "credential-status", "config": { "registry": { "type": "aws", "bucketName": "bucket-name", "region": "region" } } }' ``` **Body** ```json { "type": "credential-status", "config": { "registry": { "type": "aws", "bucketName": "bucket-name", "region": "region" } } } ``` **Option: CDN (custom domain)** **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "credential-status", "config": { "registry": { "type": "aws", "bucketName": "bucket-name", "region": "region" }, "bucketUrl": "https://custom-domain.com" } }' ``` **Body** ```json { "type": "credential-status", "config": { "registry": { "type": "aws", "bucketName": "bucket-name", "region": "region" }, "bucketUrl": "https://custom-domain.com" } } ``` **Body Parameters** - `type`: _credential-status_ - Specifies the type of service to create. In this case, it is `credential-status`. - `config`: _object_ - Storage configurations for status credential service. - `registry`: _object_ - Defines registry & access credentials for chosen registry. - `type`: _aws_ - Indicates the type of registry, which is `aws` in this context. - `bucketName`: _string_ - The name of the AWS S3 bucket. - `region`: _string_ - The AWS region where the bucket is located. - `endpointUrl`: _string_ - (Optional) The URL of the S3-compatible registry bucket, e.g. `https://s3-mock.com`. - `bucketUrl`: _string_ - (Optional) The URL of the registry bucket custom domain, e.g. `https://custom-domain.com`. **Option: Azure** **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "credential-status", "config": { "registry": { "type": "azure", "bucketName": "bucket-name", "accountUrl": "account-url" } } }' ``` **Body** ```json { "type": "credential-status", "config": { "registry": { "type": "azure", "bucketName": "bucket-name", "accountUrl": "account-url" } } } ``` **Body Parameters** - `type`: _credential-status_ - Specifies the type of service to create. In this case, it is `credential-status`. - `config`: _object_ - Storage configurations for status credential service. - `registry`: _object_ - Holds the details for the Azure registry configuration. - `type`: _azure_ - Indicates the type of registry, which is `azure` in this context. - `bucketName`: _string_ - The name of the Azure storage bucket. - `accountUrl`: _string_ - Account URL for Azure Blob Storage, e.g. "https://your-account.blob.core.windows.net". - `bucketUrl`: _string_ - (Optional) The URL of the Azure storage bucket, specified as `https://storage-name.blob.core.windows.net/bucket-name`. **Option: Google** **Error:** Not supported. **Option: S3-compatible** **Error:** Not supported. **Option: In-Memory** **Error:** Not required. ## via Config Instead of sending storage credentials inline for every Credential Status service, you can define reusable storage profiles in `waltid-enterprise-api/config/resource-access.conf` and reference them via `configRef` when creating services through the Enterprise API. The configuration file uses HOCON and defines a `resourceAccess` map, where each entry is a named storage profile: ```hocon resourceAccess = { # AWS S3 Example aws-s3-standard = { _type = "AwsS3Access" id = "s3-storage" region = "eu-north-1" bucket = { bucketName = "my-bucket" } credentials = { _type = "AwsCredentials" accessKeyId = "abc" secretKey = "xyz" } }, # Azure Blob Storage Example azure-blob-storage = { _type = "AzureBlobAccess" id = "azure-storage" bucket = { bucketName = "documents" bucketUrl = "https://example.blob.core.windows.net/" } credentials = { _type = "AzureCredentials" connectionString = "DefaultEndpointsProtocol=https;AccountName=example;AccountKey=..." } }, # Google Cloud Storage Example gcp-storage = { _type = "GcpStorageAccess" id = "gcp-storage-main" projectId = "my-project" bucket = { bucketName = "gcp-bucket-main" } credentials = { _type = "GcpCredentials" serviceAccountKeyJson = { type = "service_account" project_id = "my-project" private_key_id = "abc123..." private_key = "-----BEGIN PRIVATE KEY----- ... -----END PRIVATE KEY----- " client_email = "service-account@my-project.iam.gserviceaccount.com" client_id = "1234567890" auth_uri = "https://accounts.google.com/o/oauth2/auth" token_uri = "https://oauth2.googleapis.com/token" auth_provider_x509_cert_url = "https://www.googleapis.com/oauth2/v1/certs" client_x509_cert_url = "https://www.googleapis.com/robot/v1/metadata/x509/service-account%40my-project.iam.gserviceaccount.com" universe_domain = "googleapis.com" } } } ``` When creating a `credential-status` service, you can now reference one of these profiles instead of providing the full storage configuration inline: ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "credential-status", "config": { "configRef": "aws-s3-standard" } }' ``` **Example Request Body** ```json { "type": "credential-status", "config": { "configRef": "aws-s3-standard" } } ``` At runtime, the Enterprise API resolves the `configRef` against `resource-access.conf` whenever the Credential Status service needs to read or write a status list. This allows centralized credential management and rotation without updating individual service records in the database. ## via Registry **Option: CURL** Endpoint: `/v1/{target}/resource-api/services/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Resource/post_v1__target__resource_api_services_create) **Option: AWS** Configuration options vary based on the bucket's hosting environment and its access method. **Option: AWS domain** **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "credential-status", "config": { "registry": { "type": "aws", "bucketName": "bucket-name", "region": "region", "accessKeyId": "s3-access-key-id", "secretKey": "s3-secret-key" } } }' ``` **Body** ```json { "type": "credential-status", "config": { "registry": { "type": "aws", "bucketName": "bucket-name", "region": "region", "accessKeyId": "s3-access-key-id", "secretKey": "s3-secret-key" } } } ``` **Option: CDN (custom domain)** **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "credential-status", "config": { "registry": { "type": "aws", "bucketName": "bucket-name", "region": "region", "accessKeyId": "s3-access-key-id", "secretKey": "s3-secret-key" }, "bucketUrl": "https://custom-domain.com" } }' ``` **Body** ```json { "type": "credential-status", "config": { "registry": { "type": "aws", "bucketName": "bucket-name", "region": "region", "accessKeyId": "s3-access-key-id", "secretKey": "s3-secret-key" }, "bucketUrl": "https://custom-domain.com" } } ``` **Option 3** **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "credential-status", "config": { "registry": { "type": "aws", "bucketName": "bucket-name", "region": "region", "accessKeyId": "s3-access-key-id", "secretKey": "s3-secret-key", "endpointUrl": "https://s3-mock.com" } } }' ``` **Body** ```json { "type": "credential-status", "config": { "registry": { "type": "aws", "bucketName": "bucket-name", "region": "region", "accessKeyId": "s3-access-key-id", "secretKey": "s3-secret-key", "endpointUrl": "https://s3-mock.com" } } } ``` **Body Parameters** - `type`: _credential-status_ - Specifies the type of service to create. In this case, it is `credential-status`. - `config`: _object_ - Storage configurations for status credential service. - `registry`: _object_ - Defines registry & access credentials for chosen registry. - `type`: _aws_ - Indicates the type of registry, which is `aws` in this context. - `bucketName`: _string_ - The name of the AWS S3 bucket. - `region`: _string_ - The AWS region where the bucket is located. - `accessKeyId`: _string_ - (Optional) The access key ID for accessing the AWS S3 bucket. - `secretKey`: _string_ - (Optional) The secret access key for accessing the AWS S3 bucket. - `endpointUrl`: _string_ - (Optional) The URL of the S3-compatible registry bucket, e.g. `https://s3-mock.com`. - `bucketUrl`: _string_ - (Optional) The URL of the registry bucket custom domain, e.g. `https://custom-domain.com`. --- AWS access credentials are optional. If not provided, the default [Credential Provider Chain](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/credentials-chain.html) will be executed, relying on AWS environment configuration (e.g. EC2 instance IAM role-provided credentials). **Option: Azure** **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "credential-status", "config": { "registry": { "type": "azure", "bucketName": "bucket-name", "connectionString": "connection-string" } } }' ``` **Body** ```json { "type": "credential-status", "config": { "registry": { "type": "azure", "bucketName": "bucket-name", "connectionString": "connection-string" } } } ``` **Body Parameters** - `type`: _credential-status_ - Specifies the type of service to create. In this case, it is `credential-status`. - `config`: _object_ - Storage configurations for status credential service. - `registry`: _object_ - Holds the details for the Azure registry configuration. - `type`: _azure_ - Indicates the type of registry, which is `azure` in this context. - `bucketName`: _string_ - The name of the Azure storage bucket. - `connectionString`: _string_ - The connection string used to access the Azure storage account. - `bucketUrl`: _string_ - (Optional) The URL of the Azure storage bucket, specified as `https://storage-name.blob.core.windows.net/bucket-name`. **Option: Google** **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "credential-status", "config": { "registry": { "type": "gcp", "bucketName": "bucket-name", "projectId": "project-id", "serviceAccountKeyJson": { "type": "service_account", "project_id": "YOUR_PROJECT_ID", "private_key_id": "YOUR_PRIVATE_KEY_ID", "private_key": "-----BEGIN PRIVATE KEY----- YOUR_PRIVATE_KEY -----END PRIVATE KEY----- ", "client_email": "YOUR_SERVICE_ACCOUNT_EMAIL", "client_id": "YOUR_CLIENT_ID", "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://oauth2.googleapis.com/token", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/YOUR_SERVICE_ACCOUNT_EMAIL", "universe_domain": "googleapis.com" } } } }' ``` **Body** ```json { "type": "credential-status", "config": { "registry": { "type": "gcp", "bucketName": "bucket-name", "projectId": "project-id", "serviceAccountKeyJson": { "type": "service_account", "project_id": "YOUR_PROJECT_ID", "private_key_id": "YOUR_PRIVATE_KEY_ID", "private_key": "-----BEGIN PRIVATE KEY----- YOUR_PRIVATE_KEY -----END PRIVATE KEY----- ", "client_email": "YOUR_SERVICE_ACCOUNT_EMAIL", "client_id": "YOUR_CLIENT_ID", "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://oauth2.googleapis.com/token", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/YOUR_SERVICE_ACCOUNT_EMAIL", "universe_domain": "googleapis.com" } } } } ``` **Body Parameters** - `type`: _credential-status_ - Specifies the type of service to create. In this case, it is `credential-status`. - `config`: _object_ - Storage configurations for status credential service. - `registry`: _object_ - Holds the details for the GCP registry configuration. - `type`: _gcp_ - Indicates the type of registry, which is `gcp` in this context. - `bucketName`: _string_ - The name of the GCP storage bucket. - `projectId`: _string_ - The ID of the GCP project. - `serviceAccountKeyJson`: _object_ - Contains the service account key details for accessing GCP. - `type`: _service_account_ - Specifies the type of account - `project_id`: _string_ - The project ID for the service account. - `private_key_id`: _string_ - The ID of the private key. - `private_key`: _string_ - The private key for the service account, specified in a PEM format. - `client_email`: _string_ - The email associated with the service account. - `client_id`: _string_ - The client ID for the service account. - `auth_uri`: _string_ - The URI for authentication. - `token_uri`: _string_ - The URI for token issuance. - `auth_provider_x509_cert_url`: _string_ - The URL for the authentication provider's certificate. - `client_x509_cert_url`: _string_ - The URL for the client certificate. - `universe_domain`: _string_ - The domain for the universe of the API. - `bucketUrl`: _string_ - (Optional) The URL of the GCP storage bucket, specified as `https://storage.cloud.google.com/bucket-name`. **Option: S3-compatible** **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "credential-status", "config": { "registry": { "type": "aws", "bucketName": "bucket-name", "region": "region", "accessKeyId": "s3-access-key-id", "secretKey": "s3-secret-key", "endpointUrl": "https://s3-mock.com" } } }' ``` **Body** ```json { "type": "credential-status", "config": { "registry": { "type": "aws", "bucketName": "bucket-name", "region": "region", "accessKeyId": "s3-access-key-id", "secretKey": "s3-secret-key", "endpointUrl": "https://s3-mock.com" } } } ``` **Body Parameters** - `type`: _credential-status_ - Specifies the type of service to create. In this case, it is `credential-status`. - `config`: _object_ - Storage configurations for status credential service. - `registry`: _object_ - Defines registry & access credentials for chosen registry. - `type`: _aws_ - Indicates the type of registry, which is `aws` in this context. - `bucketName`: _string_ - The name of the AWS S3 bucket. - `region`: _string_ - The AWS region where the bucket is located. - `accessKeyId`: _string_ - (Optional) The access key ID for accessing the AWS S3 bucket. - `secretKey`: _string_ - (Optional) The secret access key for accessing the AWS S3 bucket. - `endpointUrl`: _string_ - (Optional) The URL of the S3-compatible registry bucket, e.g. `https://s3-mock.com`. **Option: In-Memory** **Error:** When using `in-memory` storage, the status credential is publicly available at:
`/v1/{target}/credential-status-service-api/status-credential/fetch`.
This endpoint is disabled by default and requires the `dev-mode` feature.
**Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "credential-status", "config": { "registry": { "type": "in-memory" } } }' ``` **Body** ```json { "type": "credential-status", "config": { "registry": { "type": "in-memory" } } } ``` --- **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant in which to create the new credential status service and the service's ID (`{organizationID}.{tenantID}.[NewCredentialStatusServiceID]`), e.g. `waltid.tenant1.credential-status-service-id` --- **Response Codes** - `201` - Service created successfully. - `401` - Invalid authentication. ## Capacity Configuration You can configure capacity settings when creating a credential status service to enable monitoring and automatic rollover when status lists approach capacity. **Option: CURL** **Endpoint:** `POST /v1/{target}/resource-api/services/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Resource%20API%20%7C%20Services/post_v1__target__resource_api_services_create) ##### Example Request ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "credential-status", "config": { "registry": { "type": "in-memory" }, "capacity": { "maxEntries": 32000, "warningThresholdPercent": 50, "criticalThresholdPercent": 80, "autoRollover": true, "rolloverSuffix": "-v" } } }' ``` **Path Parameters** - **orgID**: _String_ (required) - Your organization's Base URL. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.waltid.dev` when using the sandbox environment. - **target**: _String_ (required) - The target resource identifier specifying the organization and tenant. Format: `{organizationID}.{tenantID}`, e.g. `waltid.tenant1` **Header Parameters** - **Authorization**: _String_ (required) - Bearer token obtained from your Enterprise authentication. Format: `Bearer {token}`. **Body Parameters** - **type**: _String_ (required) - The service type to create. Must be `"credential-status"` for this service. - **config**: _Object_ (required) - Service configuration object containing: - **registry**: _Object_ (required) - Storage configuration for status credentials. See [Registry Configuration](#registry-configuration) for available options. - **capacity**: _Object_ (optional) - Capacity monitoring and rollover configuration. Contains: - **maxEntries**: _Integer_ (optional) - Maximum entries for this status list. Range: 1-32,000. Defaults to `32000`. - **warningThresholdPercent**: _Integer_ (optional) - Percentage at which WARNING status is triggered. Range: 1-99. Defaults to `80`. - **criticalThresholdPercent**: _Integer_ (optional) - Percentage at which CRITICAL status is triggered. Must be greater than `warningThresholdPercent`. Defaults to `95`. - **autoRollover**: _Boolean_ (optional) - Enable automatic creation of a new status list when the critical threshold is reached. Defaults to `false`. - **rolloverSuffix**: _String_ (optional) - Suffix appended to versioned rollover status lists (e.g., `config-v2`, `config-v3`). Defaults to `"-v"`. ### Automatic Rollover When `autoRollover` is enabled, the system automatically creates a new status list when the current one reaches the critical threshold: 1. New entries continue to be created using the same base config ID 2. The system automatically routes to the active status list version 3. New status lists are named with the configured suffix (e.g., `config-v2`, `config-v3`) 4. Existing entries remain in their original status lists **Info:** Automatic rollover requires the `ES_CREDENTIAL_STATUS_CREATE_LIST` permission in addition to `ES_CREDENTIAL_STATUS_CREATE_STATUS_ENTRY`. ### Monitoring Capacity Use the capacity endpoint to check status list usage: ``` GET /v1/{target}/credential-status-service-api/status-credential/capacity ``` See [Manage Status Credentials](https://docs.walt.id/enterprise-stack/services/credential-status-service/manage.md#check-status-list-capacity) for details on the response format and status values. -------------------------------------------------------------------------------- PAGE: Manage URL: https://docs.walt.id/enterprise-stack/services/credential-status-service/manage RAW: https://docs.walt.id/enterprise-stack/services/credential-status-service/manage.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Manage Status Credentials In this walkthrough we take a look at how you can create a status credential configuration and add or update a status entry in the configured status credential. Please make sure you have created a credential status service as explained [here](https://docs.walt.id/enterprise-stack/services/credential-status-service/setup.md). **Note:** For details on status validation, please refer to the [credential-status policy section](https://docs.walt.id/enterprise-stack/services/verifier-service/policies/parameterized-verification-policies.md#credential-status). Our implementation covers examples for each of the following standards: - [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://drafts.oauth.net/draft-ietf-oauth-status-list/draft-ietf-oauth-status-list.html) ## Create A Status Credential Configuration The first step is to create a configuration. However, the creation of a configuration does not yet issue the actual status credential. It's only after the first status entry has been created with a configuration that the status credential based on the configuration is being issued and made available for verifiers and other parties to access. When defining the configuration, we decide which key will sign the status credential, the DID of the issuer, and the config of the status itself. The config sets up properties the depending on the status credential standard, e.g.: - _W3C_ standards (BitstringStatusList, StatusList2021, RevocationList2020) - `purpose` of the status, e.g. revocation, the `statusSize` and more - _IETF_ standard (TokenStatusList) **Note:** One status credential can host up to 32.000 status list entries. In the examples below we create a configuration for a revocation status credential.
**Option: CURL** Endpoint: `/v1/{target}/credential-status-service-api/status-credential/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Credential%20Status%20Service/post_v1__target__credential_status_service_api_status_credential_create) **Example Request** **Option: BitstringStatusList** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/credential-status-service-api/status-credential/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "kid": "waltid.tenant1.kms1.key1", "did": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiMW1rQnBEYWZqVk9ONm9XVmlIZGREZXNEYXpKaXk1R0NTYko3VDU4QkJ3cyIsIngiOiJzZ1BiZWRPQjl1WEMtVG54LUVhV1IxRmg1Y25JLUxRLU43NUV1UE8wV2VFIn0", "config": { "purpose": "revocation", "type": "BitstringStatusList", "statusSize": 1, "statuses": [ { "status": "0x0", "message": "unset" }, { "status": "0x1", "message": "set" } ] } }' ``` **Option: StatusList2021** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/credential-status-service-api/status-credential/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "kid": "waltid.tenant1.kms1.key1", "did": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiMW1rQnBEYWZqVk9ONm9XVmlIZGREZXNEYXpKaXk1R0NTYko3VDU4QkJ3cyIsIngiOiJzZ1BiZWRPQjl1WEMtVG54LUVhV1IxRmg1Y25JLUxRLU43NUV1UE8wV2VFIn0", "config": { "purpose": "revocation", "type": "StatusList2021", "statusSize": 1, "statuses": [ { "status": "0x0", "message": "unset" }, { "status": "0x1", "message": "set" } ] } }' ``` **Option: RevocationList2020** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/credential-status-service-api/status-credential/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "kid": "waltid.tenant1.kms1.key1", "did": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiMW1rQnBEYWZqVk9ONm9XVmlIZGREZXNEYXpKaXk1R0NTYko3VDU4QkJ3cyIsIngiOiJzZ1BiZWRPQjl1WEMtVG54LUVhV1IxRmg1Y25JLUxRLU43NUV1UE8wV2VFIn0", "config": { "purpose": "revocation", "type": "RevocationList2020", "statusSize": 1, "statuses": [ { "status": "0x0", "message": "unset" }, { "status": "0x1", "message": "set" } ] } }' ``` **Option: TokenStatusList** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/credential-status-service-api/status-credential/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "kid": "waltid.tenant1.kms1.key1", "did": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiMW1rQnBEYWZqVk9ONm9XVmlIZGREZXNEYXpKaXk1R0NTYko3VDU4QkJ3cyIsIngiOiJzZ1BiZWRPQjl1WEMtVG54LUVhV1IxRmg1Y25JLUxRLU43NUV1UE8wV2VFIn0", "config": { "type": "TokenStatusList", "statusSize": 1, "statuses": [ { "status": "0x0", "message": "VALID" }, { "status": "0x1", "message": "INVALID" } ], "format": "JWT" } }' ``` --- **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + credential status service in which to execute the status credential creation (`{organizationID}.{tenantID}.{credentialStatusServiceID}`), e.g. `waltid.tenant1.status-service1` **Response** - `201` - Credential Status config created successfully. **Option: BODY** **Example Request Payload** **Option: BitstringStatusList** **Body** ```json { "kid": "waltid.tenant1.kms1.key1", "did": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiMW1rQnBEYWZqVk9ONm9XVmlIZGREZXNEYXpKaXk1R0NTYko3VDU4QkJ3cyIsIngiOiJzZ1BiZWRPQjl1WEMtVG54LUVhV1IxRmg1Y25JLUxRLU43NUV1UE8wV2VFIn0", "config": { "purpose": "revocation", "type": "BitstringStatusList", "statusSize": 1, "statuses": [ { "status": "0x0", "message": "unset" }, { "status": "0x1", "message": "set" } ] } } ``` --- **Body Parameters** - `kid`: _resourceIdentifier_ - Specifies a key used for singing the status credential. The key must come from a KMS service that lives under the same tenant as the credential status service. - `did`: _String_ - Specifies that DID related to the `kid`. - `config`: _Object_ - Status credential configuration. - `purpose`: _String_ - Describes the type of status e.g. `revocation`, `suspension` or any other custom type. - `type`: _String_ - Describes the status credential standard. It should be `BitstringStatusList`. - `statusSize`: _String_ - Describes the size of each status entry in bits. The default value is 1, meaning each status is represented by a single bit. If a value greater than 1 is used, it allows for more complex status messages, and the number of possible status messages must equal the number of possible values (i.e. 2^statusSize). - `statuses`: _Array_ - An array of objects, each representing a possible status value and its associated message. Each object contains: - `status`: A string representing the hexadecimal value of the status, prefixed with 0x. - `message`: A descriptive string used by developers for debugging purposes, indicating what the status value represents. It is not intended for end-user display. **Info:** **Multiple Status Lists** The Credential Status service supports multiple status lists for a single credential. This is useful in scenarios where you want to manage lists for different purposes - such as a suspension list and a revocation list. For each list you plan to manage, you will need to create a separate configuration as shown above. The setup is the same as creating a single status list configuration. **Note:** Only BitstringStatusList supports multiple status lists per credential. Other status list types (StatusList2021, RevocationList2020, TokenStatusList) support only a single status list per credential. **Option: StatusList2021** **Body** ```json { "kid": "waltid.tenant1.kms1.key1", "did": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiMW1rQnBEYWZqVk9ONm9XVmlIZGREZXNEYXpKaXk1R0NTYko3VDU4QkJ3cyIsIngiOiJzZ1BiZWRPQjl1WEMtVG54LUVhV1IxRmg1Y25JLUxRLU43NUV1UE8wV2VFIn0", "config": { "purpose": "revocation", "type": "StatusList2021", "statusSize": 1, "statuses": [ { "status": "0x0", "message": "unset" }, { "status": "0x1", "message": "set" } ] } } ``` --- **Body Parameters** _Same as for BitstringStatusList_, except the type should be `StatusList2021`. **Option: RevocationList2020** **Body** ```json { "kid": "waltid.tenant1.kms1.key1", "did": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiMW1rQnBEYWZqVk9ONm9XVmlIZGREZXNEYXpKaXk1R0NTYko3VDU4QkJ3cyIsIngiOiJzZ1BiZWRPQjl1WEMtVG54LUVhV1IxRmg1Y25JLUxRLU43NUV1UE8wV2VFIn0", "config": { "purpose": "revocation", "type": "RevocationList2020", "statusSize": 1, "statuses": [ { "status": "0x0", "message": "unset" }, { "status": "0x1", "message": "set" } ] } } ``` --- **Body Parameters** _Same as for StatusList2021_,except the type should be `RevocationList2020`. **Option: TokenStatusList** **Body** ```json { "kid": "waltid.tenant1.kms1.key1", "did": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiMW1rQnBEYWZqVk9ONm9XVmlIZGREZXNEYXpKaXk1R0NTYko3VDU4QkJ3cyIsIngiOiJzZ1BiZWRPQjl1WEMtVG54LUVhV1IxRmg1Y25JLUxRLU43NUV1UE8wV2VFIn0", "config": { "type": "TokenStatusList", "statusSize": 1, "statuses": [ { "status": "0x0", "message": "VALID" }, { "status": "0x1", "message": "INVALID" } ], "format": "JWT" } } ``` --- **Body Parameters** - `kid`: _resourceIdentifier_ - Specifies a key used for singing the status credential. The key must come from a KMS service that lives under the same tenant as the credential status service. - `did`: _String_ - Specifies that DID related to the `kid`. - `x5c`: _Array_ - Specifies the x5c chain of the key used for signing the status credential. Can be used instead of `did` for the TokenStatusList standard. - `config`: _Object_ - Configuration details for the Status List Token: - `type`: _String_ - Describes the status list standard. It should be `TokenStatusList`. - `statusSize`: _String_ -Describes the size of each status entry in bits. Allowed values are 1, 2, 4, and 8. Default is 1, meaning each status is represented by a single bit. Larger values allow for more complex status messages, with the number of possible status messages equaling `2^statusSize`. - `statuses`: _Array_ - This parameter is a JSON array that defines the possible statuses for the Referenced Tokens. Each entry in the array has: - `status`: _String_ This indicates the bit representation of the status. It uses a hexadecimal representation (e.g., "0x0", "0x1") - `message`: _String_ This provides a human-readable label for the status (e.g., "VALID", "INVALID") - **_Important!_** Values from 0x0 to 0x2 are reserved and cannot have a different message than the one defined by the standard. - `format`: _String_ This parameter indicates the format of the Status List Token. We support both `JWT` and `CWT`. ## Create A Status Entry From The Issuer Service Although we can use the credential status service to create a new status entry, it's quite a cumbersome process. That's why the [issuer service](https://docs.walt.id/enterprise-stack/services/issuer-service/overview.md) was built to directly communicate & use the credential status service. This way when you make a credential issuance request, you can reference a credential status service config + the initial status in the body of the request. The issuer service then makes the same call we just made above to create a new status entry and the result of the call then gets directly embedded into the credential. **Example Issuance Request Body With Status** **Option: BitstringStatusList** Notice the `credentialData` represents a W3C VCDM1.1 or VCMD2.0 credential. **Option: Single** ```json { "issuerKeyId": "waltid.tenant1.kms1.key1", "issuerDid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp", "authenticationMethod": "PRE_AUTHORIZED", "credentialConfigurationId": "UniversityDegree_jwt_vc_json", "credentialData": { "@context": [ "https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1" ], "id": "http://example.gov/credentials/3732", "type": [ "VerifiableCredential", "UniversityDegree" ], "issuer": { "id": "did:web:vc.transmute.world" }, "issuanceDate": "2020-03-10T04:24:12.164Z", "credentialSubject": { "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", "degree": { "type": "BachelorDegree", "name": "Bachelor of Science and Arts" } } }, // STATUS "status": { "statusCredentialConfig": "waltid.tenant1.credential-status-service1.config1", "initialStatus": "0x0" }, // "mapping": { "id": "", "issuer": { "id": "" }, "credentialSubject": { "id": "" }, "issuanceDate": "", "expirationDate": "" } } ``` **Option: Multiple** ```json { "issuerKeyId": "waltid.tenant1.kms1.key1", "issuerDid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp", "authenticationMethod": "PRE_AUTHORIZED", "credentialConfigurationId": "UniversityDegree_jwt_vc_json", "credentialData": { "@context": [ "https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1" ], "id": "http://example.gov/credentials/3732", "type": [ "VerifiableCredential", "UniversityDegree" ], "issuer": { "id": "did:web:vc.transmute.world" }, "issuanceDate": "2020-03-10T04:24:12.164Z", "credentialSubject": { "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", "degree": { "type": "BachelorDegree", "name": "Bachelor of Science and Arts" } } }, // STATUS "status": [ { "statusCredentialConfig": "waltid.tenant1.credential-status-service1.config1", "initialStatus": "0x0" }, { "statusCredentialConfig": "waltid.tenant1.credential-status-service1.config2", "initialStatus": "0x0" } ], // "mapping": { "id": "", "issuer": { "id": "" }, "credentialSubject": { "id": "" }, "issuanceDate": "", "expirationDate": "" } } ``` --- To learn about the issuance requests + the other parameters, please go [here](https://docs.walt.id/enterprise-stack/services/issuer-service/credential-issuance/vc-oid4vc.md). **Option: StatusList2021** _Same as for BitstringStatusList_. **Info:** Only single status supported by standard. It is not possible to have multiple statuses for a single credential. **Option: RevocationList2020** _Same as for StatusList2021_. **Info:** Only single status supported by standard. It is not possible to have multiple statuses for a single credential. **Option: TokenStatusList** Notice the `credentialData` represents an IETF SD-JWT-VC credential. **Info:** Only single status supported by standard. It is not possible to have multiple statuses for a single credential. ```json { "issuerKeyId": "waltid.tenant1.key-service-1.FtZSNCOUNcqPhGRX9xkBr5HZEGQbFRwnRtcrgbh-KmI", "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 }, "family_name": { "sd": false } }, "decoyMode": "NONE", "decoys": 0 }, // STATUS "status": { "statusCredentialConfig": "waltid.tenant1.credential-status-1.f4ce5c992c12216bbe47540c22ee99d7", "initialStatus": "0x0" }, // "authenticationMethod": "PRE_AUTHORIZED", "standardVersion": "DRAFT13", "expiresInSeconds": 300 } ``` To learn about the issuance requests + the other parameters, please go [here](https://docs.walt.id/enterprise-stack/services/issuer-service/credential-issuance/sd-jwt-vc-oid4vc.md). --- **Body Parameters** - `status` - Object defining the status service config + initial status - `statusCredentialConfig`: The ID of the status credential config, e.g. `waltid.tenant1.credential-status-service1.config1`. - `initialStatus`: The string representing the hexadecimal value of the status, e.g. `0x0`. **_Note!_** To ensure compatibility with the issued holder credential, the provided status credential configuration and the requested holder credential must reference the same standard. Providing any other status credential reference will cause an error due to incompatibility between the holder and status credential formats.
Click to expand and see an example of how to manually create a status entry ## Create A Status Entry **Error:** **We do not recommend manually creating status entries, as you will not be able to update the status later through our stack.** With our Status Credential configuration we can now create a status entry. With the following call we will allocate an index within the status credential encoded list for a credential we want to issue with status. The response will return a JSON structure representing the status-entry we can use in the VC issuance request. **Option: CURL** Endpoint: `/v1/{target}/credential-status-service-api/status-credential/status/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Credential%20Status%20Service/post_v1__target__credential_status_service_api_status_credential_status_create) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/credential-status-service-api/status-credential/status/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "initialStatus": "0x0" }' ``` --- **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + credential status service + credential status config ID in which to create a new status list entry. `{organizationID}.{tenantID}.{credentialStatusServiceID}.{statusCredentialConfigID}`), e.g. `waltid.tenant1.status-service1.revocation-config` **Option: BODY** **Body** ```json { "initialStatus": "0x0" } ``` --- **Body Parameters** - `initialStatus`: The string representing the hexadecimal value of the status, e.g. `0x0` **Option: RESPONSE** **Response** - `201` - Status entry created successfully. **Option: BitstringStatusList** The returned status-entry object that can be added to the "credentialStatus" property of the holder credential. ```json { "id": "http://localhost:3000/credentials/99914b932bd37a50b983c5e7c90ae93b#11597", "type": "BitstringStatusList", "statusPurpose": "revocation", "statusListIndex": "11597", "statusSize": 1, "statusListCredential": "http://localhost:3000/credentials/99914b932bd37a50b983c5e7c90ae93b", "statusMessage": [ { "status": "0x0", "message": "unset" }, { "status": "0x1", "message": "set" } ] } ``` **Example of a holder credential with Status** ```json { "@context": [ "https://www.w3.org/ns/credentials/v2", "https://www.w3.org/ns/credentials/examples/v2" ], "id": "https://example.com/credentials/23894672394", "type": [ "VerifiableCredential", "EmployeeIdCredential" ], "issuer": "did:example:12345", "validFrom": "2024-04-05T14:27:42Z", "credentialStatus": [ { "id": "http://localhost:3000/credentials/99914b932bd37a50b983c5e7c90ae93b#11597", "type": "BitstringStatusListEntry", "statusPurpose": "revocation", "statusListIndex": "11597", "statusSize": 1, "statusListCredential": "http://localhost:3000/credentials/99914b932bd37a50b983c5e7c90ae93b", "statusMessage": [ { "status": "0x0", "message": "unset" }, { "status": "0x1", "message": "set" } ] } ], "credentialSubject": { "id": "did:example:6789", "type": "Person", "employeeId": "A-123456" } } ``` **Option: StatusList2021** ```json { "id": "http://localhost:3000/credentials/99914b932bd37a50b983c5e7c90ae93b#11597", "type": "StatusList2021", "statusPurpose": "revocation", "statusListIndex": "11597", "statusSize": 1, "statusListCredential": "http://localhost:3000/credentials/99914b932bd37a50b983c5e7c90ae93b", "statusMessage": [ { "status": "0x0", "message": "unset" }, { "status": "0x1", "message": "set" } ] } ``` **Example of a holder credential with Status** ```json { "@context": [ "https://www.w3.org/ns/credentials/v2", "https://www.w3.org/ns/credentials/examples/v2" ], "id": "https://example.com/credentials/23894672394", "type": [ "VerifiableCredential", "EmployeeIdCredential" ], "issuer": "did:example:12345", "validFrom": "2024-04-05T14:27:42Z", "credentialStatus": { "id": "http://localhost:3000/credentials/99914b932bd37a50b983c5e7c90ae93b#11597", "type": "StatusList2021Entry", "statusPurpose": "revocation", "statusListIndex": "11597", "statusSize": 1, "statusListCredential": "http://localhost:3000/credentials/99914b932bd37a50b983c5e7c90ae93b", "statusMessage": [ { "status": "0x0", "message": "unset" }, { "status": "0x1", "message": "set" } ] }, "credentialSubject": { "id": "did:example:6789", "type": "Person", "employeeId": "A-123456" } } ``` **Option: RevocationList2020** ```json { "id": "http://localhost:3000/credentials/99914b932bd37a50b983c5e7c90ae93b#11597", "type": "RevocationList2020", "statusPurpose": "revocation", "statusListIndex": "11597", "statusSize": 1, "statusListCredential": "http://localhost:3000/credentials/99914b932bd37a50b983c5e7c90ae93b", "statusMessage": [ { "status": "0x0", "message": "unset" }, { "status": "0x1", "message": "set" } ] } ``` **Example of a holder credential with Status** ```json { "@context": [ "https://www.w3.org/ns/credentials/v2", "https://www.w3.org/ns/credentials/examples/v2" ], "id": "https://example.com/credentials/23894672394", "type": [ "VerifiableCredential", "EmployeeIdCredential" ], "issuer": "did:example:12345", "validFrom": "2024-04-05T14:27:42Z", "credentialStatus": { "id": "http://localhost:3000/credentials/99914b932bd37a50b983c5e7c90ae93b#11597", "type": "RevocationList2020Status", "statusPurpose": "revocation", "statusListIndex": "11597", "statusSize": 1, "statusListCredential": "http://localhost:3000/credentials/99914b932bd37a50b983c5e7c90ae93b", "statusMessage": [ { "status": "0x0", "message": "unset" }, { "status": "0x1", "message": "set" } ] }, "credentialSubject": { "id": "did:example:6789", "type": "Person", "employeeId": "A-123456" } } ``` **Option: TokenStatusList** ```json { "status_list": { "idx": 11597, "uri": "http://localhost:3000/credentials/99914b932bd37a50b983c5e7c90ae93b" } } ``` **Example of a holder credential with Status** ```json { "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:9cd6c1eb-4bb4-4128-82e0-520c527d23ff", "iat": 1741013911, "nbf": 1741013911, "exp": 1772549911, "iss": "http://waltid.enterprise.localhost:3000/v1/waltid.tenant1.issuer-service-2/issuer-service-api/openid4vc/draft13", "cnf": { "jwk": { "kty": "OKP", "crv": "Ed25519", "kid": "zxIo0ognSmR6GCy9rJblqE7UbvcQnt2vMPMzsuXXBcs", "x": "KzUaZhZaRqp2C_fEieU1VTUPF1XqJxqTIxSQgxRKJcs" } }, "vct": "http://waltid.enterprise.localhost:3000/v1/waltid.tenant1.issuer-service-2/issuer-service-api/openid4vc/draft13/identity_credential", "_sd": [ "WwLfN74YKWfsPK8jEqDIFESZ-dwhmXWyQeaRfFddZZM" ], "status": { "status_list": { "idx": 11597, "uri": "http://localhost:3000/credentials/99914b932bd37a50b983c5e7c90ae93b" } } } ```
## Check Status List Capacity Monitor the capacity of your status lists to ensure you don't run out of entries. Each status list can hold up to 32,000 entries. **Option: CURL** Endpoint: `/v1/{target}/credential-status-service-api/status-credential/capacity` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Credential%20Status%20Service) **Example Request** ```bash curl -X 'GET' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/credential-status-service-api/status-credential/capacity' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' ``` --- **Path Parameters** - `orgID`: Your organization's Base URL. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + credential status service + credential status config ID (`{organizationID}.{tenantID}.{credentialStatusServiceID}.{statusCredentialConfigID}`), e.g. `waltid.tenant1.status-service1.revocation-config` **Option: RESPONSE** **Response** ```json { "statusListId": "waltid.tenant1.status-service1.revocation-config", "usedEntries": 1500, "maxEntries": 32000, "availableEntries": 30500, "usagePercent": 4.69, "status": "HEALTHY" } ``` **Status Values** | Status | Description | |--------|-------------| | `HEALTHY` | Usage below warning threshold | | `WARNING` | Usage at or above warning threshold | | `CRITICAL` | Usage at or above critical threshold | | `FULL` | Status list has reached maximum capacity | ## Update A Credential Status **Option: CURL** Endpoint: `/v1/{target}/credential-status-service-api/status-credential/status/update` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Credential%20Status%20Service/put_v1__target__credential_status_service_api_status_credential_status_update) **Example Request** ```bash curl -X 'PUT' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/credential-status-service-api/status-credential/status/update' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "session": "waltid.tenant1.issuer-service.session1", "status": "0x1" }' ``` --- **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + credential status service + credential status config ID in which to create a new status list entry. `{organizationID}.{tenantID}.{credentialStatusServiceID}.{statusCredentialConfigID}`), e.g. `waltid.tenant1.status-service1.revocation-config` --- **Body** ```json { "session": "waltid.tenant1.issuer-service.session1", "status": "0x1" } ``` **Body Parameters** - `session`: The full path of the issuance session corresponding to the credential-offer-url, e.g. `waltid.tenant1.issuer-service.session1`. - `status`: The string representing the hexadecimal value of the updated status, e.g. `0x1`. The session id is available: - at issuance (by parsing the credential offer URL) - during issuance (via callback mechanism) - post-issuance by retrieving it from the list of events through the /v1/events/query endpoint. **Info:** **Updating Multiple Status Lists** When a credential has multiple status lists (only supported for BitstringStatusList), you can update any of the status lists associated with the credential by specifying the target status list in the `target` path parameter. The `target` should be the resource identifier of the specific status list you want to update (e.g. `waltid.tenant1.credential-status-service1.config1`).

Each status list can be updated independently using the same session ID, allowing you to manage different status purposes (such as revocation and suspension) separately. **Option: RESPONSE** **Response** - `200` - Status updated successfully. ```json { "successful": true } ``` - `400` - Bad Request Exception: ```json { "exception": true, "status": "Bad Request Exception", "code": "400", "message": "Expected index (Index(value=11597)) for status (Status(type=0x1, message=set)), but none found" } ``` ```json { "exception": true, "status": "Bad Request Exception", "code": "400", "message": "Failed to find a unique record for 99914b932bd37a50b983c5e7c90ae93b1" } ``` ```json { "exception": true, "status": "Bad Request Exception", "code": "400", "message": "Invalid status has been provided: StatusValue(value=0x10). Expected - unset, set" } ``` ```json { "exception": true, "status": "Bad Request Exception", "code": "400", "message": "Status size must be one of: [1, 2, 4, 8]" } ``` ```json { "exception": true, "status": "Bad Request Exception", "code": "400", "message": "Missing or multiple values for valid status" } ``` ```json { "exception": true, "status": "Bad Request Exception", "code": "400", "message": "Missing or multiple values for invalid status" } ``` ```json { "exception": true, "status": "Bad Request Exception", "code": "400", "message": "Missing or multiple values for suspended status" } ``` ```json { "exception": true, "status": "Bad Request Exception", "code": "400", "message": "No (or multiple) index(es) found in session: waltid.tenant1.issuer-service.session1 for status: waltid.tenant1.status-service1.revocation-config" } ``` -------------------------------------------------------------------------------- PAGE: Full API Reference URL: https://docs.walt.id/enterprise-stack/services/credential-status-service/full-api-reference RAW: https://docs.walt.id/enterprise-stack/services/credential-status-service/full-api-reference.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Full API Reference Find it [here](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Credential%20Status%20Service) -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/services/did-store-service/overview RAW: https://docs.walt.id/enterprise-stack/services/did-store-service/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview The DID Store Service provides **storage functionalities for managing DIDs & DID documents**, including storing, listing, viewing, and deleting them. It can be accessed directly via the Enterprise API or indirectly as a dependency of other services within the Enterprise Stack. **Services utilizing the DID Store as a dependency:** - [**DID Service**](https://docs.walt.id/enterprise-stack/services/did-service/overview.md): When configuring a DID Service, one or more DID Stores can be specified as dependencies. As a result, DIDs created by the DID Service are automatically stored in the associated DID Store(s). - [**Wallet Service**](https://docs.walt.id/enterprise-stack/services/wallet-service/overview.md): The Wallet Service can utilize the DID Store as a source for DIDs. Instead of explicitly providing a DID as a request parameter, the Wallet Service can reference DIDs already stored in a DID Store. ## Get Started - [Setup](https://docs.walt.id/enterprise-stack/services/did-store-service/setup.md) - How to configure & setup a DID store service -------------------------------------------------------------------------------- PAGE: Setup URL: https://docs.walt.id/enterprise-stack/services/did-store-service/setup RAW: https://docs.walt.id/enterprise-stack/services/did-store-service/setup.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Setup We will setup a DID store service inside of a tenant. If you don't have a tenant yet, you can learn how to create one [here](https://docs.walt.id/enterprise-stack/administration/tenants/create.md). **Option: CURL** Endpoint: `/v1/{target}/resource-api/services/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Resource/post_v1__target__resource_api_services_create) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "did-store" }' ``` **Body** ```json { "type": "did-store" } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant in which to create the new DID service and the service's ID (`{organizationID}.{tenantID}.[NewDIDServiceID]`), e.g. `waltid.tenant1.did-store1` **Body Parameters** - `type`: _serviceType_ - Specifies the type of service to create. In our case `did-store` --- **Response Codes** - `201` - Service created successfully. Please see our SWAGGER which includes all the available endpoints for adding, updating and deleting DIDs from the DID Store here: [DID Store Service API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20DID%20Store%20Service) -------------------------------------------------------------------------------- PAGE: Full API Reference URL: https://docs.walt.id/enterprise-stack/services/did-store-service/full-api-reference RAW: https://docs.walt.id/enterprise-stack/services/did-store-service/full-api-reference.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Full API Reference Find it [here](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20DID%20Store%20Service) -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/services/credential-store-service/overview RAW: https://docs.walt.id/enterprise-stack/services/credential-store-service/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview The Credential Store Service offers various storage functionalities for different types of credentials, including W3C, SD-JWT VC, and mDL. It allows users to store, list, view, and delete these credentials. Also, it offers various query futures to find and match credentials for a specific presentation definition. The service can be accessed directly through the Enterprise API or indirectly as a part of other services within the Enterprise Stack. **Services utilizing the Credential Store as a dependency:** - [**Wallet Service**](https://docs.walt.id/enterprise-stack/services/wallet-service/overview.md): The Wallet Service can utilize the Credential Store to both retrieve credentials from (e.g. during a presentation flow; instead of having to provide them in the API request), and to store credentials to (e.g. received credentials from an issuance flow; instead of just returning them in the endpoint response). ## Get Started - [Setup](https://docs.walt.id/enterprise-stack/services/credential-store-service/setup.md) - How to configure & setup a Credential store service -------------------------------------------------------------------------------- PAGE: Setup URL: https://docs.walt.id/enterprise-stack/services/credential-store-service/setup RAW: https://docs.walt.id/enterprise-stack/services/credential-store-service/setup.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Setup We will setup a Credential store service inside of a tenant. If you don't have a tenant yet, you can learn how to create one [here](https://docs.walt.id/enterprise-stack/administration/tenants/create.md). **Option: CURL** Endpoint: `/v1/{target}/resource-api/services/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%2F%20Resource/post_v1__target__resource_api_services_create) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "credential-store" }' ``` **Body** ```json { "type": "credential-store" } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant in which to create the new Credential Store service and the service's ID (`{organizationID}.{tenantID}.[NewCredentialServiceID]`), e.g. `waltid.tenant1.credential-store1` **Body Parameters** - `type`: _serviceType_ - Specifies the type of service to create. In our case `credential-store` --- **Response Codes** - `201` - Service created successfully. -------------------------------------------------------------------------------- PAGE: Full API Reference URL: https://docs.walt.id/enterprise-stack/services/credential-store-service/full-api-reference RAW: https://docs.walt.id/enterprise-stack/services/credential-store-service/full-api-reference.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Full API Reference Find it [here](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Credential%20Store%20Service) -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/services/did-registry/overview RAW: https://docs.walt.id/enterprise-stack/services/did-registry/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview A **DID Web Registry** facilitates the distribution of `did:web` identifiers and their associated public keys, serving as a **trust anchor** within decentralized identity ecosystems. The **DID Registry Service** in the walt.id Enterprise Stack provides a flexible, multi-tenant solution for creating and managing DID Web Registries. ## Capabilities The DID Registry Service enables: - Flexible hosting configurations for various domain and namespace combinations. - Listing of all registered `did:web` identifiers. - Resolution of a specific `did:web` into its `did.json` document. **Note:** Note that this service does not generate or persist DIDs directly. reference. Use [DID Service](https://docs.walt.id/enterprise-stack/services/did-service/overview.md) in combination with the [DID Store Service](https://docs.walt.id/enterprise-stack/services/did-store-service/overview.md) for persisting DIDs. ## Service Dependencies The diagram below illustrates how the services interact: ![DID Registry Dependencies](https://docs.walt.id/img/enterprise-stack/services/DID-Registry-Service-Dependencies.png) - **DID Registry Service**: Hosts the DIDs. Depends on the **DID Store Service**, which persists them. - **DID Service**: Creates DIDs and stores them in the DID Store Service. Uses the **KMS Service** for key operations. - **DID Hosting Alias Service** (optional): Allows tenants to use custom domains instead of the default enterprise stack domain. ## DID Registry Configuration The DID Registry Service supports two use cases: ### 1. Use Case: Organization Level DID:WEB Registries - Registry bound to the **organization** within the walt.id Enterprise Stack. - DIDs are scoped across the entire organization (not limited to individual tenants). - Best suited for entities managing multiple tenants in one stack. DIDs will have the pattern: `did:web:{sub-domain}.{domain]:{user} (e.g. did:web:org1.enterprise.com:alice)`. More information regarding [Organization Level DID:WEB Registries](https://docs.walt.id/enterprise-stack/services/did-registry/organization-level-did.md) ### 2. Use Case: Tenant Level DID:WEB Registries - Registry bound to a **tenant or sub-tenant**. - DIDs are scoped to that specific tenant. - Useful when isolation between tenants is required. DIDs will have the pattern: `did:web:{sub-domain}.{domain]:{tenant}:{user} (e.g. did:web:org1.enterprise.com:tenant1:alice)`. More information regarding [Tenant Level DID:WEB Registries](https://docs.walt.id/enterprise-stack/services/did-registry/tenant-level-did.md) ## Service & Web Resource Hierarchy The diagram below shows how Enterprise Stack resources map to web resources and how `did:web` identifiers are constructed: ![DID Web Registry Hierarchy](https://docs.walt.id/img/enterprise-stack/services/DID-Web-Registry-Single-Domain-Hierarchy.png) 1. **Organization** – The root resource in the Enterprise Stack. By default, the organization name is prefixed to the deployment domain (e.g., `org.enterprise.domain.com`). 2. **Tenant** – Addressed by the tenant name as a path parameter in the URL. 3. **DID Registry** – A resource within a tenant, addressed via an additional path parameter. 4. **DIDs** – Created by the DID Service. Each DID receives a unique path for resolution. ## Hosting Alias Service The **Hosting Alias Service** can optionally map a custom domain to an organization or (sub-)tenant. For example, the domain `tenant.com` can be bound to a tenant in the Enterprise Stack. DIDs for this tenant then follow the pattern: `did:web:tenant.com:{user} (e.g. did:web:tenant.com:alice)`. -------------------------------------------------------------------------------- PAGE: Setup URL: https://docs.walt.id/enterprise-stack/services/did-registry/setup RAW: https://docs.walt.id/enterprise-stack/services/did-registry/setup.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Setup We will set up a DID Registry Service inside a tenant. If you don't have a tenant yet, you can learn how to create one [here](https://docs.walt.id/enterprise-stack/administration/tenants/create.md). ## Create the DID Registry Service **Option: CURL** Endpoint: `/v1/{target}/resource-api/services/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Resource/post_v1__target__resource_api_services_create) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "did-registry" }' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant in which to create the new DID Registry service and the service's ID (`{organizationID}.{tenantID}.[NewDidRegistryServiceID]`), e.g. `waltid.tenant1.didregistry` **Body Parameters** - `type`: _serviceType_ - Specifies the type of service to create. In our case `did-registry` --- **Response Codes** - `201` - Service created successfully. ## Service Dependencies The DID Registry relies on the DID Store Service to retrieve and manage `DID:web` identifiers. Specifically: - When a DID:web is registered via the DID Store, it becomes accessible via the DID Registry. - The DID Registry queries the DID Store to list registered `DID:web` identifiers. - The DID Registry provides a resolution endpoint `(did.json)` to return the DID document of a `DID:web`. ## Configuration To add the DID Store as a dependency to the DID registry service, use the following API request: ```bash curl -X 'POST' \ 'http://{orgID}.enterprise-sandbox.waltid.dev/v1/waltid.users.didregistry/did-registry-service-api/dependencies/add' \ -H 'accept: */*' \ -H 'Content-Type: application/json' \ -d 'waltid.users.didstore' ``` This configuration ensures that the DID Registry can efficiently access and manage registered DID:web identifiers. -------------------------------------------------------------------------------- PAGE: Organization-level DIDs URL: https://docs.walt.id/enterprise-stack/services/did-registry/organization-level-did RAW: https://docs.walt.id/enterprise-stack/services/did-registry/organization-level-did.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Organization-level DIDs The DID Registry Service can manage organization-level DIDs. These DIDs are scoped to the entire organization and are not limited to a specific tenant. They are useful for representing entities that manage or oversee multiple tenants within the Enterprise stack. **Example:** `did:web:{orgID}.enterprise-sandbox.waltid.dev:alice` **Accessible at:** `https://{orgID}.enterprise-sandbox.waltid.dev/alice/did.json`. Organization-level DIDs are used for entities that manage or oversee tenants within the Enterprise stack. ## Create Organization-Level DID registry service **Option: CURL** Endpoint: `/v1/{target}/resource-api/services/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Resource/post_v1__target__resource_api_services_create) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "did-registry" }' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization in which to create the new organization level DID Registry service , e.g. `waltid.didregistry` **Body Parameters** - `type`: _serviceType_ - Specifies the type of service to create. In our case `did-registry` --- **Response Codes** - `201` - Service created successfully. ## Setup root DID Registry **Option: CURL** Endpoint: `/v1/organization/update-config` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Organization/post_v1_organization_update_config) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/organization/update-config' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "rootDidRegistry": "waltid.didregistry" }' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. **Body Parameters** - `rootDidRegistry`: _resourceIdentifier_ - The ID of the DID Registry Service to be used for organization-level DIDs. e.g. `waltid.didregistry` --- **Response Codes** - `200` - Configuration updated successfully. -------------------------------------------------------------------------------- PAGE: Tenant-Level DIDs URL: https://docs.walt.id/enterprise-stack/services/did-registry/tenant-level-did RAW: https://docs.walt.id/enterprise-stack/services/did-registry/tenant-level-did.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Tenant-Level DIDs The DID Registry Service is designed to work also with tenant-level DIDs. This means that the DIDs it manages are scoped to a specific tenant within the organization. When you create a DID using the DID Service, it will be associated with the tenant in which the DID Registry Service is set up. **Example:** `did:web:{orgID}.enterprise-sandbox.waltid.dev:{tenant}:bob` **Accessible At:** `https://{orgID}.enterprise-sandbox.waltid.dev/{tenant}/bob/did.json` These DIDs represent individual tenants (e.g., users) under an organization. ## Create a New Tenant with configured DID Registry Service **Option: CURL** Endpoint:`/v1/{target}/resource-api/tenants/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Resource/post_v1__target__resource_api_tenants_create) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/tenants/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "configuration": { "didRegistry": "didregistry" } }' ``` **Body** ```json { "configuration": { "didRegistry": "didregistry" } } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization in which to create the new tenant and the tenant's ID (`{organizationID}.[YourID]`), e.g. `waltid.users` **Body Parameters** - `configuration`: _Object_ - Configuration object for the tenant. - `didRegistry`: _String_ - The ID of the DID Registry service to be associated with the tenant. --- **Response Codes** - `201` - Tenant created successfully. ## Verify Configuration **Option: CURL** Endpoint: `/v1/{target}/tenant-api/tenants/config/view` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Tenant%20Management/get_v1__target__tenant_api_tenants_config_view) ```bash curl -X GET http://{orgID}.enterprise-sandbox.waltid.dev/v1/{tenant}/tenant-api/tenants/config/view \ -H 'accept: application/json' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization in which to create the new tenant and the tenant's ID (`{organizationID}.[YourID]`), e.g. `waltid.users` **Response:** ```json { "didRegistry": "{orgID}.{tenant}.didregistry" } ``` `didRegistry`: _String_ - The ID of the DID Registry service that will be associated with the tenant. ## Create the DID Registry Service **Option: CURL** Endpoint: `/v1/{target}/resource-api/services/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Resource/post_v1__target__resource_api_services_create) ⚠️ **Note:** The did registry you are creating in the tenant must exactly have the name used in the config. **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "did-registry" }' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant in which to create the new DID Registry service and the service's ID (`{organizationID}.{tenantID}.[PreviousDidRegistry]`), e.g. `waltid.users.didregistry` **Body Parameters** - `type`: _serviceType_ - Specifies the type of service to create. In our case `did-registry` --- **Response Codes** - `201` - Service created successfully. -------------------------------------------------------------------------------- PAGE: DID WEB Registry Setup Instructions URL: https://docs.walt.id/enterprise-stack/services/did-registry/did-web-registry-setup-guide RAW: https://docs.walt.id/enterprise-stack/services/did-registry/did-web-registry-setup-guide.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # DID WEB Registry Setup Instructions This guide shows all requests that are required to initiate a DID Web Registry in combination with the Enterprise API Gateway. The Enterprise API Gateway is an optional component that shields the main Enterprise API from public access. Only dedicated routs such as the endpoint for resolving did:webs, are open. Furthermore, custom domains would point to the Gateway, rather than the privately hosted Enterprise API. In the following example, the Enterprise API Gateway is hosted at the domain ```enterprise.test.waltid.cloud```. The subdomain ```waltid``` is the name of the organization. You would need to change domain- and organization-name according to your setup (See [Organization Setup](https://docs.walt.id/enterprise-stack/administration/organizations/create.md)) ## Setup ### Create Tenant Create a new tenant with a configured did-web registry. In this example the tenant name is *did-registry-tenant*. You must replace this part of all the following URLs. Note that the did-web registry name must be the same as the one used later when creating the did-web registry service. Of course, the tenant configuration could alos be updated afterwards. ``` curl -w "%{http_code}" -X'POST' \ 'https://waltid.enterprise.test.waltid.cloud/v1/did-registry-tenant/resource-api/tenants/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourBearerToken}' \ -H 'Content-Type: application/json' \ -d '{ "name": "DID Registry Example Tenant", "configuration": { "didRegistry": "waltid.did-registry-tenant.did-web-registry" } }' ``` ### Create DID Store ``` curl -w "%{http_code}" -X 'POST' \ 'https://waltid.enterprise.test.waltid.cloud/v1/did-registry-tenant.did-store/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourBearerToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "did-store" }' ``` ### Create DID Service ``` curl -w "%{http_code}" -X 'POST' \ 'https://waltid.enterprise.test.waltid.cloud/v1/did-registry-tenant.did-service/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourBearerToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "did" }' ``` ### Create Service dependency ``` curl -w "%{http_code}" -X'POST' \ 'https://waltid.enterprise.test.waltid.cloud/v1/did-registry-tenant.did-service/did-service-api/dids/dependencies/add' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourBearerToken}' \ -H 'Content-Type: application/json' \ -d 'waltid.did-registry-tenant.did-store' ``` ### Create the DID Web Registry Service ``` curl -w " %{http_code}" -X'POST' \ 'https://waltid.enterprise.test.waltid.cloud/v1/did-registry-tenant.did-web-registry/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourBearerToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "did-registry" }' ``` ### Adding the DID Store as dependency to the DID Web Registry ``` curl -w " %{http_code}" -X'POST' \ 'https://waltid.enterprise.test.waltid.cloud/v1/did-registry-tenant.did-web-registry/did-registry-service-api/dependencies/add' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourBearerToken}' \ -H 'Content-Type: application/json' \ -d 'waltid.did-registry-tenant.did-store' ``` ### Create Host Alias for Tenant ``` curl -X 'POST' \ 'https://waltid.enterprise.test.waltid.cloud/v1/waltid.registry-host-alias/host-alias-api/host-aliases/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourBearerToken}' \ -H 'Content-Type: application/json' \ -d '{ "domain": "registry.test.waltid.cloud" }' ``` ### Create a KMS ``` curl -w " %{http_code}" -X 'POST' \ 'https://waltid.enterprise.test.waltid.cloud/v1/did-registry-tenant.kms/resource-api/services/create' \ -H 'accept: */*' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer {yourBearerToken}' \ -d '{ "type": "kms" }' ``` ### Create a JWK Key ``` curl -X 'POST' \ 'https://waltid.enterprise.test.waltid.cloud/v1/did-registry-tenant.kms/kms-service-api/keys/generate' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer {yourBearerToken}' \ -d '{ "backend": "jwk", "keyType": "secp256r1" }' ``` # Creating DID Webs: Finally, DIDs can be created by calling the following endpoint ``` curl -w " %{http_code}" -X'POST' \ 'https://waltid.enterprise.test.waltid.cloud/v1/did-registry-tenant.did-service/did-service-api/dids/create/web' \ -H 'Authorization: Bearer {yourBearerToken}' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -d '{ "type": "single_key_id", "keyId": "waltid.did-registry-tenant.kms.5DeSEvJfA5-UimVwAtMhUcikL1chK-A8TZFWT9e2zBw", "domain": "registry.test.waltid.cloud", "path" : "did-registry-tenant/max" }' ``` The created DID WEB is routed the following way: **Note:** Note, the Gateway is an optional component, where a custom domain would point to. ``` did:web:registry.test.waltid.cloud:did-registry-tenant:max -> Gatway: https://registry.test.waltid.cloud/did-registry-tenant:max:did.json - should not be done -> Enterprise API: https://registry.test.waltid.cloud/did-registry-tenant/max/did.json -> Internal endpoint: https://waltid.enterprise.test.waltid.cloud/v1/waltid.did-registry-tenant.did-web-registry/did-registry-service-api/registry/registry.test.waltid.cloud:did-registry-tenant:max/did.json ``` -------------------------------------------------------------------------------- PAGE: External DID Web Hosting URL: https://docs.walt.id/enterprise-stack/services/did-registry/external-did-web-hosting RAW: https://docs.walt.id/enterprise-stack/services/did-registry/external-did-web-hosting.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # External DID Web Hosting This guide explains how to make DIDs available at a custom domain (e.g., dids.waltid.org) instead of the default enterprise domain. ## Overview By default, DIDs in the walt.id Enterprise Stack are hosted at the enterprise domain (e.g., `waltid.enterprise.cloud`). However, you may want to make these DIDs available at your own custom domain (e.g., `dids.waltid.org`). This guide explains how to set up external DID web hosting to achieve this. ## Prerequisites - Access to a walt.id Enterprise Stack organization - A tenant with DID registry service - A custom domain that you control (e.g., `dids.waltid.org`) - Access to configure DNS settings for your custom domain ## Step 1: Configure Organization-Level Root DID Registry Before configuring the tenant-level DID registry, you need to set up an organization-level root DID registry: 1. **Create Organization-Level DID Registry Service**: **Option: CURL** Endpoint: `/v1/{target}/resource-api/services/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%2F%20Resource/post_v1__target__resource_api_services_create) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise.cloud/v1/{target}/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "did-registry" }' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `waltid`, your default Base URL will be `waltid.enterprise.cloud`. - `target`: _resourceIdentifier_ - The target indicates the organization in which to create the new organization level DID Registry service, e.g. `waltid.tenant.didregistry` **Body Parameters** - `type`: _serviceType_ - Specifies the type of service to create. In our case `did-registry` 2. **Set Up Root DID Registry**: **Option: CURL** Endpoint: `/v1/organization/update-config` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Organization/post_v1_organization_update_config) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise.cloud/v1/organization/update-config' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "rootDidRegistry": "waltid.tenant.didregistry" }' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `waltid`, your default Base URL will be `waltid.enterprise.cloud`. **Body Parameters** - `rootDidRegistry`: _resourceIdentifier_ - The ID of the DID Registry Service to be used for organization-level DIDs, e.g. `waltid.tenant.didregistry` ## Step 2: Configure Tenant-Level DID Registry First, ensure that your tenant has the DID registry service properly configured: **Option: CURL** Endpoint:`/v1/{target}/tenant-api/tenants/config/update` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/TenantManagement/post_v1__target__tenant_api_tenants_config_update) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise.cloud/v1/{target}/tenant-api/tenants/config/update' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "didRegistry": "waltid.tenant.didregistry" }' ``` **Body** ```json { "didRegistry": "waltid.tenant.didregistry" } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `waltid`, your default Base URL will be `waltid.enterprise.cloud`. - `target`: _resourceIdentifier_ - The target indicates the tenant for which to update the configuration (`{organizationID}.{tenantID}`), e.g. `waltid.tenant` **Body Parameters** - `didRegistry`: _String_ - The ID of the DID Registry service to be associated with the tenant. This should be the fully qualified ID of your DID registry service. ## Step 3: Create Host Alias for Tenant Create a host alias for your tenant that points to your custom domain: **Option: CURL** Endpoint:`/v1/{target}/host-alias-api/host-aliases/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%2F%20Resource/post_v1__target__resource_api_host_aliases_create) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise.cloud/v1/{target}/host-alias-api/host-aliases/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "domain": "dids.waltid.org" }' ``` **Body** ```json { "domain": "dids.waltid.org" } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `waltid`, your default Base URL will be `waltid.enterprise.cloud`. - `target`: _resourceIdentifier_ - The target indicates the tenant for which to create the host alias (`{organizationID}.{tenantID}.{host_alias_id}), e.g. `waltid.tenant.my_alias` **Body Parameters** - `domain`: _String_ - The custom domain that you want to use for your tenant. This is the domain where your DIDs will be available. ## Step 4: Configure DNS and Reverse Proxy (Customer Side) On the customer side, you need to configure your DNS and set up a reverse proxy to point your custom domain to the enterprise stack server: 1. **DNS Configuration**: - Create a DNS record for your custom domain (e.g., `dids.waltid.org`) pointing to your reverse proxy server. 2. **Reverse Proxy Setup**: - Set up a reverse proxy (e.g., Nginx, Apache, Traefik) on your server. - Configure SSL/TLS for your custom domain. - Configure the reverse proxy to forward requests from your custom domain to the enterprise stack server. **Example Nginx Configuration**: ```nginx server { listen 443 ssl; server_name dids.waltid.org; ssl_certificate /path/to/your/certificate.crt; ssl_certificate_key /path/to/your/private.key; location / { proxy_pass https://waltid.enterprise.cloud; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } ``` **Example Kubernetes Ingress Configuration**: If you're using Kubernetes, you can set up an Ingress resource to route traffic to your enterprise API service: ```yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: dids-waltid-org-enterprise-ingress annotations: cert-manager.io/cluster-issuer: letsencrypt-prod nginx.ingress.kubernetes.io/use-regex: "true" spec: ingressClassName: "nginx" tls: - hosts: - dids.waltid.org secretName: did-waltid-org-tls rules: - host: dids.waltid.org http: paths: - path: /auth/ pathType: Prefix backend: service: name: waltid-enterprise-api port: name: http - path: /v1/ pathType: Prefix backend: service: name: waltid-enterprise-api port: name: http - path: /.well-known/vct/v1/ pathType: ImplementationSpecific backend: service: name: waltid-enterprise-api port: name: http - path: /swagger pathType: Prefix backend: service: name: waltid-enterprise-api port: name: http - path: /api.json pathType: ImplementationSpecific backend: service: name: waltid-enterprise-api port: name: http - path: /.*/did\.json$ pathType: ImplementationSpecific backend: service: name: waltid-enterprise-api port: name: http - path: /livez pathType: Prefix backend: service: name: waltid-enterprise-api port: name: http - path: /features/ pathType: Prefix backend: service: name: waltid-enterprise-api port: name: http - path: /debug/ pathType: Prefix backend: service: name: waltid-enterprise-api port: name: http ``` This Ingress configuration: - Sets up TLS for the dids.waltid.org domain using cert-manager - Routes various API paths to the waltid-enterprise-api service - Includes a regex path for DID documents (/.*/did\.json$) ## Step 5: Create DID in DID Store Create a DID in the DID Store, making sure to use your custom domain in the domain parameter: **Option: CURL** Endpoint: `/v1/{target}/did-service-api/dids/create/web` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/DID%20Service/post_v1__target__did_service_api_dids_create_web) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise.cloud/v1/{target}/did-service-api/dids/create/web' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -d '{ "type": "single_key_id", "keyId": "waltid.tenant.kms1.secp256r1", "domain": "dids.waltid.org", "path" : "user1" }' ``` **Body** ```json { "type": "single_key_id", "keyId": "waltid.tenant.kms1.secp256r1", "domain": "dids.waltid.org", "path" : "user1" } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `waltid`, your default Base URL will be `waltid.enterprise.cloud`. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + DID service in which to execute the DID creation (`{organizationID}.{tenantID}.{subtenantID}.{didServiceID}`), e.g. `waltid.tenant.did1` **Body Parameters** - `keyId`: _resourceIdentifier_ - Specifies the key for which to generate a DID + DID Document. Please make sure that the key is stored in a KMS service under the same tenant as the DID service. - `domain`: _string_ - The domain name to be used for the DID:web. This should be your custom domain (e.g., `dids.waltid.org`). - `path`: _string_ - The path to be used for the DID:web (e.g., `user1`). **Response** - `201` - DID created successfully. ```json { "did": "did:web:dids.waltid.org:user1", "document": { "context": [ "https://www.w3.org/ns/did/v1", "https://w3id.org/security/suites/jws-2020/v1" ], "id": "did:web:dids.waltid.org:user1", "verificationMethod": [ { "id": "did:web:dids.waltid.org:user1#key1", "type": "JsonWebKey2020", "controller": "did:web:dids.waltid.org:user1", "publicKeyJwk": { "kty": "EC", "crv": "P-256", "kid": "key1", "x": "...", "y": "..." } } ], "assertionMethod": [ "did:web:dids.waltid.org:user1#key1" ], "authentication": [ "did:web:dids.waltid.org:user1#key1" ], "capabilityDelegation": [ "did:web:dids.waltid.org:user1#key1" ], "capabilityInvocation": [ "did:web:dids.waltid.org:user1#key1" ], "keyAgreement": [ "did:web:dids.waltid.org:user1#key1" ] } } ``` ## Step 6: Verify DID Resolution Once you've completed the above steps, your DID should be available to resolve at your custom domain: ``` did:web:dids.waltid.org:user1 ``` You can verify this by accessing the DID document directly at: ``` https://dids.waltid.org/user1/did.json ``` ## Troubleshooting If you're having issues with external DID web hosting, check the following: 1. **Organization-Level DID Registry**: Ensure that your organization has the correct root DID registry configured. 2. **Tenant Configuration**: Ensure that your tenant has the correct DID registry configured. 3. **Host Alias**: Verify that the host alias has been created correctly with your custom domain. 4. **DNS Configuration**: Check that your DNS records are pointing to the correct server. 5. **Reverse Proxy**: Ensure that your reverse proxy is correctly configured to forward requests to the enterprise stack server. 6. **DID Creation**: Make sure you used your custom domain when creating the DID. -------------------------------------------------------------------------------- PAGE: Full API Reference URL: https://docs.walt.id/enterprise-stack/services/did-registry/full-api-reference RAW: https://docs.walt.id/enterprise-stack/services/did-registry/full-api-reference.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Full API Reference Find it [here](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20DID%20Registry%20Service) -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/services/holder-policy-store-service/overview RAW: https://docs.walt.id/enterprise-stack/services/holder-policy-store-service/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview The Holder Policy service offers a powerful mechanism to enforce rules and logic before a credential is received or presented from a wallet. This allows for fine-grained control, ensuring that data sharing meets specific organizational or security requirements when using the silent verification or issuance flows. You can learn more about it [here](https://docs.walt.id/enterprise-stack/services/wallet-service/silent-issuance-verification/overview.md). Policies are evaluated in a prioritized sequence, and their outcome can either allow or block the presentation or issuance flow. This guide details the complete workflow, from setting up the necessary services to defining and enforcing policies during a specific flow. ## Core Concepts **Policy Store**: A dedicated service (policy-store) that manages the lifecycle of your holder policies. It must be created and then [explicitly attached](https://docs.walt.id/enterprise-stack/services/holder-policy-store/setup#attach-the-policy-store-to-a-wallet-service) to a wallet service as a dependency. **Holder Policy**: A rule composed of three key parts: - **Priority:** A number that dictates the order of execution (a lower number means higher priority). - **Check:** The condition to evaluate. This can be a simple check (e.g., `BasicHolderPolicyCheck` for the presence of a claim) or a complex one (e.g., `DcqlHolderPolicyCheck` using a Decentralized Credential Query Language query). - **Action:** The result if the check is true. This can be ALLOW or BLOCK. **Policy Evaluation:** When a flow is initiated with policies enabled, the wallet queries its attached Policy Store. It evaluates policies in order of priority (lowest number first). The first policy that matches its check condition will have its action (ALLOW or BLOCK) executed, and the evaluation process stops. ## Getting Started - [Setup](https://docs.walt.id/enterprise-stack/services/holder-policy-store-service/setup.md) - How to configure & set up a Holder Policy Store service. - [Attach to Wallet Service](https://docs.walt.id/enterprise-stack/services/holder-policy-store-service/setup.md#attach-the-policy-store-to-a-wallet-service) - How to attach the Policy Store to a wallet service. - [Create a Holder Policy](https://docs.walt.id/enterprise-stack/services/holder-policy-store-service/policy-creation.md#create-a-holder-policy) - How to use the Holder Policy Store service to manage policies. - [Use with Wallet Service](https://docs.walt.id/enterprise-stack/services/holder-policy-store-service/policy-usage.md#usage-with-wallet-service) - How to use the Policy Store with a wallet service. -------------------------------------------------------------------------------- PAGE: Setup URL: https://docs.walt.id/enterprise-stack/services/holder-policy-store-service/setup RAW: https://docs.walt.id/enterprise-stack/services/holder-policy-store-service/setup.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Setup We will setup a holder policy store service inside of a tenant. If you don't have a tenant yet, you can learn how to create one [here](https://docs.walt.id/enterprise-stack/administration/tenants/create.md). **Option: CURL** Endpoint: `/v1/{target}/resource-api/services/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%2F%20Resource/post_v1__target__resource_api_services_create) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "policy-store" }' ``` **Body** ```json { "type": "policy-store" } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant in which to create the new policy Store service and the service's ID (`{organizationID}.{tenantID}.[NewPolicyStoreId]`), e.g. `waltid.tenant1.policy-store1` **Body Parameters** - `type`: _serviceType_ - Specifies the type of service to create. In our case `policy-store` --- **Response Codes** - `201` - Service created successfully. ## Attach the Policy Store to a Wallet Service To use the Policy Store, it must be attached to a wallet service. This is done by adding the Policy Store as a dependency. **Option: CURL** Endpoint: `/v1/{target}/wallet-service-api/dependencies/add` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Wallet%20Service/post_v1__target__wallet_service_api_dependencies_add) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/wallet-service-api/dependencies/add' \ -H 'accept: */*' \ -H 'Authorization : Bearer { yourToken}' \ -H 'Content-Type: application/json' \ -d 'waltid.tenant1.policy-store1' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + walletId in which to attach the policy store, e.g. `waltid.tenant1.wallet` - **Body Parameters** - `policyStoreId`: _serviceIdentifier_ - The ID of the Policy Store service to attach, e.g. `waltid.tenant1.policy-store1` - --- **Response Codes** - `201` - PolicyStoreServiceReference attached. --- -------------------------------------------------------------------------------- PAGE: Create a Holder Policy URL: https://docs.walt.id/enterprise-stack/services/holder-policy-store-service/policy-creation RAW: https://docs.walt.id/enterprise-stack/services/holder-policy-store-service/policy-creation.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Create a Holder Policy **Option: CURL** Endpoint: `/v1/{target}/policy-store-service-api/policies/add` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Wallet%20Service/post_v1__target__wallet_service_api_dependencies_add) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/policy-store-service-api/policies/add' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "_id": "waltid.tenant1.policy-store1.policy1", "policy": { "priority": 1, "description": "This policy checks for a name claim in the wallet and block the presentation if it exists.", "direction": null, "check": { "type": "basic", "claims_present": ["name"] }, "action": "BLOCK" } }' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + serviceId in which to create the new policy and the policy's ID (`{organizationID}.{tenantID}.{PolicyStore}.[NewPolicyId]`), e.g. `waltid.tenant1.policy-store1.policy1` - **Body Parameters** - `_id`: _policyId_ - The unique identifier for the policy, typically in the format `{organizationID}.{tenantID}.{PolicyStore}.[NewPolicyId]` - `policy`: _holderPolicy_ - The policy object containing: - `priority`: _integer_ - The priority of the policy (lower numbers have higher priority). - `description`: _string_ - A description of the policy. - `direction`: _string_ - The direction of the policy, can be `null` for no specific direction or "RECEIVE" | "PRESENT" for a specific direction. - `check`: _holderPolicyCheck_ - The check to perform, which can be a basic check or a DCQL query. - For a basic check, use: ```json { "type": "basic", "claims_present": ["claim1", "claim2"] } ``` - For a DCQL check, use: ```json { "type": "dcql", "dcql_query": { "credentials": [ { "id": "example_jwt_vc", "format": "JWT_VC_JSON", "meta": { "type": "W3cCredentialMeta", "typeValues": [["OpenBadgeCredential", "VerifiableCredential"]] }, "claims": [ { "path": ["credentialSubject", "achievement"] } ] } ] } } ``` - `action`: _string_ - The action to take if the check passes, either `ALLOW` or `BLOCK`. - --- **Response Codes** - `201` - Policy successfully created. --- -------------------------------------------------------------------------------- PAGE: Usage with Wallet Service URL: https://docs.walt.id/enterprise-stack/services/holder-policy-store-service/policy-usage RAW: https://docs.walt.id/enterprise-stack/services/holder-policy-store-service/policy-usage.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Usage with Wallet Service Once the Policy Store is [set up and attached](https://docs.walt.id/enterprise-stack/services/holder-policy-store-service/setup.md) to a wallet service, it can be used during the verification flow and or the issuance flow. When a wallet attempts to present or receive a credentials, the policies defined in the Policy Store will be evaluated based on their priority. If a policy's check condition is met, the action specified in the policy will be executed (either ALLOW or BLOCK). This allows for dynamic control over which credentials can be presented based on the defined policies. # Example of a Verification Flow with Policies **Option: CURL** Endpoint: `/v1/{target}/wallet-service-api/credentials/present` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Wallet%20Service/post_v1__target__wallet_service_api_credentials_present) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/wallet-service-api/credentials/present' \ -H 'accept: */*' \ -H 'Content-Type: application/json' \ -d '{ "requestUrl": "openid4vp://...", "keyReference": "org1.tenant.kms.key1", "didReference": "org1.tenant.didstore.did1", "runPolicies": true }' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + wallet which should be used to present the credential (`{organizationID}.{tenantID}.[walletID]`), e.g. `waltid.tenant1.wallet1` - - **Body Parameters** - `requestUrl` _String_ - An OID4VCP Authorization URL. - `keyReference` (optional) _String_ - The resource ID (target) of the key which owns the credential. Key can be stored in a linked KMS store or provided as a static key as described in [the setup section](https://docs.walt.id/enterprise-stack/services/holder-policy-store-service/setup.md). - `didReference` (optional) _String_ - The DID reference of a DID stored in a linked DID Store. - `runPolicies` (optional) _Boolean_ - If set to true, the wallet will run the policies defined in the attached Policy Store during the presentation flow. - --- **Response Codes** - `200` - Presentation received and is being processed. - `400` - Presentation execution was blocked by Holder Policy. --- **Response Example** ```json { "transmission_success" : true, "verifier_response": { "status": "received", "message": "Presentation received and is being processed." } } ``` # Example of a claiming Flow with Policies **Option: CURL** Endpoint: `/v1/{target}/wallet-service-api/credentials/receive` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Wallet%20Service/post_v1__target__wallet_service_api_credentials_receive) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/wallet-service-api/credentials/receive' \ -H 'accept: */*' \ -H 'Content-Type: application/json' \ -d '{ "offerUrl": "openid-credential-offer://issuer.example.org/issue/?credential_offer_uri=https%3A%2F%2Fissuer.example.org%2FcredentialOffer%3Fabc123xyz789", "keyReference": "example.kms.wallet_key", "didReference": "example.didstore.wallet_did", "runPolicies": true }' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + wallet which should be used to present the credential (`{organizationID}.{tenantID}.[walletID]`), e.g. `waltid.tenant1.wallet1` **Body Parameters** - `offerUrl` _String_ - An OID4VCI offer URL. - `keyReference` (optional) _String_ - The resource ID (target) of the key to which the received credential should be bound. - `didReference` (optional) _String_ - The DID reference of a DID stored in a linked DID Store. - `key` (optional) _Object_ - A key object or reference key object, when using an external KMS, to which the credential should be bound to. - `runPolicies` (optional) _Boolean_ - If set to true, the wallet will run the policies defined in the attached Policy Store during the claiming flow. - --- **Response Codes** - `200` - The issued credentials. - `400` - Issuance execution was not allowed by any Holder Policy. --- **Response Example** ```json { "issuanceResult": { "offeredCredential": { "format": "jwt_vc_json", "credential_definition": { "type": [ "VerifiableCredential", "OpenBadgeCredential" ] }, "cryptographic_binding_methods_supported": [ "did" ] }, "credentialResponse": { "format": "jwt_vc_json", "credential": "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaVNuRnlaWE5LUzE5WWIxUkhRVVYyYVhGTlUxRnlaWFpNTFRKSmFEZzBRMnBoY0U1UFdtVlVSaTFNTkNJc0luZ2lPaUpoYVVaRmVYaHFRMVIxWHpaV01UbE9TRmhYUVZSTk4wUmFOelkyVGxGVWIydG1NRkl6UjJKalZIQXdJbjAjSnFyZXNKS19Yb1RHQUV2aXFNU1FyZXZMLTJJaDg0Q2phcE5PWmVURi1MNCIsInR5cCI6IkpXVCIsImFsZyI6IkVkRFNBIn0.eyJpc3MiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaVNuRnlaWE5LUzE5WWIxUkhRVVYyYVhGTlUxRnlaWFpNTFRKSmFEZzBRMnBoY0U1UFdtVlVSaTFNTkNJc0luZ2lPaUpoYVVaRmVYaHFRMVIxWHpaV01UbE9TRmhYUVZSTk4wUmFOelkyVGxGVWIydG1NRkl6UjJKalZIQXdJbjAiLCJzdWIiOiJkaWQ6a2V5OnpEbmFlbmk0SEh2NDY3MUJLZlk4TmJFeUVzelhOVWViU2NFbTlUaHI0YkY5ZnQ4cXMiLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vcHVybC5pbXNnbG9iYWwub3JnL3NwZWMvb2IvdjNwMC9jb250ZXh0Lmpzb24iXSwiaWQiOiJ1cm46dXVpZDpkNTVlZmVlNS1kNDY4LTQ3OTUtYTg1MS0wMjZmMGFjNzMxZWIiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiT3BlbkJhZGdlQ3JlZGVudGlhbCJdLCJuYW1lIjoiSkZGIHggdmMtZWR1IFBsdWdGZXN0IDMgSW50ZXJvcGVyYWJpbGl0eSIsImlzc3VlciI6eyJ0eXBlIjpbIlByb2ZpbGUiXSwibmFtZSI6IkpvYnMgZm9yIHRoZSBGdXR1cmUgKEpGRikiLCJ1cmwiOiJodHRwczovL3d3dy5qZmYub3JnLyIsImltYWdlIjoiaHR0cHM6Ly93M2MtY2NnLmdpdGh1Yi5pby92Yy1lZC9wbHVnZmVzdC0xLTIwMjIvaW1hZ2VzL0pGRl9Mb2dvTG9ja3VwLnBuZyIsImlkIjoiZGlkOmp3azpleUpyZEhraU9pSlBTMUFpTENKamNuWWlPaUpGWkRJMU5URTVJaXdpYTJsa0lqb2lTbkZ5WlhOS1MxOVliMVJIUVVWMmFYRk5VMUZ5WlhaTUxUSkphRGcwUTJwaGNFNVBXbVZVUmkxTU5DSXNJbmdpT2lKaGFVWkZlWGhxUTFSMVh6WldNVGxPU0ZoWFFWUk5OMFJhTnpZMlRsRlViMnRtTUZJelIySmpWSEF3SW4wIn0sImNyZWRlbnRpYWxTdWJqZWN0Ijp7InR5cGUiOlsiQWNoaWV2ZW1lbnRTdWJqZWN0Il0sImFjaGlldmVtZW50Ijp7ImlkIjoidXJuOnV1aWQ6YWMyNTRiZDUtOGZhZC00YmIxLTlkMjktZWZkOTM4NTM2OTI2IiwidHlwZSI6WyJBY2hpZXZlbWVudCJdLCJuYW1lIjoiSkZGIHggdmMtZWR1IFBsdWdGZXN0IDMgSW50ZXJvcGVyYWJpbGl0eSIsImRlc2NyaXB0aW9uIjoiVGhpcyB3YWxsZXQgc3VwcG9ydHMgdGhlIHVzZSBvZiBXM0MgVmVyaWZpYWJsZSBDcmVkZW50aWFscyBhbmQgaGFzIGRlbW9uc3RyYXRlZCBpbnRlcm9wZXJhYmlsaXR5IGR1cmluZyB0aGUgcHJlc2VudGF0aW9uIHJlcXVlc3Qgd29ya2Zsb3cgZHVyaW5nIEpGRiB4IFZDLUVEVSBQbHVnRmVzdCAzLiIsImNyaXRlcmlhIjp7InR5cGUiOiJDcml0ZXJpYSIsIm5hcnJhdGl2ZSI6IldhbGxldCBzb2x1dGlvbnMgcHJvdmlkZXJzIGVhcm5lZCB0aGlzIGJhZGdlIGJ5IGRlbW9uc3RyYXRpbmcgaW50ZXJvcGVyYWJpbGl0eSBkdXJpbmcgdGhlIHByZXNlbnRhdGlvbiByZXF1ZXN0IHdvcmtmbG93LiBUaGlzIGluY2x1ZGVzIHN1Y2Nlc3NmdWxseSByZWNlaXZpbmcgYSBwcmVzZW50YXRpb24gcmVxdWVzdCwgYWxsb3dpbmcgdGhlIGhvbGRlciB0byBzZWxlY3QgYXQgbGVhc3QgdHdvIHR5cGVzIG9mIHZlcmlmaWFibGUgY3JlZGVudGlhbHMgdG8gY3JlYXRlIGEgdmVyaWZpYWJsZSBwcmVzZW50YXRpb24sIHJldHVybmluZyB0aGUgcHJlc2VudGF0aW9uIHRvIHRoZSByZXF1ZXN0b3IsIGFuZCBwYXNzaW5nIHZlcmlmaWNhdGlvbiBvZiB0aGUgcHJlc2VudGF0aW9uIGFuZCB0aGUgaW5jbHVkZWQgY3JlZGVudGlhbHMuIn0sImltYWdlIjp7ImlkIjoiaHR0cHM6Ly93M2MtY2NnLmdpdGh1Yi5pby92Yy1lZC9wbHVnZmVzdC0zLTIwMjMvaW1hZ2VzL0pGRi1WQy1FRFUtUExVR0ZFU1QzLWJhZGdlLWltYWdlLnBuZyIsInR5cGUiOiJJbWFnZSJ9fSwiaWQiOiJkaWQ6a2V5OnpEbmFlbmk0SEh2NDY3MUJLZlk4TmJFeUVzelhOVWViU2NFbTlUaHI0YkY5ZnQ4cXMifSwiaXNzdWFuY2VEYXRlIjoiMjAyNS0wOC0yN1QyMzo0NjowMi43NzQ5NTY2MDBaIiwiZXhwaXJhdGlvbkRhdGUiOiIyMDI2LTA4LTI3VDIzOjQ2OjAyLjc3NDk1NjYwMFoifSwianRpIjoidXJuOnV1aWQ6ZDU1ZWZlZTUtZDQ2OC00Nzk1LWE4NTEtMDI2ZjBhYzczMWViIiwiZXhwIjoxNzg3ODc0MzYyLCJpYXQiOjE3NTYzMzgzNjIsIm5iZiI6MTc1NjMzODM2Mn0.MqUTJA4e_LTHruv7giRBx9Tj9LTmUnnjqnnSsY4-azg02VPjW1QWeanRgU-r5qns_myQuMt3d82nlEUZFixcCg" } }, "stored": [ { "_id": "waltid.tenant1.credentialstore.61e152d2-9c37-4424-9fe8-5484ae286074", "credential": { "type": "vc-w3c_1_1", "disclosables": {}, "credentialData": { "@context": [ "https://www.w3.org/2018/credentials/v1", "https://purl.imsglobal.org/spec/ob/v3p0/context.json" ], "id": "urn:uuid:d55efee5-d468-4795-a851-026f0ac731eb", "type": [ "VerifiableCredential", "OpenBadgeCredential" ], "name": "JFF x vc-edu PlugFest 3 Interoperability", "issuer": { "type": [ "Profile" ], "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", "id": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiSnFyZXNKS19Yb1RHQUV2aXFNU1FyZXZMLTJJaDg0Q2phcE5PWmVURi1MNCIsIngiOiJhaUZFeXhqQ1R1XzZWMTlOSFhXQVRNN0RaNzY2TlFUb2tmMFIzR2JjVHAwIn0" }, "credentialSubject": { "type": [ "AchievementSubject" ], "achievement": { "id": "urn:uuid:ac254bd5-8fad-4bb1-9d29-efd938536926", "type": [ "Achievement" ], "name": "JFF x vc-edu PlugFest 3 Interoperability", "description": "This wallet supports the use of W3C Verifiable Credentials and has demonstrated interoperability during the presentation request workflow during JFF x VC-EDU PlugFest 3.", "criteria": { "type": "Criteria", "narrative": "Wallet solutions providers earned this badge by demonstrating interoperability during the presentation request workflow. This includes successfully receiving a presentation request, allowing the holder to select at least two types of verifiable credentials to create a verifiable presentation, returning the presentation to the requestor, and passing verification of the presentation and the included credentials." }, "image": { "id": "https://w3c-ccg.github.io/vc-ed/plugfest-3-2023/images/JFF-VC-EDU-PLUGFEST3-badge-image.png", "type": "Image" } }, "id": "did:key:zDnaeni4HHv4671BKfY8NbEyEszXNUebScEm9Thr4bF9ft8qs" }, "issuanceDate": "2025-08-27T23:46:02.774956600Z", "expirationDate": "2026-08-27T23:46:02.774956600Z" }, "issuer": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiSnFyZXNKS19Yb1RHQUV2aXFNU1FyZXZMLTJJaDg0Q2phcE5PWmVURi1MNCIsIngiOiJhaUZFeXhqQ1R1XzZWMTlOSFhXQVRNN0RaNzY2TlFUb2tmMFIzR2JjVHAwIn0", "subject": "did:key:zDnaeni4HHv4671BKfY8NbEyEszXNUebScEm9Thr4bF9ft8qs", "signature": { "type": "signature-jwt", "signature": "MqUTJA4e_LTHruv7giRBx9Tj9LTmUnnjqnnSsY4-azg02VPjW1QWeanRgU-r5qns_myQuMt3d82nlEUZFixcCg", "jwtHeader": { "kid": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiSnFyZXNKS19Yb1RHQUV2aXFNU1FyZXZMLTJJaDg0Q2phcE5PWmVURi1MNCIsIngiOiJhaUZFeXhqQ1R1XzZWMTlOSFhXQVRNN0RaNzY2TlFUb2tmMFIzR2JjVHAwIn0#JqresJK_XoTGAEviqMSQrevL-2Ih84CjapNOZeTF-L4", "typ": "JWT", "alg": "EdDSA" } }, "signed": "eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaVNuRnlaWE5LUzE5WWIxUkhRVVYyYVhGTlUxRnlaWFpNTFRKSmFEZzBRMnBoY0U1UFdtVlVSaTFNTkNJc0luZ2lPaUpoYVVaRmVYaHFRMVIxWHpaV01UbE9TRmhYUVZSTk4wUmFOelkyVGxGVWIydG1NRkl6UjJKalZIQXdJbjAjSnFyZXNKS19Yb1RHQUV2aXFNU1FyZXZMLTJJaDg0Q2phcE5PWmVURi1MNCIsInR5cCI6IkpXVCIsImFsZyI6IkVkRFNBIn0.eyJpc3MiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaVNuRnlaWE5LUzE5WWIxUkhRVVYyYVhGTlUxRnlaWFpNTFRKSmFEZzBRMnBoY0U1UFdtVlVSaTFNTkNJc0luZ2lPaUpoYVVaRmVYaHFRMVIxWHpaV01UbE9TRmhYUVZSTk4wUmFOelkyVGxGVWIydG1NRkl6UjJKalZIQXdJbjAiLCJzdWIiOiJkaWQ6a2V5OnpEbmFlbmk0SEh2NDY3MUJLZlk4TmJFeUVzelhOVWViU2NFbTlUaHI0YkY5ZnQ4cXMiLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vcHVybC5pbXNnbG9iYWwub3JnL3NwZWMvb2IvdjNwMC9jb250ZXh0Lmpzb24iXSwiaWQiOiJ1cm46dXVpZDpkNTVlZmVlNS1kNDY4LTQ3OTUtYTg1MS0wMjZmMGFjNzMxZWIiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiT3BlbkJhZGdlQ3JlZGVudGlhbCJdLCJuYW1lIjoiSkZGIHggdmMtZWR1IFBsdWdGZXN0IDMgSW50ZXJvcGVyYWJpbGl0eSIsImlzc3VlciI6eyJ0eXBlIjpbIlByb2ZpbGUiXSwibmFtZSI6IkpvYnMgZm9yIHRoZSBGdXR1cmUgKEpGRikiLCJ1cmwiOiJodHRwczovL3d3dy5qZmYub3JnLyIsImltYWdlIjoiaHR0cHM6Ly93M2MtY2NnLmdpdGh1Yi5pby92Yy1lZC9wbHVnZmVzdC0xLTIwMjIvaW1hZ2VzL0pGRl9Mb2dvTG9ja3VwLnBuZyIsImlkIjoiZGlkOmp3azpleUpyZEhraU9pSlBTMUFpTENKamNuWWlPaUpGWkRJMU5URTVJaXdpYTJsa0lqb2lTbkZ5WlhOS1MxOVliMVJIUVVWMmFYRk5VMUZ5WlhaTUxUSkphRGcwUTJwaGNFNVBXbVZVUmkxTU5DSXNJbmdpT2lKaGFVWkZlWGhxUTFSMVh6WldNVGxPU0ZoWFFWUk5OMFJhTnpZMlRsRlViMnRtTUZJelIySmpWSEF3SW4wIn0sImNyZWRlbnRpYWxTdWJqZWN0Ijp7InR5cGUiOlsiQWNoaWV2ZW1lbnRTdWJqZWN0Il0sImFjaGlldmVtZW50Ijp7ImlkIjoidXJuOnV1aWQ6YWMyNTRiZDUtOGZhZC00YmIxLTlkMjktZWZkOTM4NTM2OTI2IiwidHlwZSI6WyJBY2hpZXZlbWVudCJdLCJuYW1lIjoiSkZGIHggdmMtZWR1IFBsdWdGZXN0IDMgSW50ZXJvcGVyYWJpbGl0eSIsImRlc2NyaXB0aW9uIjoiVGhpcyB3YWxsZXQgc3VwcG9ydHMgdGhlIHVzZSBvZiBXM0MgVmVyaWZpYWJsZSBDcmVkZW50aWFscyBhbmQgaGFzIGRlbW9uc3RyYXRlZCBpbnRlcm9wZXJhYmlsaXR5IGR1cmluZyB0aGUgcHJlc2VudGF0aW9uIHJlcXVlc3Qgd29ya2Zsb3cgZHVyaW5nIEpGRiB4IFZDLUVEVSBQbHVnRmVzdCAzLiIsImNyaXRlcmlhIjp7InR5cGUiOiJDcml0ZXJpYSIsIm5hcnJhdGl2ZSI6IldhbGxldCBzb2x1dGlvbnMgcHJvdmlkZXJzIGVhcm5lZCB0aGlzIGJhZGdlIGJ5IGRlbW9uc3RyYXRpbmcgaW50ZXJvcGVyYWJpbGl0eSBkdXJpbmcgdGhlIHByZXNlbnRhdGlvbiByZXF1ZXN0IHdvcmtmbG93LiBUaGlzIGluY2x1ZGVzIHN1Y2Nlc3NmdWxseSByZWNlaXZpbmcgYSBwcmVzZW50YXRpb24gcmVxdWVzdCwgYWxsb3dpbmcgdGhlIGhvbGRlciB0byBzZWxlY3QgYXQgbGVhc3QgdHdvIHR5cGVzIG9mIHZlcmlmaWFibGUgY3JlZGVudGlhbHMgdG8gY3JlYXRlIGEgdmVyaWZpYWJsZSBwcmVzZW50YXRpb24sIHJldHVybmluZyB0aGUgcHJlc2VudGF0aW9uIHRvIHRoZSByZXF1ZXN0b3IsIGFuZCBwYXNzaW5nIHZlcmlmaWNhdGlvbiBvZiB0aGUgcHJlc2VudGF0aW9uIGFuZCB0aGUgaW5jbHVkZWQgY3JlZGVudGlhbHMuIn0sImltYWdlIjp7ImlkIjoiaHR0cHM6Ly93M2MtY2NnLmdpdGh1Yi5pby92Yy1lZC9wbHVnZmVzdC0zLTIwMjMvaW1hZ2VzL0pGRi1WQy1FRFUtUExVR0ZFU1QzLWJhZGdlLWltYWdlLnBuZyIsInR5cGUiOiJJbWFnZSJ9fSwiaWQiOiJkaWQ6a2V5OnpEbmFlbmk0SEh2NDY3MUJLZlk4TmJFeUVzelhOVWViU2NFbTlUaHI0YkY5ZnQ4cXMifSwiaXNzdWFuY2VEYXRlIjoiMjAyNS0wOC0yN1QyMzo0NjowMi43NzQ5NTY2MDBaIiwiZXhwaXJhdGlvbkRhdGUiOiIyMDI2LTA4LTI3VDIzOjQ2OjAyLjc3NDk1NjYwMFoifSwianRpIjoidXJuOnV1aWQ6ZDU1ZWZlZTUtZDQ2OC00Nzk1LWE4NTEtMDI2ZjBhYzczMWViIiwiZXhwIjoxNzg3ODc0MzYyLCJpYXQiOjE3NTYzMzgzNjIsIm5iZiI6MTc1NjMzODM2Mn0.MqUTJA4e_LTHruv7giRBx9Tj9LTmUnnjqnnSsY4-azg02VPjW1QWeanRgU-r5qns_myQuMt3d82nlEUZFixcCg", "format": "jwt_vc_json" }, "parent": "waltid.tenant1.credentialstore" } ] } ``` -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/services/issuer2-service/overview RAW: https://docs.walt.id/enterprise-stack/services/issuer2-service/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview The **Issuer2 Service** is walt.id's Enterprise solution for creating, signing, and distributing verifiable digital credentials using multiple credential formats and standards. It implements the [OID4VCI v1.0](https://docs.walt.id/concepts/data-exchange-protocols.md) specification and introduces a profile-based architecture for easier 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](https://docs.walt.id/enterprise-stack/services/issuer-service/overview.md), which supports Draft 11/13 of the OID4VCI specification, remains available for existing projects. For new projects, we recommend the **Issuer2 Service** with its profile-based architecture and OID4VCI v1.0 support. ## 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) | | **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 | --- ## How Issuance Works Issuing a credential with the Issuer2 Service follows four steps. The first two are one-time setup; the last two you repeat for every credential you issue. ```mermaid flowchart LR A["1 · Issuer Service
(credentialConfigurations)"] --> B["2 · Credential Profile
(key, DID, data template)"] B --> C["3 · Credential Offer
(OID4VCI URL)"] C --> D["4 · Wallet Claims
(credential issued)"] ``` 1. **Create the issuer service** — Declare the credential types it should support (`credentialConfigurations`) and point it at a KMS which manages the keys for signing. See [Setup](https://docs.walt.id/enterprise-stack/services/issuer2-service/setup.md). 2. **Create a credential profile** — Bind a credential type to an issuer key, DID or X509 certificate and a data template. See [Credential Profiles](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-profiles/overview.md). 3. **Create a credential offer** — Generate an OID4VCI offer URL from a profile, optionally overriding profile defaults per offer. See [Credential Offers](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-offers/overview.md). 4. **The wallet claims the offer** The [credential-issuance guides](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-issuance/sd-jwt-vc.md) take you through all four steps end to end for each format ([SD-JWT VC](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-issuance/sd-jwt-vc.md), [W3C VC](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-issuance/w3c-vc.md), [mDL](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-issuance/mdl.md)). --- ## Core Features ### Profile-Based Issuance The Issuer2 Service introduces a **profile-based architecture** that simplifies credential issuance: - **Credential Profiles** – Define reusable configurations for each credential type, including issuer keys, credential data templates, status configuration, and notification settings. - **Credential Offers** – Create offers from profiles with runtime overrides, supporting both pre-authorized and authorization code flows. ### Credential Exchange The Issuer2 Service 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 ID Token, VP Token, username/password login, or integration with external authorization servers like Keycloak). See [Protocol Flows](https://docs.walt.id/enterprise-stack/services/issuer2-service/protocol-flows/overview.md). - **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/enterprise-stack/services/issuer2-service/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`. ### Credential Branding Credential appearance in wallets can be defined via: - [**Issuer Metadata**](https://docs.walt.id/enterprise-stack/services/issuer2-service/configurations/display-configuration.md) – Branding per credential type (background color, text color, logo, description). - **Embedded in Credential** – Include branding directly in the issued credential for case-specific styling. ### Credential Status & Lifecycle - Built-in status management through the [Credential Status Service](https://docs.walt.id/enterprise-stack/services/credential-status-service/overview.md), supporting StatusList2021, Bitstring Status List, and Token Status List. - Different credential formats have different status type compatibility: - **W3C JWT/SD-JWT**: Bitstring Status List, StatusList2021 - **SD-JWT VC (IETF)**: Token Status List - **mDoc**: Token Status List - Configure credential status at the profile level or override per offer. - Credentials can include expiration and "valid from" dates. ### Keys & DIDs - **Issuer Keys** – Store and manage keys via the [KMS Service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md). The KMS service can use external KMS providers (e.g. Azure Key Vault, AWS KMS, OCI, Hashicorp Vault, ...) or store the keys in the Enterprise Stack database (only recommended for non-production use cases). - **DIDs** – Create and store DIDs via the [DID Service](https://docs.walt.id/enterprise-stack/services/did-service/overview.md), the [DID Registry](https://docs.walt.id/enterprise-stack/services/did-registry/overview.md) and the [DID Store](https://docs.walt.id/enterprise-stack/services/did-store-service/overview.md) - **X.509 Certificates** – Support for X.509 certificate chains (`x5Chain`) for mDL and other certificate-based credentials. ### Notifications & Session Events - Webhook-based notifications for real-time updates on issuance events, with bearer-token authentication for secure delivery. - A Server-Sent Events (SSE) stream for monitoring issuance session progress live. - Configure notifications at the profile level or override per offer. See [Notifications & Session Events](https://docs.walt.id/enterprise-stack/services/issuer2-service/notifications.md). ### Client Authentication - Client authentication is configured with `clientAuthenticationConfig` on the issuer service. You can enable one or both of: **anonymous pre-authorized code** (`preauth-anonymous` — OID4VCI token requests using `grant_type=urn:ietf:params:oauth:grant-type:pre-authorized_code` without `client_id`), and **attestation-based client authentication** (`client-attestation` — OAuth 2.0 Attestation-Based Client Auth). - When `client-attestation` is configured, only trusted wallets that present valid `OAuth-Client-Attestation` and `OAuth-Client-Attestation-PoP` headers can complete the token step; attestation JWT signatures are verified using a **static JWK**, a **KMS key**, or **X.509 chain** trust anchors (including regulated environments such as VICAL and eIDAS). - The issuer's RFC 8414 authorization server metadata reflects the active methods (for example `token_endpoint_auth_methods_supported`, `pre_authorized_grant_anonymous_access_supported`). - See [Client Attestation — Issuer Integration](https://docs.walt.id/enterprise-stack/services/client-attestation-service/issuer-integration.md) for configuration details. ### Session Data Retention & Auto-Purge - Issuance sessions can contain personally identifiable information (PII) such as offer payloads, subject data, and interaction logs. Enable the optional data-retention job to define a maximum issuer-session age, purge schedule, and safety guardrails so expired sessions are removed automatically. - Find out more about the [Data Retention](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/data-retention.md) configuration options where you can set `maxIssuerSessionAge`, cron `schedule`, dry-run behavior, and deletion limits to meet your compliance requirements. --- ## Getting Started - [Setup](https://docs.walt.id/enterprise-stack/services/issuer2-service/setup.md) – Create an Issuer2 service inside your tenant. - [Issue SD-JWT VC / W3C VC / mDL](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-issuance/sd-jwt-vc.md) – End-to-end guides from an empty service to a credential in a wallet. - [Credential Profiles](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-profiles/overview.md) – Create and manage reusable credential configurations. - [Credential Offers](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-offers/overview.md) – Create credential offers from profiles. - [Protocol Flows](https://docs.walt.id/enterprise-stack/services/issuer2-service/protocol-flows/overview.md) – Pre-authorized and authorization code flow sequences. - [Configurations](https://docs.walt.id/enterprise-stack/services/issuer2-service/configurations/overview.md) – Manage display metadata and service settings. - [Data Functions](https://docs.walt.id/enterprise-stack/services/issuer2-service/data-functions.md) – Populate credentials with dynamic data. - [Notifications & Session Events](https://docs.walt.id/enterprise-stack/services/issuer2-service/notifications.md) – Track issuance events via webhooks or SSE. - [Full API Reference](https://docs.walt.id/enterprise-stack/services/issuer2-service/full-api-reference.md) – Complete Swagger documentation. -------------------------------------------------------------------------------- PAGE: Setup URL: https://docs.walt.id/enterprise-stack/services/issuer2-service/setup RAW: https://docs.walt.id/enterprise-stack/services/issuer2-service/setup.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Setup This guide walks you through creating an Issuer2 service inside a tenant. The service declares which credential types it can issue (`credentialConfigurations`) and which KMS signs its tokens. If you don't have a tenant yet, learn how to create one [here](https://docs.walt.id/enterprise-stack/administration/tenants/create.md). ## Prerequisites Before creating an Issuer2 service, ensure you have: - **A tenant** — An existing tenant in your organization. - **A KMS service** — A [Key Management Service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) for storing signing keys. - **A token key** — A key in the KMS used to sign OID4VCI access tokens. --- ## Create an Issuer2 Service Services are created through the generic resource API. The body's `type` field selects the service kind (`issuer2`), and the new service's own ID is taken from the `target` in the path. **Option: CURL** **Endpoint:** `POST /v1/{target}/resource-api/services/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Resource/post_v1__target__resource_api_services_create) ##### Example Request ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/services/create' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "issuer2", "baseUrl": "https://myorg.enterprise-sandbox.waltid.dev", "kms": "waltid.tenant1.kms1", "tokenKeyId": "waltid.tenant1.kms1.tokenKey", "credentialConfigurations": { "identity_credential_vc+sd-jwt": { "format": "dc+sd-jwt", "vct": "{vctBaseURL}/identity_credential", "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"] } } }, "UniversityDegree_jwt_vc_json": { "format": "jwt_vc_json", "cryptographic_binding_methods_supported": ["jwk", "did:key", "did:web", "did:jwk"], "credential_signing_alg_values_supported": ["ES256", "EdDSA"], "proof_types_supported": { "jwt": { "proof_signing_alg_values_supported": ["ES256", "EdDSA"] } }, "credential_definition": { "type": ["VerifiableCredential", "UniversityDegree"] } }, "org.iso.18013.5.1.mDL": { "format": "mso_mdoc", "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"] } } } }, "issuerDisplayConfiguration": [ { "name": "My Organization Issuer", "locale": "en-US", "logo": { "uri": "https://example.com/logo.png", "alt_text": "Organization logo" } } ] }' ``` **Path Parameters** - **orgID**: _String_ (required) - Your organization ID, i.e. its base URL. For an organization named `test`, this is `test.enterprise-sandbox.waltid.dev` on the sandbox environment. - **target**: _String_ (required) - The resource identifier for the new issuer service, `{organizationID}.{tenantID}.{newIssuerServiceID}`, e.g. `waltid.tenant1.issuer1`. **Header Parameters** - **Authorization**: _String_ (required) - Bearer token for Enterprise Stack authentication. Format: `Bearer {token}`. **Body Parameters** - **type**: _String_ (required) - Must be `issuer2` for the Issuer2 Service. - **kms**: _resourceIdentifier_ (required) - Reference to a [KMS service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) in the same tenant, e.g. `waltid.tenant1.kms1`. - **tokenKeyId**: _resourceIdentifier_ (required) - Reference to a key in the KMS used to sign OID4VCI access tokens, e.g. `waltid.tenant1.kms1.tokenKey`. - **credentialConfigurations**: _Object_ (required) - Map of the credential types this issuer supports, keyed by credential configuration ID. Each value declares `format`, signing/binding support, and format-specific fields (`vct`, `credential_definition`, or `doctype`). The example uses the self-hosted `{vctBaseURL}` VCT placeholder and integer COSE signing algorithms for the mDoc. See [Credential Types](https://docs.walt.id/enterprise-stack/services/issuer2-service/configurations/credential-types.md) for the full field reference, validation rules, and per-format options. - **baseUrl**: _String_ (optional) - The public URL where the issuer is reachable; included in credential offers and OpenID metadata, e.g. `https://{orgID}.yourEnterpriseStackUrl.com`. - **issuerDisplayConfiguration**: _Array_ (optional) - Display metadata for the issuer, one entry per locale (`name`, `locale`, `logo`). See [Display Configuration](https://docs.walt.id/enterprise-stack/services/issuer2-service/configurations/display-configuration.md). - **sdJwtVcTypeMetadataConfiguration**: _Object_ (optional) - Not shown above. Supplies a custom SD-JWT VC Type Metadata document to serve for a self-hosted `{vctBaseURL}` VCT, keyed by the same credential configuration ID as in `credentialConfigurations` (only valid for `dc+sd-jwt` configurations). If omitted, the issuer serves a minimal auto-generated document. This is distinct from a configuration's `credential_metadata` (wallet display/claims). See [VCT Handling](https://docs.walt.id/enterprise-stack/services/issuer2-service/configurations/credential-types.md#vct-handling). - **clientAuthenticationConfig**: _Object_ (optional) - Declares which client authentication methods the issuer supports (anonymous pre-authorized code access and/or attestation-based wallet authentication). When a `client-attestation` method is included, token requests must carry valid `OAuth-Client-Attestation` and `OAuth-Client-Attestation-PoP` headers. See [Client Attestation — Issuer Integration](https://docs.walt.id/enterprise-stack/services/client-attestation-service/issuer-integration.md) for JSON shape, examples, and how this maps to authorization server metadata.
**Note:** Omit `clientAuthenticationConfig` entirely, or set `supportedMethods` to an empty list, to keep the default behavior where this layer does not enforce client authentication. Use `supportedMethods` with only `preauth-anonymous` when you want OID4VCI pre-authorized token requests without `client_id` but no wallet attestation. --- ##### Example Response ```json { "id": "waltid.tenant1.issuer1", "type": "issuer2", "baseUrl": "https://myorg.enterprise-sandbox.waltid.dev", "kms": "waltid.tenant1.kms1", "tokenKeyId": "waltid.tenant1.kms1.tokenKey", "credentialConfigurations": { "...": "..." }, "issuerDisplayConfiguration": [ "..." ] } ``` **Response Fields** - **id**: _String_ - The resource identifier of the created issuer service, `{organizationID}.{tenantID}.{issuerServiceID}`. - **type**: _String_ - The service type, `issuer2`. **Response Codes** - `201` — Service created successfully. - `400` — Invalid request body (e.g. an SD-JWT VC configuration missing `vct`). - `401` — Invalid or missing authentication token. - `409` — A service with this ID already exists. 🎉 You've created an Issuer2 service. --- ## Credential Types The `credentialConfigurations` map above declares one entry per credential type. Its shape depends on the `format` (`vct` for SD-JWT VC, `credential_definition` for W3C JWT, `doctype` for mDoc), and the service validates format-specific rules on create — binding/proof co-dependency, DID method form, and JOSE-vs-COSE signing identifiers. For the full field reference, validation rules, per-format examples, and VCT handling, see [Credential Types Configuration](https://docs.walt.id/enterprise-stack/services/issuer2-service/configurations/credential-types.md). --- ## Next Steps After creating your Issuer2 service: 1. [Issue your first credential](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-issuance/sd-jwt-vc.md) – End-to-end guide from profile to a credential in a wallet. 2. [Create credential profiles](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-profiles/create-profile.md) – Define reusable configurations for each credential type. 3. [Configure display metadata](https://docs.walt.id/enterprise-stack/services/issuer2-service/configurations/display-configuration.md) – Customize how your issuer appears in wallets. -------------------------------------------------------------------------------- PAGE: Issuing SD-JWT VC Credentials via OID4VCI URL: https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-issuance/sd-jwt-vc RAW: https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-issuance/sd-jwt-vc.md STACK: Enterprise Stack (commercial) — 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 end to end with the Enterprise Issuer2 Service. You'll create a minimal [credential profile](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-profiles/overview.md), turn it into a [credential offer](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-offers/overview.md), and claim it in a wallet. [**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 rather than the whole credential. [**OID4VCI**](https://docs.walt.id/concepts/data-exchange-protocols/openid4vci.md): The protocol used to deliver the credential to a wallet. ## Prerequisites Before you begin, ensure you have: - **An Issuer2 service** with an SD-JWT VC credential configuration (format `dc+sd-jwt`, with a `vct`). See [Setup](https://docs.walt.id/enterprise-stack/services/issuer2-service/setup.md). The `vct` can be an external URL or a self-hosted `{vctBaseURL}` placeholder — see [VCT Handling](https://docs.walt.id/enterprise-stack/services/issuer2-service/configurations/credential-types.md#vct-handling). - **A KMS key** for signing, in a [KMS Service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) under the same tenant. - **An issuer DID** created via the [DID Service](https://docs.walt.id/enterprise-stack/services/did-service/overview.md). - **A wallet to receive into** — an Enterprise Stack [Wallet Service](https://docs.walt.id/enterprise-stack/services/wallet-service/overview.md) (see [Receiving an SD-JWT VC](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/sd-jwt-vc.md)), or any OID4VCI-compatible wallet app. Throughout this guide: - **orgID** is your organization ID, e.g. `test.enterprise-sandbox.waltid.dev`. - **target** is your issuer service, `{organizationID}.{tenantID}.{issuerServiceID}`, e.g. `waltid.tenant1.issuer1`. --- ## Flow Overview 1. **Create a profile** — Bind the `identity_credential_vc+sd-jwt` type to your key, DID, and a data template. 2. **Create an offer** — Generate an OID4VCI offer URL from the profile. 3. **Claim the offer** — Hand the URL to a wallet. --- ## Step 1: Create a Profile **Option: CURL** **Endpoint:** `POST /v2/{target}.{profileId}/issuer-service-api/credentials/profiles` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html) The new profile's own ID is the **last path segment** of the target (like service creation), i.e. `{target}.{profileId}` — here `waltid.tenant1.issuer1.profile-abc123`. ##### Example Request ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{target}.profile-abc123/issuer-service-api/credentials/profiles' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "name": "Identity Credential Profile", "credentialConfigurationId": "identity_credential_vc+sd-jwt", "issuerKeyId": "waltid.tenant1.kms1.key1", "issuerDid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp", "credentialData": { "given_name": "John", "family_name": "Doe", "birthdate": "1990-01-15" }, "selectiveDisclosure": { "fields": { "birthdate": { "sd": true } } }, "mapping": { "iat": "", "exp": "" } }' ``` **Body Parameters** - **credentialConfigurationId**: _String_ (required) - Must match an SD-JWT VC (`dc+sd-jwt`) configuration on your issuer service. - **issuerKeyId**: _resourceIdentifier_ (required) - A key in your KMS, e.g. `waltid.tenant1.kms1.key1`. - **issuerDid**: _String_ (optional) - The issuer DID. - **credentialData**: _Object_ (required) - The claims to issue. - **selectiveDisclosure**: _Object_ (optional) - Which claims the holder can selectively disclose. Here, `birthdate` is made disclosable. See [Create a Profile](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-profiles/create-profile.md) for every field. --- ##### Example Response ```json { "profileId": "profile-abc123", "name": "Identity Credential Profile", "version": 1, "credentialConfigurationId": "identity_credential_vc+sd-jwt", "createdAt": 1704067200000, "updatedAt": 1704067200000 } ``` The example above is shortened — the response also echoes your submitted `issuerDid`, `issuerKeyId`, `credentialData`, `mapping`, and `selectiveDisclosure`. **Response Fields** - **profileId**: _String_ - The profile's own ID (the last path segment you chose), e.g. `profile-abc123`. The offer in Step 2 targets the **full** path `{target}.{profileId}`, e.g. `waltid.tenant1.issuer1.profile-abc123`. **Response Codes** - `201` — Profile created. - `400` — Invalid request body. - `401` — Invalid or missing authentication token. --- ## Step 2: Create a Credential Offer Create the offer against the **profile** as the target — the `profileId` from Step 1. **Option: CURL** **Endpoint:** `POST /v2/{profileTarget}/issuer-service-api/credentials/offers` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html) ##### Example Request ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/waltid.tenant1.issuer1.profile-abc123/issuer-service-api/credentials/offers' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "authMethod": "PRE_AUTHORIZED" }' ``` **Body Parameters** - **authMethod**: _String_ (required) - `PRE_AUTHORIZED` requires no user login and is the simplest way to start. See [Protocol Flows](https://docs.walt.id/enterprise-stack/services/issuer2-service/protocol-flows/overview.md) for the alternative. See [Create an Offer](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-offers/create-offer.md) for PIN, expiration, delivery mode, and runtime overrides. --- ##### Example Response ```json { "offerId": "48f1dc54-134c-4e10-b889-17f3c0a595bf", "profileId": "profile-abc123", "profileVersion": 1, "authMethod": "PRE_AUTHORIZED", "expiresAt": 1782898162344, "credentialOffer": "openid-credential-offer://?credential_offer_uri=https%3A%2F%2Fwaltid.enterprise-sandbox.waltid.dev%2Fv2%2Fwaltid.tenant1.issuer1%2Fissuer-service-api%2Fopenid4vci%2Fcredential-offer%3Fid%3D48f1dc54-134c-4e10-b889-17f3c0a595bf" } ``` **Response Fields** - **credentialOffer**: _String_ - The OID4VCI credential offer URL. Turn it into a QR code or deep link for the wallet. - **expiresAt**: _Long_ - Unix timestamp (ms) when the offer expires (default: 300 seconds after creation). --- ## Claiming the Offer Hand the `credentialOffer` URL to a wallet to receive the credential. With the Enterprise Stack [Wallet Service](https://docs.walt.id/enterprise-stack/services/wallet-service/overview.md) this is a single API call — see **[Receiving an SD-JWT VC](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/sd-jwt-vc.md)**, which passes the offer URL to the wallet's OID4VCI receive endpoint (Wallet API v2), resolves the offer, requests the credential, and stores it. To claim it in any other wallet: generate a QR code from the `credentialOffer` URL which the wallet can scan or open it as a deep link. --- ## Selective Disclosure The profile above marks `birthdate` as selectively disclosable via `selectiveDisclosure.fields`. Any claim you don't mark is always disclosed. To make more claims optional, add them under `fields` with `{ "sd": true }`. See [Create a Profile](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-profiles/create-profile.md) for the full selective-disclosure and decoy options. ## Using the Authorization Code Flow Instead The example above used `PRE_AUTHORIZED` — no user login. To have the holder authenticate against an external IdP (e.g. Keycloak) and map ID-token claims into the credential via `idTokenClaimsMapping`, use `authMethod: "AUTHORIZED"`. See [Authorization Code Flow](https://docs.walt.id/enterprise-stack/services/issuer2-service/protocol-flows/authorization-code-flow.md). --- ## Next Steps - **Verify the credential you just issued** — Try [Verifying SD-JWT VC Credentials](https://docs.walt.id/enterprise-stack/services/verifier2-service/credential-verification/sd-jwt-vc-oid4vp.md). - **Populate credentials dynamically** — See [Data Functions](https://docs.walt.id/enterprise-stack/services/issuer2-service/data-functions.md). - **Track issuance events** — See [Notifications & Session Events](https://docs.walt.id/enterprise-stack/services/issuer2-service/notifications.md). - **Issue other credential types** — Try [W3C VC](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-issuance/w3c-vc.md) or [mDL](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-issuance/mdl.md). -------------------------------------------------------------------------------- PAGE: Issuing W3C VC Credentials via OID4VCI URL: https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-issuance/w3c-vc RAW: https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-issuance/w3c-vc.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Issuing W3C VC 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) (JWT-signed) end to end with the Enterprise Issuer2 Service. You'll create a minimal [credential profile](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-profiles/overview.md), turn it into a [credential offer](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-offers/overview.md), and claim it in a wallet. [**W3C VC**](https://docs.walt.id/concepts/digital-credentials/verifiable-credentials-w3c.md): The W3C Verifiable Credentials data model, here signed as a JWT (`jwt_vc_json`). [**OID4VCI**](https://docs.walt.id/concepts/data-exchange-protocols/openid4vci.md): The protocol used to deliver the credential to a wallet. ## Prerequisites Before you begin, ensure you have: - **An Issuer2 service** with a W3C credential configuration (format `jwt_vc_json`, with a `credential_definition`). See [Setup](https://docs.walt.id/enterprise-stack/services/issuer2-service/setup.md). - **A KMS key** for signing, in a [KMS Service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) under the same tenant. - **An issuer DID** created via the [DID Service](https://docs.walt.id/enterprise-stack/services/did-service/overview.md). - **A wallet to receive into** — an Enterprise Stack [Wallet Service](https://docs.walt.id/enterprise-stack/services/wallet-service/overview.md) (see [Receiving a W3C VC](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/w3c-vc.md)), or any OID4VCI-compatible wallet app. Throughout this guide: - **orgID** is your organization ID, e.g. `test.enterprise-sandbox.waltid.dev`. - **target** is your issuer service, `{organizationID}.{tenantID}.{issuerServiceID}`, e.g. `waltid.tenant1.issuer1`. --- ## Flow Overview 1. **Create a profile** — Bind the `UniversityDegree_jwt_vc_json` type to your key, DID, and a data template. 2. **Create an offer** — Generate an OID4VCI offer URL from the profile. 3. **Claim the offer** — Hand the URL to a wallet. --- ## Step 1: Create a Profile **Option: CURL** **Endpoint:** `POST /v2/{target}.{profileId}/issuer-service-api/credentials/profiles` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html) The new profile's own ID is the **last path segment** of the target (like service creation), i.e. `{target}.{profileId}` — here `waltid.tenant1.issuer1.profile-def456`. ##### Example Request ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{target}.profile-def456/issuer-service-api/credentials/profiles' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "name": "University Degree Profile", "credentialConfigurationId": "UniversityDegree_jwt_vc_json", "issuerKeyId": "waltid.tenant1.kms1.key1", "issuerDid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp", "w3cVersion": "W3CV2", "credentialData": { "@context": [ "https://www.w3.org/ns/credentials/v2", "https://www.w3.org/ns/credentials/examples/v2" ], "type": ["VerifiableCredential", "UniversityDegree"], "credentialSubject": { "degree": { "type": "BachelorDegree", "name": "Bachelor of Science and Arts" } } }, "mapping": { "id": "", "issuer": { "id": "" }, "credentialSubject": { "id": "" }, "validFrom": "", "validUntil": "" } }' ``` **Body Parameters** - **credentialConfigurationId**: _String_ (required) - Must match a W3C (`jwt_vc_json`) configuration on your issuer service. - **issuerKeyId**: _resourceIdentifier_ (required) - A key in your KMS. - **issuerDid**: _String_ (optional) - The issuer DID. - **credentialData**: _Object_ (required) - The credential body (`@context`, `type`, `credentialSubject`). - **w3cVersion**: _String_ (optional) - `W3CV11` or `W3CV2`. Controls which VCDM version is issued and whether `issuanceDate`/`expirationDate` or `validFrom`/`validUntil` are used. See [Create a Profile](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-profiles/create-profile.md) for every field. **Note:** When `w3cVersion` is `W3CV2`, the service applies the correct VCDM v2 `@context` and uses `validFrom`/`validUntil` in place of `issuanceDate`/`expirationDate`, regardless of what you put in `credentialData`. --- ##### Example Response ```json { "profileId": "profile-def456", "name": "University Degree Profile", "version": 1, "credentialConfigurationId": "UniversityDegree_jwt_vc_json", "createdAt": 1704067200000, "updatedAt": 1704067200000 } ``` The example above is shortened — the response also echoes your submitted `issuerDid`, `issuerKeyId`, `credentialData`, `mapping`, and `w3cVersion`. **Response Fields** - **profileId**: _String_ - The profile's own ID (the last path segment you chose), e.g. `profile-def456`. The offer in Step 2 targets the **full** path `{target}.{profileId}`, e.g. `waltid.tenant1.issuer1.profile-def456`. **Response Codes** - `201` — Profile created. - `400` — Invalid request body. - `401` — Invalid or missing authentication token. --- ## Step 2: Create a Credential Offer Create the offer against the **profile** as the target — the `profileId` from Step 1. **Option: CURL** **Endpoint:** `POST /v2/{profileTarget}/issuer-service-api/credentials/offers` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html) ##### Example Request ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/waltid.tenant1.issuer1.profile-def456/issuer-service-api/credentials/offers' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "authMethod": "PRE_AUTHORIZED" }' ``` **Body Parameters** - **authMethod**: _String_ (required) - `PRE_AUTHORIZED` requires no user login. See [Protocol Flows](https://docs.walt.id/enterprise-stack/services/issuer2-service/protocol-flows/overview.md) for the alternative. See [Create an Offer](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-offers/create-offer.md) for PIN, expiration, delivery mode, and runtime overrides. --- ##### Example Response ```json { "offerId": "abc123-def456-ghi789", "profileId": "profile-def456", "profileVersion": 1, "authMethod": "PRE_AUTHORIZED", "expiresAt": 1704067500000, "credentialOffer": "openid-credential-offer://?credential_offer_uri=https%3A%2F%2Fwaltid.enterprise-sandbox.waltid.dev%2Fv2%2Fwaltid.tenant1.issuer1%2Fissuer-service-api%2Fopenid4vci%2Fcredential-offer%3Fid%3Dabc123-def456-ghi789" } ``` **Response Fields** - **credentialOffer**: _String_ - The OID4VCI credential offer URL. Turn it into a QR code or deep link for the wallet. --- ## Claiming the Offer Hand the `credentialOffer` URL to a wallet to receive the credential. With the Enterprise Stack [Wallet Service](https://docs.walt.id/enterprise-stack/services/wallet-service/overview.md) this is a single API call — see **[Receiving a W3C VC](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/w3c-vc.md)**, which passes the offer URL to the wallet's OID4VCI receive endpoint (Wallet API v2), resolves the offer, requests the credential, and stores it. To claim it in any other wallet: generate a QR code from the `credentialOffer` URL which the wallet can scan or open it as a deep link. --- ## Using the Authorization Code Flow Instead The example above used `PRE_AUTHORIZED` — no user login. To have the holder authenticate against an external IdP and map ID-token claims into the credential via `idTokenClaimsMapping`, use `authMethod: "AUTHORIZED"`. See [Authorization Code Flow](https://docs.walt.id/enterprise-stack/services/issuer2-service/protocol-flows/authorization-code-flow.md). --- ## Next Steps - **Verify the credential you just issued** — Try [Verifying W3C Credentials](https://docs.walt.id/enterprise-stack/services/verifier2-service/credential-verification/vc-oid4vp.md). - **Populate credentials dynamically** — See [Data Functions](https://docs.walt.id/enterprise-stack/services/issuer2-service/data-functions.md). - **Track issuance events** — See [Notifications & Session Events](https://docs.walt.id/enterprise-stack/services/issuer2-service/notifications.md). - **Issue other credential types** — Try [SD-JWT VC](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-issuance/sd-jwt-vc.md) or [mDL](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-issuance/mdl.md). -------------------------------------------------------------------------------- PAGE: Issuing Mobile Driver's Licenses (mDL) via OID4VCI URL: https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-issuance/mdl RAW: https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-issuance/mdl.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Issuing Mobile Driver's Licenses (mDL) via OID4VCI This guide shows you how to issue an [ISO 18013-5 mDL](https://docs.walt.id/concepts/digital-credentials/mdoc-mdl-iso.md) (mDoc) credential end to end with the Enterprise Issuer2 Service. You'll create a minimal [credential profile](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-profiles/overview.md), turn it into a [credential offer](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-offers/overview.md), and claim it in a wallet. [**mDL / mDoc**](https://docs.walt.id/concepts/digital-credentials/mdoc-mdl-iso.md): The ISO/IEC 18013-5 mobile document format, CBOR-encoded and signed with an X.509 certificate. [**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:** **mDoc signing differs from JWT credentials.** mDocs are signed with an **X.509 certificate chain** (`x5Chain`), not a DID — `issuerDid` is not supported for mDoc. Any date or binary field also needs a `mDocNameSpacesDataMappingConfig` entry so it is encoded to the correct CBOR type. ## Prerequisites Before you begin, ensure you have: - **An Issuer2 service** with an mDoc credential configuration (format `mso_mdoc`, with a `doctype`). See [Setup](https://docs.walt.id/enterprise-stack/services/issuer2-service/setup.md). - **A KMS key** for signing, in a [KMS Service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) under the same tenant. - **An X.509 certificate chain** for the issuer key, from the [X509 Service](https://docs.walt.id/enterprise-stack/services/x509-service/overview.md). - **A wallet to receive into** — an Enterprise Stack [Wallet Service](https://docs.walt.id/enterprise-stack/services/wallet-service/overview.md) (see [Receiving an mDL](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/mdl.md)), or any OID4VCI-compatible wallet app with mDoc support. Throughout this guide: - **orgID** is your organization ID, e.g. `test.enterprise-sandbox.waltid.dev`. - **target** is your issuer service, `{organizationID}.{tenantID}.{issuerServiceID}`, e.g. `waltid.tenant1.issuer1`. --- ## Flow Overview 1. **Create a profile** — Bind the `org.iso.18013.5.1.mDL` type to your key, X.509 chain, and a data template. 2. **Create an offer** — Generate an OID4VCI offer URL from the profile. 3. **Claim the offer** — Hand the URL to a wallet. --- ## Step 1: Create a Profile **Option: CURL** **Endpoint:** `POST /v2/{target}.{profileId}/issuer-service-api/credentials/profiles` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html) The new profile's own ID is the **last path segment** of the target (like service creation), i.e. `{target}.{profileId}` — here `waltid.tenant1.issuer1.profile-mdl789`. ##### Example Request ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{target}.profile-mdl789/issuer-service-api/credentials/profiles' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "name": "Mobile Driving License Profile", "credentialConfigurationId": "org.iso.18013.5.1.mDL", "issuerKeyId": "waltid.tenant1.kms1.key1", "x5Chain": [ { "type": "pem-encoded-x509-certificate-descriptor", "pemEncodedCertificate": "-----BEGIN CERTIFICATE-----\nMIIB...\n-----END CERTIFICATE-----\n" } ], "credentialData": { "org.iso.18013.5.1": { "family_name": "Doe", "given_name": "John", "birth_date": "1990-01-15", "issue_date": "2024-01-01", "expiry_date": "2029-01-01", "document_number": "D1234567" } }, "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" } } } } }' ``` **Body Parameters** - **credentialConfigurationId**: _String_ (required) - Must match an mDoc (`mso_mdoc`) configuration on your issuer service. - **issuerKeyId**: _resourceIdentifier_ (required) - A key in your KMS. - **x5Chain**: _Array_ (required for mDoc) - The issuer's X.509 certificate chain, issuer certificate first. Each entry is a descriptor object `{ "type": "pem-encoded-x509-certificate-descriptor", "pemEncodedCertificate": "" }`. (A bare PEM string is also accepted for backwards compatibility, but the API always stores and returns the object form.) - **credentialData**: _Object_ (required) - Claims nested under the mDoc namespace (`org.iso.18013.5.1`). - **mDocNameSpacesDataMappingConfig**: _Object_ (required for date/binary fields) - Type conversions for CBOR encoding. Here, the three dates are converted to CBOR full-date. See [Create a Profile](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-profiles/create-profile.md) for every field and all conversion types. --- ##### Example Response ```json { "profileId": "profile-mdl789", "name": "Mobile Driving License Profile", "version": 1, "credentialConfigurationId": "org.iso.18013.5.1.mDL", "createdAt": 1704067200000, "updatedAt": 1704067200000 } ``` The example above is shortened — the response also echoes your submitted `issuerKeyId`, `x5Chain`, `credentialData`, and `mDocNameSpacesDataMappingConfig` (mDoc profiles sign with an X.509 chain, so there is no `issuerDid` or `mapping`). **Response Fields** - **profileId**: _String_ - The profile's own ID (the last path segment you chose), e.g. `profile-mdl789`. The offer in Step 2 targets the **full** path `{target}.{profileId}`, e.g. `waltid.tenant1.issuer1.profile-mdl789`. **Response Codes** - `201` — Profile created. - `400` — Invalid request body. - `401` — Invalid or missing authentication token. --- ## Step 2: Create a Credential Offer Create the offer against the **profile** as the target — the `profileId` from Step 1. **Option: CURL** **Endpoint:** `POST /v2/{profileTarget}/issuer-service-api/credentials/offers` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html) ##### Example Request ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/waltid.tenant1.issuer1.profile-mdl789/issuer-service-api/credentials/offers' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "authMethod": "PRE_AUTHORIZED" }' ``` **Body Parameters** - **authMethod**: _String_ (required) - `PRE_AUTHORIZED` requires no user login. See [Protocol Flows](https://docs.walt.id/enterprise-stack/services/issuer2-service/protocol-flows/overview.md) for the alternative. See [Create an Offer](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-offers/create-offer.md) for PIN, expiration, delivery mode, and runtime overrides. --- ##### Example Response ```json { "offerId": "mdoc123-456-789", "profileId": "profile-mdl789", "profileVersion": 1, "authMethod": "PRE_AUTHORIZED", "expiresAt": 1704067500000, "credentialOffer": "openid-credential-offer://?credential_offer_uri=https%3A%2F%2Fwaltid.enterprise-sandbox.waltid.dev%2Fv2%2Fwaltid.tenant1.issuer1%2Fissuer-service-api%2Fopenid4vci%2Fcredential-offer%3Fid%3Dmdoc123-456-789" } ``` **Response Fields** - **credentialOffer**: _String_ - The OID4VCI credential offer URL. Turn it into a QR code or deep link for the wallet. --- ## Claiming the Offer Hand the `credentialOffer` URL to a wallet to receive the credential. With the Enterprise Stack [Wallet Service](https://docs.walt.id/enterprise-stack/services/wallet-service/overview.md) this is a single API call — see **[Receiving an mDL](https://docs.walt.id/enterprise-stack/services/wallet-service/credential-receiving/mdl.md)**, which passes the offer URL to the wallet's OID4VCI receive endpoint (Wallet API v2), resolves the offer, requests the credential, and stores it. To claim it in any other wallet: generate a QR code from the `credentialOffer` URL which the wallet can scan or open it as a deep link. --- ## mDoc Data Type Mapping CBOR requires specific types for dates and binary data (e.g. a `portrait`) that JSON can't represent natively. Every such field needs an entry in `mDocNameSpacesDataMappingConfig`. Available conversions include `stringToFullDate`, `stringToTDate`, `base64StringToByteString`, and `base64UrlStringToByteString` — see [Create a Profile → mDoc Data Mapping](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-profiles/create-profile.md#mdoc-data-mapping) for the full reference. ## Using the Authorization Code Flow Instead The example above used `PRE_AUTHORIZED` — no user login. To have the holder authenticate against an external IdP first, use `authMethod: "AUTHORIZED"`. See [Authorization Code Flow](https://docs.walt.id/enterprise-stack/services/issuer2-service/protocol-flows/authorization-code-flow.md). --- ## Next Steps - **Verify the credential you just issued** — Try [Verifying mDL Credentials](https://docs.walt.id/enterprise-stack/services/verifier2-service/credential-verification/mdl-oid4vp.md). - **Populate credentials dynamically** — See [Data Functions](https://docs.walt.id/enterprise-stack/services/issuer2-service/data-functions.md). - **Track issuance events** — See [Notifications & Session Events](https://docs.walt.id/enterprise-stack/services/issuer2-service/notifications.md). - **Issue other credential types** — Try [SD-JWT VC](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-issuance/sd-jwt-vc.md) or [W3C VC](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-issuance/w3c-vc.md). -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-profiles/overview RAW: https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-profiles/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview **Credential Profiles** are the foundation of the Issuer2 Service'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 credential configuration declared on your issuer service). - **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 credentials. - **Selective Disclosure** – Which SD-JWT claims a holder can selectively disclose. - **Credential Status** – Configuration for credential revocation/suspension. - **Notifications** – Webhook settings for issuance events. ## Profile Lifecycle 1. **Create** – Define a profile with your desired configuration. 2. **Use** – Create credential offers that reference the profile. 3. **Update** – Modify the profile configuration (creates a new version). 4. **Delete** – Permanently remove the profile when no longer needed. **Note:** Profiles support versioning. When you update a profile, a new version is created. Existing offers continue to use the profile version they were created with. **Error:** **Deletion is permanent.** Deleting a profile removes it entirely from the system — there is no soft-delete or `INACTIVE` state. Once deleted, a profile cannot be restored or referenced for new offers. Existing credential offers that were created from the profile are unaffected and continue to work until they expire. ## Supported Credential Formats Profiles can be created for any credential format your issuer service supports: | 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) | ## Next Steps - [Create a Profile](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-profiles/create-profile.md) – Step-by-step guide to creating your first profile. - [Manage Profiles](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-profiles/manage-profiles.md) – View, update, and delete profiles. -------------------------------------------------------------------------------- PAGE: Create a Credential Profile URL: https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-profiles/create-profile RAW: https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-profiles/create-profile.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Create a Credential Profile This guide walks you through creating a credential profile, which defines the default configuration for issuing a specific type of credential. ## Prerequisites Before creating a profile, ensure you have: - **An Issuer2 service** — A running issuer service. See [Setup](https://docs.walt.id/enterprise-stack/services/issuer2-service/setup.md). - **A KMS key** — A key for signing credentials, in a [KMS Service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) under the same tenant. - **An issuer identity** — Either a [DID](https://docs.walt.id/enterprise-stack/services/did-service/overview.md) or an [X.509 certificate](https://docs.walt.id/enterprise-stack/services/x509-service/overview.md) (choose per the format table below). The table below shows which issuer identification method (DID or X.509 certificate) is supported for each credential format: | Format | DIDs | X.509 Certificates | |--------|:----:|:------------------:| | JWT (W3C) | ✓ | ✓ | | SD-JWT (W3C) | ✓ | ✓ | | SD-JWT VC (IETF) | ✓ | ✓ | | ISO mDoc | – | ✓ | _✓ supported · – not available_ --- ## Create a Profile **Option: CURL** **Endpoint:** `POST /v2/{target}.{profileId}/issuer-service-api/credentials/profiles` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html) The new profile's own ID is the **last path segment** of the target (like service creation), i.e. `{target}.{profileId}` — here `waltid.tenant1.issuer1.profile-abc123`. ##### Example Request ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{target}.profile-abc123/issuer-service-api/credentials/profiles' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "name": "University Degree Profile", "credentialConfigurationId": "UniversityDegree_jwt_vc_json", "issuerKeyId": "waltid.tenant1.kms1.key1", "issuerDid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp", "w3cVersion": "W3CV2", "credentialData": { "@context": [ "https://www.w3.org/ns/credentials/v2", "https://www.w3.org/ns/credentials/examples/v2" ], "type": ["VerifiableCredential", "UniversityDegree"], "credentialSubject": { "degree": { "type": "BachelorDegree", "name": "Bachelor of Science and Arts" } } }, "mapping": { "id": "", "issuer": { "id": "" }, "credentialSubject": { "id": "" }, "validFrom": "", "validUntil": "" } }' ``` **Path Parameters** - **orgID**: _String_ (required) - Your organization ID, e.g. `test.enterprise-sandbox.waltid.dev`. - **target**: _String_ (required) - The issuer service path followed by the new profile's own ID as the final segment, `{organizationID}.{tenantID}.{issuerServiceID}.{profileId}`, e.g. `waltid.tenant1.issuer1.profile-abc123`. The `{profileId}` you choose here becomes the profile's ID. **Header Parameters** - **Authorization**: _String_ (required) - Bearer token for Enterprise Stack authentication. Format: `Bearer {token}`. **Body Parameters** - **name**: _String_ (required) - A human-readable name for the profile. - **credentialConfigurationId**: _String_ (required) - The credential configuration ID this profile issues. Must match a credential configuration declared on your issuer service. - **issuerKeyId**: _resourceIdentifier_ (required) - The key ID of a key stored in a [KMS service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) under the same tenant, e.g. `waltid.tenant1.kms1.key1`. - **credentialData**: _Object_ (required) - The credential data template. Structure depends on the credential format. - **issuerDid**: _String_ (optional) - The DID of the issuer. Not supported for mDocs. - **x5Chain**: _Array_ (optional) - X.509 certificate chain for signing (required for mDoc). The first certificate is the issuer's; provide the full chain if required. Each entry is a descriptor object `{ "type": "pem-encoded-x509-certificate-descriptor", "pemEncodedCertificate": "" }`. (A bare PEM string is also accepted for backwards compatibility, but the API always stores and returns the object form.) - **mapping**: _Object_ (optional) - Data mapping with [data functions](https://docs.walt.id/enterprise-stack/services/issuer2-service/data-functions.md) for dynamic value insertion at claim time. - **selectiveDisclosure**: _Object_ (optional) - Which SD-JWT claims a holder can selectively disclose, plus optional decoy configuration. Applies to SD-JWT VC and W3C SD-JWT credentials. - **idTokenClaimsMapping**: _Object_ (optional) - Maps claims from external IdP tokens to credential data fields using JSONPath. See [ID Token Claims Mapping](#id-token-claims-mapping). - **mDocNameSpacesDataMappingConfig**: _Object_ (optional) - Type conversion configuration for mDoc credentials. See [mDoc Data Mapping](#mdoc-data-mapping). - **credentialStatus**: _Object_ (optional) - Configuration for credential status (revocation/suspension). See [Credential Status](#credential-status). - **notifications**: _Object_ (optional) - Webhook notification settings. See [Notifications](https://docs.walt.id/enterprise-stack/services/issuer2-service/notifications.md). - **w3cVersion**: _String_ (optional) - W3C data model version of the issued credential. Either `W3CV11` for [VCDM v1.1](https://www.w3.org/TR/vc-data-model-1.1/) or `W3CV2` for [v2](https://www.w3.org/TR/vc-data-model/). **Note:** The `w3cVersion` parameter controls the format of the **issued credential**. When set to `W3CV2`, the service automatically applies the correct VCDM v2 `@context` (`https://www.w3.org/ns/credentials/v2`) and uses `validFrom`/`validUntil` in place of `issuanceDate`/`expirationDate`, regardless of what is provided in `credentialData`. --- ##### Example Response ```json { "profileId": "profile-abc123", "name": "University Degree Profile", "version": 1, "credentialConfigurationId": "UniversityDegree_jwt_vc_json", "issuerKeyId": "waltid.tenant1.kms1.key1", "issuerDid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp", "credentialData": { "...": "..." }, "mapping": { "...": "..." }, "w3cVersion": "W3CV2", "createdAt": 1704067200000, "updatedAt": 1704067200000 } ``` The service stores and echoes the full profile object; `credentialData` and `mapping` are elided above for brevity. Format-specific fields are included when set — `w3cVersion` for W3C, `selectiveDisclosure` for SD-JWT VC, `mDocNameSpacesDataMappingConfig` for mDoc. **Response Fields** - **profileId**: _String_ - The profile's own ID — the last path segment you chose, e.g. `profile-abc123`. To address the profile elsewhere (e.g. as an offer target), use the full path `{target}.{profileId}`. - **version**: _Integer_ - The profile version (starts at 1, increments on update). - **createdAt** / **updatedAt**: _Long_ - Unix timestamps (ms). **Response Codes** - `201` — Profile created successfully. - `400` — Invalid request body. - `401` — Invalid or missing authentication token. 🎉 You've created a credential profile. --- ## Advanced Configuration ### ID Token Claims Mapping When using the authorization code flow with an external IdP, you can map claims from the ID token to credential data fields: ```json { "idTokenClaimsMapping": { "$.family_name": "$.credentialSubject.familyName", "$.given_name": "$.credentialSubject.givenName", "$.email": "$.credentialSubject.email" } } ``` The keys are JSONPath expressions pointing to claims in the ID token, and the values are JSONPath expressions pointing to fields in the credential data. ### mDoc Data Mapping For mDoc/mDL credentials, you need to specify type conversions for CBOR encoding: ```json { "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" }, "portrait": { "type": "string", "conversionType": "base64StringToByteString" } } } } } ``` **Available Conversion Types:** | Conversion Type | Description | |-----------------|-------------| | `stringToFullDate` | Converts ISO date string (e.g. `2024-01-15`) to CBOR full-date (tag 1004). | | `stringToTDate` | Converts ISO timestamp string to CBOR tdate (tag 0). | | `base64StringToByteString` | Converts standard base64-encoded string to CBOR byte string. | | `base64UrlStringToByteString` | Converts base64url-encoded string to CBOR byte string. | ### Credential Status Configure credential status for revocation/suspension support: ```json { "credentialStatus": { "statusCredentialConfig": "waltid.tenant1.credentialstatus.config1", "initialStatus": "0x0" } } ``` - **statusCredentialConfig**: _resourceIdentifier_ - Reference to a credential status configuration in the [Credential Status Service](https://docs.walt.id/enterprise-stack/services/credential-status-service/overview.md). - **initialStatus**: _String_ - Initial status value (e.g. `"0x0"` for valid). **Note:** Different credential formats support different status list types: - **W3C JWT/SD-JWT**: Bitstring Status List, StatusList2021 - **SD-JWT VC (IETF)**: Token Status List - **mDoc**: Token Status List **Note:** **Automatic rollover**: If the referenced credential status service has `autoRollover` enabled, new status lists are created automatically when capacity thresholds are reached. This is transparent to the issuer — you can keep using the same `statusCredentialConfig` reference. Learn more about [automatic rollover](https://docs.walt.id/enterprise-stack/services/credential-status-service/setup.md#capacity-configuration). --- ## Example Profiles ### SD-JWT VC Profile ```json { "name": "Identity Credential Profile", "credentialConfigurationId": "identity_credential_vc+sd-jwt", "issuerKeyId": "waltid.tenant1.kms1.key1", "issuerDid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp", "credentialData": { "given_name": "John", "family_name": "Doe", "birthdate": "1990-01-15" }, "selectiveDisclosure": { "fields": { "birthdate": { "sd": true } } }, "mapping": { "iat": "", "exp": "" } } ``` ### mDoc Profile with X.509 Certificate ```json { "name": "Photo ID Profile", "credentialConfigurationId": "org.iso.23220.photoid.1", "issuerKeyId": "waltid.tenant1.kms1.key1", "x5Chain": [ { "type": "pem-encoded-x509-certificate-descriptor", "pemEncodedCertificate": "-----BEGIN CERTIFICATE-----\nMIIB...\n-----END CERTIFICATE-----\n" } ], "credentialData": { "org.iso.23220.photoid.1": { "given_name_unicode": "John", "family_name_unicode": "Doe", "birth_date": "1990-01-15", "issuance_date": "2024-01-01", "expiry_date": "2029-01-01", "portrait": "base64-encoded-image-data" } }, "mDocNameSpacesDataMappingConfig": { "org.iso.23220.photoid.1": { "entriesConfigMap": { "birth_date": { "type": "string", "conversionType": "stringToFullDate" }, "issuance_date": { "type": "string", "conversionType": "stringToFullDate" }, "expiry_date": { "type": "string", "conversionType": "stringToFullDate" }, "portrait": { "type": "string", "conversionType": "base64StringToByteString" } } } } } ``` ### Profile with Notifications ```json { "name": "Notified Credential Profile", "credentialConfigurationId": "UniversityDegree_jwt_vc_json", "issuerKeyId": "waltid.tenant1.kms1.key1", "issuerDid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp", "credentialData": { "...": "..." }, "notifications": { "webhook": { "url": "https://your-server.com/webhook/issuance", "bearerToken": "your-secret-token" } } } ``` --- ## Next Steps - [Manage Profiles](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-profiles/manage-profiles.md) – View, update, and delete profiles. - [Create Credential Offers](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-offers/overview.md) – Use your profile to create credential offers. -------------------------------------------------------------------------------- PAGE: Manage Credential Profiles URL: https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-profiles/manage-profiles RAW: https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-profiles/manage-profiles.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Manage Credential Profiles This guide covers how to manage your credential profiles after creation: listing, viewing, updating, and deleting them. Every endpoint below shares the same path parameters and authentication: - **orgID**: _String_ (required) - Your organization ID, e.g. `test.enterprise-sandbox.waltid.dev`. - **target**: _String_ (required) - For **List**, the issuer service path `{organizationID}.{tenantID}.{issuerServiceID}` (e.g. `waltid.tenant1.issuer1`). For **View**, **Update**, and **Delete**, append the profile's own ID as the final path segment: `{organizationID}.{tenantID}.{issuerServiceID}.{profileId}` (e.g. `waltid.tenant1.issuer1.profile-abc123`). - **Authorization**: _String_ (required) - Bearer token. Format: `Bearer {token}`. --- ## List All Profiles Retrieve every credential profile for your issuer service. **Option: CURL** **Endpoint:** `GET /v2/{target}/issuer-service-api/credentials/profiles/list` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html) ##### Example Request ```bash curl -X 'GET' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{target}/issuer-service-api/credentials/profiles/list' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' ``` --- ##### Example Response ```json [ { "profileId": "profile-abc123", "name": "University Degree Profile", "version": 1, "credentialConfigurationId": "UniversityDegree_jwt_vc_json", "createdAt": 1704067200000, "updatedAt": 1704067200000 }, { "profileId": "profile-def456", "name": "Identity Credential Profile", "version": 2, "credentialConfigurationId": "identity_credential_vc+sd-jwt", "createdAt": 1704067200000, "updatedAt": 1704153600000 } ] ``` **Response Codes** - `200` — List returned (empty array if no profiles exist). - `401` — Invalid or missing authentication token. **Note:** The list returns every profile that currently exists. Deleted profiles are removed from storage and no longer appear here. --- ## View a Profile Retrieve the full details of a specific credential profile. **Option: CURL** **Endpoint:** `GET /v2/{target}.{profileId}/issuer-service-api/credentials/profiles` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html) The profile to view is identified by the **last path segment** of the target, `{target}.{profileId}`. ##### Example Request ```bash curl -X 'GET' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/waltid.tenant1.issuer1.profile-abc123/issuer-service-api/credentials/profiles' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' ``` **Path Parameters** - **profileId**: _String_ (required) - The profile's own ID, appended to the issuer service target as the final segment (`{target}.{profileId}`). --- ##### Example Response ```json { "profileId": "profile-abc123", "name": "University Degree Profile", "version": 1, "credentialConfigurationId": "UniversityDegree_jwt_vc_json", "issuerKeyId": "waltid.tenant1.kms1.key1", "issuerDid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp", "credentialData": { "...": "..." }, "mapping": { "...": "..." }, "createdAt": 1704067200000, "updatedAt": 1704067200000 } ``` **Response Codes** - `200` — Profile returned. - `404` — No profile with that ID exists. - `401` — Invalid or missing authentication token. --- ## Update a Profile Update an existing credential profile. This creates a new version of the profile. **Note:** When you update a profile, a new version is created. Existing credential offers continue to use the profile version they were created with; new offers use the latest version. **Option: CURL** **Endpoint:** `PUT /v2/{target}.{profileId}/issuer-service-api/credentials/profiles` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html) The profile to update is identified by the **last path segment** of the target, `{target}.{profileId}`. ##### Example Request ```bash curl -X 'PUT' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/waltid.tenant1.issuer1.profile-abc123/issuer-service-api/credentials/profiles' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "name": "University Degree Profile (Updated)", "credentialConfigurationId": "UniversityDegree_jwt_vc_json", "issuerKeyId": "waltid.tenant1.kms1.key1", "issuerDid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp", "credentialData": { "@context": [ "https://www.w3.org/ns/credentials/v2" ], "type": ["VerifiableCredential", "UniversityDegree"], "credentialSubject": { "degree": { "type": "BachelorDegree", "name": "Bachelor of Science and Arts" } } }, "mapping": { "id": "", "validFrom": "", "validUntil": "" } }' ``` **Path Parameters** - **profileId**: _String_ (required) - The profile's own ID, appended to the issuer service target as the final segment (`{target}.{profileId}`). **Body Parameters** The body has the same shape as [Create a Profile](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-profiles/create-profile.md). Server-managed fields (`profileId`, `version`, `createdAt`, `updatedAt`) are accepted but ignored. --- ##### Example Response ```json { "profileId": "profile-abc123", "name": "University Degree Profile (Updated)", "version": 2, "credentialConfigurationId": "UniversityDegree_jwt_vc_json", "issuerKeyId": "waltid.tenant1.kms1.key1", "issuerDid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp", "credentialData": { "...": "..." }, "mapping": { "...": "..." }, "createdAt": 1704067200000, "updatedAt": 1704153600000 } ``` **Response Codes** - `200` — Profile updated; `version` incremented. - `400` — Invalid request body. - `401` — Invalid or missing authentication token. --- ## Delete a Profile Permanently delete a credential profile. The profile is removed from storage and can no longer be used to create new credential offers. **Error:** **This is a hard delete and cannot be undone.** The profile is completely removed from the system. If you may need the profile (or its configuration) later, export or back it up before deleting. **Note:** Existing credential offers created from this profile are **not affected** by the deletion. They were captured at offer-creation time and continue to work until they expire. **Option: CURL** **Endpoint:** `DELETE /v2/{target}.{profileId}/issuer-service-api/credentials/profiles` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html) The profile to delete is identified by the **last path segment** of the target, `{target}.{profileId}`. ##### Example Request ```bash curl -X 'DELETE' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/waltid.tenant1.issuer1.profile-abc123/issuer-service-api/credentials/profiles' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' ``` **Path Parameters** - **profileId**: _String_ (required) - The profile's own ID, appended to the issuer service target as the final segment (`{target}.{profileId}`). --- ##### Example Response An empty body with status `200`. **Response Codes** - `200` — Profile deleted (or did not exist). The response body is empty. - `401` — Invalid or missing authentication token. --- ## Next Steps - [Create Credential Offers](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-offers/overview.md) – Use your profiles to create credential offers. - [Notifications & Session Events](https://docs.walt.id/enterprise-stack/services/issuer2-service/notifications.md) – Configure webhook notifications for your profiles. -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-offers/overview RAW: https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-offers/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview **Credential Offers** are the mechanism for initiating credential issuance in the Issuer2 Service. An offer is created from a [credential profile](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-profiles/overview.md) and generates 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 against a profile. 2. **Share URL** – Send the credential offer URL to the user (QR code, deep link, etc.). 3. **Wallet Claims** – The 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/enterprise-stack/services/issuer2-service/protocol-flows/overview.md) for the full sequence diagrams and guidance on 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: it keeps URLs short and lets the issuer track when offers are resolved. ## Issuer State Mode For the authorization code flow, you can include an `issuer_state` parameter so the issuer can correlate the later authorization request with this offer's session: | Mode | Description | |------|-------------| | `INCLUDE` | Include issuer state for session correlation. | | `OMIT` | No issuer state. | `issuerStateMode` applies only to `AUTHORIZED` offers; for `PRE_AUTHORIZED` offers it must be omitted. See [Runtime Overrides](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-offers/create-offer.md#runtime-overrides) for how issuer state interacts with per-offer overrides. ## Next Steps - [Create an Offer](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-offers/create-offer.md) – Step-by-step guide to creating credential offers. - [Notifications & Session Events](https://docs.walt.id/enterprise-stack/services/issuer2-service/notifications.md) – Monitor issuance progress in real time. -------------------------------------------------------------------------------- PAGE: Create a Credential Offer URL: https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-offers/create-offer RAW: https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-offers/create-offer.md STACK: Enterprise Stack (commercial) — 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 any compliant wallet can claim. ## Prerequisites Before creating an offer, ensure you have: - **An Issuer2 service** — A running issuer service. See [Setup](https://docs.walt.id/enterprise-stack/services/issuer2-service/setup.md). - **A credential profile** — An existing profile to issue from. See [Create a Profile](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-profiles/create-profile.md). --- ## Create an Offer The offer is created against the **profile** as the target — its full resource path, `{organizationID}.{tenantID}.{issuerServiceID}.{profileId}`. **Option: CURL** **Endpoint:** `POST /v2/{profileTarget}/issuer-service-api/credentials/offers` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html) ##### Example Request ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{profileTarget}/issuer-service-api/credentials/offers' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "authMethod": "PRE_AUTHORIZED", "valueMode": "BY_REFERENCE", "expiresInSeconds": 300 }' ``` **Path Parameters** - **orgID**: _String_ (required) - Your organization ID, e.g. `test.enterprise-sandbox.waltid.dev`. - **profileTarget**: _String_ (required) - The resource identifier of the credential profile, `{organizationID}.{tenantID}.{issuerServiceID}.{profileId}`, e.g. `waltid.tenant1.issuer1.profile-abc123`. **Header Parameters** - **Authorization**: _String_ (required) - Bearer token. Format: `Bearer {token}`. **Body Parameters** - **authMethod**: _String_ (required) - The OID4VCI flow used to claim the offer. Options: - `"PRE_AUTHORIZED"` — Pre-authorized code flow (no user authentication). - `"AUTHORIZED"` — Authorization code flow (user authentication required). - **valueMode**: _String_ (optional) - How the offer is delivered (default: `BY_REFERENCE`). Options: - `"BY_REFERENCE"` — Offer URL contains a reference; the wallet fetches the full offer. - `"BY_VALUE"` — Full credential offer embedded in the URL. - **issuerStateMode**: _String_ (optional) - Whether to include issuer state. Valid **only** for `AUTHORIZED` offers; omit it for `PRE_AUTHORIZED`. Options: - `"INCLUDE"` — Include issuer state for session correlation with the authorization server. - `"OMIT"` — No issuer state. - **expiresInSeconds**: _Integer_ (optional) - Offer validity duration in seconds (default: 300). Set to `-1` for no expiration. - **txCode**: _Object_ (optional) - Transaction code (PIN) configuration for the pre-authorized flow. See [Transaction Code (PIN)](#transaction-code-pin). - **txCodeValue**: _String_ (optional) - A specific PIN value. If omitted, one is generated. - **runtimeOverrides**: _Object_ (optional) - Override profile values for this offer only. See [Runtime Overrides](#runtime-overrides). - **sessionId**: _String_ (optional) - Session ID for the issuance session. If omitted, a random UUID is generated. --- ##### Example Response ```json { "offerId": "abc123-def456-ghi789", "profileId": "profile-abc123", "profileVersion": 1, "authMethod": "PRE_AUTHORIZED", "issuerStateMode": null, "expiresAt": 1704067500000, "credentialOffer": "openid-credential-offer://?credential_offer_uri=https%3A%2F%2Fwaltid.enterprise-sandbox.waltid.dev%2Fv2%2Fwaltid.tenant1.issuer1%2Fissuer-service-api%2Fopenid4vci%2Fcredential-offer%3Fid%3Dabc123-def456-ghi789" } ``` **Response Fields** - **offerId**: _String_ - Unique identifier for this offer (also the issuance session ID). - **profileId**: _String_ - The profile used to create this offer. - **profileVersion**: _Integer_ - The version of the profile used. - **authMethod**: _String_ - The authentication method. - **issuerStateMode**: _String_ - The issuer state mode (present only for `AUTHORIZED` offers). - **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. **Response Codes** - `201` — Offer created successfully. - `400` — Invalid request body. - `401` — Invalid or missing authentication token. 🎉 You've created a credential offer. Hand the `credentialOffer` URL to a wallet as a QR code or deep link to claim it. --- ## Transaction Code (PIN) For the pre-authorized flow, you can require a PIN for additional security. ```json { "authMethod": "PRE_AUTHORIZED", "txCode": { "input_mode": "numeric", "length": 6, "description": "Please enter the PIN sent to your email" } } ``` **txCode Parameters:** - **input_mode**: _String_ (optional) - Input type: `numeric` or `text` (default: `numeric`). - **length**: _Integer_ (optional) - Length of the PIN. - **description**: _String_ (optional) - Description shown to the user. To set the PIN value yourself, provide `txCodeValue`: ```json { "authMethod": "PRE_AUTHORIZED", "txCode": { "input_mode": "numeric", "length": 6 }, "txCodeValue": "123456" } ``` --- ## Runtime Overrides You can override any profile value for a specific offer using `runtimeOverrides`: ```json { "authMethod": "PRE_AUTHORIZED", "runtimeOverrides": { "issuerDid": "did:key:z6MkNewDid...", "issuerKeyId": "waltid.tenant1.kms1.differentKey", "subjectId": "did:key:z6MkSubjectDid...", "w3cVersion": "W3CV2", "credentialData": { "@context": ["https://www.w3.org/ns/credentials/v2"], "type": ["VerifiableCredential", "CustomCredential"], "credentialSubject": { "customField": "customValue" } }, "mapping": { "id": "" }, "credentialStatus": { "statusCredentialConfig": "waltid.tenant1.credentialstatus.config2", "initialStatus": "0x0" }, "notifications": { "webhook": { "url": "https://different-webhook.com/callback" } } } } ``` ### Available Override Fields | Field | Description | |-------|-------------| | `issuerDid` | Override the issuer DID. | | `issuerKeyId` | Override the signing key. | | `x5Chain` | Override the X.509 certificate chain. | | `subjectId` | Set the credential subject ID. | | `credentialData` | Override the credential data. | | `mapping` | Override the data mapping. | | `selectiveDisclosure` | Override the SD-JWT selective disclosure configuration. | | `idTokenClaimsMapping` | Override the ID token claims mapping. | | `mDocNameSpacesDataMappingConfig` | Override the mDoc data mapping. | | `credentialStatus` | Override the credential status configuration. | | `notifications` | Override the notification settings. | | `w3cVersion` | Override the W3C data model version. | **Note:** `issuer_state` correlates 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 present in the offer the wallet sends it back in the authorization request. `AUTHORIZED` offers omit `issuer_state` unless `issuerStateMode` is `INCLUDE` (the default is `OMIT`). Because offer-specific `runtimeOverrides` are correlated to the issuance session via `issuer_state`, set `issuerStateMode` to `INCLUDE` whenever an `AUTHORIZED` offer uses `runtimeOverrides` — otherwise, with no `issuer_state`, the authorization request cannot be matched back to this offer's overrides. Note that the create request itself is **not** rejected if you combine `runtimeOverrides` with `OMIT` (it returns `201`); the overrides simply cannot take effect. --- ## Examples ### Pre-Authorized Flow with PIN ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{profileTarget}/issuer-service-api/credentials/offers' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "authMethod": "PRE_AUTHORIZED", "txCode": { "input_mode": "numeric", "length": 4 }, "expiresInSeconds": 600 }' ``` ### Authorization Code Flow ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{profileTarget}/issuer-service-api/credentials/offers' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "authMethod": "AUTHORIZED", "issuerStateMode": "INCLUDE", "expiresInSeconds": 900 }' ``` The same `issuer_state` from the offer is forwarded to the authorization server in the authorization request, e.g.: ```text https://keycloak.demo.walt.id/realms/myrealm/protocol/openid-connect/auth?client_id=issuer_api&redirect_uri=.../issuer-service-api/openid4vci/callback&scope=openid_profile&state=74b5206d7a15c9ec&response_type=code&issuer_state=cadbeaf4-d412-4ce3-b8d4-e8d5c70d4a3f ``` See [Authorization Code Flow](https://docs.walt.id/enterprise-stack/services/issuer2-service/protocol-flows/authorization-code-flow.md) for the full sequence. ### Offer with Custom Credential Data ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{profileTarget}/issuer-service-api/credentials/offers' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "authMethod": "PRE_AUTHORIZED", "runtimeOverrides": { "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" } } } } }' ``` ### Offer by Value ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{profileTarget}/issuer-service-api/credentials/offers' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "authMethod": "PRE_AUTHORIZED", "valueMode": "BY_VALUE" }' ``` --- ## Using the Credential Offer The `credentialOffer` URL in the response can be: 1. **Displayed as a QR code** – The user scans it with their wallet app. 2. **Sent as a deep link** – The user clicks the link on a mobile device. 3. **Embedded in an email/message** – The user clicks to open in their wallet. When the wallet resolves this URL, it fetches the full credential offer, displays the credential details to the user, and requests the credential using the appropriate flow. --- ## Next Steps - [Notifications & Session Events](https://docs.walt.id/enterprise-stack/services/issuer2-service/notifications.md) – Monitor issuance progress in real time. - [Protocol Flows](https://docs.walt.id/enterprise-stack/services/issuer2-service/protocol-flows/overview.md) – Understand the pre-authorized and authorization code sequences. -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/services/issuer2-service/protocol-flows/overview RAW: https://docs.walt.id/enterprise-stack/services/issuer2-service/protocol-flows/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview The Issuer2 Service supports two OID4VCI flows for claiming a credential offer, selected via the `authMethod` field when [creating a credential offer](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-offers/create-offer.md). This section walks through the full sequence for each so you know what happens between your application, the wallet, the 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 — the holder logs in at an external IdP | | **Best for** | Known users (already verified) and data known 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 | An external OAuth/OIDC provider configured via `authProviderConfiguration` on the issuer service (see [Setup](https://docs.walt.id/enterprise-stack/services/issuer2-service/setup.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/enterprise-stack/services/issuer2-service/protocol-flows/pre-authorized-flow.md) — Sequence diagram for issuing a credential with no user login. - [Authorization Code Flow](https://docs.walt.id/enterprise-stack/services/issuer2-service/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/enterprise-stack/services/issuer2-service/protocol-flows/pre-authorized-flow RAW: https://docs.walt.id/enterprise-stack/services/issuer2-service/protocol-flows/pre-authorized-flow.md STACK: Enterprise Stack (commercial) — 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/enterprise-stack/services/issuer2-service/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 Service** — 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 Service App->>Issuer: POST /v2/{profileTarget}/issuer-service-api/credentials/offers
Bearer token · {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 issuer metadata
(.well-known/openid-credential-issuer) Issuer-->>Wallet: Issuer metadata Wallet->>Issuer: POST .../openid4vci/token
(pre-authorized_code [+ tx_code]) activate Issuer Issuer->>Issuer: Validate code (+ PIN if configured) Issuer-->>Wallet: access_token deactivate Issuer Wallet->>Issuer: POST .../openid4vci/nonce activate Issuer Issuer->>Issuer: Generate fresh c_nonce Issuer-->>Wallet: {c_nonce} deactivate Issuer Wallet->>Wallet: Generate proof of possession
(sign c_nonce with holder key) Wallet->>Issuer: POST .../openid4vci/credential
(access_token, proof) activate Issuer Issuer->>Issuer: Resolve profile, apply
mapping / data functions Issuer->>Issuer: Sign credential with issuer key Issuer-->>Wallet: 200 OK
{credential} deactivate Issuer Wallet->>Wallet: Store credential ``` **Note:** This diagram is a simplified overview focused on how the Issuer2 Service 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). **Note:** Only the **offer-creation** call is authenticated with your Enterprise Stack Bearer token. The wallet-facing OID4VCI endpoints (`token`, `nonce`, `credential`) are public. ## Related - [Credential Issuance](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-issuance/sd-jwt-vc.md) — Try this flow end to end. - [Create an Offer](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-offers/create-offer.md) — `txCode` and other offer parameters. - [Authorization Code Flow](https://docs.walt.id/enterprise-stack/services/issuer2-service/protocol-flows/authorization-code-flow.md) — The alternative flow, when user login is required. -------------------------------------------------------------------------------- PAGE: Authorization Code Flow URL: https://docs.walt.id/enterprise-stack/services/issuer2-service/protocol-flows/authorization-code-flow RAW: https://docs.walt.id/enterprise-stack/services/issuer2-service/protocol-flows/authorization-code-flow.md STACK: Enterprise Stack (commercial) — 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 Service mapping the returned ID token claims into the credential. Set `"authMethod": "AUTHORIZED"` when [creating the offer](https://docs.walt.id/enterprise-stack/services/issuer2-service/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 Service** — manages the issuance session, correlates `issuer_state`, applies `idTokenClaimsMapping`, and signs the credential. - **External IdP** — authenticates the holder and issues the ID token, configured via `authProviderConfiguration` on the issuer service. **Error:** This flow requires an external OAuth/OIDC provider configured via `authProviderConfiguration` when [creating the issuer service](https://docs.walt.id/enterprise-stack/services/issuer2-service/setup.md) **before** you create an `AUTHORIZED` offer. ```mermaid sequenceDiagram participant App as Your Application participant Wallet as Wallet
(Holder) participant Issuer as Issuer2 Service participant IdP as External IdP
(e.g. Keycloak) App->>Issuer: POST /v2/{profileTarget}/issuer-service-api/credentials/offers
Bearer token · {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 issuer metadata
(.well-known/openid-credential-issuer) Issuer-->>Wallet: Issuer metadata Wallet->>Issuer: GET .../openid4vci/authorize
(response_type=code, issuer_state) activate Issuer Issuer->>Issuer: Resolve issuance session from issuer_state Issuer-->>Wallet: 302 redirect to IdP authorize URL deactivate Issuer Wallet-->>IdP: Follow redirect to authorize URL IdP->>IdP: User authenticates IdP-->>Issuer: Redirect to
.../issuer-service-api/openid4vci/callback?code=...&state=... activate Issuer Issuer->>IdP: Exchange code for tokens
(handled by the issuer's OAuth client) IdP-->>Issuer: id_token (+ access_token) Issuer->>Issuer: Look up session by state,
apply idTokenClaimsMapping into credentialData Issuer-->>Wallet: 302 redirect to wallet's redirect_uri
with the issuer's own authorization code deactivate Issuer Wallet->>Issuer: POST .../openid4vci/token
(authorization_code) Issuer-->>Wallet: access_token Wallet->>Issuer: POST .../openid4vci/nonce activate Issuer Issuer->>Issuer: Generate fresh c_nonce Issuer-->>Wallet: {c_nonce} deactivate Issuer Wallet->>Wallet: Generate proof of possession
(sign c_nonce with holder key) Wallet->>Issuer: POST .../openid4vci/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 Service 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 Service correlates the authorization request it receives back at `.../openid4vci/authorize` with the issuance session created when the offer was made. Set `"issuerStateMode": "INCLUDE"` when creating the offer to include it. `AUTHORIZED` offers omit `issuer_state` unless `issuerStateMode` is `INCLUDE`. Because per-offer overrides need this correlation, an `AUTHORIZED` offer that uses `runtimeOverrides` **must** set `issuerStateMode` to `INCLUDE` — see [Runtime Overrides](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-offers/create-offer.md#runtime-overrides). ## Related - [Setup](https://docs.walt.id/enterprise-stack/services/issuer2-service/setup.md) — Configure `authProviderConfiguration` for the external IdP. - [Create an Offer](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-offers/create-offer.md) — `issuerStateMode` and other offer parameters. - [Create a Profile — ID Token Claims Mapping](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-profiles/create-profile.md#id-token-claims-mapping) — Map ID token claims into credential fields. - [Pre-Authorized Code Flow](https://docs.walt.id/enterprise-stack/services/issuer2-service/protocol-flows/pre-authorized-flow.md) — The simpler alternative, when user login isn't needed. -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/services/issuer2-service/configurations/overview RAW: https://docs.walt.id/enterprise-stack/services/issuer2-service/configurations/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview The Issuer2 Service provides several configuration options to customize its behavior and appearance. This section covers: - [Display Configuration](https://docs.walt.id/enterprise-stack/services/issuer2-service/configurations/display-configuration.md) – Configure how your issuer appears in wallets. - [Credential Types](https://docs.walt.id/enterprise-stack/services/issuer2-service/configurations/credential-types.md) – Manage supported credential configurations. - [Status List Aggregation](https://docs.walt.id/enterprise-stack/services/issuer2-service/configurations/status-list-aggregation.md) – Discover and cache status lists for offline verification. - [Client Authentication & Attestation](https://docs.walt.id/enterprise-stack/services/client-attestation-service/issuer-integration.md) – Configure `clientAuthenticationConfig` (anonymous pre-authorized access and/or attestation-based wallet authentication). ## Configuration Hierarchy Configuration in the Issuer2 Service follows a hierarchy: 1. **Service Configuration** – Base settings defined when creating the issuer service. 2. **Credential Profiles** – Per-credential-type settings that override service defaults. 3. **Offer Overrides** – Per-offer settings that override profile defaults. This lets you set sensible defaults at the service level while keeping flexibility for specific use cases. -------------------------------------------------------------------------------- PAGE: Display Configuration URL: https://docs.walt.id/enterprise-stack/services/issuer2-service/configurations/display-configuration RAW: https://docs.walt.id/enterprise-stack/services/issuer2-service/configurations/display-configuration.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Display Configuration The display configuration controls how your issuer service appears in wallet applications. This metadata is exposed via the OpenID4VCI discovery endpoints and helps users identify your organization when receiving credentials. Every endpoint below shares the same path parameters and authentication: - **orgID**: _String_ (required) - Your organization ID, e.g. `test.enterprise-sandbox.waltid.dev`. - **target**: _String_ (required) - The resource identifier of the issuer service, `{organizationID}.{tenantID}.{issuerServiceID}`, e.g. `waltid.tenant1.issuer1`. - **Authorization**: _String_ (required) - Bearer token. Format: `Bearer {token}`. --- ## View Display Configuration Retrieve the current display configuration for your issuer service. **Option: CURL** **Endpoint:** `GET /v2/{target}/issuer-service-api/configuration/openid-metadata/display/view` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html) ##### Example Request ```bash curl -X 'GET' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{target}/issuer-service-api/configuration/openid-metadata/display/view' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' ``` --- ##### Example Response ```json [ { "name": "walt.id Enterprise Issuer", "locale": "en-US", "logo": { "uri": "https://cdn.walt.id/issuer/logo.png", "alt_text": "walt.id logo" } } ] ``` **Response Codes** - `200` — Display configuration returned. - `401` — Invalid or missing authentication token. --- ## Update Display Configuration Replace the display metadata for your issuer service. The body is an array of display entries, one per locale. **Option: CURL** **Endpoint:** `PUT /v2/{target}/issuer-service-api/configuration/openid-metadata/display/update` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html) ##### Example Request ```bash curl -X 'PUT' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{target}/issuer-service-api/configuration/openid-metadata/display/update' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '[ { "name": "My Organization Issuer", "locale": "en-US", "logo": { "uri": "https://example.com/logo.png", "alt_text": "My Organization logo" } }, { "name": "Mon Organisation Émetteur", "locale": "fr-FR", "logo": { "uri": "https://example.com/logo.png", "alt_text": "Logo de mon organisation" } } ]' ``` **Body Parameters** The body is a JSON array of display entries: - **name**: _String_ (optional) - Display name of the issuer. - **locale**: _String_ (optional) - Language/locale code (e.g. `en-US`, `de-DE`, `fr-FR`). Each locale may appear only once. - **logo**: _Object_ (optional) - Logo configuration. - **uri**: _String_ (required) - URL to the logo image. - **alt_text**: _String_ (optional) - Alternative text for the logo. --- ##### Example Response ```json [ { "name": "My Organization Issuer", "locale": "en-US", "logo": { "uri": "https://example.com/logo.png", "alt_text": "My Organization logo" } } ] ``` **Response Codes** - `200` — Display configuration updated. - `400` — Invalid request body (e.g. duplicate locales). - `401` — Invalid or missing authentication token. --- ## Delete Display Configuration Remove the display configuration from your issuer service. **Option: CURL** **Endpoint:** `DELETE /v2/{target}/issuer-service-api/configuration/openid-metadata/display/delete` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html) ##### Example Request ```bash curl -X 'DELETE' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{target}/issuer-service-api/configuration/openid-metadata/display/delete' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' ``` --- ##### Example Response An empty body with status `200`. **Response Codes** - `200` — Display configuration deleted successfully. - `401` — Invalid or missing authentication token. --- ## Display Configuration Properties | Property | Type | Description | |----------|------|-------------| | `name` | String | Display name of the issuer. | | `locale` | String | Language/locale code (e.g. `en-US`, `de-DE`, `fr-FR`). | | `logo` | Object | Logo configuration. | | `logo.uri` | String | URL to the logo image. | | `logo.alt_text` | String | Alternative text for the logo. | ## Multi-Language Support The display configuration supports multiple locales, allowing wallets to show localized issuer information based on the user's language preference: ```json [ { "name": "University of Technology", "locale": "en-US", "logo": { "uri": "https://university.edu/logo.png", "alt_text": "University logo" } }, { "name": "Technische Universität", "locale": "de-DE", "logo": { "uri": "https://university.edu/logo.png", "alt_text": "Universitätslogo" } }, { "name": "Université de Technologie", "locale": "fr-FR", "logo": { "uri": "https://university.edu/logo.png", "alt_text": "Logo de l'université" } } ] ``` The display metadata is served as part of the issuer's OpenID4VCI credential issuer metadata, which wallets fetch to discover and display issuer information during credential issuance. -------------------------------------------------------------------------------- PAGE: Credential Types Configuration URL: https://docs.walt.id/enterprise-stack/services/issuer2-service/configurations/credential-types RAW: https://docs.walt.id/enterprise-stack/services/issuer2-service/configurations/credential-types.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Credential Types Configuration The `credentialConfigurations` object declares which credential types your Issuer2 Service can issue. It is part of the [service create/update body](https://docs.walt.id/enterprise-stack/services/issuer2-service/setup.md) and is published as the OpenID4VCI `credential_configurations_supported` metadata at the issuer's `.well-known/openid-credential-issuer` endpoint. `credentialConfigurations` is a **map keyed by credential configuration ID** (e.g. `"UniversityDegree_jwt_vc_json"`). That ID is the contract point with the rest of the system: - [Credential profiles](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-profiles/create-profile.md) reference it via `credentialConfigurationId`. - Wallets request this credential by it — directly via `authorization_details` (`credential_configuration_id`), or via the OAuth `scope` parameter (matched against the configuration's `scope` field, or against this ID when no `scope` is set). ## Supported Formats | Format | `format` value | Description | |--------|----------------|-------------| | SD-JWT VC | `dc+sd-jwt` | IETF SD-JWT Verifiable Credentials | | W3C JWT | `jwt_vc_json` | W3C Verifiable Credentials, JWT-signed | | mDoc | `mso_mdoc` | ISO 18013-5 Mobile Documents | ## Fields Each entry in `credentialConfigurations` accepts the following fields: | Field | Type | Required | Description | |-------|------|----------|-------------| | `format` | String (enum) | **Yes** | Credential format: `dc+sd-jwt`, `jwt_vc_json`, or `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 (`jwt_vc_json`): `{ "@context": [...], "type": [...] }`. `type` must include `VerifiableCredential`. | | `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` (required for SD-JWT VC). See [VCT Handling](#vct-handling). | | `credential_signing_alg_values_supported` | Array | No | Format-specific — JOSE strings or COSE integers. See [Signing Algorithms](#signing-algorithms). Non-empty if present. | | `cryptographic_binding_methods_supported` | Array | No | Key binding methods. Co-dependent with `proof_types_supported` — see [Binding Methods & Proof Types](#binding-methods-and-proof-types). | | `proof_types_supported` | Object | No | Wallet key-proof types. Co-dependent with `cryptographic_binding_methods_supported`. | | `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. A wallet can request the credential by its configuration ID even with nothing else set, but a production-ready type normally also declares `cryptographic_binding_methods_supported`, `proof_types_supported`, and `credential_signing_alg_values_supported` (for holder key binding), and often a `scope` for OAuth-scope-based requests. ## 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. Providing only one is rejected at service create/update. `cryptographic_binding_methods_supported` accepts: | Method | Use case | |--------|----------| | `jwk` | SD-JWT VC key binding | | `did:{method}` — e.g. `did:key`, `did:jwk`, `did:web`, `did:ebsi` | W3C credentials with DID subjects | | `cose_key` | mDoc credentials | `proof_types_supported` is a map keyed by proof type (typically `jwt`); each entry's `proof_signing_alg_values_supported` must be non-empty: ```json { "proof_types_supported": { "jwt": { "proof_signing_alg_values_supported": ["ES256"] } } } ``` ## Signing Algorithms The value of `credential_signing_alg_values_supported` is validated against the `format`: | Format | Expected values | Example | |--------|-----------------|---------| | `dc+sd-jwt`, `jwt_vc_json` | JOSE algorithm names (strings) | `["ES256"]` | | `mso_mdoc` | COSE algorithm identifiers (integers) | `[-7, -9]` | A **string** value is always interpreted as a JOSE identifier, so passing `"ES256"` for `mso_mdoc` is rejected — use the integer COSE form. | Algorithm | Key Type | JOSE (JWT formats) | COSE (mDoc) | Description | |-----------|----------|:------------------:|:-----------:|-------------| | ES256 | P-256 (secp256r1) | `"ES256"` | `-7` | ECDSA with SHA-256 | | ES384 | P-384 | `"ES384"` | — | ECDSA with SHA-384 | | ES512 | P-521 | `"ES512"` | — | ECDSA with SHA-512 | | EdDSA | Ed25519 | `"EdDSA"` | — | Edwards-curve Digital Signature Algorithm | | RS256 | RSA | `"RS256"` | — | RSASSA-PKCS1-v1_5 with SHA-256 | | PS256 | RSA | `"PS256"` | — | RSASSA-PSS with SHA-256 | **Note:** `-7` is the COSE identifier for ECDSA with SHA-256, the algorithm used for ISO 18013-5 mDL signing. A configuration can advertise several COSE identifiers (e.g. `[-7, -9]`); values are integers from the [IANA COSE Algorithms registry](https://www.iana.org/assignments/cose/cose.xhtml#algorithms). ## VCT Handling Every SD-JWT VC configuration must declare a `vct` (Verifiable Credential Type) — a URL that uniquely identifies the credential type and points to its [Type Metadata document](https://datatracker.ietf.org/doc/draft-ietf-oauth-sd-jwt-vc/). The Issuer2 Service supports two ways to provide it: - **External VCT** — a plain `https://` URL whose Type Metadata document is hosted somewhere else. - **Self-hosted VCT** — a `{vctBaseURL}` placeholder that makes the issuer service resolve the URL and **serve the Type Metadata document itself**. ### External VCT Point `vct` at a Type Metadata document you host (or that a registry hosts). The issuer uses the value verbatim and serves nothing of its own: ```json { "identity_credential": { "format": "dc+sd-jwt", "vct": "https://example.com/credentials/identity_credential" } } ``` ### Self-hosted VCT Use the literal placeholder `{vctBaseURL}` to have the issuer host the Type Metadata document. The value **must** be of the form `{vctBaseURL}/{type}` — the placeholder followed by exactly one path segment: ```json { "identity_credential": { "format": "dc+sd-jwt", "vct": "{vctBaseURL}/identity_credential" } } ``` **Error:** Note the exact token: **`{vctBaseURL}`** — curly braces, capital `URL`. Self-hosting is triggered **only** when the `vct` contains this exact string. A different spelling. e.g. a lowercase-`url` variant (`{vctBaseUrl}`), or any typo — is **not** rejected: it is silently stored as a literal external `vct`, so the issuer self-hosts nothing and wallets receive an unresolvable URL. Once the value **does** use the `{vctBaseURL}` token, it must **start with** the placeholder and be followed by exactly one path segment (`{vctBaseURL}/identity_credential`). A prefix before the placeholder, a trailing slash, or extra path segments fails validation with a `400` at service create/update time. **Resolution.** At runtime the placeholder expands to: ``` {baseUrl}/.well-known/vct/v2/{target}/issuer-service-api/openid4vci ``` where `{baseUrl}` is the issuer service's `baseUrl` (or the host-derived base URL) and `{target}` is `{organizationID}.{tenantID}.{issuerServiceID}`. So `{vctBaseURL}/identity_credential` becomes, in the credential issuer metadata: ``` {baseUrl}/.well-known/vct/v2/{org}.{tenant}.{issuer}/issuer-service-api/openid4vci/identity_credential ``` That resolved URL is what wallets see as the `vct` in the issuer's `credential_configurations_supported`, and it is also the **public, unauthenticated** endpoint the issuer serves the Type Metadata document from (`GET` — no token required). ### Providing the Type Metadata document For self-hosted VCTs, supply the document to be served via the top-level `sdJwtVcTypeMetadataConfiguration` map on the issuer service, keyed by the **same credential configuration ID** as in `credentialConfigurations`: ```json { "credentialConfigurations": { "identity_credential": { "format": "dc+sd-jwt", "vct": "{vctBaseURL}/identity_credential", "scope": "identity_credential", "cryptographic_binding_methods_supported": ["jwk"], "credential_signing_alg_values_supported": ["ES256"], "proof_types_supported": { "jwt": { "proof_signing_alg_values_supported": ["ES256"] } } } }, "sdJwtVcTypeMetadataConfiguration": { "identity_credential": { "name": "Identity Credential", "description": "A verifiable identity credential", "schema_uri": "https://example.com/schemas/identity_credential.json", "schema_uri#integrity": "sha256-..." } } } ``` The served Type Metadata document supports these fields (per the SD-JWT VC Type Metadata draft): | Property | Required | Description | |----------|----------|-------------| | `vct` | No | The credential type identifier. If provided, it must resolve to the **same** value as the `vct` on the matching credential configuration (you may use the `{vctBaseURL}/{type}` placeholder here too). If omitted, the resolved VCT is filled in automatically. | | `name` | No | Human-readable name of the type. | | `description` | No | Human-readable description of the type. | | `extends` | No | VCT of a type this type extends. | | `extends#integrity` | No | Integrity metadata (SRI) for the `extends` document. | | `schema` | No | An inline JSON Schema for the credential. Mutually exclusive with `schema_uri`. | | `schema_uri` | No | URL of a JSON Schema for the credential. Mutually exclusive with `schema`. | | `schema_uri#integrity` | No | Integrity metadata (SRI) for the `schema_uri` document. Requires `schema_uri`. | Any other member you include is served verbatim as well, so you can add further [SD-JWT VC Type Metadata](https://datatracker.ietf.org/doc/draft-ietf-oauth-sd-jwt-vc/) properties (or custom ones) beyond those above. **Note:** Each key in `sdJwtVcTypeMetadataConfiguration` must match a `credentialConfigurations` entry whose `format` is `dc+sd-jwt`; a key with no matching SD-JWT VC configuration fails validation with a `400`. **Note:** `sdJwtVcTypeMetadataConfiguration` is **optional** even for self-hosted VCTs. If you use `{vctBaseURL}` but provide no document, the issuer serves a minimal default (the resolved `vct`, a `name` equal to the type, and a generated `description`). Provide the document when you need schema references, `extends`, or richer metadata. ## Credential Metadata Per-credential display and claim metadata lives **under `credential_metadata`** — as `credential_metadata.display` and `credential_metadata.claims`. **Error:** A `display` or `claims` key placed **directly** on the credential configuration is not interpreted as OpenID4VCI metadata — it is treated as a custom parameter and republished without validation. Always nest them under `credential_metadata`. ### Display (`credential_metadata.display`) An optional list of display entries, one per locale (locales must be unique): | Property | Required | Description | |----------|----------|-------------| | `name` | Yes | Credential display name (non-blank). | | `locale` | No | BCP-47 locale, e.g. `en-US`. | | `logo` | No | `{ uri, alt_text }` — `uri` required and must include a scheme. | | `description` | No | Description of the credential. | | `background_color` | No | Hex color, e.g. `#12107c`. | | `background_image` | No | `{ uri }` — must include a scheme. | | `text_color` | No | Hex color, e.g. `#FFFFFF`. | Multi-language example: ```json { "credential_metadata": { "display": [ { "name": "Identity Credential", "locale": "en-US", "description": "Your verified identity" }, { "name": "Identitätsnachweis", "locale": "de-DE", "description": "Ihr verifizierter Identitätsnachweis" } ] } } ``` ### Claims (`credential_metadata.claims`) An optional list of claim descriptions. This metadata is used by wallets to display information about the credential; it does not affect the actual credential content. | Property | Required | Description | |----------|----------|-------------| | `path` | Yes | Array of strings representing the path to the claim (non-empty, no blank segments). | | `mandatory` | No | Whether the claim is required (`true`) or optional (`false`). | | `display` | No | Array of `{ name, locale }` entries; locales must be unique. | The `path` format depends on the format: | Format | Path example | Notes | |--------|--------------|-------| | SD-JWT VC | `["given_name"]`, `["address", "city"]` | Claim name; nested claims add segments. | | W3C JWT | `["credentialSubject", "name"]` | Path from the credential root. | | mDoc | `["org.iso.18013.5.1", "family_name"]` | Namespace as the first segment, then the claim name. | ## Credential Configuration Examples ### SD-JWT VC (dc+sd-jwt) ```json { "identity_credential": { "format": "dc+sd-jwt", "scope": "identity_credential", "vct": "{vctBaseURL}/identity_credential", "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", "description": "Your verified identity", "background_color": "#12107c", "text_color": "#FFFFFF" } ], "claims": [ { "path": ["given_name"], "mandatory": true, "display": [{ "name": "First Name", "locale": "en-US" }] }, { "path": ["family_name"], "mandatory": true, "display": [{ "name": "Last Name", "locale": "en-US" }] }, { "path": ["birthdate"], "mandatory": false, "display": [{ "name": "Date of Birth", "locale": "en-US" }] }, { "path": ["address", "locality"], "mandatory": false, "display": [{ "name": "City", "locale": "en-US" }] } ] } } } ``` ### W3C JWT VC (jwt_vc_json) ```json { "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", "description": "Official university degree certificate", "background_color": "#1a365d", "text_color": "#FFFFFF", "logo": { "uri": "https://university.example.com/logo.png", "alt_text": "University Logo" } } ], "claims": [ { "path": ["credentialSubject", "degree", "name"], "mandatory": true, "display": [{ "name": "Degree Name", "locale": "en-US" }] }, { "path": ["credentialSubject", "givenName"], "mandatory": true, "display": [{ "name": "First Name", "locale": "en-US" }] }, { "path": ["credentialSubject", "familyName"], "mandatory": true, "display": [{ "name": "Last Name", "locale": "en-US" }] } ] } } } ``` ### mDoc (mso_mdoc) `mso_mdoc` uses integer COSE signing identifiers (`-7` = ES256) and `cose_key` binding. Claim paths are namespaced. ```json { "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], "proof_types_supported": { "jwt": { "proof_signing_alg_values_supported": ["ES256"] } }, "credential_metadata": { "display": [ { "name": "Mobile Driving License", "locale": "en-US", "description": "Your digital driving license", "background_color": "#003366", "text_color": "#FFFFFF", "logo": { "uri": "https://dmv.example.com/logo.png", "alt_text": "DMV Logo" } } ], "claims": [ { "path": ["org.iso.18013.5.1", "family_name"], "mandatory": true, "display": [{ "name": "Family Name", "locale": "en-US" }] }, { "path": ["org.iso.18013.5.1", "given_name"], "mandatory": true, "display": [{ "name": "Given Name", "locale": "en-US" }] }, { "path": ["org.iso.18013.5.1", "birth_date"], "mandatory": true, "display": [{ "name": "Date of Birth", "locale": "en-US" }] }, { "path": ["org.iso.18013.5.1", "document_number"], "mandatory": true, "display": [{ "name": "Document Number", "locale": "en-US" }] }, { "path": ["org.iso.18013.5.1", "age_over_18"], "mandatory": false, "display": [{ "name": "Age Over 18", "locale": "en-US" }] } ] } } } ``` ## Complete Example A full issuer service create body with all three formats: ```json { "type": "issuer2", "baseUrl": "https://myorg.enterprise-sandbox.waltid.dev", "kms": "waltid.tenant1.kms1", "tokenKeyId": "waltid.tenant1.kms1.tokenKey", "credentialConfigurations": { "identity_credential": { "format": "dc+sd-jwt", "scope": "identity_credential", "vct": "{vctBaseURL}/identity_credential", "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", "did:jwk"], "credential_signing_alg_values_supported": ["ES256", "EdDSA"], "proof_types_supported": { "jwt": { "proof_signing_alg_values_supported": ["ES256", "EdDSA"] } }, "credential_definition": { "type": ["VerifiableCredential", "UniversityDegree"] }, "credential_metadata": { "display": [ { "name": "University Degree", "locale": "en-US", "background_color": "#1a365d", "text_color": "#FFFFFF" } ] } }, "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], "proof_types_supported": { "jwt": { "proof_signing_alg_values_supported": ["ES256"] } }, "credential_metadata": { "display": [ { "name": "Mobile Driving License", "locale": "en-US", "background_color": "#003366", "text_color": "#FFFFFF" } ] } } }, "sdJwtVcTypeMetadataConfiguration": { "identity_credential": { "name": "Identity Credential", "description": "A verifiable identity credential" } }, "issuerDisplayConfiguration": [ { "name": "My Organization", "locale": "en-US" } ] } ``` ## Credential Configuration ID The map key for each credential type in `credentialConfigurations` becomes the `credentialConfigurationId`. This ID is used when: 1. Creating [credential profiles](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-profiles/create-profile.md) (`credentialConfigurationId`). 2. Referencing credentials in the OpenID4VCI metadata. 3. Requesting specific credential types — via `authorization_details` (`credential_configuration_id`) or the OAuth `scope` parameter. A common convention is `{credentialType}_{format}`, e.g.: - `UniversityDegree_jwt_vc_json` - `identity_credential` (SD-JWT VC) - `org.iso.18013.5.1.mDL` (mDoc) ## Next Steps - [Setup](https://docs.walt.id/enterprise-stack/services/issuer2-service/setup.md) – Create the issuer service that carries these configurations. - [Create a Profile](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-profiles/create-profile.md) – Bind a credential configuration to a key and data template. - [Display Configuration](https://docs.walt.id/enterprise-stack/services/issuer2-service/configurations/display-configuration.md) – Configure issuer-level branding. -------------------------------------------------------------------------------- PAGE: Status List Aggregation URL: https://docs.walt.id/enterprise-stack/services/issuer2-service/configurations/status-list-aggregation RAW: https://docs.walt.id/enterprise-stack/services/issuer2-service/configurations/status-list-aggregation.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Status List Aggregation The Issuer2 Service provides a **status list aggregation** feature that lets verifiers and wallets discover and cache all status lists used by an issuer. This enables efficient offline verification of credential status. It is based on the mechanism described in the [Token Status List](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-status-list-15) standard. ## Overview When credentials are issued with status (revocation/suspension), the status information is stored in status list credentials. The status list aggregation endpoint provides a single location where verifiers can discover all status lists used by an issuer, making it easier to: - **Pre-fetch status lists** for offline verification. - **Cache status lists** to reduce network requests during verification. - **Monitor status lists** for changes. ## Discovery via Authorization Server Metadata The status list aggregation endpoint is advertised in the OAuth 2.0 Authorization Server Metadata, available at the standard well-known endpoint: ```text GET /.well-known/oauth-authorization-server/v2/openid4vci ``` ### Example Response ```json { "issuer": "https://myorg.enterprise-sandbox.waltid.dev/v2/waltid.tenant1.issuer1/issuer-service-api/openid4vci", "authorization_endpoint": "https://myorg.enterprise-sandbox.waltid.dev/v2/waltid.tenant1.issuer1/issuer-service-api/openid4vci/authorize", "token_endpoint": "https://myorg.enterprise-sandbox.waltid.dev/v2/waltid.tenant1.issuer1/issuer-service-api/openid4vci/token", "jwks_uri": "https://myorg.enterprise-sandbox.waltid.dev/v2/waltid.tenant1.issuer1/issuer-service-api/openid4vci/jwks", "status_list_aggregation_endpoint": "https://myorg.enterprise-sandbox.waltid.dev/v2/waltid.tenant1.issuer1/issuer-service-api/openid4vci/status-list-aggregation", "code_challenge_methods_supported": ["S256"] } ``` The `status_list_aggregation_endpoint` field contains the URL where verifiers can fetch the aggregated status lists. --- ## Status List Aggregation Endpoint The aggregation endpoint returns the URLs of all status list credentials used by the issuer. This endpoint is **public** — no authentication is required, so verifiers and wallets can fetch it directly. **Option: CURL** **Endpoint:** `GET /v2/{target}/issuer-service-api/openid4vci/status-list-aggregation` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html) ##### Example Request ```bash curl -X 'GET' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{target}/issuer-service-api/openid4vci/status-list-aggregation' \ -H 'accept: application/json' ``` **Path Parameters** - **orgID**: _String_ (required) - Your organization ID, e.g. `test.enterprise-sandbox.waltid.dev`. - **target**: _String_ (required) - The resource identifier of the issuer service, `{organizationID}.{tenantID}.{issuerServiceID}`. --- ##### Example Response ```json { "status_lists": [ "https://myorg.enterprise-sandbox.waltid.dev/v1/waltid.tenant1.credentialstatus/credential-status-api/status-list/revocation-config", "https://myorg.enterprise-sandbox.waltid.dev/v1/waltid.tenant1.credentialstatus/credential-status-api/status-list/suspension-config" ] } ``` **Response Fields** - **status_lists**: _Array of Strings_ - URLs to the status list credentials used by the issuer. **Response Codes** - `200` — Aggregation returned. ### Response Format The `status_lists` array contains one string per status list URL. The URLs are: - **Sorted alphabetically** for consistent ordering. - **Deduplicated** — each unique status list URL appears only once. - **Filtered by format** — only status lists referenced by SD-JWT VC (`dc+sd-jwt`) and mDoc (`mso_mdoc`) profiles are included. --- ## How Status Lists Are Aggregated The aggregation endpoint collects status list URLs by: 1. **Listing credential profiles** — All profiles on the issuer service are considered. 2. **Filtering by format** — Only profiles whose credential configuration format is SD-JWT VC (`dc+sd-jwt`) or mDoc (`mso_mdoc`) are kept; W3C JWT credentials use different status mechanisms. 3. **Extracting status configurations** — Each profile's `credentialStatus` configuration is examined. 4. **Resolving URLs** — The referenced status credential configurations are resolved to their public URLs via the [Credential Status Service](https://docs.walt.id/enterprise-stack/services/credential-status-service/overview.md). 5. **Deduplicating and sorting** — Multiple profiles may reference the same status list; duplicates are removed and the result is sorted alphabetically. --- ## Limitations - **Token Status List only** — Aggregation covers SD-JWT VC and mDoc profiles, which use the Token Status List mechanism. - **No change notifications** — The endpoint must be polled; there is no push mechanism. --- ## Related Documentation - [Credential Profiles](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-profiles/create-profile.md) – Configure credential status in profiles. - [Credential Status Service](https://docs.walt.id/enterprise-stack/services/credential-status-service/overview.md) – Manage status lists. -------------------------------------------------------------------------------- PAGE: Data Functions URL: https://docs.walt.id/enterprise-stack/services/issuer2-service/data-functions RAW: https://docs.walt.id/enterprise-stack/services/issuer2-service/data-functions.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Data Functions The **Issuer2 Service** supports **data functions** when issuing credentials. These functions ensure that issued credentials always contain the most current and accurate information. **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, see the [credential-issuance guides](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-issuance/sd-jwt-vc.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 subject did. #### 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": { "url": "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" : { "url" : "https://university.example.edu/public/background.png", "alt_text" : "a background image of a university" }, "secondary_image" : { "url" : "https://university.example.edu/public/secondary.png", "alt_text" : "a secondary image of a university" } } ] } ```
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": { "url": "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": { "url": "https://university.example.edu/public/background.png", "alt_text": "a background image of a university" }, "secondary_image": { "url": "https://university.example.edu/public/secondary.png", "alt_text": "a secondary 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:ssZZ` -------------------------------------------------------------------------------- PAGE: Notifications & Session Events URL: https://docs.walt.id/enterprise-stack/services/issuer2-service/notifications RAW: https://docs.walt.id/enterprise-stack/services/issuer2-service/notifications.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Notifications & Session Events The Issuer2 Service offers two ways to observe issuance in real time: - **Webhooks (notifications)** — the issuer pushes events to your server. Best for server-to-server integration and production workflows. - **Server-Sent Events (SSE)** — your client streams events from the issuer. Best for live UI, such as showing issuance progress in a browser. Both surface the same underlying issuance-session events. | | Webhooks | SSE | |---|----------|-----| | **Best for** | Server-to-server, production | Browser/client apps | | **Direction** | Issuer pushes to your server | Client streams from issuer | | **Reliability** | More reliable for long-running processes | Connection may drop | | **Setup** | Requires a public endpoint | No server needed | --- ## Webhooks Webhook notifications are configured in the `notifications` object of a [credential profile](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-profiles/create-profile.md) or as a [runtime override](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-offers/create-offer.md#runtime-overrides) when creating an offer. ### Configuration ```json { "notifications": { "webhook": { "url": "https://your-server.com/webhook/issuance", "bearer_token": "your-secret-token" } } } ``` **Configuration Properties** - **webhook.url**: _String_ (required) - The URL to receive webhook notifications. - **webhook.bearer_token**: _String_ (optional) - Bearer token sent with the webhook (as `Authorization: Bearer `) for authentication. - **webhook.basic_auth_username** / **webhook.basic_auth_password**: _String_ (optional) - Basic-auth credentials sent with the webhook instead of a bearer token. **Note:** Configure notifications at the **profile** level to apply to every offer from that profile, or as a **per-offer** runtime override to change them for a single offer. ### Webhook Events Your endpoint receives POST requests for the issuance-session events below. The event names are the same across webhooks and SSE: | Event | When triggered | |-------|----------------| | `resolved_credential_offer` | A wallet resolves the credential offer. | | `requested_token` | A wallet requests an access token. | | `sdjwt_issue` | An SD-JWT VC credential is issued. | | `jwt_issue` | A W3C JWT credential is issued. | | `generated_mdoc` | An mDoc credential is generated. | | `issuance_status` | The session status changes (e.g. `SUCCESSFUL`, `EXPIRED`). | Each webhook is a POST whose JSON body is the same envelope used for [SSE](#session-events-sse): `target`, `event`, and the full `session` object. `target` is the **issuance-session ID** (the `offerId`), not the issuer service path, and `event` holds one of the event names above. ```json { "target": "9cf9e1f6-dc53-48f2-8124-b160433d7728", "event": "resolved_credential_offer", "session": { "id": "9cf9e1f6-dc53-48f2-8124-b160433d7728", "status": "ACTIVE", "...": "..." } } ``` ### Example Endpoint (Node.js/Express) ```javascript const express = require('express'); const app = express(); app.use(express.json()); app.post('/webhook/issuance', (req, res) => { if (req.headers.authorization !== 'Bearer your-secret-token') { return res.status(401).send('Unauthorized'); } const { event, target, session } = req.body; console.log(`Issuance event '${event}' for session ${target}, status: ${session?.status}`); res.status(200).send('OK'); }); app.listen(3000); ``` ### Best Practices 1. **Secure your endpoint** – Use HTTPS and verify the bearer token. 2. **Respond quickly** – Return `200` promptly; process events asynchronously if needed. 3. **Handle retries** – Implement idempotency in case of duplicate deliveries. 4. **Log events** – Keep records of received events for debugging and auditing 5. **Monitor failures** – If your endpoint is unreachable, issuance still continues, but the event may be lost. Fall back to SSE or polling if you must not miss events. --- ## Session Events (SSE) The SSE endpoint streams issuance-session events to a connected client in real time. Use it to monitor the progress of a credential offer as it is claimed. **Option: CURL** **Endpoint:** `GET /v2/{target}/issuer-service-api/issuance-session/{issuance-session}/events` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html) ##### Example Request ```bash curl -N \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{target}/issuer-service-api/issuance-session/{offerId}/events' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Accept: text/event-stream' ``` **Path Parameters** - **orgID**: _String_ (required) - Your organization ID, e.g. `test.enterprise-sandbox.waltid.dev`. - **target**: _String_ (required) - The resource identifier of the issuer service, `{organizationID}.{tenantID}.{issuerServiceID}`. - **issuance-session**: _String_ (required) - The `offerId` returned when creating the credential offer. **Header Parameters** - **Authorization**: _String_ (required) - Bearer token. Format: `Bearer {token}`. --- ##### Example Response Each event is delivered as a single SSE `data:` line. The event name is carried inside the JSON payload's `event` property (the same `{target, event, session}` envelope as the webhook). The first line is an empty priming frame sent when the connection opens: ```text data: {} data: {"target":"9cf9e1f6-dc53-48f2-8124-b160433d7728","event":"resolved_credential_offer","session":{ ... }} data: {"target":"9cf9e1f6-dc53-48f2-8124-b160433d7728","event":"requested_token","session":{ ... }} ``` ### Event Types The SSE stream emits the same issuance-session events listed under [Webhook Events](#webhook-events): `resolved_credential_offer`, `requested_token`, `sdjwt_issue`, `jwt_issue`, `generated_mdoc`, and `issuance_status`. ### Event Format Every event is a single `data:` line whose value is the JSON envelope; there is no SSE `event:` line: ``` data: {"target":"{offerId}","event":"{eventType}","session":{ ... }} ``` ### Best Practices 1. **Handle reconnection** – SSE connections may drop; implement reconnection logic. 2. **Set timeouts** – Close connections after the expected session duration. 3. **Prefer webhooks for production** – For server-to-server integration, webhooks are more reliable than SSE. 4. **Monitor Multiple Sessions** – Open separate SSE connections for each session you want to monitor --- ## Next Steps - [Create a Profile](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-profiles/create-profile.md) – Add notifications to your profiles. - [Create an Offer](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-offers/create-offer.md) – Override notifications per offer. -------------------------------------------------------------------------------- PAGE: Full API Reference URL: https://docs.walt.id/enterprise-stack/services/issuer2-service/full-api-reference RAW: https://docs.walt.id/enterprise-stack/services/issuer2-service/full-api-reference.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Full API Reference Find the complete, interactive API reference [here](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Issuer%20Service%20Api2). -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/services/x509-store-service/overview RAW: https://docs.walt.id/enterprise-stack/services/x509-store-service/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview The X.509 Store Service manages the peristence of X.509 certificates inside the Enterprise resource tree. It supports both generic certificate entries and VICAL entries for IACA certificates. The service can be used directly through the Enterprise API, attached to the X.509 Certificate Service so issued certificates are persisted automatically, or attached to the VICAL Service as a source of publishable IACA entries. **Note:** The X.509 Store Service is controlled by the `x509` feature flag. The flag is enabled by default, and the checked-in enterprise `_features.conf` does not disable it, so the service is available unless you explicitly add `x509` to `disabledFeatures`. ## Service Dependencies The X.509 Store Service does not require dependency services to operate. It can, however, be attached as a dependency of the [X.509 Certificate Service](https://docs.walt.id/enterprise-stack/services/x509-service/overview.md) so generated IACA and Document Signer certificates are stored automatically. It can also be attached to the [VICAL Service](https://docs.walt.id/enterprise-stack/services/vical-service/overview.md). Each VICAL publication reads all attached stores and includes every stored `vical-entry` certificate it finds. ## Core Features - Store generic X.509 certificates as `base` entries. - Store IACA certificates as `vical-entry` entries with VICAL complementary metadata. - List full stored entries or just stored certificate IDs. - Read, update, and delete stored certificates by full target path. - Act as a persistence target for X.509 issuance and as trust-material input for VICAL publication. ## Get Started - [Setup](https://docs.walt.id/enterprise-stack/services/x509-store-service/setup.md) - Create an X.509 Store service and optionally attach it to an X.509 Certificate Service. - [Manage Certificates](https://docs.walt.id/enterprise-stack/services/x509-store-service/manage-certificates.md) - Add, list, read, update, and delete stored certificates. -------------------------------------------------------------------------------- PAGE: Setup URL: https://docs.walt.id/enterprise-stack/services/x509-store-service/setup RAW: https://docs.walt.id/enterprise-stack/services/x509-store-service/setup.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Setup Create an X.509 Store service when you need to persist certificates directly through the Enterprise API or indirectly from an attached X.509 Certificate Service. ## Create the service **Option: CURL** Endpoint: `/v1/{target}/resource-api/services/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%2F%20Resource/post_v1__target__resource_api_services_create) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "x509-store" }' ``` **Body** ```json { "type": "x509-store" } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.waltid.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant in which to create the new X.509 Store service and the service's ID (`{organizationID}.{tenantID}.[NewX509StoreServiceID]`), e.g. `test.tenant1.x509-store-1`. **Body Parameters** - `type`: _serviceType_ - Specifies the type of service to create. In this case `x509-store`. **Response Codes** - `201` - Service created successfully. ## Attach the store to an X.509 Certificate Service This step is optional. Attach the X.509 Store to an existing X.509 Certificate Service if you want issued IACA or Document Signer certificates to be written into the store automatically. **Option: CURL** Endpoint: `/v1/{target}/x509-service-api/dependencies/add` **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/x509-service-api/dependencies/add' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d 'test.tenant1.x509-store-1' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.waltid.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + X.509 service to which to add an existing X.509 Store (`{organizationID}.{tenantID}.{x509ServiceID}`), e.g. `test.tenant1.x509-service-1`. **Body Parameters** - `dependency`: _String_ - JSON string containing the fully-qualified path of the X.509 Store service to link, for example `test.tenant1.x509-store-1`. **Response Codes** - `201` - Dependency attached successfully. **Note:** When one or more X.509 Stores are attached to an X.509 Certificate Service, issued certificates are stored in all attached stores using one shared `storedCertificateId` child ID. Issuance responses return full stored certificate paths in `storedCertificateIds`. **Note:** If no X.509 Store is attached, `storedCertificateId` is ignored on X.509 issuance requests and `storedCertificateIds` is omitted from issuance responses. ## Attach the store to a VICAL Service This step is optional. Attach the X.509 Store to an existing VICAL Service if the store contains `vical-entry` IACA certificates that should be included in published VICAL versions. **Option: CURL** Endpoint: `/v1/{target}/vical-service-api/dependencies/add` **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/vical-service-api/dependencies/add' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d 'test.tenant1.x509-store-1' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.waltid.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + VICAL service to which to add an existing X.509 Store (`{organizationID}.{tenantID}.{vicalServiceID}`), e.g. `test.tenant1.vical-service`. **Body Parameters** - `dependency`: _String_ - JSON string containing the fully-qualified path of the X.509 Store service to link, for example `test.tenant1.x509-store-1`. **Response Codes** - `201` - Dependency attached successfully. **Note:** Each VICAL publication reads all attached X.509 Stores and includes only certificates stored as `vical-entry`. -------------------------------------------------------------------------------- PAGE: Manage Certificates URL: https://docs.walt.id/enterprise-stack/services/x509-store-service/manage-certificates RAW: https://docs.walt.id/enterprise-stack/services/x509-store-service/manage-certificates.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Manage Certificates Use the X.509 Store Service to store generic certificates or VICAL entries, list what is already stored, and manage individual entries by full target path. Service reference: [Swagger API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20X.509%20Certificate%20Store) ## Shared Path Parameters - `orgID`: When performing operations within an organization, use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.waltid.dev` when using the sandbox environment. - `target`: For `list` and `list-ids`, use the X.509 Store service path (`{organizationID}.{tenantID}.{x509StoreServiceID}`), for example `test.tenant1.x509-store-1`. For `add`, use the store path to generate a certificate ID automatically, or use a full child certificate path to choose the ID yourself, for example `test.tenant1.x509-store-1.base-001`. For `get`, `update`, and `delete`, use the full stored certificate path. ## List stored certificates **Option: CURL** Endpoint: `/v1/{target}/x509-store-api/certificates` ```bash curl -X 'GET' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/x509-store-api/certificates?limit=50&offset=0&sort=createdDateTime' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' ``` **Query Parameters** - `limit`: _Integer (optional)_ – Maximum number of entries to return. Defaults to `100`. - `offset`: _Integer (optional)_ – Number of entries to skip. Defaults to `0`. - `sort`: _String (optional)_ – One of `createdDateTime`, `createdDateTime+`, `createdDateTime-`, `updatedDateTime`, `updatedDateTime+`, `updatedDateTime-`. Invalid values return `400 Bad Request` — see the [pagination reference](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/pagination.md) for the full contract. **Example Response** ```json [ { "_id": "test.tenant1.x509-store-1.base-001", "data": { "type": "base", "pem": "-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----" } }, { "_id": "test.tenant1.x509-store-1.vical-001", "data": { "type": "vical-entry", "pem": "-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----", "complementaryMetadata": { "docType": ["org.iso.18013.5.1.mDL"], "certificateProfile": ["1.0.18013.5.1.7"] } } } ] ``` **Response Codes** - `200` - Stored certificates retrieved successfully. ## List stored certificate IDs **Option: CURL** Endpoint: `/v1/{target}/x509-store-api/certificates/ids` ```bash curl -X 'GET' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/x509-store-api/certificates/ids?limit=50&offset=0' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' ``` **Query Parameters** - `limit`: _Integer (optional)_ – Maximum number of entries to return. Defaults to `100`. - `offset`: _Integer (optional)_ – Number of entries to skip. Defaults to `0`. `sort` is not supported on this endpoint. Invalid values return `400 Bad Request` — see the [pagination reference](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/pagination.md) for the full contract. **Example Response** ```json [ "test.tenant1.x509-store-1.base-001", "test.tenant1.x509-store-1.vical-001" ] ``` **Response Codes** - `200` - Stored certificate IDs retrieved successfully. ## Add a base certificate Use the store target to generate a certificate ID automatically. Use a full child certificate target, such as `test.tenant1.x509-store-1.base-001`, when you want to choose the stored certificate ID. **Option: CURL** Endpoint: `/v1/{target}/x509-store-api/certificates` ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/x509-store-api/certificates' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "base", "certificatePem": "-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----" }' ``` **Body Parameters** - `type`: Set to `base` to store a generic X.509 certificate entry. - `certificatePem`: PEM-encoded X.509 certificate to store. **Example Response** ```json { "_id": "test.tenant1.x509-store-1.base-001", "data": { "type": "base", "pem": "-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----" } } ``` **Response Codes** - `201` - Certificate stored successfully. The stored certificate ID comes from the request target. When the target is the store itself, the service generates a UUID child ID. ## Add a VICAL entry Use the store target to generate a certificate ID automatically. Use a full child certificate target, such as `test.tenant1.x509-store-1.vical-001`, when you want to choose the stored certificate ID. **Option: CURL** Endpoint: `/v1/{target}/x509-store-api/certificates` ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/x509-store-api/certificates' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "vical-entry", "certificatePem": "-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----", "complementaryMetadata": { "docType": ["org.iso.18013.5.1.mDL"], "certificateProfile": ["1.0.18013.5.1.7"] } }' ``` **Body Parameters** - `type`: Set to `vical-entry` to store an IACA certificate plus VICAL metadata. - `certificatePem`: PEM-encoded IACA certificate to store. - `complementaryMetadata.docType`: Required list of document types covered by the IACA certificate. - `complementaryMetadata.certificateProfile`: Optional list of certificate-profile identifiers to store with the entry. **Example Response** ```json { "_id": "test.tenant1.x509-store-1.vical-001", "data": { "type": "vical-entry", "pem": "-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----", "complementaryMetadata": { "docType": ["org.iso.18013.5.1.mDL"], "certificateProfile": ["1.0.18013.5.1.7"] } } } ``` **Response Codes** - `201` - VICAL entry stored successfully. A `vical-entry` requires a valid IACA certificate and non-empty `complementaryMetadata.docType`. `certificateProfile`, when provided, must also be non-empty. ## Get a stored certificate **Option: CURL** Endpoint: `/v1/{target}/x509-store-api/certificates` ```bash curl -X 'GET' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/x509-store-api/certificates' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' ``` **Example Response** ```json { "_id": "test.tenant1.x509-store-1.vical-001", "data": { "type": "vical-entry", "pem": "-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----", "complementaryMetadata": { "docType": ["org.iso.18013.5.1.mDL"], "certificateProfile": ["1.0.18013.5.1.7"] } } } ``` **Response Codes** - `200` - Stored certificate retrieved successfully. ## Update a stored certificate **Option: CURL** Endpoint: `/v1/{target}/x509-store-api/certificates` ```bash curl -X 'PUT' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/x509-store-api/certificates' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "vical-entry", "certificatePem": "-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----", "complementaryMetadata": { "docType": ["org.iso.18013.5.1.mDL"], "certificateProfile": ["1.0.18013.5.1.7"] } }' ``` **Body Parameters** - `type`: Use `base` to replace the stored entry with a generic certificate, or `vical-entry` to replace it with an IACA certificate plus VICAL metadata. - `certificatePem`: PEM-encoded certificate that should replace the current stored payload. - `complementaryMetadata`: Required when `type` is `vical-entry`. The target identifies which stored certificate is updated. You can switch between `base` and `vical-entry` on update. **Response Codes** - `204` - Certificate updated successfully. ## Delete a stored certificate **Option: CURL** Endpoint: `/v1/{target}/x509-store-api/certificates` ```bash curl -X 'DELETE' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/x509-store-api/certificates' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' ``` **Response Codes** - `200` - Certificate deleted successfully. -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/services/x509-service/overview RAW: https://docs.walt.id/enterprise-stack/services/x509-service/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview The X.509 Certificate Service issues X.509 certificates for PKI deployments, including ISO/IEC 18013-5 compliant certificates for mobile driving licence (mDL) ecosystems. It supports self-signed IACA certificates, Document Signer certificates, generic CA and leaf certificates, and PKCS#10 Certificate Signing Requests (CSRs). By default, the service is stateless. If one or more [X.509 Store services](https://docs.walt.id/enterprise-stack/services/x509-store-service/overview.md) are attached as dependencies, issued certificates can also be persisted automatically. **Note:** The X.509 Certificate Service is controlled by the `x509` feature flag. The flag is enabled by default via the [`_features.conf`](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/features.md). This means the service is available unless you explicitly add `x509` to `disabledFeatures`. ## Service Dependencies The X.509 Certificate Service supports two dependency types: - [Key Management Service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) for requests that use `kms-hosted-key-descriptor`. - [X.509 Store Service](https://docs.walt.id/enterprise-stack/services/x509-store-service/overview.md) for persisting issued certificates. If a request uses `jwk-encoded-key-descriptor`, no KMS dependency is required for that key input. ## Core Features - **Generic Certificates**: Issue self-signed or CA-signed X.509 certificates with full control over extensions, key usage, and validity. - **ISO mDL Certificates**: Issue self-signed IACA root certificates and Document Signer certificates anchored to an IACA. - **CSR-based Issuance**: Accept CSR PEM as a subject public key source for Document Signer and generic certificate issuance, enabling proof-of-possession workflows. - **Flexible Key Sources**: Accept signing keys from linked KMS services or directly as JWKs. - **Automatic Persistence**: Persist issued certificates into all attached X.509 Store services when configured. - **CSR Creation**: Generate PKCS#10 Certificate Signing Requests for use in external CA workflows or CSR-based certificate issuance. ## Get Started - [Setup](https://docs.walt.id/enterprise-stack/services/x509-service/setup.md) - Create the service and attach optional dependency services. - [Issue Certificates](https://docs.walt.id/enterprise-stack/services/x509-service/issue-certificates.md) - Create IACA, Document Signer, and generic certificates. - [CSR Workflows](https://docs.walt.id/enterprise-stack/services/x509-service/csr-workflows.md) - Create CSRs and use them for certificate issuance. -------------------------------------------------------------------------------- PAGE: Setup URL: https://docs.walt.id/enterprise-stack/services/x509-service/setup RAW: https://docs.walt.id/enterprise-stack/services/x509-service/setup.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Setup Create an X.509 Certificate Service when you need to issue IACA or Document Signer certificates through the Enterprise API. ## Create the service **Option: CURL** Endpoint: `/v1/{target}/resource-api/services/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%2F%20Resource/post_v1__target__resource_api_services_create) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "x509-service" }' ``` **Body** ```json { "type": "x509-service" } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.waltid.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant in which to create the new X.509 Certificate service and the service's ID (`{organizationID}.{tenantID}.[NewX509ServiceID]`), e.g. `test.tenant1.x509-service-1`. **Body Parameters** - `type`: _serviceType_ - Specifies the type of service to create. In this case `x509-service`. **Response Codes** - `201` - Service created successfully. ## Attach a KMS service Attach a KMS service if you want to use `kms-hosted-key-descriptor` in issuance requests. **Option: CURL** Endpoint: `/v1/{target}/x509-service-api/dependencies/add` **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/x509-service-api/dependencies/add' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d 'test.tenant1.kms1' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.waltid.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + X.509 Certificate service to which to add an existing dependency service (`{organizationID}.{tenantID}.{x509ServiceID}`), e.g. `test.tenant1.x509-service-1`. **Body Parameters** - `dependency`: _String_ - JSON string containing the fully-qualified path of the KMS service to link, for example `test.tenant1.kms1`. **Response Codes** - `201` - Dependency attached successfully. **Note:** If a request uses `jwk-encoded-key-descriptor`, the key is supplied directly in the request and no KMS dependency is required for that key input. ## Attach an X.509 Store service Attach one or more X.509 Store services if you want issued certificates to be persisted automatically. **Option: CURL** Endpoint: `/v1/{target}/x509-service-api/dependencies/add` **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/x509-service-api/dependencies/add' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d 'test.tenant1.x509-store-1' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.waltid.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + X.509 Certificate service to which to add an existing dependency service (`{organizationID}.{tenantID}.{x509ServiceID}`), e.g. `test.tenant1.x509-service-1`. **Body Parameters** - `dependency`: _String_ - JSON string containing the fully-qualified path of the X.509 Store service to link, for example `test.tenant1.x509-store-1`. **Response Codes** - `201` - Dependency attached successfully. **Note:** When one or more X.509 Store services are attached, the X.509 Certificate Service uses one shared `storedCertificateId` child ID across all attached stores for each issuance request. The issuance response returns full stored certificate paths in `storedCertificateIds`. -------------------------------------------------------------------------------- PAGE: Issue Certificates URL: https://docs.walt.id/enterprise-stack/services/x509-service/issue-certificates RAW: https://docs.walt.id/enterprise-stack/services/x509-service/issue-certificates.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Issue Certificates Use the X.509 Certificate Service to issue IACA root certificates, Document Signer certificates, and generic X.509 certificates. Service reference: [Swagger API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20X.509%20Certificate%20Service) **Note:** If one or more [X.509 Store services](https://docs.walt.id/enterprise-stack/services/x509-store-service/overview.md) are attached, the service uses one shared `storedCertificateId` child ID across all attached stores. When you omit `storedCertificateId`, the service generates a UUID automatically. If no store is attached, the field is ignored and `storedCertificateIds` is omitted from the response. ## Shared Path Parameters - `orgID`: When performing operations within an organization, use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.waltid.dev` when using the sandbox environment. - `target`: The full X.509 Certificate service path (`{organizationID}.{tenantID}.{x509ServiceID}`), for example `test.tenant1.x509-service-1`. ## Create an IACA certificate **Option: CURL** Endpoint: `/v1/{target}/x509-service-api/iso/iacas` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20X.509%20Certificate%20Service) ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/x509-service-api/iso/iacas' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "storedCertificateId": "iaca-prod-2026-q1", "certificateData": { "country": "US", "commonName": "Example IACA", "issuerAlternativeNameConf": { "uri": "https://iaca.example.com" } }, "vicalEntryComplementaryMetadata": { "docType": ["org.iso.18013.5.1.mDL"], "certificateProfile": ["org.iso.18013.5.1.iaca"] }, "iacaKeyDesc": { "type": "kms-hosted-key-descriptor", "keyIdPath": "test.tenant1.kms1.secp256r1" } }' ``` **Body Parameters** - `certificateData`: Subject and validity data for the generated IACA certificate. - `iacaKeyDesc`: Signing key descriptor. Use either `kms-hosted-key-descriptor` or `jwk-encoded-key-descriptor`. - `storedCertificateId`: Optional child certificate ID used when one or more X.509 Store dependencies are attached. The response returns the resulting full stored certificate paths in `storedCertificateIds`. - `vicalEntryComplementaryMetadata`: Optional metadata. When provided and stores are attached, the issued IACA is stored as a VICAL entry; otherwise it is stored as a generic certificate. **Example Response** ```json { "certificatePEM": "-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----", "storedCertificateIds": [ "test.tenant1.x509-store-1.iaca-prod-2026-q1" ], "certificateData": { "country": "US", "commonName": "Example IACA", "notBefore": "", "notAfter": "", "issuerAlternativeNameConf": { "uri": "https://iaca.example.com" } } } ``` **Response Codes** - `200` - IACA certificate issued successfully. **Note:** If no X.509 Store dependency is attached, `storedCertificateId` is ignored on the request and `storedCertificateIds` is omitted from the response. ## Create a Document Signer certificate **Option: CURL** Endpoint: `/v1/{target}/x509-service-api/iso/document-signers` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20X.509%20Certificate%20Service) ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/x509-service-api/iso/document-signers' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "storedCertificateId": "ds-mainline-2026-01", "iacaSigner": { "type": "iaca-cert-data-descriptor", "iacaKeyDesc": { "type": "kms-hosted-key-descriptor", "keyIdPath": "test.tenant1.kms1.iaca-secp256r1" }, "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://crl-example.com" }, "dsPublicKeySource": { "type": "key-descriptor", "keyDescriptor": { "type": "kms-hosted-key-descriptor", "keyIdPath": "test.tenant1.kms1.ds-secp256r1" } } }' ``` **Body Parameters** - `iacaSigner`: Describes the IACA used to issue the Document Signer certificate. Use either `iaca-cert-data-descriptor` or `iaca-pem-cert-descriptor`. - `certificateData`: Subject and validity data for the generated Document Signer certificate. - `dsPublicKeySource`: Source of the Document Signer's public key. Use either: - `key-descriptor`: Direct key reference via `kms-hosted-key-descriptor` or `jwk-encoded-key-descriptor`. - `csr`: CSR PEM string for CSR-based issuance (see [CSR Workflows](https://docs.walt.id/enterprise-stack/services/x509-service/csr-workflows.md)). - `dsKeyDescriptor`: Legacy field for direct key descriptor. Use `dsPublicKeySource` for new integrations. - `storedCertificateId`: Optional child certificate ID used when one or more X.509 Store dependencies are attached. The response returns the resulting full stored certificate paths in `storedCertificateIds`. **Example Response** ```json { "certificatePEM": "-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----", "storedCertificateIds": [ "test.tenant1.x509-store-1.ds-mainline-2026-01" ], "certificateData": { "country": "US", "commonName": "Example DS", "notBefore": "", "notAfter": "", "crlDistributionPointUri": "https://crl-example.com" } } ``` **Response Codes** - `200` - Document Signer certificate issued successfully. If a provided `storedCertificateId` already exists in any attached X.509 Store, or if any attached store write fails, the issuance request fails. ## Create a generic certificate Generic certificates provide full control over X.509 certificate attributes including subject name, key usage, extended key usage, validity period, and CA constraints. They can be self-signed or signed by a CA key. **Option: CURL** Endpoint: `/v1/{target}/x509-service-api/certificate` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20X.509%20Certificate%20Service) **Self-signed leaf certificate** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/x509-service-api/certificate' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "storedCertificateId": "generic-leaf-01", "certificateData": { "subjectName": { "commonName": "Example Leaf", "country": "US", "organizationName": "Example Org" }, "isCertificateAuthority": false, "keyUsage": ["DigitalSignature"] }, "subjectPublicKeySource": { "type": "key-descriptor", "keyDescriptor": { "type": "kms-hosted-key-descriptor", "keyIdPath": "test.tenant1.kms1.leaf-secp256r1" } }, "issuer": { "type": "self-signed" } }' ``` **CA-signed certificate** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/x509-service-api/certificate' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "storedCertificateId": "generic-intermediate-01", "certificateData": { "subjectName": { "commonName": "Example Intermediate CA", "country": "US" }, "isCertificateAuthority": true, "pathLengthConstraint": 0, "keyUsage": ["KeyCertSign", "CRLSign"] }, "subjectPublicKeySource": { "type": "key-descriptor", "keyDescriptor": { "type": "kms-hosted-key-descriptor", "keyIdPath": "test.tenant1.kms1.intermediate-secp256r1" } }, "issuer": { "type": "ca-signed", "issuerKeyDescriptor": { "type": "kms-hosted-key-descriptor", "keyIdPath": "test.tenant1.kms1.root-secp256r1" } } }' ``` **Body Parameters** - `certificateData`: Certificate attributes including subject name, validity, CA constraints, and key usage. - `subjectName`: Distinguished name fields (`commonName`, `country`, `organizationName`, etc.). - `issuerName`: Optional issuer distinguished name. Defaults to subject name for self-signed certificates. - `notBefore` / `notAfter`: Optional validity period. Defaults to now and 365 days from now. - `isCertificateAuthority`: Set to `true` for CA certificates. - `pathLengthConstraint`: Optional path length for CA certificates. - `keyUsage`: Array of key usage values (`DigitalSignature`, `KeyCertSign`, `CRLSign`, etc.). - `extendedKeyUsageOids`: Optional array of extended key usage OIDs. - `subjectAlternativeNames`: Optional SAN values (`dnsNames`, `uris`, `emails`, `ipAddresses`). - `crlDistributionPointUri`: Optional CRL distribution point. - `subjectPublicKeySource`: Source of the subject's public key. Use either: - `key-descriptor`: Direct key reference via `kms-hosted-key-descriptor` or `jwk-encoded-key-descriptor`. - `csr`: CSR PEM string for CSR-based issuance (see [CSR Workflows](https://docs.walt.id/enterprise-stack/services/x509-service/csr-workflows.md)). - `issuer`: Issuer configuration. Use either: - `self-signed`: The certificate is self-signed using the subject key. - `ca-signed`: The certificate is signed by a CA key specified in `issuerKeyDescriptor`. - `storedCertificateId`: Optional child certificate ID for storage in attached X.509 Stores. **Example Response** ```json { "storedCertificateIds": ["test.tenant1.x509-store-1.generic-leaf-01"], "certificatePEM": "-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----", "certificateData": { "subjectName": { "commonName": "Example Leaf", "country": "US", "organizationName": "Example Org" }, "issuerName": { "commonName": "Example Leaf", "country": "US", "organizationName": "Example Org" }, "notBefore": "", "notAfter": "", "isCertificateAuthority": false, "keyUsage": ["DigitalSignature"], "publicKeyJwk": { "kty": "EC", "crv": "P-256", "x": "...", "y": "..." } } } ``` **Response Codes** - `200` - Generic certificate issued successfully. **Note:** Self-signed certificates require a `key-descriptor` subject public key source since the same key is used for signing. CSR-based issuance requires a `ca-signed` issuer. -------------------------------------------------------------------------------- PAGE: CSR Workflows URL: https://docs.walt.id/enterprise-stack/services/x509-service/csr-workflows RAW: https://docs.walt.id/enterprise-stack/services/x509-service/csr-workflows.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # CSR Workflows The X.509 Certificate Service supports PKCS#10 Certificate Signing Request (CSR) creation and CSR-based certificate issuance. This enables proof-of-possession workflows where the certificate requester proves control of the private key. Service reference: [Swagger API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20X.509%20Certificate%20Service) ## Shared Path Parameters - `orgID`: When performing operations within an organization, use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.waltid.dev` when using the sandbox environment. - `target`: The full X.509 Certificate service path (`{organizationID}.{tenantID}.{x509ServiceID}`), for example `test.tenant1.x509-service-1`. ## Create a CSR Create a PKCS#10 Certificate Signing Request. The CSR is signed by the subject key, proving possession of the private key. The returned CSR PEM can be submitted to an external CA or used as a subject public key source when creating certificates through this service. **Option: Generic CSR** **Option: CURL** Endpoint: `/v1/{target}/x509-service-api/csrs` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20X.509%20Certificate%20Service) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/x509-service-api/csrs' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "generic", "subjectKeyDescriptor": { "type": "kms-hosted-key-descriptor", "keyIdPath": "test.tenant1.kms1.leaf-secp256r1" }, "subjectName": { "commonName": "Example Leaf", "country": "US", "organizationName": "Example Org" }, "subjectAlternativeNames": { "dnsNames": ["leaf.example.com"] } }' ``` **Body Parameters** - `type`: _String_ - Set to `"generic"` for generic CSR. - `subjectKeyDescriptor`: _Object_ - Key descriptor for the subject key. - `type`: _String_ - Descriptor type. Use `"kms-hosted-key-descriptor"` for keys stored in a KMS service or `"jwk-encoded-key-descriptor"` for directly provided JWK keys. - `keyIdPath`: _String_ - (For KMS-hosted keys) The full key path in format `{organizationID}.{tenantID}.{kmsServiceID}.{keyID}`, e.g. `test.tenant1.kms1.leaf-secp256r1`. - `subjectName`: _Object_ - Distinguished name fields (`commonName`, `country`, `organizationName`, `stateOrProvinceName`, `localityName`, `organizationalUnitName`). - `subjectAlternativeNames`: _Object_ (optional) - SAN values (`dnsNames`, `uris`, `emails`, `ipAddresses`). --- **Example Response** ```json { "csrPEM": "-----BEGIN CERTIFICATE REQUEST----- MIIB... -----END CERTIFICATE REQUEST-----", "csrData": { "subjectName": { "commonName": "Example Leaf", "country": "US", "organizationName": "Example Org" }, "subjectAlternativeNames": { "dnsNames": ["leaf.example.com"] }, "publicKeyJwk": { "kty": "EC", "crv": "P-256", "x": "q0xi21uudhkC1QMykfYryN9bcDx480iJLSaRI05CWgY", "y": "NhD-09xqrUgE2-qSt8g5imePgKPPNVpYoHmsTL2f4io" } } } ``` **Response Codes** - `200` - CSR created successfully. **Option: ISO Document Signer CSR** **Option: CURL** Endpoint: `/v1/{target}/x509-service-api/csrs` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20X.509%20Certificate%20Service) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/x509-service-api/csrs' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "iso-document-signer", "subjectKeyDescriptor": { "type": "kms-hosted-key-descriptor", "keyIdPath": "test.tenant1.kms1.ds-secp256r1" }, "certificateData": { "country": "US", "commonName": "Example DS", "crlDistributionPointUri": "https://crl-example.com" } }' ``` **Body Parameters** - `type`: _String_ - Set to `"iso-document-signer"` for ISO Document Signer CSR. - `subjectKeyDescriptor`: _Object_ - Key descriptor for the Document Signer key. - `type`: _String_ - Descriptor type. Use `"kms-hosted-key-descriptor"` for keys stored in a KMS service or `"jwk-encoded-key-descriptor"` for directly provided JWK keys. - `keyIdPath`: _String_ - (For KMS-hosted keys) The full key path in format `{organizationID}.{tenantID}.{kmsServiceID}.{keyID}`, e.g. `test.tenant1.kms1.ds-secp256r1`. - `certificateData`: _Object_ - Document Signer profile data (`country`, `commonName`, `crlDistributionPointUri`, and optional `stateOrProvinceName`, `organizationName`, `localityName`). --- **Example Response** ```json { "csrPEM": "-----BEGIN CERTIFICATE REQUEST----- MIHcMIGEAgEAMCIxCzAJBgNVBAYTAlVTMRMwEQYDVQQDDApFeGFtcGxlIERTMFkw EwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEjVOxFXwcpNWbLsvSCzVW9aU3sdgvr7hu eF/tR3nGYPaParfLDME3ehFy9yDGq5sntm8BaOvt11uoYkaCcadHZ6AAMAoGCCqG SM49BAMCA0cAMEQCIBIXGH273+3dv0nvyftf7qToNCmCK/DB4qSYv93HvSAAAiBK r5aymSNZaILhKCQxOa1xirT3RmZ3Rw/w+OBTfEDk4A== -----END CERTIFICATE REQUEST-----", "csrData": { "subjectName": { "commonName": "Example DS", "country": "US" }, "publicKeyJwk": { "kty": "EC", "crv": "P-256", "x": "jVOxFXwcpNWbLsvSCzVW9aU3sdgvr7hueF_tR3nGYPY", "y": "j2q3ywzBN3oRcvcgxqubJ7ZvAWjr7ddbqGJGgnGnR2c" } } } ``` **Response Codes** - `200` - CSR created successfully. **Note:** The CSR endpoint is stateless. The service does not persist CSR state. Store the returned `csrPEM` for later use when creating certificates. ## Issue a certificate from a CSR Both generic certificates and Document Signer certificates can be issued using a CSR as the subject public key source. This workflow verifies proof-of-possession before issuing the certificate. ### Generic certificate from CSR **Option: CURL** Endpoint: `/v1/{target}/x509-service-api/certificate` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20X.509%20Certificate%20Service) ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/x509-service-api/certificate' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "storedCertificateId": "generic-leaf-from-csr", "certificateData": { "subjectName": { "commonName": "Example Leaf From CSR", "country": "US" }, "keyUsage": ["DigitalSignature"] }, "subjectPublicKeySource": { "type": "csr", "csrPEM": "-----BEGIN CERTIFICATE REQUEST----- MIIB... -----END CERTIFICATE REQUEST-----" }, "issuer": { "type": "ca-signed", "issuerKeyDescriptor": { "type": "kms-hosted-key-descriptor", "keyIdPath": "test.tenant1.kms1.iaca-secp256r1" } } }' ``` **Body Parameters** - `subjectPublicKeySource`: _Object_ - Source of the subject's public key. - `type`: _String_ - Set to `"csr"` for CSR-based issuance. - `csrPEM`: _String_ - The CSR in PEM format (begins with `-----BEGIN CERTIFICATE REQUEST-----`). - `issuer`: _Object_ - Issuer configuration. Must be `ca-signed` for CSR-based issuance. Self-signed certificates cannot use CSR input. - `type`: _String_ - Set to `"ca-signed"`. - `issuerKeyDescriptor`: _Object_ - Key descriptor for the CA signing key. - `type`: _String_ - Descriptor type. Use `"kms-hosted-key-descriptor"` for keys stored in a KMS service or `"jwk-encoded-key-descriptor"` for directly provided JWK keys. - `keyIdPath`: _String_ - (For KMS-hosted keys) The full key path in format `{organizationID}.{tenantID}.{kmsServiceID}.{keyID}`, e.g. `test.tenant1.kms1.iaca-secp256r1`. - Other parameters are the same as [generic certificate creation](https://docs.walt.id/enterprise-stack/services/x509-service/issue-certificates.md#create-a-generic-certificate). **Response Codes** - `200` - Certificate issued successfully. ### Document Signer certificate from CSR **Option: CURL** Endpoint: `/v1/{target}/x509-service-api/iso/document-signers` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20X.509%20Certificate%20Service) ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/x509-service-api/iso/document-signers' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "storedCertificateId": "ds-from-csr-2026-01", "iacaSigner": { "type": "iaca-cert-data-descriptor", "iacaKeyDesc": { "type": "kms-hosted-key-descriptor", "keyIdPath": "test.tenant1.kms1.iaca-secp256r1" }, "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://crl-example.com" }, "dsPublicKeySource": { "type": "csr", "csrPEM": "-----BEGIN CERTIFICATE REQUEST----- MIIB... -----END CERTIFICATE REQUEST-----" } }' ``` **Body Parameters** - `dsPublicKeySource`: Set `type` to `csr` and provide the `csrPEM` string. This replaces the legacy `dsKeyDescriptor` field. - Other parameters are the same as [Document Signer certificate creation](https://docs.walt.id/enterprise-stack/services/x509-service/issue-certificates.md#create-a-document-signer-certificate). **Note:** The `dsKeyDescriptor` field is still supported for backward compatibility, but `dsPublicKeySource` is preferred for new integrations. The two fields are mutually exclusive. **Response Codes** - `200` - Document Signer certificate issued successfully. ## End-to-end CSR workflow example This example demonstrates a complete CSR-based issuance workflow: 1. **Create a CSR** using a key hosted in the KMS 2. **Submit the CSR** to create a CA-signed certificate ```bash # Step 1: Create a CSR CSR_RESPONSE=$(curl -s -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/x509-service-api/csrs' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "generic", "subjectKeyDescriptor": { "type": "kms-hosted-key-descriptor", "keyIdPath": "test.tenant1.kms1.leaf-secp256r1" }, "subjectName": { "commonName": "Example Leaf", "country": "US" } }') # Extract the CSR PEM (requires jq) CSR_PEM=$(echo "$CSR_RESPONSE" | jq -r '.csrPEM') # Step 2: Create a certificate from the CSR curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/x509-service-api/certificate' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d "{ \"certificateData\": { \"subjectName\": { \"commonName\": \"Example Leaf\", \"country\": \"US\" }, \"keyUsage\": [\"DigitalSignature\"] }, \"subjectPublicKeySource\": { \"type\": \"csr\", \"csrPEM\": $(echo "$CSR_PEM" | jq -Rs .) }, \"issuer\": { \"type\": \"ca-signed\", \"issuerKeyDescriptor\": { \"type\": \"kms-hosted-key-descriptor\", \"keyIdPath\": \"test.tenant1.kms1.ca-secp256r1\" } } }" ``` ## Use cases ### External CA integration Generate CSRs through the X.509 Certificate Service and submit them to external Certificate Authorities. The CSR proves possession of the private key stored in your KMS. ### Delegated key management Allow external parties to generate their own key pairs and submit CSRs. The X.509 Certificate Service can then issue certificates using the CSR's public key while the private key remains with the requester. ### Compliance workflows Some compliance frameworks require proof-of-possession during certificate issuance. CSR-based workflows satisfy this requirement by having the certificate requester sign the CSR with their private key. -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/services/vical-service/overview RAW: https://docs.walt.id/enterprise-stack/services/vical-service/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview The VICAL Service publishes signed Verifiable Issuer Certificate Authority Lists (VICALs) for mdoc and mDL ecosystems. Each published version is built from the `vical-entry` certificates stored in its linked [X.509 Store services](https://docs.walt.id/enterprise-stack/services/x509-store-service/overview.md). **Note:** The VICAL Service is controlled by the `vical` feature flag. The flag is disabled by default, and the checked-in enterprise `_features.conf` leaves it commented out. Add `vical` to `enabledFeatures` before using the publication or public registry routes. ## Service Dependencies The VICAL Service uses linked [Key Management Services](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) and [X.509 Store services](https://docs.walt.id/enterprise-stack/services/x509-store-service/overview.md). The signing key is resolved from KMS. The signer certificate, optional signer certificate chain, and publishable IACA entries are resolved from X.509 stores. Each publication reads all attached X.509 stores and includes every stored `vical-entry` certificate in the generated VICAL. More specifically, the VICAL service configuration references: - A signing key stored in a [Key Management Service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md). - The VICAL signer's certificate stored in an [X.509 Store Service](https://docs.walt.id/enterprise-stack/services/x509-store-service/overview.md). - An optional signer certificate chain when the signer certificate is not self-signed, all entries of which are also stored in an [X.509 Store Service](https://docs.walt.id/enterprise-stack/services/x509-store-service/overview.md). ## Core Features - Publish a new signed VICAL version from the current `vical-entry` data found across linked X.509 stores. - List published version targets for reuse with version-specific registry routes. - Resolve the latest published artifact, manifest, and trust material through public endpoints. - Resolve a specific published artifact, manifest, and trust material by version target. - Return registry artifacts as `hex`, `base64`, or raw `cbor`. ## Get Started - [Setup](https://docs.walt.id/enterprise-stack/services/vical-service/setup.md) - Create the service, configure the signer references, and attach X.509 stores. - [Publish and Resolve VICAL Versions](https://docs.walt.id/enterprise-stack/services/vical-service/publish-and-resolve.md) - Publish a version and use the latest or version-specific registry endpoints. -------------------------------------------------------------------------------- PAGE: Setup URL: https://docs.walt.id/enterprise-stack/services/vical-service/setup RAW: https://docs.walt.id/enterprise-stack/services/vical-service/setup.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Setup Create a VICAL Service when you need to publish signed VICAL feeds from stored IACA certificates. ## Before you start - Have an existing signing key available, typically in a [Key Management Service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md). - Have an existing signer certificate stored in an [X.509 Store Service](https://docs.walt.id/enterprise-stack/services/x509-store-service/overview.md). - If the signer certificate is not self-signed, have the signer certificate chain stored as separate certificates as well. - If you need source entries for publication, add `vical-entry` records to an [X.509 Store Service](https://docs.walt.id/enterprise-stack/services/x509-store-service/manage-certificates.md). ## Create the service **Option: CURL** Endpoint: `/v1/{target}/resource-api/services/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%2F%20Resource/post_v1__target__resource_api_services_create) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "vical-service", "signingKeyId": "test.tenant1.kms1.vical-signing-key", "signerCertificateId": "test.tenant1.x509-store-1.vical-signer", "signerCertificateChainIds": [ "test.tenant1.x509-store-1.vical-root" ] }' ``` **Body** ```json { "type": "vical-service", "signingKeyId": "test.tenant1.kms1.vical-signing-key", "signerCertificateId": "test.tenant1.x509-store-1.vical-signer", "signerCertificateChainIds": [ "test.tenant1.x509-store-1.vical-root" ] } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.waltid.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant in which to create the new VICAL service and the service's ID (`{organizationID}.{tenantID}.[NewVicalServiceID]`), e.g. `test.tenant1.vical-service`. **Body Parameters** - `type`: _serviceType_ - Specifies the type of service to create. In this case `vical-service`. - `signingKeyId`: _resourceIdentifier_ - Fully-qualified path of the signing key used to sign published VICAL artifacts. - `signerCertificateId`: _resourceIdentifier_ - Fully-qualified path of the stored signer certificate that matches `signingKeyId`. - `signerCertificateChainIds`: _resourceIdentifier[]_ - Optional list of fully-qualified stored certificate paths representing the signer certificate chain. **Response Codes** - `201` - Service created successfully. **Note:** The signer certificate must match the configured signing key. If the signer certificate is self-signed, omit `signerCertificateChainIds`. If it is not self-signed, provide a populated certificate chain. **Note:** The KMS service that owns `signingKeyId` and the X.509 Store services that own the configured certificate IDs are required dependencies for the VICAL Service. ## Attach an X.509 Store service Attach one or more X.509 Store services so the VICAL Service has `vical-entry` certificates to publish. **Option: CURL** Endpoint: `/v1/{target}/vical-service-api/dependencies/add` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20VICAL%20Service%20%7C%20Publication%20Management) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/vical-service-api/dependencies/add' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d 'test.tenant1.x509-store-1' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.waltid.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + VICAL service to which to add an existing dependency service (`{organizationID}.{tenantID}.{vicalServiceID}`), e.g. `test.tenant1.vical-service`. **Body Parameters** - `dependency`: _String_ - JSON string containing the fully-qualified path of the X.509 Store service to link, for example `test.tenant1.x509-store-1`. **Response Codes** - `201` - Dependency attached successfully. **Note:** Each publication reads all attached X.509 stores and includes only stored entries of type `vical-entry`. If no attached store contains at least one `vical-entry`, publication cannot succeed. You can inspect or update the signer references later through `/v1/{target}/vical-service-api/configuration/view` and `/v1/{target}/vical-service-api/configuration/update`. -------------------------------------------------------------------------------- PAGE: Publish and Resolve VICAL Versions URL: https://docs.walt.id/enterprise-stack/services/vical-service/publish-and-resolve RAW: https://docs.walt.id/enterprise-stack/services/vical-service/publish-and-resolve.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Publish and Resolve VICAL Versions Use the authenticated publication routes to create new signed versions, then use the public registry routes to distribute the latest or a specific published version. Service references: - [Publication API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20VICAL%20Service%20%7C%20Publication%20Management) - [Public Registry API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20VICAL%20Service%20%7C%20Public%20Registry) ## Shared Path Parameters - `orgID`: When performing operations within an organization, use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.waltid.dev` when using the sandbox environment. - `target`: For `publish`, `versions/ids`, and the `latest` registry routes, use the VICAL service path (`{organizationID}.{tenantID}.{vicalServiceID}`), for example `test.tenant1.vical-service`. For the version-specific `versions` registry routes, use the `versionIdPath` returned by `publish` or one of the version targets returned by `versions/ids`, for example `test.tenant1.vical-service.versions.production-2026-03-23`. ## Publish a new VICAL version Endpoint: `/v1/{target}/vical-service-api/publish` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20VICAL%20Service%20%7C%20Publication%20Management) **Option: CURL** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/vical-service-api/publish' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "vicalProvider": "Example mDL Trust Provider", "versionId": "production-2026-03-23", "issuanceDate": "2026-03-23T10:15:30Z", "nextUpdate": "2026-04-23T10:15:30Z" }' ``` **Example Response** ```json { "versionIdPath": "test.tenant1.vical-service.versions.production-2026-03-23", "version": "1.0", "entryCount": 3, "vicalProvider": "Example mDL Trust Provider", "date": "2026-03-23T10:15:30Z", "vicalIssueId": 12, "nextUpdate": "2026-04-23T10:15:30Z" } ``` **Body Parameters** - `vicalProvider`: Name of the provider written into the published VICAL. - `versionId`: Optional version identifier. If omitted, the service generates a UUID automatically. - `issuanceDate`: Optional publication timestamp. If omitted, the current time is used. - `nextUpdate`: Optional next planned update timestamp. When provided, it must be later than `issuanceDate`. **Response Codes** - `201` - VICAL version published successfully. **Note:** Publishing reads all attached X.509 stores and includes every stored `vical-entry` certificate it finds. The response includes `versionIdPath`, which you can reuse with the version-specific registry routes below. ## List published version targets Endpoint: `/v1/{target}/vical-service-api/versions/ids` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20VICAL%20Service%20%7C%20Publication%20Management) **Option: CURL** ```bash curl -X 'GET' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/vical-service-api/versions/ids' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' ``` **Example Response** ```json [ "test.tenant1.vical-service.versions.production-2026-03-20", "test.tenant1.vical-service.versions.production-2026-03-23" ] ``` **Response Codes** - `200` - Published version targets retrieved successfully. ## Resolve the latest published artifact Endpoint: `/v1/{target}/vical-service-api/latest` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20VICAL%20Service%20%7C%20Public%20Registry) This route is public and does not require a bearer token. **Option: CURL** ```bash curl -X 'GET' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/vical-service-api/latest?format=hex' \ -H 'accept: application/json' ``` **Example Response** ```json "d28443a10126a1045820aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899" ``` **Query Parameters** - `format`: Optional artifact encoding. Supported values are `hex` (default), `base64`, and `cbor`. **Response Codes** - `200` - Latest published artifact retrieved successfully. **Note:** When `format=cbor`, the endpoint returns raw `application/cbor` bytes. Save the response to a file if you want the binary artifact directly. ## Resolve the latest manifest Endpoint: `/v1/{target}/vical-service-api/latest/manifest` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20VICAL%20Service%20%7C%20Public%20Registry) This route is public and does not require a bearer token. **Option: CURL** ```bash curl -X 'GET' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/vical-service-api/latest/manifest' \ -H 'accept: application/json' ``` **Example Response** ```json { "version": "1.0", "entryCount": 3, "vicalProvider": "Example mDL Trust Provider", "date": "2026-03-23T10:15:30Z", "vicalIssueId": 12, "nextUpdate": "2026-04-23T10:15:30Z" } ``` **Response Codes** - `200` - Latest manifest retrieved successfully. ## Resolve the latest trust material Endpoint: `/v1/{target}/vical-service-api/latest/trust-material` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20VICAL%20Service%20%7C%20Public%20Registry) This route is public and does not require a bearer token. **Option: CURL** ```bash curl -X 'GET' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/vical-service-api/latest/trust-material' \ -H 'accept: application/json' ``` **Example Response** ```json { "signerCertificatePem": "-----BEGIN CERTIFICATE----- MIIB4z... -----END CERTIFICATE-----", "signerCertificateChainPem": [ "-----BEGIN CERTIFICATE----- MIIB8T... -----END CERTIFICATE-----" ] } ``` **Response Codes** - `200` - Latest trust material retrieved successfully. ## Resolve a specific published version Use the `versionIdPath` returned by `publish` or one of the published version targets returned by `versions/ids`, for example `test.tenant1.vical-service.versions.production-2026-03-23`. ### Resolve a specific artifact Endpoint: `/v1/{target}/vical-service-api/versions` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20VICAL%20Service%20%7C%20Public%20Registry) This route is public and does not require a bearer token. **Option: CURL** ```bash curl -X 'GET' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/vical-service-api/versions?format=base64' \ -H 'accept: application/json' ``` **Example Response** ```json "0oRDoQEmoQRYIKq7zN3u/wARIjNEVWZ3iJmqu8zd7v8AESIzRFVmd4iZ" ``` **Response Codes** - `200` - Version-specific artifact retrieved successfully. ### Resolve a specific manifest Endpoint: `/v1/{target}/vical-service-api/versions/manifest` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20VICAL%20Service%20%7C%20Public%20Registry) This route is public and does not require a bearer token. The response body matches the latest manifest shape shown above. **Option: CURL** ```bash curl -X 'GET' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/vical-service-api/versions/manifest' \ -H 'accept: application/json' ``` **Response Codes** - `200` - Version-specific manifest retrieved successfully. ### Resolve a specific trust material snapshot Endpoint: `/v1/{target}/vical-service-api/versions/trust-material` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20VICAL%20Service%20%7C%20Public%20Registry) This route is public and does not require a bearer token. The response body matches the latest trust material shape shown above. **Option: CURL** ```bash curl -X 'GET' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/vical-service-api/versions/trust-material' \ -H 'accept: application/json' ``` **Response Codes** - `200` - Version-specific trust material retrieved successfully. **Note:** Publish at least one version before calling the `latest` registry routes. The version-specific `versions` routes become usable as soon as you have a published version target. -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/services/trust-registry-service/overview RAW: https://docs.walt.id/enterprise-stack/services/trust-registry-service/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview The Trust Registry Service manages ETSI trust lists for credential verification. It enables verifying whether a credential issuer is listed in an official trust list—such as an EU member state's Trusted Service List (TSL) or an industry-specific trust list. When linked to a [Verifier2 Service](https://docs.walt.id/enterprise-stack/services/verifier2-service/overview.md), the Trust Registry enables the `etsi-trust-list` verification policy to resolve issuer certificates against loaded trust sources without external network calls at verification time. **Note:** The Trust Registry Service is controlled by the `trust-registry` feature flag. Enable it by adding it to `enabledFeatures` in your [`_features.conf`](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/features.md). ## Key Capabilities - **Multi-format trust list support** — Load trust lists in TSL XML (ETSI TS 119 612), LoTE JSON (ETSI TS 119 602), or PILOT format (EWC). - **XMLDSig signature validation** — Cryptographically verify trust list authenticity. - **Certificate resolution** — Look up issuer certificates against all loaded trust sources. - **Verifier2 integration** — Link to Verifier2 for automatic trust verification during credential presentation. - **Multi-tenant isolation** — Each organization/tenant gets its own trust registry instance. ## Service Dependencies The Trust Registry Service can be linked **to** other services: - [Verifier2 Service](https://docs.walt.id/enterprise-stack/services/verifier2-service/overview.md) — When Verifier2 has the Trust Registry as a dependency, the `etsi-trust-list` policy automatically uses it for certificate resolution. ## Supported Trust List Formats | Format | Standard | Example | |--------|----------|---------| | TSL XML | ETSI TS 119 612 | [Austrian TSL](https://www.signatur.rtr.at/currenttl.xml) | | LoTE JSON | ETSI TS 119 602 | Custom trust lists in JSON format | | PILOT | EWC Consortium | [EWC Trust List](https://ewc-consortium.github.io/ewc-trust-list/EWC-TL) | ## Entity Types Trust lists contain entities classified by their role in the credential ecosystem: | Entity Type | Description | |-------------|-------------| | `PID_PROVIDER` | Personal ID credential issuers (mDL, national ID) | | `WALLET_PROVIDER` | Authorized wallet applications | | `ATTESTATION_PROVIDER` | Attestation and certificate issuers | | `TRUST_SERVICE_PROVIDER` | Generic eIDAS trust services | ## Trust Decision Model When resolving a certificate, the service returns a rich decision object: | Decision | Meaning | |----------|---------| | `TRUSTED` | Certificate found in a valid, fresh trust source | | `NOT_TRUSTED` | Certificate not found in any loaded trust source | | `STALE_SOURCE` | Certificate found, but the trust source is outdated | | `UNKNOWN` | Unable to determine trust status | Each decision includes: - **Authenticity state** — Whether the trust list's XMLDSig signature was validated (`VALIDATED`, `SKIPPED_DEMO`, `FAILED`) - **Freshness state** — Whether the source is current (`FRESH`, `STALE`, `EXPIRED`) - **Matched entity details** — Information about the trusted entity and service that matched ## Get Started - [Setup](https://docs.walt.id/enterprise-stack/services/trust-registry-service/setup.md) — Create the service. - [Trust Source Management](https://docs.walt.id/enterprise-stack/services/trust-registry-service/trust-source-management.md) — Load, list, refresh, and delete trust sources. - [Verifier Integration](https://docs.walt.id/enterprise-stack/services/trust-registry-service/verifier-integration.md) — Link to Verifier2 for automatic trust verification. - [API Reference](4.api-reference.md) — Complete endpoint documentation. -------------------------------------------------------------------------------- PAGE: Setup URL: https://docs.walt.id/enterprise-stack/services/trust-registry-service/setup RAW: https://docs.walt.id/enterprise-stack/services/trust-registry-service/setup.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Setup Create a Trust Registry Service instance for your organization. ## Prerequisites Ensure you have the `trust-registry` feature enabled via the `_features.conf`: ```hocon enabledFeatures = [ trust-registry ] ``` ## Create the Service **Option: CURL** **Endpoint:** `POST /v1/{target}/resource-api/services/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%2F%20Resource/post_v1__target__resource_api_services_create) ##### Example Request ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "trust-registry", "validateSignaturesByDefault": true }' ``` **Path Parameters** - `orgID`: _String_ - Your organization's Base URL prefix, e.g. `myorg.enterprise-sandbox.waltid.dev`. - `target`: _String_ - The target path for the new service: `{organizationID}.{tenantID}.{serviceID}`, e.g. `myorg.tenant1.trust-registry`. **Header Parameters** - `Authorization`: _String_ - Bearer token for your organization. **Body** - `type`: _String_ - Must be `trust-registry`. - `validateSignaturesByDefault`: _Boolean (optional)_ - Whether to validate XMLDSig signatures on TSL sources by default. Defaults to `true`. --- ##### Example Response | Status | Description | |--------|-------------| | `201` | Service created successfully | | `400` | Invalid request | | `401` | Invalid or missing authentication token | Once the service is created, load trust sources into it. See [Trust Source Management](https://docs.walt.id/enterprise-stack/services/trust-registry-service/trust-source-management.md). -------------------------------------------------------------------------------- PAGE: Trust Source Management URL: https://docs.walt.id/enterprise-stack/services/trust-registry-service/trust-source-management RAW: https://docs.walt.id/enterprise-stack/services/trust-registry-service/trust-source-management.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Trust Source Management Manage the trust sources loaded into your Trust Registry Service. Each trust source is identified by a unique `sourceId` and contains entities, services, and certificate identities parsed from a trust list. ## Load a Trust Source Load a trust list into the Trust Registry from a remote URL or inline content. Each source is identified by a unique `sourceId`. ### Load from URL **Option: CURL** **Endpoint:** `POST /v1/{target}/trust-registry-api/sources/load` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Trust%20Registry/post_v1__target__trust_registry_api_sources_load) ##### Example Request ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/trust-registry-api/sources/load' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "sourceId": "ewc-pilot", "url": "https://ewc-consortium.github.io/ewc-trust-list/EWC-TL", "validateSignature": false }' ``` **Path Parameters** - `orgID`: _String_ - Your organization's Base URL prefix, e.g. `myorg.enterprise-sandbox.waltid.dev`. - `target`: _String_ - The Trust Registry service path, e.g. `myorg.tenant1.trust-registry`. **Header Parameters** - `Authorization`: _String_ - Bearer token for your organization. **Body** - `sourceId`: _String_ - Unique identifier for this trust source. - `url`: _String_ - URL to fetch the trust list from. Either `url` or `content` must be provided. - `validateSignature`: _Boolean_ - Whether to validate the XMLDSig signature. Use `true` for official production trust lists, `false` for unsigned pilot lists. --- ##### Example Response ```json { "sourceId": "ewc-pilot", "success": true, "entitiesLoaded": 19, "servicesLoaded": 29, "identitiesLoaded": 38 } ``` If the source could not be loaded (e.g. network failure or signature validation failure), the response is still `200` but with `success: false` and an `error` description: ```json { "sourceId": "ewc-pilot", "success": false, "error": "Failed to fetch from https://ewc-consortium.github.io/ewc-trust-list/EWC-TL: Connection timed out" } ``` | Status | Description | |--------|-------------| | `200` | Request processed — check `success` field in the response body | | `422` | Source could not be loaded — returned when loading fails at the trust list parsing level | ### Load from Content For local or custom trust lists, provide the content directly instead of a URL. **Option: CURL** **Endpoint:** `POST /v1/{target}/trust-registry-api/sources/load` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Trust%20Registry/post_v1__target__trust_registry_api_sources_load) ##### Example Request ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/trust-registry-api/sources/load' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "sourceId": "my-custom-list", "content": "{\"listMetadata\":{\"listId\":\"my-list\",\"listType\":\"mdl-issuers\",\"territory\":\"US\"},\"trustedEntities\":[...]}", "validateSignature": false }' ``` **Path Parameters** - `orgID`: _String_ - Your organization's Base URL prefix. - `target`: _String_ - The Trust Registry service path. **Header Parameters** - `Authorization`: _String_ - Bearer token for your organization. **Body** - `sourceId`: _String_ - Unique identifier for this trust source. - `content`: _String_ - The trust list content as a JSON string (LoTE format) or XML string (TSL format). Either `content` or `url` must be provided. - `validateSignature`: _Boolean_ - Whether to validate the XMLDSig signature. --- ##### Example Response ```json { "sourceId": "my-custom-list", "success": true, "entitiesLoaded": 4, "servicesLoaded": 6, "identitiesLoaded": 8 } ``` If the content cannot be parsed or signature validation fails, the response is still `200` but with `success: false` and an `error` description: ```json { "sourceId": "my-custom-list", "success": false, "error": "XMLDSig signature validation failed: Signature validation failed - digest mismatch" } ``` | Status | Description | |--------|-------------| | `200` | Request processed — check `success` field in the response body | | `422` | Source could not be loaded — returned when loading fails at the trust list parsing level | **Info:** XMLDSig signature validation provides cryptographic assurance that a trust list has not been tampered with. Always set `validateSignature: true` when loading official production trust lists. Use `validateSignature: false` only for unsigned pilot or test lists. ## List Loaded Sources **Option: CURL** **Endpoint:** `GET /v1/{target}/trust-registry-api/sources` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Trust%20Registry/get_v1__target__trust_registry_api_sources) ##### Example Request ```bash curl -X 'GET' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/trust-registry-api/sources' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' ``` **Path Parameters** - `orgID`: _String_ - Your organization's Base URL prefix. - `target`: _String_ - The Trust Registry service path. **Header Parameters** - `Authorization`: _String_ - Bearer token for your organization. --- ##### Example Response Returns an array of all loaded trust sources and their current state. ```json [ { "sourceId": "austria-tsl", "sourceFamily": "TSL", "displayName": "TSL AT (seq 72)", "sourceUrl": "https://www.signatur.rtr.at/currenttl.xml", "territory": "AT", "issueDate": "2026-03-11T15:17:42Z", "nextUpdate": "2026-09-11T14:17:42Z", "sequenceNumber": "72", "authenticityState": "VALIDATED", "freshnessState": "FRESH", "metadata": { "tslVersion": "5", "signerSubjectDN": "CN=Trusted List 9,OU=Fachbereich Telekommunikation und Post,O=Rundfunk und Telekom Regulierungs-GmbH,C=AT", "signerIssuerDN": "CN=RTR Services 5,O=Rundfunk und Telekom Regulierungs-GmbH,C=AT", "signerSerialNumber": "312100091138082272680184838882066682156716496761" } }, { "sourceId": "ewc-pilot", "sourceFamily": "TSL", "displayName": "TSL TT (seq 1)", "sourceUrl": "https://ewc-consortium.github.io/ewc-trust-list/EWC-TL", "territory": "TT", "issueDate": "2025-02-13T01:00:00Z", "nextUpdate": "2025-08-13T00:00:00Z", "sequenceNumber": "1", "authenticityState": "SKIPPED_DEMO", "freshnessState": "EXPIRED", "metadata": { "tslVersion": "5" } } ] ``` **Response Fields** - `sourceId`: _String_ - The identifier assigned when the source was loaded. - `sourceFamily`: _String_ - The format of the trust list. One of `TSL` (ETSI TS 119 612 XML), `LOTE` (ETSI TS 119 602 JSON or XML), or `PILOT`. Detected automatically from the content. - `displayName`: _String_ - A human-readable label derived from the list content. For TSL sources: `"TSL {territory} (seq {sequenceNumber})"`. For LoTE sources: the `listId` from the list's own metadata. - `sourceUrl`: _String (nullable)_ - The URL the list was fetched from. Null when the source was loaded from inline content and no `sourceUrl` was provided in the request body. - `territory`: _String (nullable)_ - The ISO country or territory code taken from the list's own scheme information. Null for LoTE sources that do not declare a territory. - `issueDate`: _String (nullable)_ - ISO 8601 timestamp from the list's own metadata indicating when the list was issued. - `nextUpdate`: _String (nullable)_ - ISO 8601 timestamp from the list's own metadata indicating when the list is expected to be updated. Drives the `freshnessState` calculation. Null when the list does not declare a next-update date. - `sequenceNumber`: _String (nullable)_ - The revision counter from the list's own metadata. Null when the list does not declare a sequence number. - `authenticityState`: _String_ - Result of XMLDSig signature validation. One of: - `VALIDATED` — signature was present and passed validation. - `FAILED` — signature was present but failed validation. - `SKIPPED_DEMO` — validation was skipped because `validateSignature: false` was used, or the list is unsigned and signature is not required. - `UNKNOWN` — authenticity state could not be determined. - `freshnessState`: _String_ - Staleness of the list, computed against `nextUpdate` at load time. One of: - `FRESH` — current time is before `nextUpdate`. - `STALE` — current time is within 24 hours of `nextUpdate`. - `EXPIRED` — current time is past `nextUpdate`. - `UNKNOWN` — `nextUpdate` was not present in the list. - `metadata`: _Object_ - Source-family-specific key/value pairs extracted from the list. For TSL sources: always includes `tslVersion`; includes `signerSubjectDN`, `signerIssuerDN`, and `signerSerialNumber` when signature validation was performed. For LoTE JSON sources: includes `listType` when present in the list metadata. | Status | Description | |--------|-------------| | `200` | Sources retrieved successfully | ## Refresh a Trust Source Re-fetch and re-parse a previously loaded trust source to pick up updates from the origin URL. Trust sources are persisted to the database and survive service restarts, but the content is not updated automatically — use this endpoint to refresh on demand. **Option: CURL** **Endpoint:** `POST /v1/{target}/trust-registry-api/sources/{sourceId}/refresh` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Trust%20Registry/post_v1__target__trust_registry_api_sources__sourceId__refresh) ##### Example Request ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/trust-registry-api/sources/{sourceId}/refresh' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' ``` **Path Parameters** - `orgID`: _String_ - Your organization's Base URL prefix. - `target`: _String_ - The Trust Registry service path. - `sourceId`: _String_ - The ID of the trust source to refresh. **Header Parameters** - `Authorization`: _String_ - Bearer token for your organization. --- ##### Example Response ```json { "sourceId": "ewc-pilot", "success": true, "entitiesLoaded": 19, "servicesLoaded": 29, "identitiesLoaded": 38 } ``` If the source cannot be re-fetched (e.g. the origin URL is unreachable), the response is still `200` but with `success: false`: ```json { "sourceId": "ewc-pilot", "success": false, "error": "Failed to fetch from https://ewc-consortium.github.io/ewc-trust-list/EWC-TL: Connection timed out" } ``` | Status | Description | |--------|-------------| | `200` | Request processed — check `success` field in the response body | | `422` | Refresh failed at the trust list parsing level | -------------------------------------------------------------------------------- PAGE: Trust Resolution URL: https://docs.walt.id/enterprise-stack/services/trust-registry-service/trust-resolution RAW: https://docs.walt.id/enterprise-stack/services/trust-registry-service/trust-resolution.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Trust Resolution Directly query trust status against the loaded trust sources. Use these endpoints to resolve whether a certificate, certificate fingerprint, or provider ID is trusted — for example, when debugging trust decisions, building custom integrations, or calling the Trust Registry from outside the standard Verifier2 flow. ## Resolve by Certificate Check whether a certificate is trusted by looking it up across all loaded trust sources. Accepts PEM or base64-encoded DER format. **Option: CURL** **Endpoint:** `POST /v1/{target}/trust-registry-api/resolve/certificate` ##### Example Request ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{trustRegistryTarget}/trust-registry-api/resolve/certificate' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "certificatePemOrDer": "MIIBkTCB+wIJAKHBfpEaYDcxMA0GCSqGSIb3DQEBCwUA...", "expectedEntityType": "PID_PROVIDER" }' ``` **Path Parameters** - `orgID`: _String_ — Your organization's Base URL prefix. - `trustRegistryTarget`: _String_ — The Trust Registry service path, e.g. `myorg.tenant1.trust-registry`. **Header Parameters** - `Authorization`: _String_ — Bearer token for authentication. Format: `Bearer {yourToken}`. **Body** - `certificatePemOrDer`: _String, required_ — The certificate in PEM format (`-----BEGIN CERTIFICATE-----...`) or base64-encoded DER. - `instant`: _String, optional_ — ISO-8601 timestamp for point-in-time trust evaluation. When omitted, defaults to the current time. - `expectedEntityType`: _String, optional_ — Filter results to a specific entity type. Valid values: `PID_PROVIDER`, `WALLET_PROVIDER`, `ATTESTATION_PROVIDER`, `TRUST_SERVICE_PROVIDER`, `ACCESS_CERTIFICATE_PROVIDER`, `RELYING_PARTY_PROVIDER`, `OTHER`. When omitted, all entity types are considered. - `expectedServiceType`: _String, optional_ — Filter results to a specific service type URI. When omitted, all service types are considered. --- ##### Example Response ```json { "decision": "TRUSTED", "sourceFreshness": "FRESH", "authenticity": "VALIDATED", "matchedSource": { "sourceId": "eu-wallets", "sourceFamily": "LOTE", "displayName": "EU Wallet Providers", "sourceUrl": "https://trust.example.eu/wallet-providers.json", "territory": "EU", "issueDate": "2026-01-01T00:00:00Z", "nextUpdate": "2026-07-01T00:00:00Z", "authenticityState": "VALIDATED", "freshnessState": "FRESH" }, "matchedEntity": { "entityId": "AT-WALLET-001", "sourceId": "eu-wallets", "entityType": "WALLET_PROVIDER", "legalName": "Demo Wallet Provider GmbH", "country": "AT" }, "matchedService": { "serviceId": "wallet-service", "sourceId": "eu-wallets", "entityId": "AT-WALLET-001", "serviceType": "WALLET_INSTANCE_ATTESTATION", "status": "GRANTED", "statusStart": "2026-01-01T00:00:00Z" }, "evidence": [], "warnings": [] } ``` --- ## Resolve by Certificate SHA-256 Check trust using only the certificate's SHA-256 fingerprint. Faster than submitting the full certificate when the fingerprint is already available. **Option: CURL** **Endpoint:** `POST /v1/{target}/trust-registry-api/resolve/certificate-sha256` ##### Example Request ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{trustRegistryTarget}/trust-registry-api/resolve/certificate-sha256' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "certificateSha256Hex": "9f3df3b70633c3d23f5ef04d5d1e7f1d715b9683d8744cd38ec1a8114ec99f00", "expectedEntityType": "WALLET_PROVIDER" }' ``` **Path Parameters** - `orgID`: _String_ — Your organization's Base URL prefix. - `trustRegistryTarget`: _String_ — The Trust Registry service path, e.g. `myorg.tenant1.trust-registry`. **Header Parameters** - `Authorization`: _String_ — Bearer token for authentication. Format: `Bearer {yourToken}`. **Body** - `certificateSha256Hex`: _String, required_ — The lowercase hex-encoded SHA-256 fingerprint of the certificate (64 hex characters). - `instant`: _String, optional_ — ISO-8601 timestamp for point-in-time trust evaluation. When omitted, defaults to the current time. - `expectedEntityType`: _String, optional_ — Filter results to a specific entity type. Valid values: `PID_PROVIDER`, `WALLET_PROVIDER`, `ATTESTATION_PROVIDER`, `TRUST_SERVICE_PROVIDER`, `ACCESS_CERTIFICATE_PROVIDER`, `RELYING_PARTY_PROVIDER`, `OTHER`. When omitted, all entity types are considered. - `expectedServiceType`: _String, optional_ — Filter results to a specific service type URI. When omitted, all service types are considered. --- ##### Example Response The response shape is identical to [Resolve by Certificate](#resolve-by-certificate). See the response field reference below. --- ## Resolve by Provider ID Check whether an entity is trusted by its provider identifier. Useful when the credential's `iss` field or a known entity ID is available but the raw certificate is not. **Option: CURL** **Endpoint:** `POST /v1/{target}/trust-registry-api/resolve/provider-id` ##### Example Request ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{trustRegistryTarget}/trust-registry-api/resolve/provider-id' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "providerId": "AT-PID-001", "expectedEntityType": "PID_PROVIDER" }' ``` **Path Parameters** - `orgID`: _String_ — Your organization's Base URL prefix. - `trustRegistryTarget`: _String_ — The Trust Registry service path, e.g. `myorg.tenant1.trust-registry`. **Header Parameters** - `Authorization`: _String_ — Bearer token for authentication. Format: `Bearer {yourToken}`. **Body** - `providerId`: _String, required_ — The entity ID of the provider to look up, as registered in the trust source. - `instant`: _String, optional_ — ISO-8601 timestamp for point-in-time trust evaluation. When omitted, defaults to the current time. - `expectedEntityType`: _String, optional_ — Filter results to a specific entity type. Valid values: `PID_PROVIDER`, `WALLET_PROVIDER`, `ATTESTATION_PROVIDER`, `TRUST_SERVICE_PROVIDER`, `ACCESS_CERTIFICATE_PROVIDER`, `RELYING_PARTY_PROVIDER`, `OTHER`. When omitted, all entity types are considered. **Note:** `resolve/provider-id` does not support `expectedServiceType` filtering. Only `expectedEntityType` is available for this endpoint. --- ## Response Reference All three endpoints return a `TrustDecision` object with the following fields: - `decision`: The trust resolution outcome. Possible values: | Value | Meaning | |-------|---------| | `TRUSTED` | Found in a valid, fresh trust source | | `NOT_TRUSTED` | Not found in any loaded trust source | | `STALE_SOURCE` | Found, but the trust source is outdated | | `MULTIPLE_MATCHES` | Matched more than one entity — ambiguous | | `UNSUPPORTED_SOURCE` | Trust source format is not supported | | `PROCESSING_ERROR` | An error occurred during resolution | | `UNKNOWN` | Could not determine trust status | - `sourceFreshness`: Freshness state of the matched source. Values: `FRESH`, `STALE`, `EXPIRED`, `UNKNOWN`. - `authenticity`: XMLDSig validation state of the matched source. Values: `VALIDATED`, `FAILED`, `SKIPPED_DEMO`, `UNKNOWN`. - `matchedSource` _(optional)_: The trust source that produced the match. - `sourceId`: Unique identifier of the source. - `sourceFamily`: `TSL`, `LOTE`, or `PILOT`. - `displayName`: Human-readable name of the source. - `sourceUrl` _(optional)_: URL the source was loaded from. - `territory` _(optional)_: Territory or country code associated with the source. - `issueDate` _(optional)_: ISO-8601 timestamp when the trust list was issued. - `nextUpdate` _(optional)_: ISO-8601 timestamp when the trust list is due for update. - `sequenceNumber` _(optional)_: Sequence number from the trust list, when present. - `authenticityState`: XMLDSig validation state for this source. Values: `VALIDATED`, `FAILED`, `SKIPPED_DEMO`, `UNKNOWN`. - `freshnessState`: Freshness state of this source. Values: `FRESH`, `STALE`, `EXPIRED`, `UNKNOWN`. - `metadata`: Map of additional key-value metadata. Empty by default. - `matchedEntity` _(optional)_: The entity whose identity matched. - `entityId`: The entity's identifier within the trust source. - `sourceId`: The source this entity belongs to. - `entityType`: The entity type. Values: `PID_PROVIDER`, `WALLET_PROVIDER`, `ATTESTATION_PROVIDER`, `TRUST_SERVICE_PROVIDER`, `ACCESS_CERTIFICATE_PROVIDER`, `RELYING_PARTY_PROVIDER`, `OTHER`. - `legalName`: Registered legal name of the entity. - `tradeName` _(optional)_: Trading name of the entity. - `registrationNumber` _(optional)_: Official registration number. - `country` _(optional)_: ISO 3166-1 alpha-2 country code. - `metadata`: Map of additional key-value metadata. Empty by default. - `matchedService` _(optional)_: The specific trust service entry that matched. - `serviceId`: The service's identifier within the trust source. - `sourceId`: The source this service belongs to. - `entityId`: The entity this service belongs to. - `serviceType`: The service type URI as defined in the trust list. - `status`: Current trust status. Values: `GRANTED`, `RECOGNIZED`, `ACCREDITED`, `SUPERVISED`, `DEPRECATED`, `SUSPENDED`, `REVOKED`, `WITHDRAWN`, `EXPIRED`, `UNKNOWN`. - `statusStart` _(optional)_: ISO-8601 timestamp when the current status became effective. - `metadata`: Map of additional key-value metadata. Empty by default. - `evidence` _(array)_: Additional trust evidence entries. Each item has `type`, `value`, and `metadata`. - `warnings` _(array)_: Non-fatal warnings, e.g. when a source is stale but a match was found. -------------------------------------------------------------------------------- PAGE: Verifier Integration URL: https://docs.walt.id/enterprise-stack/services/trust-registry-service/verifier-integration RAW: https://docs.walt.id/enterprise-stack/services/trust-registry-service/verifier-integration.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Verifier Integration Link the Trust Registry to a Verifier2 Service to enable automatic trust verification during credential presentation. This allows the `etsi-trust-list` policy to resolve issuer certificates without external network calls at verification time. ## Architecture ``` ┌─────────────────────────────────────────────────────────────────┐ │ Verification Flow │ │ │ │ Credential ──► Verifier2 ──► etsi-trust-list policy │ │ │ │ │ │ │ dependency │ resolves via │ │ ▼ ▼ │ │ Trust Registry Service ◄─────┘ │ │ │ │ │ │ queries │ │ ▼ │ │ Loaded Trust Sources (TSL, LoTE, PILOT) │ └─────────────────────────────────────────────────────────────────┘ ``` ## Prerequisites 1. A Trust Registry Service with at least one trust source loaded (see [Setup](https://docs.walt.id/enterprise-stack/services/trust-registry-service/setup.md) and [Trust Source Management](https://docs.walt.id/enterprise-stack/services/trust-registry-service/trust-source-management.md)). 2. A Verifier2 Service in the same tenant. ## Link Trust Registry to Verifier2 Add the Trust Registry as a dependency of your Verifier2 service: **Option: CURL** Endpoint: `/v1/{target}/verifier2-service-api/dependencies/add` **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{verifier2Target}/verifier2-service-api/dependencies/add' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: text/plain' \ -d 'myorg.tenant1.trust-registry' ``` **Path Parameters** - `orgID`: _String_ — Your organization's Base URL prefix. - `verifier2Target`: _String_ — The Verifier2 service path, e.g. `myorg.tenant1.verifier2`. **Header Parameters** - `Authorization`: _String_ — Bearer token for authentication. Format: `Bearer {yourToken}`. **Body** Plain text containing the Trust Registry service path, e.g. `myorg.tenant1.trust-registry`. **Response Codes** - `201` — Dependency added successfully. **Note:** The body must be sent as `text/plain`, not JSON. The value is the full path to the Trust Registry service. ## Verify the Link List the Verifier2 dependencies to confirm the Trust Registry is linked: **Option: CURL** **Endpoint:** `GET /v1/{verifier2Target}/verifier2-service-api/dependencies/list` ##### Example Request ```bash curl -X 'GET' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{verifier2Target}/verifier2-service-api/dependencies/list' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' ``` **Path Parameters** - `orgID`: _String_ — Your organization's Base URL prefix. - `verifier2Target`: _String_ — The Verifier2 service path, e.g. `myorg.tenant1.verifier2`. **Header Parameters** - `Authorization`: _String_ — Bearer token for authentication. Format: `Bearer {yourToken}`. --- ##### Example Response Returns an array of all services registered as dependencies of the Verifier2 service. ```json [ { "_id": "myorg.tenant1.trust-registry", "type": "trust-registry" } ] ``` ## Use the etsi-trust-list Policy The `etsi-trust-list` policy is a standard Verifier2 VC policy. You add it to the `vc_policies` array when creating a verification session — the same way you would add `signature` or any other policy. The policy snippet looks like this: ```json { "policy": "etsi-trust-list", "expectedEntityType": "PID_PROVIDER", "allowStaleSource": false, "requireAuthenticated": true } ``` Place this inside the `policies.vc_policies` array of your session creation request body. For a complete walkthrough of creating a verification session — including the full request structure, `flow_type`, `dcql_query`, and response fields — see [Credential Verification](https://docs.walt.id/enterprise-stack/services/verifier2-service/credential-verification/vc-oid4vp.md). **Note:** When Verifier2 has the Trust Registry linked as a dependency (as set up above), the policy resolves certificates automatically using the pre-loaded trust sources. No additional URL or inline configuration is required. ## Policy Resolution Modes The `etsi-trust-list` policy supports three resolution modes. Choose the one that fits your deployment: ### 1. Linked Service Mode (Recommended) Uses the Trust Registry you linked in the previous step. The policy discovers and queries it automatically at verification time — no extra configuration needed. ```json { "policy": "etsi-trust-list", "expectedEntityType": "PID_PROVIDER" } ``` This is the recommended mode for enterprise deployments. Trust lists are pre-loaded into the Trust Registry Service, so verification requires no outbound network calls. ### 2. Remote Service Mode Queries a separate Trust Registry service by URL. Use this when your Verifier2 and Trust Registry run in different tenants, or when you need to delegate trust resolution to an external service. ```json { "policy": "etsi-trust-list", "trustRegistryUrl": "https://trust-registry.example.com", "expectedEntityType": "PID_PROVIDER" } ``` ### 3. Inline Mode Fetches and processes a trust list directly at verification time, without a Trust Registry service. Suitable for testing or simple deployments where operating a dedicated Trust Registry Service is not warranted. ```json { "policy": "etsi-trust-list", "trustLists": [ "https://ewc-consortium.github.io/ewc-trust-list/EWC-TL" ], "expectedEntityType": "PID_PROVIDER" } ``` **Error:** Inline Mode fetches the trust list on every verification request. This adds latency and creates a runtime dependency on the availability of the external URL. Avoid this mode in production. ## Policy Configuration Options The following parameters are supported across all resolution modes unless noted otherwise: | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `expectedEntityType` | string | `null` | Filter by entity type. Valid values: `PID_PROVIDER`, `WALLET_PROVIDER`, `ATTESTATION_PROVIDER`, `TRUST_SERVICE_PROVIDER`. When omitted, no entity type filter is applied. | | `expectedServiceType` | string | `null` | Filter by service type URI. When omitted, no service type filter is applied. | | `allowStaleSource` | boolean | `false` | When `true`, accepts two stale cases as trusted: a `STALE_SOURCE` decision, and a `TRUSTED` decision where the source's freshness is `STALE`. Both cases fail when `false`. Note: a source with `sourceFreshness: EXPIRED` always fails regardless of this setting. | | `requireAuthenticated` | boolean | `false` | When `true`, requires the trust source to have `VALIDATED` authenticity (XMLDSig signature verified). When `false`, sources with `SKIPPED_DEMO` or `UNKNOWN` authenticity are still accepted. | | `trustRegistryUrl` | string | — | _(Remote mode only)_ The URL of the external Trust Registry Service to query. | | `trustLists` | array of strings | — | _(Inline mode only)_ One or more trust list URLs to fetch and process at verification time. | | `validateSignatures` | boolean | `true` | _(Inline mode only)_ When `true`, validates XMLDSig signatures when loading trust lists inline. Set to `false` to skip signature validation, e.g. for trust lists without a valid XMLDSig. | ### Example Configurations **Strict production mode** — entity type enforced, stale sources rejected, XMLDSig required: ```json { "policy": "etsi-trust-list", "expectedEntityType": "PID_PROVIDER", "allowStaleSource": false, "requireAuthenticated": true } ``` **Permissive demo mode** — useful during development when trust sources may not yet be fully authenticated: ```json { "policy": "etsi-trust-list", "expectedEntityType": "PID_PROVIDER", "allowStaleSource": true, "requireAuthenticated": false } ``` ## Verification Result Once a wallet has responded to the verification request and the session is complete, retrieve the session result using the [session info endpoint](https://docs.walt.id/enterprise-stack/services/verifier2-service/credential-verification/vc-oid4vp.md). The `policyResults` field contains one entry per applied policy. For the `etsi-trust-list` policy, a successful result looks like this: ```json { "policy": "etsi-trust-list", "success": true, "result": { "trusted": true, "decision": "TRUSTED", "matchedEntity": { "entityId": "entity-123", "entityType": "PID_PROVIDER", "legalName": "Example PID Provider", "country": "DE" }, "matchedService": { "serviceId": "service-456", "serviceType": "http://uri.etsi.org/TrstSvc/Svctype/IdV", "status": "http://uri.etsi.org/TrstSvc/TrustedList/Svcstatus/granted" }, "matchedSource": { "sourceId": "ewc-pilot", "sourceFamily": "LOTE", "displayName": "EWC Pilot Trust List" }, "sourceFreshness": "FRESH", "authenticity": "VALIDATED" } } ``` **Result fields:** - `trusted`: Always `true` for a successful policy result. - `decision`: The trust resolution outcome. `TRUSTED` means the issuer was found in a valid trusted source. `STALE_SOURCE` means the source was found but is stale — only returned as a success when `allowStaleSource: true` is set. - `matchedEntity` _(optional)_: The entity in the trust source whose certificate matched. - `entityId`: The entity's identifier within the trust source. - `entityType`: The type of the entity, e.g. `PID_PROVIDER`, `WALLET_PROVIDER`, `ATTESTATION_PROVIDER`. - `legalName`: The registered legal name of the entity. - `country` _(optional)_: The country code of the entity, when available. - `matchedService` _(optional)_: The specific trust service entry that matched the certificate. - `serviceId`: The service's identifier within the trust source. - `serviceType`: The service type URI as defined in the trust list. - `status`: The service status URI as defined in the trust list. - `matchedSource`: The trust source that produced the match. - `sourceId`: The ID of the trust source as registered in the Trust Registry. - `sourceFamily`: The trust list family, e.g. `TSL` or `LOTE`. - `displayName`: The human-readable name of the trust source. - `sourceFreshness`: Freshness state of the trust source at verification time. Possible values: `FRESH`, `STALE`, `EXPIRED`, `UNKNOWN`. - `authenticity`: XMLDSig validation state of the trust source. Possible values: `VALIDATED`, `SKIPPED_DEMO`, `UNKNOWN`. - `warnings` _(optional)_: Array of warning strings, present only when the policy succeeded with caveats (e.g. stale source accepted). -------------------------------------------------------------------------------- PAGE: Full API Reference URL: https://docs.walt.id/enterprise-stack/services/trust-registry-service/full-api-reference RAW: https://docs.walt.id/enterprise-stack/services/trust-registry-service/full-api-reference.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Full API Reference Find it [here](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Trust%20Registry%20Service) -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/services/client-attestation-service/overview RAW: https://docs.walt.id/enterprise-stack/services/client-attestation-service/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview The **Client Attestation Service** enables secure wallet authentication during credential issuance flows. It implements the [OAuth 2.0 Attestation-Based Client Authentication](https://datatracker.ietf.org/doc/draft-ietf-oauth-attestation-based-client-auth/) specification, allowing issuers to verify that credential requests originate from trusted wallet applications. ## What is Client Attestation? Client attestation is a mechanism that allows an OAuth 2.0 authorization server (in this case, the Issuer2 Service) to verify the identity and integrity of a client application (wallet) before issuing credentials. This is particularly important in high-assurance credential issuance scenarios where the issuer needs to ensure that: 1. The wallet application is genuine and has not been tampered with 2. The wallet is authorized to receive credentials 3. The credential request originates from a trusted source ## How It Works The client attestation flow involves three parties: 1. **Client Attester Service** – A trusted backend service that issues attestation JWTs to wallets 2. **Wallet Service** – The wallet application that obtains attestations and presents them to issuers 3. **Issuer2 Service** – The credential issuer that verifies attestations before issuing credentials ```mermaid sequenceDiagram participant W as Wallet participant CA as Client Attester participant I as Issuer2 Note over W,CA: 1. Wallet obtains attestation W->>CA: Request attestation (instance key) CA->>CA: Sign attestation JWT CA-->>W: Attestation JWT + expiry Note over W,I: 2. Wallet requests credential W->>W: Build PoP JWT (signed with instance key) W->>I: Token request + OAuth-Client-Attestation + OAuth-Client-Attestation-PoP headers I->>I: Verify attestation signature I->>I: Verify PoP signature matches cnf.jwk I->>I: Validate claims (exp, aud, iat, jti) I-->>W: Access token W->>I: Credential request I-->>W: Credential ``` ## Key Concepts ### Attestation JWT The attestation JWT is signed by the Client Attester Service and contains: - `sub` – The client identifier (wallet ID) - `exp` – Expiration timestamp - `cnf.jwk` – The wallet's instance public key (used to verify the PoP) ### Proof of Possession (PoP) JWT The PoP JWT is signed by the wallet using its instance key and proves that the wallet possesses the private key corresponding to the `cnf.jwk` in the attestation. It contains: - `aud` – The issuer's RFC 8414 issuer identifier URL - `iat` – Issuance timestamp - `jti` – Unique identifier for replay protection ### HTTP Headers Per the specification, attestation is transmitted via two HTTP headers on the token request: - `OAuth-Client-Attestation` – Contains the attestation JWT - `OAuth-Client-Attestation-PoP` – Contains the PoP JWT ## Verification Methods The Issuer2 Service supports three methods for verifying attestation JWT signatures: | Method | Description | Use Case | |--------|-------------|----------| | **Static JWK** | Verify against a static public key provided inline | Simple deployments, testing | | **KMS Key** | Verify against a key stored in the KMS | When attester and issuer share the same enterprise instance | | **X.509 Chain** | Verify via X.509 certificate chain validation; the leaf certificate public key must match the JWT `alg` (ECDSA **ES256**/**ES384**/**ES512**, or RSA **RS256**/**RS384**/**RS512** and **PS256**/**PS384**/**PS512**) | Regulated environments (e.g., VICAL, eIDAS) | ## Service Dependencies The Client Attestation Service requires: - **KMS Service** – For storing the attestation signing key ## Get Started - [Setup](https://docs.walt.id/enterprise-stack/services/client-attestation-service/setup.md) – How to create and configure a Client Attestation Service - [Wallet Integration](https://docs.walt.id/enterprise-stack/services/client-attestation-service/wallet-integration.md) – How wallets obtain and use attestations - [Issuer Integration](https://docs.walt.id/enterprise-stack/services/client-attestation-service/issuer-integration.md) – How to configure issuers to require attestation ## Operational Endpoints All lifecycle, configuration, dependency, and metadata operations for this service are documented under Administration → Service Operations: - [Create this service](https://docs.walt.id/enterprise-stack/administration/services/create.md) - [View or update configuration](https://docs.walt.id/enterprise-stack/administration/services/configuration/view.md) - [Manage dependency services](https://docs.walt.id/enterprise-stack/administration/services/dependency-services/view.md) -------------------------------------------------------------------------------- PAGE: Setup URL: https://docs.walt.id/enterprise-stack/services/client-attestation-service/setup RAW: https://docs.walt.id/enterprise-stack/services/client-attestation-service/setup.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Setup This guide walks you through setting up a Client Attestation Service inside a tenant. If you don't have a tenant yet, you can learn how to create one [here](https://docs.walt.id/enterprise-stack/administration/tenants/create.md). ## Prerequisites Before creating a Client Attestation Service, ensure you have: 1. **Tenant** – An existing tenant in your organization 2. **KMS Service** – A [Key Management Service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) for storing the attestation signing key 3. **Signing Key** – A key in the KMS that will be used to sign attestation JWTs ## Create a Signing Key First, create or import a key in your KMS that will be used to sign attestation JWTs. The key should use an algorithm suitable for JWT signing (e.g., ES256, EdDSA). **Option: CURL** Endpoint: `/v1/{target}/kms-api/keys/generate` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20KMS/post_v1__target__kms_api_keys_generate) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/kms-api/keys/generate' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "backend": "jwk", "keyType": "secp256r1" }' ``` **Path Parameters** - `orgID` – Your organization's Base URL - `target` – The KMS service target with the new key ID (`{organizationID}.{tenantID}.{kmsID}.{newKeyID}`), e.g. `waltid.tenant1.kms1.attester-signing-key` ## Create the Client Attestation Service **Option: CURL** Endpoint: `/v1/{target}/resource-api/services/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Resource/post_v1__target__resource_api_services_create) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "client-attester", "signingKeyId": "waltid.tenant1.kms1.attester-signing-key", "attestationValiditySeconds": 86400 }' ``` **Body** ```json { "type": "client-attester", "signingKeyId": "waltid.tenant1.kms1.attester-signing-key", "attestationValiditySeconds": 86400 } ``` **Path Parameters** - `orgID` – Your organization's Base URL. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target` – The resource identifier for the new client attester service (`{organizationID}.{tenantID}.{newClientAttesterServiceID}`), e.g. `waltid.tenant1.client-attester1` **Body Parameters** - `type`: _String_ – Must be `client-attester` for the Client Attestation Service - `signingKeyId`: _resourceIdentifier_ – Reference to a key in the KMS used for signing attestation JWTs. E.g. `waltid.tenant1.kms1.attester-signing-key` - `attestationValiditySeconds`: _Long_ – (Optional) How long attestation JWTs remain valid, in seconds. Default: `86400` (24 hours) --- **Response Codes** - `201` – Service created successfully - `400` – Invalid request body - `409` – Service with this ID already exists ## Link the KMS Dependency After creating the Client Attestation Service, link it to the KMS service that contains the signing key. **Option: CURL** Endpoint: `/v1/{target}/resource-api/dependencies/add` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Resource/post_v1__target__resource_api_dependencies_add) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/dependencies/add' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: text/plain' \ -d 'waltid.tenant1.kms1' ``` **Path Parameters** - `orgID` – Your organization's Base URL - `target` – The client attester service target (`{organizationID}.{tenantID}.{clientAttesterServiceID}`), e.g. `waltid.tenant1.client-attester1` **Body** The resource identifier of the KMS service to link, e.g. `waltid.tenant1.kms1` --- **Response Codes** - `201` – Dependency added successfully ## Configuration Options | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `signingKeyId` | String | Required | Reference to the KMS key used for signing attestation JWTs | | `attestationValiditySeconds` | Long | `86400` | How long attestation JWTs remain valid (in seconds) | ## Next Steps After creating your Client Attestation Service: 1. [Configure Wallet Integration](https://docs.walt.id/enterprise-stack/services/client-attestation-service/wallet-integration.md) – Set up wallets to obtain attestations 2. [Configure Issuer Integration](https://docs.walt.id/enterprise-stack/services/client-attestation-service/issuer-integration.md) – Set `clientAuthenticationConfig` on Issuer2 (for example `client-attestation` and optional `preauth-anonymous`) -------------------------------------------------------------------------------- PAGE: Wallet Integration URL: https://docs.walt.id/enterprise-stack/services/client-attestation-service/wallet-integration RAW: https://docs.walt.id/enterprise-stack/services/client-attestation-service/wallet-integration.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Wallet Integration This guide explains how to configure the Wallet Service to obtain and use client attestations when requesting credentials from issuers that require attestation-based client authentication. ## Overview The wallet integration involves two steps: 1. **Obtain an attestation** – The wallet requests an attestation JWT from a Client Attestation Service 2. **Use the attestation** – The wallet includes the attestation in credential requests to issuers ## Prerequisites Before configuring wallet attestation: 1. **Wallet Service** – An existing [Wallet Service](https://docs.walt.id/enterprise-stack/services/wallet-service/setup.md) in your tenant 2. **Client Attestation Service** – A configured [Client Attestation Service](https://docs.walt.id/enterprise-stack/services/client-attestation-service/setup.md) in your tenant 3. **Wallet Key** – A key in the wallet's KMS that will be used as the instance key ## Link the Client Attester to the Wallet First, add the Client Attestation Service as a dependency of the Wallet Service. **Option: CURL** Endpoint: `/v1/{target}/wallet-service-api/dependencies/add` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Wallet%20Service/post_v1__target__wallet_service_api_dependencies_add) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/wallet-service-api/dependencies/add' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: text/plain' \ -d 'waltid.tenant1.client-attester1' ``` **Path Parameters** - `orgID` – Your organization's Base URL - `target` – The wallet service target (`{organizationID}.{tenantID}.{walletServiceID}`), e.g. `waltid.tenant1.wallet1` **Body** The resource identifier of the Client Attestation Service to link, e.g. `waltid.tenant1.client-attester1` --- **Response Codes** - `201` – Dependency added successfully ## Obtain a Client Attestation The wallet must obtain an attestation JWT before it can request credentials from issuers that require attestation. The attestation is stored in the wallet's resource tree and can be reused until it expires. **Option: CURL** Endpoint: `/v1/{target}/wallet-service-api/client-attestation/obtain` **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/wallet-service-api/client-attestation/obtain' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "clientAttesterServiceRef": "waltid.tenant1.client-attester1", "instanceKeyReference": "waltid.tenant1.kms1.wallet_key" }' ``` **Body** ```json { "clientAttesterServiceRef": "waltid.tenant1.client-attester1", "instanceKeyReference": "waltid.tenant1.kms1.wallet_key" } ``` **Path Parameters** - `orgID` – Your organization's Base URL - `target` – The wallet service target (`{organizationID}.{tenantID}.{walletServiceID}`), e.g. `waltid.tenant1.wallet1` **Body Parameters** - `clientAttesterServiceRef`: _resourceIdentifier_ – Reference to the Client Attestation Service that will issue the attestation - `instanceKeyReference`: _resourceIdentifier_ – Reference to the wallet's instance key in the KMS. The public key will be embedded in the attestation's `cnf.jwk` claim. --- **Response** ```json { "clientAttestationJwt": "eyJhbGciOiJFUzI1NiIsInR5cCI6Im9hdXRoLWNsaWVudC1hdHRlc3RhdGlvbitqd3QifQ...", "expiresAt": 1715270400 } ``` **Response Fields** - `clientAttestationJwt`: _String_ – The signed attestation JWT - `expiresAt`: _Long_ – Unix timestamp when the attestation expires --- **Response Codes** - `200` – Attestation obtained successfully - `400` – Invalid request - `404` – Client Attester Service or key not found ## Check Current Attestation You can retrieve the wallet's current stored attestation to check its status or expiration. **Option: CURL** Endpoint: `/v1/{target}/wallet-service-api/client-attestation/current` **Example Request** ```bash curl -X 'GET' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/wallet-service-api/client-attestation/current' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' ``` **Path Parameters** - `orgID` – Your organization's Base URL - `target` – The wallet service target (`{organizationID}.{tenantID}.{walletServiceID}`), e.g. `waltid.tenant1.wallet1` --- **Response** ```json { "clientAttestationJwt": "eyJhbGciOiJFUzI1NiIsInR5cCI6Im9hdXRoLWNsaWVudC1hdHRlc3RhdGlvbitqd3QifQ...", "expiresAt": 1715270400, "instanceKeyId": "waltid.tenant1.kms1.wallet_key" } ``` --- **Response Codes** - `200` – Current attestation returned - `404` – No attestation found for this wallet ## Discover What the Issuer Expects Before driving a receive flow, resolve the issuer’s **OAuth 2.0 Authorization Server metadata** (RFC 8414) for the OID4VCI authorization server referenced by the offer. In particular: - If `token_endpoint_auth_methods_supported` includes `attest_jwt_client_auth`, the issuer is configured for attestation-based client authentication and the wallet must send the `OAuth-Client-Attestation` and `OAuth-Client-Attestation-PoP` headers on the token request (for example by setting `useClientAttestation: true` on wallet receive APIs, after obtaining an attestation). - If `pre_authorized_grant_anonymous_access_supported` is `true`, the issuer allows **pre-authorized code** token requests **without** `client_id` when no attestation is used. If it is `false` and attestation is not advertised, your client may need to send `client_id` or use another supported method per the issuer’s configuration. These flags follow the issuer’s `clientAuthenticationConfig` in the enterprise API (see [Issuer integration](https://docs.walt.id/enterprise-stack/services/client-attestation-service/issuer-integration.md)). ## Use Attestation When Receiving Credentials When requesting credentials from an issuer that requires attestation, set `useClientAttestation: true` in the receive request. The wallet will automatically: 1. Load the stored attestation 2. Build a fresh PoP JWT signed with the instance key 3. Include both in the token request headers **Option: CURL** Endpoint: `/v2/{target}/wallet-service-api/credentials/receive/pre-authorized` **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{target}/wallet-service-api/credentials/receive/pre-authorized' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "offerUrl": "openid-credential-offer://issuer.example.org/issue/?credential_offer_uri=...", "keyReference": "waltid.tenant1.kms1.wallet_key", "useClientAttestation": true }' ``` **Body** ```json { "offerUrl": "openid-credential-offer://issuer.example.org/issue/?credential_offer_uri=...", "keyReference": "waltid.tenant1.kms1.wallet_key", "useClientAttestation": true } ``` **Body Parameters** - `offerUrl`: _String_ – The credential offer URL from the issuer - `keyReference`: _resourceIdentifier_ – Reference to the wallet's key (must match the instance key used when obtaining the attestation) - `useClientAttestation`: _Boolean_ – When `true`, the wallet includes attestation headers in the token request ## Attestation Lifecycle ### Expiration Attestations have a configurable validity period (default: 24 hours). When an attestation expires: 1. The wallet will receive an error when trying to use it 2. The issuer may return a `use_fresh_attestation` error code 3. The wallet must obtain a new attestation before retrying ### Refresh Strategy For production deployments, consider implementing an attestation refresh strategy: - **Proactive refresh** – Obtain a new attestation before the current one expires - **Reactive refresh** – Obtain a new attestation when the issuer returns `use_fresh_attestation` ## Error Handling | Error | Description | Resolution | |-------|-------------|------------| | `use_fresh_attestation` | Attestation has expired | Obtain a new attestation and retry | | `invalid_client` | Attestation signature verification failed | Check that the issuer trusts the attester's key | | `invalid_client` | PoP signature verification failed | Ensure the wallet is using the correct instance key | | `invalid_client` | PoP `aud` mismatch | Verify the issuer's RFC 8414 issuer identifier URL | ## Next Steps - [Configure Issuer Integration](https://docs.walt.id/enterprise-stack/services/client-attestation-service/issuer-integration.md) – Set up issuers to require attestation verification -------------------------------------------------------------------------------- PAGE: Issuer Integration URL: https://docs.walt.id/enterprise-stack/services/client-attestation-service/issuer-integration RAW: https://docs.walt.id/enterprise-stack/services/client-attestation-service/issuer-integration.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Issuer Integration This guide explains how to configure the Issuer2 Service to require attestation-based client authentication when issuing credentials. ## Overview Issuer2 configures client authentication with `clientAuthenticationConfig`. When a **`client-attestation`** method is present, token requests must include valid `OAuth-Client-Attestation` and `OAuth-Client-Attestation-PoP` headers. The issuer then verifies: 1. The attestation JWT signature against a trusted attester key (or X.509 chain) 2. The PoP JWT signature against the `cnf.jwk` from the attestation 3. Claims such as expiration, audience, timestamps, and replay protection You can combine **`preauth-anonymous`** with **`client-attestation`**: wallets may then use anonymous pre-authorized code token requests (no `client_id`, no attestation headers) **or** present attestation headers, depending on the flow. Omit `clientAuthenticationConfig` or use an empty `supportedMethods` list to keep legacy issuer2 behavior where this layer does not enforce client authentication. ## Prerequisites Before configuring issuer attestation: 1. **Issuer2 Service** – An existing [Issuer2 Service](https://docs.walt.id/enterprise-stack/services/issuer2-service/setup.md) in your tenant 2. **Attester trust material** – Public JWK, KMS key reference, or X.509 trust anchors matching how the Client Attestation Service signs attestations ## Configuration Options Use the **`clientAuthenticationConfig`** object on the Issuer2 Service. It contains a **`supportedMethods`** array. Each element is a tagged object (`type` discriminator): | `type` | Purpose | |--------|---------| | `preauth-anonymous` | Allows OID4VCI **pre-authorized code** token requests **without** `client_id` when no attestation headers are sent. At most one entry. | | `client-attestation` | Requires valid attestation headers on the token request whenever that path is used. Includes nested **`config`** (see below). At most one entry. | Example — **attestation only** (strict wallet authentication): ```json { "clientAuthenticationConfig": { "supportedMethods": [ { "type": "client-attestation", "config": { "verificationMethod": { "...": "see below" }, "clockSkewSeconds": 300, "replayWindowSeconds": 300 } } ] } } ``` Example — **anonymous pre-authorized code only** (no attestation): ```json { "clientAuthenticationConfig": { "supportedMethods": [ { "type": "preauth-anonymous" } ] } } ``` Example — **both** (wallets can use anonymous pre-auth for pre-authorized offers without `client_id`, or send attestation when required): ```json { "clientAuthenticationConfig": { "supportedMethods": [ { "type": "preauth-anonymous" }, { "type": "client-attestation", "config": { "verificationMethod": { "...": "see below" } } } ] } } ``` ### `client-attestation` → `config` parameters These fields apply to the nested object under `client-attestation`: | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `verificationMethod` | Object | Required | How to verify the attestation JWT signature (see below) | | `clockSkewSeconds` | Long | `300` | Allowed clock skew for time-based validations (exp, iat) | | `replayWindowSeconds` | Long | `300` | Replay window for PoP `iat` | ## Verification Methods ### Static JWK Verify the attestation JWT using a static public key provided inline. This is the simplest method, suitable for testing or when the attester key is known and stable. ```json { "verificationMethod": { "type": "static-jwk", "jwk": { "kty": "EC", "crv": "P-256", "x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU", "y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0" } } } ``` ### KMS Key Verify against a key stored in the KMS. This is useful when the attester and issuer share the same enterprise instance. ```json { "verificationMethod": { "type": "kms-key", "keyReference": "waltid.tenant1.kms1.attester-signing-key" } } ``` ### X.509 Chain Verify via X.509 certificate chain validation. The attestation JWT must carry an `x5c` header with the certificate chain, which is validated against configured trust anchors. This method is required for regulated environments (e.g., VICAL, eIDAS) where attesters must be certified. ```json { "verificationMethod": { "type": "x509-chain", "trustedRootCertificatesPem": [ "-----BEGIN CERTIFICATE----- MIIB... -----END CERTIFICATE-----" ] } } ``` ## Create an Issuer with Client Attestation **Option: CURL** Endpoint: `/v1/{target}/resource-api/services/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Resource/post_v1__target__resource_api_services_create) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "issuer2", "baseUrl": "https://myorg.enterprise-sandbox.waltid.dev", "kms": "waltid.tenant1.kms1", "tokenKeyId": "waltid.tenant1.kms1.tokenKey", "supportedCredentialTypes": { "identity_credential_vc+sd-jwt": { "format": "dc+sd-jwt", "vct": "https://example.com/credentials/identity_credential", "cryptographic_binding_methods_supported": ["jwk"], "credential_signing_alg_values_supported": ["ES256"] } }, "clientAuthenticationConfig": { "supportedMethods": [ { "type": "client-attestation", "config": { "verificationMethod": { "type": "static-jwk", "jwk": { "kty": "EC", "crv": "P-256", "x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU", "y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0" } }, "clockSkewSeconds": 300, "replayWindowSeconds": 300 } } ] } }' ``` **Body** ```json { "type": "issuer2", "baseUrl": "https://myorg.enterprise-sandbox.waltid.dev", "kms": "waltid.tenant1.kms1", "tokenKeyId": "waltid.tenant1.kms1.tokenKey", "supportedCredentialTypes": { "identity_credential_vc+sd-jwt": { "format": "dc+sd-jwt", "vct": "https://example.com/credentials/identity_credential", "cryptographic_binding_methods_supported": ["jwk"], "credential_signing_alg_values_supported": ["ES256"] } }, "clientAuthenticationConfig": { "supportedMethods": [ { "type": "client-attestation", "config": { "verificationMethod": { "type": "static-jwk", "jwk": { "kty": "EC", "crv": "P-256", "x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU", "y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0" } }, "clockSkewSeconds": 300, "replayWindowSeconds": 300 } } ] } } ``` ## Update an Existing Issuer To add or modify client attestation configuration on an existing Issuer2 Service, use the configuration update endpoint. **Option: CURL** Endpoint: `/v1/{target}/resource-api/configuration` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Resource/put_v1__target__resource_api_configuration) **Example Request** ```bash curl -X 'PUT' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/configuration' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "clientAuthenticationConfig": { "supportedMethods": [ { "type": "client-attestation", "config": { "verificationMethod": { "type": "kms-key", "keyReference": "waltid.tenant1.kms1.attester-signing-key" } } } ] } }' ``` **Path Parameters** - `orgID` – Your organization's Base URL - `target` – The issuer2 service target (`{organizationID}.{tenantID}.{issuer2ServiceID}`), e.g. `waltid.tenant1.issuer1` ## Authorization Server Metadata The issuer’s OAuth 2.0 Authorization Server metadata (RFC 8414) reflects `clientAuthenticationConfig`: - **`token_endpoint_auth_methods_supported`** includes `attest_jwt_client_auth` only when a `client-attestation` method is configured. - When attestation is advertised, **`client_attestation_signing_alg_values_supported`** and **`client_attestation_pop_signing_alg_values_supported`** are currently emitted as **`["ES256"]`** each (what wallets should use for those JWTs in this profile). - **`pre_authorized_grant_anonymous_access_supported`** is **`true`** only when a **`preauth-anonymous`** method is included. It is **`false`** when attestation alone is configured. Example - **attestation only**: ```json { "issuer": "https://myorg.enterprise-sandbox.waltid.dev/v2/waltid.tenant1.issuer1/issuer-service-api/openid4vci", "token_endpoint": "https://myorg.enterprise-sandbox.waltid.dev/v2/waltid.tenant1.issuer1/issuer-service-api/openid4vci/token", "token_endpoint_auth_methods_supported": ["attest_jwt_client_auth"], "client_attestation_signing_alg_values_supported": ["ES256"], "client_attestation_pop_signing_alg_values_supported": ["ES256"], "pre_authorized_grant_anonymous_access_supported": false } ``` Example - **`preauth-anonymous` only** (no attestation in metadata): ```json { "issuer": "https://myorg.enterprise-sandbox.waltid.dev/v2/waltid.tenant1.issuer1/issuer-service-api/openid4vci", "token_endpoint": "https://myorg.enterprise-sandbox.waltid.dev/v2/waltid.tenant1.issuer1/issuer-service-api/openid4vci/token", "pre_authorized_grant_anonymous_access_supported": true } ``` Wallets should read this metadata before the token request to decide whether to attach attestation headers, send `client_id`, or rely on anonymous pre-authorized code. ## Verification Flow When attestation headers are present (or required because `client-attestation` is the only usable method for the request), the issuer performs the following verification steps: 1. **Routing** – If either attestation header is present, the issuer validates both headers together. If attestation is configured but neither header is present, the request is rejected unless the **`preauth-anonymous`** path applies (pre-authorized code grant **without** `client_id`). Sending only one of the two headers fails validation. 2. **Attestation JWT Signature** – Verify the attestation JWT signature against the configured verification method 3. **Attestation JWT Type** – Verify `typ` is `oauth-client-attestation+jwt` 4. **Subject Claim** – Verify `sub` is present and matches `client_id` if provided 5. **Expiration** – Verify `exp` is not in the past (with clock skew tolerance) 6. **Confirmation Key** – Extract `cnf.jwk` (must not be a private key) 7. **PoP JWT Signature** – Verify the PoP JWT signature against `cnf.jwk` 8. **PoP JWT Type** – Verify `typ` is `oauth-client-attestation-pop+jwt` 9. **Audience** – Verify `aud` matches the issuer's RFC 8414 issuer identifier URL 10. **Issuance Time** – Verify `iat` is within the replay window 11. **Replay Protection** – Verify `jti` has not been used before ## Error Responses When attestation verification fails, the issuer returns an OAuth 2.0 error response: | Error Code | Description | |------------|-------------| | `invalid_client` | Token request does not match configured client authentication (for example missing attestation headers when `client-attestation` is required, only one of the two attestation headers present, invalid signatures, claim validation failure, replay detected, or attestation headers sent when this issuer does not accept them) | | `use_fresh_attestation` | Attestation JWT has expired | **Example Error Response** ```json { "error": "invalid_client", "error_description": "Invalid client attestation JWT signature" } ``` ## Best Practices 1. **Use KMS Key or X.509 Chain in Production** – Static JWK is convenient for testing but harder to rotate 2. **Set Appropriate Validity Periods** – Balance security (shorter) with usability (longer) 3. **Monitor for Replay Attacks** – The issuer logs warnings when replay is detected 4. **Rotate Attester Keys Periodically** – Update the verification method when rotating keys 5. **Use X.509 Chain for Regulated Environments** – Required for VICAL, eIDAS, and similar frameworks -------------------------------------------------------------------------------- PAGE: Full API Reference URL: https://docs.walt.id/enterprise-stack/services/client-attestation-service/full-api-reference RAW: https://docs.walt.id/enterprise-stack/services/client-attestation-service/full-api-reference.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Full API Reference Find it [here](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Client%20Attester%20Service) -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/services/oidc-bridge-service/overview RAW: https://docs.walt.id/enterprise-stack/services/oidc-bridge-service/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview The **OIDC Bridge Service** lets traditional Identity and Access Management (IAM) systems accept Verifiable Credential (VC) based login using standard OpenID Connect (OIDC). It acts as an OIDC Identity Provider (IdP): your IAM system integrates with it exactly like any other OIDC provider, and users log in by presenting a credential from their wallet instead of a username and password. **Note:** The OIDC Bridge Service is controlled by the `oidc-bridge` feature flag. Enable it by adding it to `enabledFeatures` in your [`_features.conf`](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/features.md). ## Use Cases - **VC-enabled SSO** — Let users log into enterprise applications with their digital identity credentials. - **IAM modernization** — Add VC support to existing Keycloak, Ory, Auth0, Okta, or Azure Entra ID deployments without custom integration code. - **Passwordless authentication** — Replace passwords with cryptographically verified credentials such as an mDL (mobile Driver's License). - **Data minimization** — Use selective disclosure credentials to request only the claims you need. - **Trust enforcement** — Accept credentials only from trusted issuers via the [Trust Registry Service](https://docs.walt.id/enterprise-stack/services/trust-registry-service). ## How It Works The OIDC Bridge sits between your IAM system and the holder's wallet. To the IAM system it looks like a standard OIDC provider; behind the scenes it uses the [Verifier2 Service](https://docs.walt.id/enterprise-stack/services/verifier2-service) to request and validate the credential presentation, and the [Key Management Service](https://docs.walt.id/enterprise-stack/services/key-management-service) to sign the issued ID tokens. Both must be created before the bridge — see [Setup](https://docs.walt.id/enterprise-stack/services/oidc-bridge-service/setup.md#prerequisites). ```mermaid sequenceDiagram participant App as App / RP participant IAM as IAM System participant Bridge as OIDC Bridge participant V2 as Verifier2 participant Wallet as Wallet (Holder) App->>IAM: 1. Log in IAM->>Bridge: 2. OIDC /authorize Bridge->>V2: 3. Create presentation request Bridge->>Wallet: 4. Present credential (QR / DC API / deep link / web) Wallet->>Bridge: 5. VP response Bridge->>Bridge: 6. Validate presentation + map claims Bridge->>IAM: 7. Authorization code IAM->>Bridge: 8. Token exchange IAM->>App: 9. User logged in ``` 1. The **application** redirects the user to the **IAM system** to log in. 2. The IAM system offers a "Login with Verifiable Credential" option and redirects to the OIDC Bridge `authorize` endpoint. 3. The OIDC Bridge creates a Verifier2 presentation request and shows a branded presentation selection page. 4. The user presents a credential from their wallet using one of the enabled flows. 5. Verifier2 validates the presentation and the OIDC Bridge extracts claims using the configured [claim mappings](https://docs.walt.id/enterprise-stack/services/oidc-bridge-service/claim-mappings.md). 6. The OIDC Bridge returns an authorization code to the IAM system. 7. The IAM system exchanges the code at the `token` endpoint for an ID token (and access token) containing the mapped claims. 8. The IAM system creates or updates the user account and completes the login. ## Supported Standards | Area | Support | |:-------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **OIDC Protocol** | OpenID Connect 1.0, Authorization Code Flow (PKCE supported) | | **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) | | **Presentation Flows** | Cross-device (QR code), Same-device (deep link), [Digital Credentials API](https://docs.walt.id/concepts/data-exchange-protocols/dc-api.md), Web Wallet redirect | | **Signature Algorithms**| ed25519, secp256k1, secp256r1, RSA | | **Token Formats** | JWT (ID token), opaque (access token) | Credential validation, presentation flows, and signature support are provided by the [Verifier2 Service](https://docs.walt.id/enterprise-stack/services/verifier2-service) — see its documentation for the authoritative list of supported formats and options. ## Core Capabilities - **Multi-flow presentation** — Four presentation methods (QR code, Digital Credentials API, deep link, web wallet), each individually enabled and customizable. See [Multi-Flow Configuration](https://docs.walt.id/enterprise-stack/services/oidc-bridge-service/multi-flow-config.md). - **Flexible claim extraction** — Map credential attributes to OIDC claims with JSONPath, with optional transforms (`NONE`, `LOWERCASE`, `UPPERCASE`, `hash_sha256`). Works with both namespaced (mDL) and flat (SD-JWT VC) claim structures. See [Claim Mappings](https://docs.walt.id/enterprise-stack/services/oidc-bridge-service/claim-mappings.md). - **Multi-client support** — A single OIDC Bridge instance can serve multiple IAM systems, each with its own client credentials, redirect URI allowlist, scopes, and claim mappings. - **Credential query (DCQL)** — Request specific credentials and attributes using DCQL. The query lives in the flow's `verificationSetup`, which is the Verifier2 `VerificationSessionSetup` — see [Verifier2 credential verification](https://docs.walt.id/enterprise-stack/services/verifier2-service/credential-verification/mdl-oid4vp.md) and the [DCQL concept](https://docs.walt.id/concepts/data-exchange-protocols/dcql.md) for the full shape. - **UI customization** — Brand name, primary color, logo, and per-flow button labels on the presentation selection page. **Presentation selection page:** ![OIDC Bridge Presentation UI](https://docs.walt.id/img/enterprise-stack/services/OIDC-Bridge-Credential-Presentation-UI.png) *Users see all enabled flows with your configured button labels, brand name, and primary color.* ## OIDC Endpoints The bridge exposes a standard, discoverable OIDC surface under `/v1/{org}.{tenant}.oidc-bridge/oidc-bridge-api/`. Point your IAM system at the discovery document (`.well-known/openid-configuration`) and it resolves the `authorize`, `token`, `jwks`, and `userinfo` endpoints automatically. See [Setup](https://docs.walt.id/enterprise-stack/services/oidc-bridge-service/setup.md#step-3-verify-oidc-endpoints) to verify them. ## IAM System Compatibility The OIDC Bridge implements standard OIDC 1.0 and works with any compliant IAM system: - Keycloak (tested — see [Keycloak Integration](https://docs.walt.id/enterprise-stack/services/oidc-bridge-service/iam-integrations/keycloak.md)) - Ory, Auth0, Okta, Azure Entra ID, Google Identity Platform - Any OIDC-compliant relying party ## Security Features - **PKCE (Proof Key for Code Exchange)** — Required for all authorization flows - **Client authentication** — Supports client_secret_post and client_secret_basic - **State parameter** — CSRF protection - **Nonce parameter** — Replay attack prevention - **Token expiration** — Configurable ID token and access token lifetimes - **Redirect URI validation** — Strict allowlist enforcement - **Session timeout** — Configurable presentation timeout (default: 5 minutes) ## Getting Started - [Setup](https://docs.walt.id/enterprise-stack/services/oidc-bridge-service/setup.md) — Create and configure an OIDC Bridge service. - [Keycloak Integration](https://docs.walt.id/enterprise-stack/services/oidc-bridge-service/iam-integrations/keycloak.md) — Step-by-step guide for Keycloak. - [Claim Mappings](https://docs.walt.id/enterprise-stack/services/oidc-bridge-service/claim-mappings.md) — Map credential attributes to OIDC claims. - [Multi-Flow Configuration](https://docs.walt.id/enterprise-stack/services/oidc-bridge-service/multi-flow-config.md) — Set up the QR, DC API, deep link, and web wallet flows. -------------------------------------------------------------------------------- PAGE: Setup URL: https://docs.walt.id/enterprise-stack/services/oidc-bridge-service/setup RAW: https://docs.walt.id/enterprise-stack/services/oidc-bridge-service/setup.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Setup This guide walks through creating, configuring, and verifying an OIDC Bridge service. We will set up the service inside a tenant. If you don't have a tenant yet, you can learn how to create one [here](https://docs.walt.id/enterprise-stack/administration/tenants/create.md). ## Prerequisites The OIDC Bridge depends on two other services, which must exist before it can serve logins. You reference them in the `dependencies` array of the create request ([Step 2](#step-2-create-the-oidc-bridge-service)): - [Key Management Service (KMS)](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) — signs the issued ID tokens. - [Verifier2 Service](https://docs.walt.id/enterprise-stack/services/verifier2-service/overview.md) — requests and validates the credential presentation. You will also need: - Your IAM system's redirect URI. ## Step 1: Enable the Feature Add `oidc-bridge` to `enabledFeatures` in your [`_features.conf`](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/features.md): ```hocon enabledFeatures = [ # ... other features oidc-bridge ] ``` Restart the Enterprise Stack for the feature to take effect. ## Step 2: Create the OIDC Bridge Service **Option: CURL** Endpoint: `/v1/{target}/resource-api/services/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Resource/post_v1__target__resource_api_services_create) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/services/create' \ -H 'accept: application/json' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "oidc-bridge", "issuerUrl": "https://enterprise.example.com", "clients": { "my-iam": { "clientId": "my-iam", "clientSecret": "***", "redirectUris": ["https://iam.example.com/callback"], "allowedScopes": ["openid", "profile", "email"] } }, "defaultClaimMappings": [ { "oidcClaim": "sub", "credentialPath": "$[\"org.iso.18013.5.1\"][\"document_number\"]", "transform": "NONE" }, { "oidcClaim": "given_name", "credentialPath": "$[\"org.iso.18013.5.1\"][\"given_name\"]", "transform": "NONE" }, { "oidcClaim": "family_name", "credentialPath": "$[\"org.iso.18013.5.1\"][\"family_name\"]", "transform": "NONE" } ], "flows": { "qr": { "enabled": true, "buttonLabel": "Scan QR Code", "verificationSetup": { "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", "given_name"] }, { "path": ["org.iso.18013.5.1", "family_name"] }, { "path": ["org.iso.18013.5.1", "document_number"] } ] } ] } } } } }, "presentationTimeoutSeconds": 300, "uiConfig": { "brandName": "My Company", "primaryColor": "#3B82F6", "webWalletBaseUrl": "https://wallet.example.com" }, "dependencies": [ "{organizationID}.{tenantID}.kms", "{organizationID}.{tenantID}.verifier2" ] }' ``` **warning:** **Every enabled flow needs a `verificationSetup`.** This is the credential request (the DCQL query / OID4VP Authorization Request) the OIDC bridge sends to the wallet. **Path Parameters** - `orgID`: _String_ (required) - Your organization's base URL or a valid host alias. For example, if your organization is named `test`, your default base URL is `test.enterprise-sandbox.waltid.dev` on the sandbox environment. - `target`: _resourceIdentifier_ (required) - The organization + tenant to create the service in, plus the new service's ID: `{organizationID}.{tenantID}.oidc-bridge`, e.g. `waltid.tenant1.oidc-bridge`. **Header Parameters** - `Authorization`: _String_ (required) - Bearer token for Enterprise Stack authentication. Format: `Bearer {token}`. **Body Parameters** - `type`: _serviceType_ (required) - The type of service to create. Must be `"oidc-bridge"`. - `issuerUrl`: _String_ (required) - Base URL of your Enterprise Stack. Used to build the OIDC `issuer` (see [Step 3](#step-3-verify-oidc-endpoints)). - `clients`: _Object_ (required) - Map of client ID to client configuration. One entry per IAM system.
Expand to see client configuration properties - `clientId`: _String_ - OIDC client identifier. - `clientSecret`: _String_ - Client secret for authentication. - `clientName` (optional): _String_ - Human-readable client name. - `redirectUris`: _Array of Strings_ - Allowed redirect URIs (exact match). - `allowedScopes` (optional): _Array of Strings_ - Allowed OIDC scopes. Defaults to `["openid", "profile", "email"]`. - `claimMappings` (optional): _Array_ - Overrides `defaultClaimMappings` for this client. - `verificationSetup` (optional): _Object_ - Overrides the flow's verification setup for this client. See [Verification Setup and DCQL](#verification-setup-and-dcql).
- `defaultClaimMappings` (optional): _Array_ - Claim mappings applied when a client has none of its own. Defaults to `[]`. Each mapping has:
Expand to see claim mapping properties - `oidcClaim`: _String_ - Target OIDC claim name (e.g. `given_name`). - `credentialPath`: _String_ - JSONPath to the credential attribute. - `transform` (optional): _String_ - One of `NONE`, `LOWERCASE`, `UPPERCASE`, `hash_sha256`. Defaults to `NONE`. - `credentialId` (optional): String - For multi-credential (DCQL) queries, the credential-query `id` in the flow's `dcql_query` to extract this claim from. If omitted, the first presented credential whose credentialPath resolves is used. If set but no matching credential was presented, the mapping is skipped (the claim is omitted). See [Claim Mappings](https://docs.walt.id/enterprise-stack/services/oidc-bridge-service/claim-mappings.md) for details.
- `flows` (optional): _Object_ - Presentation flow configuration, keyed by `qr`, `dc_api`, `deep_link`, and `web_wallet`.
Expand to see flow configuration properties Each flow accepts: - `enabled`: _Boolean_ - Enable/disable this flow. Defaults to `false`. - `buttonLabel` (optional): _String_ - Text shown on the presentation-selection button. - `targetUrl` (optional): _String_ - (`web_wallet` only) URL to redirect to. - `verificationSetup`: _Object_ - The credential request (DCQL query) for this flow. **Required for the flow to work** unless a client-level or service-level setup covers it (see the resolution order in the callout under [Step 2](#step-2-create-the-oidc-bridge-service)). Its value is a Verifier2 verification request. See [Verification Request](#verification-setup-and-dcql) and [Multi-Flow Configuration](https://docs.walt.id/enterprise-stack/services/oidc-bridge-service/multi-flow-config.md).
- `tokenLifetime` (optional): _Object_ - Token lifetimes.
Expand to see token lifetime properties - `idTokenExpirySeconds`: _Number_ - ID token lifetime. Defaults to `3600`. - `accessTokenExpirySeconds`: _Number_ - Access token lifetime. Defaults to `3600`. - `authCodeExpirySeconds`: _Number_ - Authorization code lifetime. Defaults to `300`.
- `presentationTimeoutSeconds` (optional): _Number_ - How long a presentation session stays valid. Defaults to `300`. - `uiConfig` (optional): _Object_ - Presentation-page branding.
Expand to see UI configuration properties - `brandName` (optional): _String_ - Organization name shown on the presentation page. - `primaryColor` (optional): _String_ - Hex color for branding. - `logoUrl` (optional): _String_ - Logo URL. - `webWalletBaseUrl` (optional): _String_ - Base URL for the web wallet flow.
- `dependencies` (optional): _Array of Strings_ - Resource paths of the services this bridge depends on: the [KMS](https://docs.walt.id/enterprise-stack/services/key-management-service) and [Verifier2](https://docs.walt.id/enterprise-stack/services/verifier2-service) services. Each listed service must already exist — otherwise the whole create request fails with `400`. **Response Codes** - `201` - Service created successfully. The response body is the full created configuration, including server-applied defaults (`enabled`, `allowedScopes`, `tokenLifetime`, `presentationTimeoutSeconds`). **Example Response** ```json { "type": "oidc-bridge", "traversable": true, "_id": "{target}", "issuerUrl": "https://enterprise.example.com", "enabled": true, "clients": { "my-iam": { "clientId": "my-iam", "clientSecret": "***", "redirectUris": ["https://iam.example.com/callback"], "allowedScopes": ["openid", "profile", "email"] } }, "defaultClaimMappings": [ ... ], "tokenLifetime": { "idTokenExpirySeconds": 3600, "accessTokenExpirySeconds": 3600, "authCodeExpirySeconds": 300 }, "presentationTimeoutSeconds": 300, "parent": "{organizationID}.{tenantID}", "createdAt": "2026-01-01T00:00:00Z", "updatedAt": "2026-01-01T00:00:00Z" } ``` ### Verification Setup The `verificationSetup` included in each flow in [Create the OIDC Bridge Service](#step-2-create-the-oidc-bridge-service) above is the same object you would send to Verifier2 directly. ```json { "verificationSetup": { "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", "given_name"] }, { "path": ["org.iso.18013.5.1", "family_name"] }, { "path": ["org.iso.18013.5.1", "document_number"] } ] }] } } } } ``` For the full `VerificationSessionSetup` shape (flow types, policies, signing/encryption keys, `expectedOrigins` for DC API) and DCQL syntax, see the Verifier2 docs — this is the authoritative reference: - [Verifier2 credential verification](https://docs.walt.id/enterprise-stack/services/verifier2-service/credential-verification/mdl-oid4vp.md) - [DCQL query language](https://docs.walt.id/concepts/data-exchange-protocols/dcql.md) (and the [DCQL builder](https://dcql.walt.id/)) ## Step 3: Verify OIDC Endpoints All OIDC endpoints are exposed under `/v1/{target}/oidc-bridge-api/`: | Endpoint | Description | |----------|-------------| | `.well-known/openid-configuration` | OIDC discovery document | | `authorize` | Authorization endpoint (user-facing) | | `token` | Token endpoint (backend) | | `jwks` | JSON Web Key Set (public keys) | | `userinfo` | UserInfo endpoint | Check that the bridge is serving them: ```bash # Discovery document curl 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/oidc-bridge-api/.well-known/openid-configuration' # JWKS (public keys) curl 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/oidc-bridge-api/jwks' ``` **Example Response** (discovery document) ```json { "issuer": "https://enterprise.example.com/v1/{target}/oidc-bridge-api", "authorization_endpoint": "https://enterprise.example.com/v1/{target}/oidc-bridge-api/authorize", "token_endpoint": "https://enterprise.example.com/v1/{target}/oidc-bridge-api/token", "userinfo_endpoint": "https://enterprise.example.com/v1/{target}/oidc-bridge-api/userinfo", "jwks_uri": "https://enterprise.example.com/v1/{target}/oidc-bridge-api/jwks", "response_types_supported": ["code"], "grant_types_supported": ["authorization_code"], "subject_types_supported": ["public"], "id_token_signing_alg_values_supported": ["ES256", "EdDSA"], "scopes_supported": ["openid", "profile", "email"], "token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post"], "code_challenge_methods_supported": ["S256"], "claims_supported": ["sub", "iss", "aud", "iat", "exp", "auth_time", "nonce", "name", "email", "given_name", "family_name"] } ``` **info:** The `issuer` value is your `issuerUrl` **plus the `/v1/{target}/oidc-bridge-api` path** — not the bare `issuerUrl`. Configure your IAM system with the full discovery URL and it derives the rest. ## Step 4: Configure Your IAM System Use the discovery URL to configure your IAM system (Keycloak, Auth0, etc.): **Discovery URL:** ``` https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/oidc-bridge-api/.well-known/openid-configuration ``` **Client credentials:** - Client ID and Client Secret — from your `clients` configuration. - Client authentication: `client_secret_post` or `client_secret_basic`. See [Keycloak Integration](https://docs.walt.id/enterprise-stack/services/oidc-bridge-service/iam-integrations/keycloak.md) for a complete example. ## Step 5: Test the Flow 1. Navigate to your IAM system's login page. 2. Click "Login with Verifiable Credential" (or similar). 3. You are redirected to the OIDC Bridge presentation page. 4. Choose a presentation method (QR, DC API, deep link, or web wallet). 5. Present your credential. 6. You are redirected back to the IAM system, logged in. **Example presentation page:** ![OIDC Bridge Presentation UI](https://docs.walt.id/img/enterprise-stack/services/OIDC-Bridge-Credential-Presentation-UI.png) Users see all enabled flows with the button labels you configured. --- ## Credential Type Examples Claim paths and DCQL metadata differ by credential format. The two most common are mDL (namespaced) and SD-JWT VC (flat). ### mDL (ISO 18013-5.1) mDL credentials use **namespaced claim paths** under `org.iso.18013.5.1`: ```json { "defaultClaimMappings": [ { "oidcClaim": "sub", "credentialPath": "$[\"org.iso.18013.5.1\"][\"document_number\"]", "transform": "NONE" }, { "oidcClaim": "given_name", "credentialPath": "$[\"org.iso.18013.5.1\"][\"given_name\"]", "transform": "NONE" }, { "oidcClaim": "family_name", "credentialPath": "$[\"org.iso.18013.5.1\"][\"family_name\"]", "transform": "NONE" }, { "oidcClaim": "birthdate", "credentialPath": "$[\"org.iso.18013.5.1\"][\"birth_date\"]", "transform": "NONE" } ] } ``` Matching DCQL for the QR flow: ```json { "flows": { "qr": { "enabled": true, "verificationSetup": { "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", "given_name"] }, { "path": ["org.iso.18013.5.1", "family_name"] }, { "path": ["org.iso.18013.5.1", "birth_date"] }, { "path": ["org.iso.18013.5.1", "document_number"] } ] }] } } } } } } ``` ### SD-JWT VC SD-JWT VCs use **flat claim paths** (no namespace prefix): ```json { "defaultClaimMappings": [ { "oidcClaim": "sub", "credentialPath": "$.email", "transform": "hash_sha256" }, { "oidcClaim": "given_name", "credentialPath": "$.given_name", "transform": "NONE" }, { "oidcClaim": "family_name", "credentialPath": "$.family_name", "transform": "NONE" }, { "oidcClaim": "email", "credentialPath": "$.email", "transform": "LOWERCASE" }, { "oidcClaim": "birthdate", "credentialPath": "$.birthdate", "transform": "NONE" } ] } ``` Matching DCQL for the QR flow: ```json { "flows": { "qr": { "enabled": true, "verificationSetup": { "flow_type": "cross_device", "core_flow": { "dcql_query": { "credentials": [{ "id": "identity-vc", "format": "dc+sd-jwt", "meta": { "vct_values": ["IdentityCredential"] }, "claims": [ { "path": ["given_name"] }, { "path": ["family_name"] }, { "path": ["email"] }, { "path": ["birthdate"] } ] }] } } } } } } ``` ### mDL vs SD-JWT VC: Key Differences | Aspect | mDL | SD-JWT VC | |--------|-----|-----------| | **Standard** | ISO 18013-5 | IETF SD-JWT VC | | **Format** | `mso_mdoc` | `dc+sd-jwt` | | **Claim structure** | Namespaced (`org.iso.18013.5.1`) | Flat (root level) | | **Example path** | `$["org.iso.18013.5.1"]["given_name"]` | `$.given_name` | | **DCQL metadata** | `doctype_value` | `vct_values` | | **Typical use** | Government IDs (driver's license) | General verifiable credentials | For multi-credential (OR) queries and per-credential claim mappings with `credentialId`, see [Claim Mappings](https://docs.walt.id/enterprise-stack/services/oidc-bridge-service/claim-mappings.md#multi-credential-queries). ## Updating Configuration Update an existing OIDC Bridge by sending the **full** configuration to the update endpoint. The update replaces the stored configuration wholesale; attached dependencies are preserved. **Option: CURL** Endpoint: `/v1/{target}/oidc-bridge-api/configuration/update` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html) **Example Request** ```bash curl -X 'PUT' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/oidc-bridge-api/configuration/update' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "_id": "{target}", "issuerUrl": "https://enterprise.example.com", "clients": { "...": "..." }, "defaultClaimMappings": [ ... ], "flows": { "...": "..." } }' ``` **Body Parameters** - The full service configuration, using the same fields as [Step 2](#step-2-create-the-oidc-bridge-service), with two differences: the body **must include `_id`** and **must not include `type`**. (Create is the opposite: `type` is required and `_id` is taken from the path.) **Response Codes** - `200` - Configuration updated. ## Deleting the Service **Option: CURL** Endpoint: `/v1/{target}/resource-api/resources/delete-recursive` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html) **Example Request** ```bash curl -X 'DELETE' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/resources/delete-recursive' \ -H 'Authorization: Bearer {yourToken}' ``` **Response Codes** - `202` - Service and all of its sessions deleted. **warning:** Deleting the OIDC Bridge breaks any IAM integrations using it. Ensure all relying parties are migrated before deletion. ## Troubleshooting ### `invalid_client` Error **Cause:** Client credentials mismatch. **Fix:** Verify `clientId` and `clientSecret` match exactly in both the OIDC Bridge and your IAM system. ### `redirect_uri` Mismatch Error **Cause:** Redirect URI not in the allowlist. **Fix:** Ensure the redirect URI in your IAM system matches one of the `redirectUris` exactly (protocol, host, port, and path). ### Discovery Document 404 **Cause:** OIDC Bridge not created, or the feature is not enabled. **Fix:** 1. Check the feature is enabled: `oidc-bridge` in `_features.conf`. 2. Verify the service exists: `GET /v1/{target}/oidc-bridge-api/configuration/view`. 3. Check the path: `/oidc-bridge-api/.well-known/openid-configuration`. ### Service Creation Fails with a Cast Error **Cause:** The create request was sent to the tenant path instead of the full service path, so the server tried to overwrite the tenant. **Fix:** POST to the **full service target** — `/v1/{organizationID}.{tenantID}.oidc-bridge/resource-api/services/create` — not `/v1/{organizationID}.{tenantID}/...`. ### Presentation Timeout **Cause:** The user didn't complete the presentation in time. **Fix:** Increase `presentationTimeoutSeconds` (default: `300`). ## Next Steps - [Keycloak Integration](https://docs.walt.id/enterprise-stack/services/oidc-bridge-service/iam-integrations/keycloak.md) — Complete Keycloak setup guide. - [Claim Mappings](https://docs.walt.id/enterprise-stack/services/oidc-bridge-service/claim-mappings.md) — Advanced claim extraction. - [Multi-Flow Configuration](https://docs.walt.id/enterprise-stack/services/oidc-bridge-service/multi-flow-config.md) — Configure presentation flows. -------------------------------------------------------------------------------- PAGE: Keycloak Integration URL: https://docs.walt.id/enterprise-stack/services/oidc-bridge-service/iam-integrations/keycloak RAW: https://docs.walt.id/enterprise-stack/services/oidc-bridge-service/iam-integrations/keycloak.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Keycloak Integration This guide demonstrates how to integrate the OIDC Bridge with Keycloak, enabling users to log in with Verifiable Credentials. ## Overview Keycloak will treat the OIDC Bridge as an external OIDC Identity Provider. When users click "Login with Verifiable Credential," Keycloak redirects them to the OIDC Bridge, which handles credential presentation and returns OIDC tokens. ## Prerequisites - OIDC Bridge service created, together with its KMS and Verifier2 dependencies (see [Setup](https://docs.walt.id/enterprise-stack/services/oidc-bridge-service/setup.md)). - **The bridge's KMS contains at least one key.** The bridge signs ID tokens with the first key in its attached KMS; if the KMS is empty, the `jwks` endpoint returns no keys and token issuance fails. Generate one with the [KMS Create Key](https://docs.walt.id/enterprise-stack/services/key-management-service/local.md) endpoint. - Keycloak instance running, with admin access. - **Keycloak can reach the bridge's issuer URL.** Keycloak fetches the discovery document and exchanges the code server-to-server, so the bridge hostname must resolve from wherever Keycloak runs. If Keycloak runs in Docker, add a host mapping (e.g. `extra_hosts`) so the bridge hostname resolves to the host from inside the container. ## Step 1: Create the OIDC Bridge with a Keycloak Client Create the bridge with a client entry for Keycloak. Each enabled flow includes its own `verificationSetup` (the credential request), so the service works as soon as it is created. **Option: CURL** Endpoint: `/v1/{target}/resource-api/services/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Resource/post_v1__target__resource_api_services_create) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{org}.{tenant}.oidc-bridge/resource-api/services/create' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "oidc-bridge", "issuerUrl": "https://enterprise.example.com", "clients": { "keycloak": { "clientId": "keycloak", "clientSecret": "keycloak-client-secret", "redirectUris": [ "https://keycloak.example.com/realms/{realm}/broker/oidc-bridge/endpoint" ], "allowedScopes": ["openid", "profile", "email"] } }, "defaultClaimMappings": [ { "oidcClaim": "sub", "credentialPath": "$[\"org.iso.18013.5.1\"][\"document_number\"]", "transform": "NONE" }, { "oidcClaim": "given_name", "credentialPath": "$[\"org.iso.18013.5.1\"][\"given_name\"]", "transform": "NONE" }, { "oidcClaim": "family_name", "credentialPath": "$[\"org.iso.18013.5.1\"][\"family_name\"]", "transform": "NONE" }, { "oidcClaim": "birthdate", "credentialPath": "$[\"org.iso.18013.5.1\"][\"birth_date\"]", "transform": "NONE" } ], "flows": { "qr": { "enabled": true, "buttonLabel": "Scan QR Code", "verificationSetup": { "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", "given_name"] }, { "path": ["org.iso.18013.5.1", "family_name"] }, { "path": ["org.iso.18013.5.1", "birth_date"] }, { "path": ["org.iso.18013.5.1", "document_number"] } ] } ] } } } }, "dc_api": { "enabled": true, "buttonLabel": "Browser Wallet", "verificationSetup": { "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", "given_name"] }, { "path": ["org.iso.18013.5.1", "family_name"] }, { "path": ["org.iso.18013.5.1", "birth_date"] }, { "path": ["org.iso.18013.5.1", "document_number"] } ] } ] } } } } }, "dependencies": [ "{org}.{tenant}.kms", "{org}.{tenant}.verifier2" ] }' ``` **info:** **The redirect URI must exactly match Keycloak's broker endpoint** for the provider alias you configure in Step 2: `https://{keycloak-host}/realms/{realm-name}/broker/{provider-alias}/endpoint` This example uses the alias `oidc-bridge`. ## Step 2: Configure Keycloak Identity Provider ### 2.1 Access Keycloak Admin Console 1. Navigate to: `https://keycloak.example.com/admin` 2. Log in with admin credentials 3. Select your realm ### 2.2 Add the Identity Provider 1. Go to **Identity Providers** in the left menu. 2. Click **Add provider** → **OpenID Connect v1.0**. 3. Fill in the add form: | Field | Value | |-------|-------| | **Alias** | `oidc-bridge` (or your preferred alias — it must match the redirect URI you allowed in [Step 1](#step-1-create-the-oidc-bridge-with-a-keycloak-client)) | | **Display name** | `Login with Verifiable Credential` | | **Use discovery endpoint** | On | | **Discovery endpoint** | `https://enterprise.example.com/v1/{org}.{tenant}.oidc-bridge/oidc-bridge-api/.well-known/openid-configuration` | | **Client authentication** | `Client secret sent in the request body` | | **Client ID** | `keycloak` | | **Client Secret** | `keycloak-client-secret` | When the discovery endpoint validates (green check), Keycloak fetches the bridge's metadata and auto-fills the Authorization URL, Token URL, JWKS URL, and Issuer. 4. Click **Add**. **info:** **PKCE and Scopes can't be set while adding the provider.** On the Add screen the **Use PKCE** toggle is disabled and there is no PKCE Method or Scopes field — Keycloak only exposes them on the provider's **Settings** page. Configure them in the next step. ### 2.3 Enable PKCE and Scopes Clicking **Add** opens the new provider's **Settings** page. Set the following, then click **Save**: | Field | Value | Location on the Settings page | |-------|-------|-------------------------------| | **Use PKCE** | On | *OpenID Connect settings* | | **PKCE Method** | `S256` | appears only after **Use PKCE** is switched On | | **Scopes** | `openid profile email` | *OpenID Connect settings* → expand **Advanced** | The **Redirect URI** shown at the top of this page (`https://{keycloak-host}/realms/{realm}/broker/{alias}/endpoint`) must exactly match one of the bridge client's `redirectUris` from Step 1 — with the alias `oidc-bridge` it already does. ### 2.4 Configure Claim Mappers The claim mappings in Step 1 emit `sub`, `given_name`, `family_name`, and `birthdate`. Keycloak uses `sub` as the federated user identity automatically, so you add mappers for the remaining claims. `given_name` and `family_name` map onto Keycloak's built-in profile fields; `birthdate` has no built-in field, so it becomes a **custom attribute**: 1. Go to **Identity Providers** → **oidc-bridge** → **Mappers** 2. Click **Add mapper** Create these mappers: #### Given Name Mapper - **Name:** `given-name-mapper` - **Mapper Type:** `Attribute Importer` - **Claim:** `given_name` - **User Attribute Name:** `firstName` #### Family Name Mapper - **Name:** `family-name-mapper` - **Mapper Type:** `Attribute Importer` - **Claim:** `family_name` - **User Attribute Name:** `lastName` #### Birthdate Mapper (custom attribute) - **Name:** `birthdate-mapper` - **Mapper Type:** `Attribute Importer` - **Claim:** `birthdate` - **User Attribute Name:** `birthdate` Because `birthdate` is not a built-in Keycloak field, it lands as a custom attribute on the user's **Attributes** tab. **Keycloak 24+ drops unmanaged attributes by default.** Custom attributes (like `birthdate`) that aren't defined in the realm's user profile are silently discarded — the mapper runs, but the attribute never appears on the user. To keep them, do one of the following: - **Enable unmanaged attributes:** *Realm settings → General → Unmanaged Attributes* → set to `Enabled` (or `Enabled (admin only)`). - **Define the attribute:** add `birthdate` under *Realm settings → User profile*, which also gives it a proper label and validation. **Note:** Add a mapper for any additional claim the OIDC Bridge emits — the **Claim** name must match an `oidcClaim` in the bridge's claim mappings. To surface a new claim (e.g. `email`), add it to the bridge's `defaultClaimMappings` (and the flow's DCQL) first, then map it here. Any claim without a built-in Keycloak field appears as a custom attribute (subject to the unmanaged-attributes note above). ## Step 3: Test the Integration ### 3.1 Navigate to Login Page Go to your Keycloak-protected application or the Keycloak account page: ``` https://keycloak.example.com/realms/{realm}/account ``` ### 3.2 Initiate VC Login Click **"Login with Verifiable Credential"** (or your configured display name). You'll be redirected to the OIDC Bridge presentation page. ### 3.3 Present Credential Choose your preferred presentation method: ![OIDC Bridge Presentation UI](https://docs.walt.id/img/enterprise-stack/services/OIDC-Bridge-Credential-Presentation-UI.png) - **Scan QR Code** — Use your mobile wallet - **Browser Wallet** — If using Chrome with DC API - **Open Mobile Wallet** — For same-device flow - **Open Web Wallet** — Redirect to web wallet Present your mDL or other credential when prompted. ### 3.4 Complete Login After successful presentation: - OIDC Bridge validates the credential - Extracts claims based on your mappings - Issues OIDC tokens - Redirects back to Keycloak - Keycloak creates/updates the user account - You're logged in! ## Step 4: Verify User Data in Keycloak 1. Go to **Users** in Keycloak Admin 2. Find the newly created user 3. Check the user's details and **Attributes** tab You should see: - `firstName`, `lastName` — from the mappers, on the built-in profile fields. - `birthdate` — under the **Attributes** tab, as a custom attribute. - The federated identity is keyed on `sub` (the mDL `document_number`), shown under the user's **Identity provider links**. ## Advanced Configuration ### Auto-Create Users By default, Keycloak creates users on first login. To customize: 1. Go to **Identity Providers** → **oidc-bridge** 2. Under **First Login Flow**, select a flow (default: `first broker login`) 3. Customize the flow in **Authentication** → **Flows** ### Require Email Verification To require email verification after VC login: 1. Edit the `first broker login` flow 2. Add a **Verify Email** step 3. Set to **Required** **warning:** **Note:** mDL credentials don't contain email addresses by default. You may need to use a different credential type or set email manually. ### Link Existing Accounts To allow users to link their VC login to existing Keycloak accounts: 1. Go to **Identity Providers** → **oidc-bridge** 2. Set **First Login Flow** to `first broker login` 3. This flow prompts users to link or create accounts ### Trust Only Specific Issuers Integrate with the [Trust Registry Service](https://docs.walt.id/enterprise-stack/services/trust-registry-service/overview.md): 1. Link Trust Registry to your Verifier2 service 2. Enable the `etsi-trust-list` policy in Verifier2 3. Only credentials from trusted issuers will be accepted ## Next Steps - [Claim Mappings](https://docs.walt.id/enterprise-stack/services/oidc-bridge-service/claim-mappings.md) — Customize attribute extraction - [Multi-Flow Configuration](https://docs.walt.id/enterprise-stack/services/oidc-bridge-service/multi-flow-config.md) — Configure presentation methods - [Trust Registry Integration](https://docs.walt.id/enterprise-stack/services/trust-registry-service/verifier-integration.md) — Add issuer trust verification -------------------------------------------------------------------------------- PAGE: Claim Mappings URL: https://docs.walt.id/enterprise-stack/services/oidc-bridge-service/claim-mappings RAW: https://docs.walt.id/enterprise-stack/services/oidc-bridge-service/claim-mappings.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Claim Mappings Claim mappings define how attributes from verifiable credentials are extracted and mapped to standard OIDC claims that IAM systems can consume. The OIDC Bridge uses JSONPath expressions to extract values from credentials and optionally transform them before including them in ID tokens. ## Understanding Claim Mappings When a user presents a verifiable credential, the OIDC Bridge: 1. **Validates** the credential signature and structure 2. **Extracts** attribute values using JSONPath expressions 3. **Transforms** values if configured (lowercase, hash, etc.) 4. **Maps** extracted values to OIDC claim names 5. **Includes** the claims in the ID token returned to the IAM system ``` ┌────────────────────┐ JSONPath ┌───────────────────┐ │ Verifiable │ ───────────────────▶ │ OIDC ID Token │ │ Credential │ Extraction + Transform │ │ │ │ │ │ │ { │ │ { │ │ "given_name": │ $.given_name ────▶ │ "given_name": │ │ "John", │ │ "John", │ │ "family_name": │ $.family_name ───▶ │ "family_name": │ │ "DOE", │ + LOWERCASE │ "doe", │ │ "email": │ $.email ─────────▶ │ "email": │ │ "JOHN@X.COM" │ + LOWERCASE │ "john@x.com" │ │ } │ │ } │ └────────────────────┘ └───────────────────┘ ``` ## Claim Mapping Structure Each claim mapping consists of three required fields: ```json { "oidcClaim": "given_name", "credentialPath": "$.given_name", "transform": "NONE" } ``` | Field | Description | |-------|-------------| | **oidcClaim** | The target OIDC claim name (e.g., `sub`, `email`, `given_name`) | | **credentialPath** | JSONPath expression to extract the value from the credential | | **transform** | Optional transformation to apply (default: `NONE`) | | **credentialId** | (Optional) For multi-credential queries, specify which credential to extract from | ## Credential Path Expressions The `credentialPath` is a JSONPath expression evaluated against the **decoded credential's data object** — not a wrapper envelope. For an mDL this object is keyed by the ISO namespace, which is why the namespace itself (`org.iso.18013.5.1`) is the top-level segment of every path. Getting this root wrong is the most common cause of a mapping that silently produces no claim. The `credentialPath` format therefore depends on the credential type: ### SD-JWT VC (Flat Structure) SD-JWT VCs use simple JSONPath expressions: ```json { "oidcClaim": "given_name", "credentialPath": "$.given_name" } ``` **Example SD-JWT VC payload:** ```json { "given_name": "John", "family_name": "Doe", "email": "john.doe@example.com", "birthdate": "1990-01-15" } ``` **Common SD-JWT VC paths:** - `$.given_name` → Given name - `$.family_name` → Family name - `$.email` → Email address - `$.birthdate` → Birth date - `$.phone_number` → Phone number - `$.address` → Full address object - `$.age_over_18` → Boolean age verification ### mDL (Namespaced Structure) mDL credentials use the ISO namespace `org.iso.18013.5.1`: ```json { "oidcClaim": "given_name", "credentialPath": "$[\"org.iso.18013.5.1\"][\"given_name\"]" } ``` **Example mDL payload:** ```json { "org.iso.18013.5.1": { "given_name": "John", "family_name": "Doe", "birth_date": "1990-01-15", "document_number": "DL123456789" } } ``` **Common mDL paths:** - `$["org.iso.18013.5.1"]["given_name"]` → Given name - `$["org.iso.18013.5.1"]["family_name"]` → Family name - `$["org.iso.18013.5.1"]["birth_date"]` → Birth date - `$["org.iso.18013.5.1"]["document_number"]` → License number - `$["org.iso.18013.5.1"]["portrait"]` → Photo (JPEG bytes) - `$["org.iso.18013.5.1"]["age_over_18"]` → Boolean age verification ### W3C Verifiable Credentials (JWT) JWT VCs typically nest attributes under `credentialSubject`: ```json { "oidcClaim": "given_name", "credentialPath": "$.credentialSubject.given_name" } ``` ## Transform Options Transforms are applied after extraction but before inclusion in the ID token. ### NONE (Default) Pass the value through unchanged: ```json { "oidcClaim": "given_name", "credentialPath": "$.given_name", "transform": "NONE" } ``` **Example:** - Input: `"John"` - Output: `"John"` ### LOWERCASE Convert to lowercase (useful for email addresses): ```json { "oidcClaim": "email", "credentialPath": "$.email", "transform": "LOWERCASE" } ``` **Example:** - Input: `"JOHN.DOE@EXAMPLE.COM"` - Output: `"john.doe@example.com"` ### UPPERCASE Convert to uppercase: ```json { "oidcClaim": "country_code", "credentialPath": "$.country", "transform": "UPPERCASE" } ``` **Example:** - Input: `"us"` - Output: `"US"` ### HASH_SHA256 Hash the value using SHA-256 (useful for pseudonymization). Note the config value is lowercase `hash_sha256` — unlike the other transforms, whose config values match their names exactly: ```json { "oidcClaim": "sub", "credentialPath": "$[\"org.iso.18013.5.1\"][\"document_number\"]", "transform": "hash_sha256" } ``` **Example:** - Input: `"DL123456789"` - Output: `"5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"` **info:** **Privacy Tip:** Use `hash_sha256` for the `sub` claim when you don't want the IAM system to store raw credential identifiers. ## Standard OIDC Claims The OIDC specification defines these standard claims: | Claim | Description | Example | |-------|-------------|---------| | `sub` | Subject identifier (unique user ID) | `"did:key:z6Mk..."` or hashed value | | `name` | Full name | `"John Doe"` | | `given_name` | First name | `"John"` | | `family_name` | Last name | `"Doe"` | | `middle_name` | Middle name | `"Robert"` | | `nickname` | Nickname | `"Johnny"` | | `preferred_username` | Username | `"johndoe"` | | `email` | Email address | `"john.doe@example.com"` | | `email_verified` | Email verification status | `true` | | `phone_number` | Phone number | `"+1-202-555-0123"` | | `phone_number_verified` | Phone verification status | `true` | | `birthdate` | Birth date (ISO 8601) | `"1990-01-15"` | | `gender` | Gender | `"male"` | | `picture` | Profile picture URL | `"https://..."` | | `address` | Address object | `{"street_address": "123 Main St", ...}` | **Note:** You can also define **custom claims** beyond the standard OIDC set. These will be included in the ID token and can be consumed by your IAM system. ## Configuration Examples ### Example 1: Basic Identity (SD-JWT VC) Map a simple identity credential to standard OIDC claims: ```json { "defaultClaimMappings": [ { "oidcClaim": "sub", "credentialPath": "$.email", "transform": "hash_sha256" }, { "oidcClaim": "given_name", "credentialPath": "$.given_name", "transform": "NONE" }, { "oidcClaim": "family_name", "credentialPath": "$.family_name", "transform": "NONE" }, { "oidcClaim": "email", "credentialPath": "$.email", "transform": "LOWERCASE" }, { "oidcClaim": "birthdate", "credentialPath": "$.birthdate", "transform": "NONE" } ] } ``` **Resulting ID Token:** ```json { "sub": "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8", "given_name": "John", "family_name": "Doe", "email": "john.doe@example.com", "birthdate": "1990-01-15", "iss": "https://enterprise.example.com", "aud": "my-iam-client", "exp": 1716048000, "iat": 1716044400 } ``` ### Example 2: mDL Credential Map a mobile driver's license to OIDC claims: ```json { "defaultClaimMappings": [ { "oidcClaim": "sub", "credentialPath": "$[\"org.iso.18013.5.1\"][\"document_number\"]", "transform": "NONE" }, { "oidcClaim": "given_name", "credentialPath": "$[\"org.iso.18013.5.1\"][\"given_name\"]", "transform": "NONE" }, { "oidcClaim": "family_name", "credentialPath": "$[\"org.iso.18013.5.1\"][\"family_name\"]", "transform": "NONE" }, { "oidcClaim": "birthdate", "credentialPath": "$[\"org.iso.18013.5.1\"][\"birth_date\"]", "transform": "NONE" }, { "oidcClaim": "picture", "credentialPath": "$[\"org.iso.18013.5.1\"][\"portrait\"]", "transform": "NONE" }, { "oidcClaim": "age_over_18", "credentialPath": "$[\"org.iso.18013.5.1\"][\"age_over_18\"]", "transform": "NONE" } ] } ``` ### Example 3: Employment Credential Map an employment credential to custom claims: ```json { "defaultClaimMappings": [ { "oidcClaim": "sub", "credentialPath": "$.employee_id", "transform": "NONE" }, { "oidcClaim": "email", "credentialPath": "$.work_email", "transform": "LOWERCASE" }, { "oidcClaim": "department", "credentialPath": "$.department", "transform": "NONE" }, { "oidcClaim": "job_title", "credentialPath": "$.job_title", "transform": "NONE" }, { "oidcClaim": "employee_level", "credentialPath": "$.level", "transform": "NONE" } ] } ``` ## Per-Client Claim Mappings Different OIDC clients can have different claim mapping requirements. Override the default mappings per client: ```json { "defaultClaimMappings": [ { "oidcClaim": "sub", "credentialPath": "$.email", "transform": "hash_sha256" }, { "oidcClaim": "given_name", "credentialPath": "$.given_name", "transform": "NONE" } ], "clients": { "keycloak": { "clientId": "keycloak", "clientSecret": "***", "redirectUris": ["https://keycloak.example.com/callback"], "claimMappings": [ { "oidcClaim": "preferred_username", "credentialPath": "$.email", "transform": "LOWERCASE" }, { "oidcClaim": "given_name", "credentialPath": "$.given_name", "transform": "NONE" }, { "oidcClaim": "family_name", "credentialPath": "$.family_name", "transform": "NONE" } ] }, "okta": { "clientId": "okta", "clientSecret": "***", "redirectUris": ["https://okta.example.com/callback"], "claimMappings": [ { "oidcClaim": "sub", "credentialPath": "$.email", "transform": "NONE" }, { "oidcClaim": "email", "credentialPath": "$.email", "transform": "LOWERCASE" } ] } } } ``` **info:** When per-client `claimMappings` are provided, they **replace** (not merge with) the `defaultClaimMappings` for that client. ## Multi-Credential Queries When requesting multiple credentials (OR logic), use the `credentialId` field to specify which credential each mapping applies to: ```json { "defaultClaimMappings": [ { "credentialId": "option-mdl", "credentialPath": "$[\"org.iso.18013.5.1\"][\"given_name\"]", "oidcClaim": "given_name", "transform": "NONE" }, { "credentialId": "option-pid", "credentialPath": "$.given_name", "oidcClaim": "given_name", "transform": "NONE" }, { "credentialId": "option-mdl", "credentialPath": "$[\"org.iso.18013.5.1\"][\"family_name\"]", "oidcClaim": "family_name", "transform": "NONE" }, { "credentialId": "option-pid", "credentialPath": "$.family_name", "oidcClaim": "family_name", "transform": "NONE" } ] } ``` The OIDC Bridge will use the mappings that match the credential type the user actually presented. ## Handling Missing Claims If a credential doesn't contain a requested attribute (or the JSONPath doesn't resolve), the OIDC Bridge will: 1. **Skip** the claim (it won't appear in the ID token) 2. **Continue** processing other claims The skip is silent — no warning is logged — so an absent claim in the ID token, rather than an error, is your signal that a `credentialPath` didn't resolve. **warning:** Ensure your DCQL query requests all attributes you're mapping. If you don't request an attribute, the credential may not include it in the response, and the claim mapping will fail. ## Best Practices ### 1. Use Stable Identifiers for `sub` The `sub` claim should be: - **Unique** per user - **Stable** over time - **Privacy-preserving** (consider hashing) **Good choices:** - Hashed document number: `hash_sha256` of `document_number` - DID subject identifier - Hashed email address **Avoid:** - Raw personally identifiable information - Values that can change (phone numbers) ### 2. Normalize Email Addresses Always apply `LOWERCASE` transform to email claims: ```json { "oidcClaim": "email", "credentialPath": "$.email", "transform": "LOWERCASE" } ``` ## Next Steps - [Multi-Flow Configuration](https://docs.walt.id/enterprise-stack/services/oidc-bridge-service/multi-flow-config.md) — Configure presentation methods - [Keycloak Integration](https://docs.walt.id/enterprise-stack/services/oidc-bridge-service/iam-integrations/keycloak.md) — See claim mapping in action - [Setup](https://docs.walt.id/enterprise-stack/services/oidc-bridge-service/setup.md) — Create your OIDC Bridge service -------------------------------------------------------------------------------- PAGE: Multi-Flow Configuration URL: https://docs.walt.id/enterprise-stack/services/oidc-bridge-service/multi-flow-config RAW: https://docs.walt.id/enterprise-stack/services/oidc-bridge-service/multi-flow-config.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Multi-Flow Configuration The OIDC Bridge supports four different credential presentation methods, each optimized for different user scenarios. You can enable/disable flows individually, customize button labels, and configure flow-specific verification settings. ## Overview When users are redirected to the OIDC Bridge during login, they see a presentation selection page with buttons for each enabled flow: ![OIDC Bridge Presentation UI](https://docs.walt.id/img/enterprise-stack/services/OIDC-Bridge-Credential-Presentation-UI.png) **Available flows:** | Flow | Description | User Experience | Device | Browser Support | |------|-------------|-----------------|--------|-----------------| | **QR Code** | Cross-device presentation | Scan QR with mobile wallet | Any device with camera | All browsers | | **DC API** | Browser-native credential API | Browser shows credential picker | Same device | Chrome 128+, Edge | | **Deep Link** | Direct wallet launch | Opens mobile wallet app | Same device (mobile) | All mobile browsers | | **Web Wallet** | Browser-based wallet | Redirects to web wallet | Same device | All browsers | ## Configuration Structure Flows are configured in the `flows` object when creating an OIDC Bridge service: ```json { "type": "oidc-bridge", "issuerUrl": "https://enterprise.example.com", "flows": { "qr": { "enabled": true, "buttonLabel": "Scan QR Code", "verificationSetup": { ... } }, "dc_api": { "enabled": true, "buttonLabel": "Browser Wallet", "verificationSetup": { ... } }, "deep_link": { "enabled": true, "buttonLabel": "Open Mobile Wallet", "verificationSetup": { ... } }, "web_wallet": { "enabled": true, "buttonLabel": "Open Web Wallet", "targetUrl": "https://wallet.example.com", "verificationSetup": { ... } } } } ``` ### Common Flow Fields | Field | Required | Description | |-------|----------|-------------| | `enabled` | Yes | Enable or disable this flow | | `buttonLabel` | No | Text shown on the presentation button (default varies by flow) | | `verificationSetup` | Yes* | The credential request (DCQL query) sent to the wallet for this flow. See [Which `flow_type` to use](#which-flow_type-to-use). | \* Each enabled flow needs a `verificationSetup` unless a client-level or service-level setup covers it. ## Which `flow_type` to use Each flow's `verificationSetup` is structure like a Verifier2 verification request including a `flow_type` field. **For the OIDC Bridge, always configure every flow with `flow_type: cross_device`** — including the DC API flow: ```json "verificationSetup": { "flow_type": "cross_device", "core_flow": { "dcql_query": { ... } } } ``` ## Flow 1: QR Code (Cross-Device) The QR code flow enables users to scan a QR code with their mobile wallet and present credentials from their phone. ### How It Works ```mermaid sequenceDiagram participant Desktop as Desktop Browser participant Bridge as OIDC Bridge participant Mobile as Mobile Wallet participant IAM as IAM System Desktop->>Bridge: 1. Click "Scan QR Code" Bridge->>Desktop: 2. Display QR code Mobile->>Bridge: 3. Scan QR & fetch request Mobile->>Mobile: 4. User approves credential Mobile->>Bridge: 5. Submit credential presentation Bridge->>Bridge: 6. Validate credential Desktop->>Bridge: 7. Poll for completion Bridge->>Desktop: 8. Redirect with auth code Desktop->>IAM: 9. Complete login ``` ### Configuration ```json { "flows": { "qr": { "enabled": true, "buttonLabel": "Scan QR Code with Mobile Wallet", "verificationSetup": { "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", "given_name"] }, { "path": ["org.iso.18013.5.1", "family_name"] }, { "path": ["org.iso.18013.5.1", "birth_date"] } ] }] } } } } } } ``` ### When to Use - **Desktop users** who have credentials on their mobile device - **Public kiosks** where users shouldn't log into their wallet - **Cross-device security** — credentials never touch the login device ### User Experience 1. User clicks "Scan QR Code with Mobile Wallet" 2. QR code appears on screen 3. User opens their mobile wallet and scans the QR code 4. Wallet shows credential request and asks for approval 5. User approves and the credential is sent 6. Desktop browser automatically redirects to complete login ## Flow 2: DC API (Digital Credentials API) The DC API flow uses the W3C Digital Credentials API (browser-native) for same-device credential presentation. ### How It Works ```mermaid sequenceDiagram participant User participant Browser as Browser participant DCAPI as Digital Credentials API participant Wallet as Wallet participant Bridge as OIDC Bridge User->>Browser: 1. Click "Use Browser Wallet" Browser->>DCAPI: 2. navigator.credentials.get() DCAPI->>Browser: 3. Show credential picker User->>Browser: 4. Select credential Browser->>Wallet: 5. Request credential Wallet->>Wallet: 6. User approves Wallet->>Browser: 7. Return credential Browser->>Bridge: 8. Submit presentation Bridge->>Bridge: 9. Validate credential Bridge->>Browser: 10. Redirect with auth code ``` ### Configuration ```json { "flows": { "dc_api": { "enabled": true, "buttonLabel": "Use Browser Wallet", "verificationSetup": { "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", "given_name"] }, { "path": ["org.iso.18013.5.1", "family_name"] } ] }] } }, "expectedOrigins": ["https://enterprise.example.com"] } } } } ``` ### Important: Expected Origins The `expectedOrigins` field is **required** for DC API flows. It specifies which origins are allowed to receive the credential presentation: ```json { "expectedOrigins": [ "https://enterprise.example.com", "https://iam.example.com" ] } ``` **warning:** The DC API **requires HTTPS**. Localhost HTTP URLs are automatically converted to HTTPS by the OIDC Bridge for testing. ### When to Use - **Modern browsers** (Chrome 128+) - **Same-device login** where the credential is stored in a system wallet - **Streamlined UX** — no QR scanning needed - **High security** — credentials managed by the OS ### User Experience 1. User clicks "Use Browser Wallet" 2. Browser shows native credential picker UI 3. User selects credential from system wallet (Google Wallet, Apple Wallet, etc.) 4. Browser automatically submits the credential 5. User is logged in ### Testing DC API To test DC API support: ```bash # Check browser support open https://digital-credentials.walt.id/ # Test with your OIDC Bridge # 1. Ensure HTTPS is enabled # 2. Configure expectedOrigins # 3. Use a browser that supports the DC API eg. Chrome 128+ # 4. Have matching credentials Wallet ``` ## Flow 3: Deep Link (Mobile Same-Device) The deep link flow uses the `openid4vp://` URL scheme to launch the mobile wallet app directly. ### How It Works ```mermaid sequenceDiagram participant User participant Browser as Mobile Browser participant OS as Mobile OS participant Wallet as Wallet App participant Bridge as OIDC Bridge User->>Browser: 1. Click "Open Mobile Wallet" Browser->>OS: 2. openid4vp:// deep link OS->>OS: 3. Show app picker (if multiple wallets) User->>OS: 4. Select wallet app OS->>Wallet: 5. Launch with request Wallet->>Wallet: 6. User approves credential Wallet->>Bridge: 7. Submit presentation Bridge->>Bridge: 8. Validate credential Wallet->>Browser: 9. Reopen browser with result Browser->>Browser: 10. Complete login ``` ### Configuration ```json { "flows": { "deep_link": { "enabled": true, "buttonLabel": "Open Mobile Wallet App", "verificationSetup": { "flow_type": "cross_device", "core_flow": { "dcql_query": { "credentials": [{ "id": "identity", "format": "dc+sd-jwt", "meta": { "vct_values": ["IdentityCredential"] }, "claims": [ { "path": ["given_name"] }, { "path": ["family_name"] }, { "path": ["email"] } ] }] } } } } } } ``` ### When to Use - **Mobile browsers** where users have wallet apps installed - **Native app integration** — wallet apps can handle `openid4vp://` scheme - **Same-device login** without browser-native API support ### User Experience 1. User clicks "Open Mobile Wallet App" on their phone 2. OS shows app picker (if multiple wallets installed) 3. Wallet app opens with credential request 4. User approves presentation 5. Browser automatically reopens and completes login ## Flow 4: Web Wallet The web wallet flow redirects users to a browser-based wallet interface. ### How It Works ```mermaid sequenceDiagram participant User participant Bridge as OIDC Bridge participant WebWallet as Web Wallet participant IAM as IAM System User->>Bridge: 1. Click "Use Web Wallet" Bridge->>WebWallet: 2. Redirect with request_uri WebWallet->>WebWallet: 3. Fetch credentials WebWallet->>User: 4. Show credential selection User->>WebWallet: 5. Approve presentation WebWallet->>Bridge: 6. Submit presentation Bridge->>Bridge: 7. Validate credential Bridge->>User: 8. Redirect with auth code User->>IAM: 9. Complete login ``` ### Configuration ```json { "flows": { "web_wallet": { "enabled": true, "buttonLabel": "Use Web Wallet", "targetUrl": "https://wallet.example.com/present", "verificationSetup": { "flow_type": "cross_device", "core_flow": { "dcql_query": { "credentials": [{ "id": "identity", "format": "dc+sd-jwt", "meta": { "vct_values": ["IdentityCredential"] }, "claims": [ { "path": ["given_name"] }, { "path": ["family_name"] } ] }] } } } } }, "uiConfig": { "webWalletBaseUrl": "https://wallet.example.com" } } ``` ### Required Fields | Field | Description | |-------|-------------| | `targetUrl` | Full URL to redirect to (overrides `webWalletBaseUrl`) | | `uiConfig.webWalletBaseUrl` | Base URL for web wallet (used if `targetUrl` not specified) | The OIDC Bridge constructs the redirect URL with query parameters: ``` https://wallet.example.com/present?request_uri={verifier2_request_uri} ``` ### When to Use - **Enterprise web wallets** deployed alongside the Enterprise Stack - **Browser-only environments** (no mobile wallet) - **Managed credentials** stored in web wallet - **Consistent branding** — wallet UI matches enterprise theme ### User Experience 1. User clicks "Use Web Wallet" 2. Browser redirects to web wallet page 3. Web wallet shows credentials and asks for approval 4. User approves presentation 5. Web wallet redirects back to OIDC Bridge 6. User is logged in ### Integration with Enterprise UI If using the walt.id Enterprise UI web wallet: ```json { "flows": { "web_wallet": { "enabled": true, "buttonLabel": "Open walt.id Web Wallet", "targetUrl": "https://enterprise-ui.example.com/present" } }, "uiConfig": { "webWalletBaseUrl": "https://enterprise-ui.example.com" } } ``` ## Per-Flow Verification Setup Each flow can have its own DCQL query. This is useful when: - **QR flow** accepts mDL (mobile driver's license) - **DC API flow** accepts SD-JWT VC (browser-stored) - **Web wallet flow** accepts any credential type ### Example: Different Credentials Per Flow ```json { "flows": { "qr": { "enabled": true, "buttonLabel": "Scan mDL with Phone", "verificationSetup": { "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", "given_name"] }, { "path": ["org.iso.18013.5.1", "family_name"] } ] }] } } } }, "dc_api": { "enabled": true, "buttonLabel": "Browser Credential", "verificationSetup": { "flow_type": "cross_device", "core_flow": { "dcql_query": { "credentials": [{ "id": "identity", "format": "dc+sd-jwt", "meta": { "vct_values": ["IdentityCredential"] }, "claims": [ { "path": ["given_name"] }, { "path": ["family_name"] } ] }] } } } }, "web_wallet": { "enabled": true, "buttonLabel": "Web Wallet (Any Type)", "targetUrl": "https://wallet.example.com/present", "verificationSetup": { "flow_type": "cross_device", "core_flow": { "dcql_query": { "credentials": [ { "id": "option-mdl", "format": "mso_mdoc", "meta": { "doctype_value": "org.iso.18013.5.1.mDL" }, "claims": [ { "path": ["org.iso.18013.5.1", "given_name"] }, { "path": ["org.iso.18013.5.1", "family_name"] } ] }, { "id": "option-vc", "format": "dc+sd-jwt", "meta": { "vct_values": ["IdentityCredential"] }, "claims": [ { "path": ["given_name"] }, { "path": ["family_name"] } ] } ], "credential_sets": [{ "options": [["option-mdl"], ["option-vc"]], "required": true }] } } } } } } ``` **info:** If no per-flow `verificationSetup` is provided, the OIDC Bridge uses `defaultVerificationSetup` from the service configuration. ## UI Customization Customize the presentation selection page appearance: ```json { "uiConfig": { "brandName": "Acme Corporation", "primaryColor": "#3B82F6", "logoUrl": "https://example.com/logo.png", "webWalletBaseUrl": "https://wallet.example.com" } } ``` ### UI Configuration Fields | Field | Description | Example | |-------|-------------|---------| | `brandName` | Organization name shown at top of page | `"Acme Corp"` | | `primaryColor` | Hex color for buttons and accents | `"#3B82F6"` | | `logoUrl` | URL to organization logo (shown above brand name) | `"https://example.com/logo.png"` | | `webWalletBaseUrl` | Default base URL for web wallet redirects | `"https://wallet.example.com"` | ## Recommended Configurations ### Configuration 1: Mobile-First (QR + Deep Link) For scenarios where most users will present from mobile wallets: ```json { "flows": { "qr": { "enabled": true, "buttonLabel": "Scan with Mobile Wallet" }, "deep_link": { "enabled": true, "buttonLabel": "Open Wallet App" }, "dc_api": { "enabled": false }, "web_wallet": { "enabled": false } } } ``` ### Configuration 2: Modern Browser (DC API + Web Wallet) For controlled environments with modern browsers: ```json { "flows": { "qr": { "enabled": false }, "deep_link": { "enabled": false }, "dc_api": { "enabled": true, "buttonLabel": "Use System Wallet" }, "web_wallet": { "enabled": true, "buttonLabel": "Use Web Wallet", "targetUrl": "https://wallet.example.com/present" } } } ``` ### Configuration 3: All Options (Maximum Flexibility) Enable all flows to support all user scenarios: ```json { "flows": { "qr": { "enabled": true, "buttonLabel": "Scan QR Code" }, "deep_link": { "enabled": true, "buttonLabel": "Open Mobile Wallet" }, "dc_api": { "enabled": true, "buttonLabel": "Browser Wallet" }, "web_wallet": { "enabled": true, "buttonLabel": "Web Wallet" } } } ``` ## Next Steps - [Claim Mappings](https://docs.walt.id/enterprise-stack/services/oidc-bridge-service/claim-mappings.md) — Configure credential-to-claim mappings - [Setup](https://docs.walt.id/enterprise-stack/services/oidc-bridge-service/setup.md) — Create your OIDC Bridge service - [Keycloak Integration](https://docs.walt.id/enterprise-stack/services/oidc-bridge-service/iam-integrations/keycloak.md) — See multi-flow in action -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/services/verifier-service/overview RAW: https://docs.walt.id/enterprise-stack/services/verifier-service/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview walt.id's **Verifier Service** enables organizations to verify digital identity credentials in a secure, privacy‑preserving and standards compliant way. Users can also use a management GUI for handling multiple verifier configurations, creating credential requests, applying verification policies and viewing analytics. **Error:** This service will be deprecated in Q2 2026 in favour of the new Verifier2 Service.

The Verifier2 service is built around the stable release of the OID4VP 1.0 specification. It includes many breaking changes from the draft specifications supported by this verifier service. We would recommend using the verifier2 service for new projects. [Documentation for the verifier2 service can be found here](https://docs.walt.id/enterprise-stack/services/verifier2-service/overview.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:** | [OID4VP](https://docs.walt.id/concepts/data-exchange-protocols.md) (Draft 14, 20), [ISO/IEC 18013-7](https://docs.walt.id/concepts/data-exchange-protocols.md) | | **Signature Algorithms:** | ed25519, secp256k1, secp256r1, RSA | --- ## Core Features ### Credential Exchange Credential verification is performed via OID4VP. Authorization requests can be created using the Presentation Definition protocol. Multiple draft versions (14 and 20) are supported. Requests for ISO/IEC 18013-7 compliant mDLs are supported as well. ### Credential & Attribute Request Definition Credential requests define the credential formats, types and attributes that should be provided by a wallet. They can be expressed simply by combining credential types and formats, with full flexibility using Presentation Definition. Support for DCQL will be added soon. ### Credential Validation Received credentials are validated against configurable policies. Static checks such as signature verification and schema compliance are provided out of the box. Parameterized policies enable features like issuer allow lists and webhooks, and custom logic can be implemented with REGO policies executed by the Open Policy Agent. See the [Policies](https://docs.walt.id/enterprise-stack/services/verifier-service/policies/overview.md) section for details. ### Management & Analytics The Enterprise GUI allows creating and viewing credential requests, applying verification policies and monitoring sessions through the events dashboard. --- ## Getting Started - [Setup](https://docs.walt.id/enterprise-stack/services/verifier-service/setup.md) – Configure a verifier service - [Verify W3C credentials](https://docs.walt.id/enterprise-stack/services/verifier-service/credential-verification/vc-oid4vc.md) – Walkthrough on how to verify a W3C credential - [Verify SD-JWT VC credentials](https://docs.walt.id/enterprise-stack/services/verifier-service/credential-verification/sd-jwt-vc-oid4vc.md) – Walkthrough on how to verify an SD-JWT VC credential - [Verify mDL/mDOC credentials](https://docs.walt.id/enterprise-stack/services/verifier-service/credential-verification/mdl-oid4vc.md) – Walkthrough on how to verify an mDL/mDOC credential -------------------------------------------------------------------------------- PAGE: Setup URL: https://docs.walt.id/enterprise-stack/services/verifier-service/setup RAW: https://docs.walt.id/enterprise-stack/services/verifier-service/setup.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Setup We will setup a Verifier service inside of a tenant. If you don't have a tenant yet, you can learn how to create one [here](https://docs.walt.id/enterprise-stack/administration/tenants/create.md). **Option: CURL** Endpoint: `/v1/{target}/resource-api/services/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Resource/post_v1__target__resource_api_services_create) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/services/create' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "type": "verifier", "baseUrl": "http://localhost:3000", "metadataConfig": { "name": "walt.id Enterprise Verifier", "logoUri": "https://cdn.walt.id/verifier/logo.png" } }' ``` **Body** ```json { "type": "verifier", "baseUrl": "http://localhost:3000", "metadataConfig": { "name": "walt.id Enterprise Verifier", "logoUri": "https://cdn.walt.id/verifier/logo.png" } } ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant in which to create the new Verifier service and the service's ID (`{organizationID}.{tenantID}.[NewVerifierServiceID]`), e.g. `waltid.tenant1.verifier1` **Body Parameters** - `type`: _serviceType_ - Specifies the type of service to create. In our case `verifier` - `baseUrl`: _String_ - This URL will be included in the generated OIDC4VC offer, allowing the wallet to know how to reach the verifier. It should reflect your organization's base URL. The general format for this URL is `https://{orgID}.yourEnterpriseStackUrl.com`. For example, if your organization is named **myorg** and the Enterprise Stack is hosted at the domain **enterprise-stack.com**, your base URL would be: https://myorg.enterprise-stack.com. - `metadataConfig`: _Object_ - Verifier metadata config. Can be used to edit e.g. externally exposed human-readable verifier name and logo. Learn more in general [here](https://docs.walt.id/enterprise-stack/services/verifier-service/configurations/openid-metadata.md) or expand below to see object property options.

Expand to learn more about metadata config object properties **Metadata Config Object** ```json "metadataConfig": { "name": "walt.id Enterprise Verifier", "logoUri": "https://cdn.walt.id/verifier/logo.png" } ``` - `name` (optional) _String_: Human-readable string name of the verifier. - `logoUri` (optional) _String_: URL string that references a logo for the verifier.
--- **Response Codes** - `201` - Service created successfully. -------------------------------------------------------------------------------- PAGE: Verifying Verifiable Credentials via OID4VC URL: https://docs.walt.id/enterprise-stack/services/verifier-service/credential-verification/vc-oid4vc RAW: https://docs.walt.id/enterprise-stack/services/verifier-service/credential-verification/vc-oid4vc.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Verifying Verifiable Credentials (W3C) via OID4VC This guide provides a comprehensive walkthrough for verifying a Verifiable Credential based on the W3C standard using the walt.id Enterprise verifier API. The verification process will utilize the OID4VP 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. **OID4VP**: A protocol specifying how parties can present VCs in a way that's consistent and secure across platforms ensuring interoperability. ## Enterprise Service Dependencies To verify credentials, you need to have the following enterprise services setup: 1. [Verifier Service](https://docs.walt.id/enterprise-stack/services/verifier-service/overview.md) - Have a running verifier service. Setup instructions can be found [here](https://docs.walt.id/enterprise-stack/services/verifier-service/setup.md). ## Preparing the Verification First, determine the type of Verifiable Credential to be verified. Although this guide focuses on a "VerifiableDiploma," 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 Verifiable Diploma, 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' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/verifier-service-api/credentials/verify' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'authorizeBaseUrl: openid4vp://authorize' \ -H 'responseMode: direct_post' \ -H 'successRedirectUri: https://example.com/success/{id}' \ -H 'errorRedirectUri: https://example.com/error/{id}' \ -H 'statusCallbackUri: https://example.com/status_callback/{id}' \ -H 'statusCallbackApiKey: myAPIKey' \ -H 'stateId: myUniqueStateValue' \ -H 'Content-Type: application/json' \ -d '{ "request_credentials": [ { "type": "VerifiableDiploma", "format": "jwt_vc_json" } ] }' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + verifier service with which to execute the credential verification (`{organizationID}.{tenantID}.{verifierServiceID}`), e.g. `waltid.tenant1.verifier1` **Header Parameters** - **authorizeBaseUrl** - is used to modify the start of the OID4VC request URL. If you are using the cross-device flow, where you will display the URL as a QR code, you can leave the value as `openid4vp://authorize` or if you don't know the wallet the user will be using to claim the credential. If you are using the same device flow, where you already know the user's wallet and want the user to be able to go directly to it, you can use the wallet URL path that is able to receive an OIDC request as a query parameter. Our wallet for example can receive OID4VC requests 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/enterprise-stack/services/verifier-service/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're not only specifying the types of credentials to verify, but also defining specific policies that should be executed upon their verification. These include Verifiable Credential (VC) policies applied to credentials, or Verifiable Presentation (VP) policies applied to the VP. The default policy applied for both VP and VC(s) is the signature policy. A list of available policies can be found [here](https://docs.walt.id/enterprise-stack/services/verifier-service/policies/overview.md). It's important to note that VC policies, once defined, are applied globally - that is, they are enacted on all requested credentials within the scope of the verification. In a subsequent section, we'll delve into how to customize and apply specific policies to individual credentials. We will now specify these in the verification request alongside the `request_credentials` parameter (previously explained) using `vc_policies` and `vp_policies`. These represent lists of policies - either one, or both, can be provided. The structure for both is identical. VC and VP policies are formatted as lists: the policy can be represented as a string if no arguments are required, or as an object if arguments are needed. **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' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/verifier-service-api/credentials/verify' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'authorizeBaseUrl: openid4vp://authorize' \ -H 'responseMode: direct_post' \ -H 'successRedirectUri: https://example.com/success?id=$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": "VerifiableDiploma", "format": "jwt_vc_json" } ] }' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + verifier service with which to execute the credential verification (`{organizationID}.{tenantID}.{verifierServiceID}`), e.g. `waltid.tenant1.verifier1` **Header Parameters** - **authorizeBaseUrl** - is used to modify the start of the OID4VC request URL. If you are using the cross-device flow, where you will display the URL as a QR code, you can leave the value as `openid4vp://authorize` or if you don't know the wallet the user will be using to claim the credential. If you are using the same device flow, where you already know the user's wallet and want the user to be able to go directly to it, you can use the wallet URL path that is able to receive an OIDC request as a query parameter. Our wallet for example can receive OID4VC requests 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/enterprise-stack/services/verifier-service/policies/overview.md) - **vc_policies** - Policies applied to all requested credentials. A list of policies can be found [here](https://docs.walt.id/enterprise-stack/services/verifier-service/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. - **expires_in_seconds** - (Optional) _Integer_ - The number of seconds the generated URL will be valid for. If not specified, the default will never expire.
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/enterprise-stack/services/verifier-service/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/enterprise-stack/services/verifier-service/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/enterprise-stack/services/verifier-service/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' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/verifier-service-api/credentials/verify' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'authorizeBaseUrl: openid4vp://authorize' \ -H 'responseMode: direct_post' \ -H 'successRedirectUri: https://example.com/success?id=$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" } ] } ] }' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + verifier service with which to execute the credential verification (`{organizationID}.{tenantID}.{verifierServiceID}`), e.g. `waltid.tenant1.verifier1` **Header Parameters** - **authorizeBaseUrl** - is used to modify the start of the OID4VC request URL. If you are using the cross-device flow, where you will display the URL as a QR code, you can leave the value as `openid4vp://authorize` or if you don't know the wallet the user will be using to claim the credential. If you are using the same device flow, where you already know the user's wallet and want the user to be able to go directly to it, you can use the wallet URL path that is able to receive an OIDC request as a query parameter. Our wallet for example can receive OID4VC requests 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/enterprise-stack/services/verifier-service/policies/overview.md) - **vc_policies** - Policies applied to all requested credentials. A list of policies can be found [here](https://docs.walt.id/enterprise-stack/services/verifier-service/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/enterprise-stack/services/verifier-service/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 `is_holder` verifies that the credential was issued to the entity presenting it. The fields you select must therefore originate from credentials bound 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 This constraint makes sure multiple credentials talk about the same subject. It compares the given fields across descriptors and requires all of them to reference 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, 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. ## 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=waltid.tenant1.verifier1.fb94470b-bcee-4ed7-93f5-906a4f5f585d... ``` Making the call to receive the verification result ```bash curl -X 'GET' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/verifier-service-api/credentials/sessions/view' \ -H 'accept: */*' -H 'Authorization: Bearer {yourToken}' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + verifier service + sessionID to request (`{organizationID}.{tenantID}.{verifierServiceID}.{sessionID}`), e.g. `waltid.tenant1.verifier1.fb94470b-bcee-4ed7-93f5-906a4f5f585d` ## Verification Status Policies Response The response of the verification status call will contain the status of the verification policies applied to the credential(s) presented by the user. The policy results will be in the following format: ```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. -------------------------------------------------------------------------------- PAGE: Verifying SD-JWT VC Credentials via OID4VC URL: https://docs.walt.id/enterprise-stack/services/verifier-service/credential-verification/sd-jwt-vc-oid4vc RAW: https://docs.walt.id/enterprise-stack/services/verifier-service/credential-verification/sd-jwt-vc-oid4vc.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Verifying SD-JWT VC credentials via OID4VC 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 Enterprise 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. ## Enterprise Service Dependencies To verify credentials, you need to have the following enterprise services setup: 1. [Verifier Service](https://docs.walt.id/enterprise-stack/services/verifier-service/overview.md) - Have a running verifier service. Setup instructions can be found [here](https://docs.walt.id/enterprise-stack/services/verifier-service/setup.md). ## Preparing the Verification First, determine the type of SD-JWT VC Credential to be verified. Although this guide focuses on a "VerifiableDiploma," 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' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/verifier-service-api/credentials/verify' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'authorizeBaseUrl: openid4vp://authorize' \ -H 'responseMode: direct_post' \ -H 'successRedirectUri: https://example.com/success?id=$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/VerifiableDiploma", "format": "vc+sd-jwt" } ] }' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + verifier service with which to execute the credential verification (`{organizationID}.{tenantID}.{verifierServiceID}`), e.g. `waltid.tenant1.verifier1` **Header Parameters** - **authorizeBaseUrl** - is used to modify the start of the OID4VC request URL. 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/enterprise-stack/services/verifier-service/policies/overview.md) - **vc_policies** - Policies applied to all requested credentials. A list of policies can be found [here](https://docs.walt.id/enterprise-stack/services/verifier-service/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. - **expires_in_seconds** - (Optional) _Integer_ - The number of seconds the generated URL will be valid for. If not specified, the default will never expire.
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/enterprise-stack/services/verifier-service/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're not only specifying the types of credentials to verify, but also defining specific policies that should be executed upon their verification. These include Verifiable Credential (VC) policies applied to credentials, or Verifiable Presentation (VP) policies applied to the VP. The default policy applied for both VP and VC(s) is the signature policy. A list of available policies can be found [here](https://docs.walt.id/enterprise-stack/services/verifier-service/policies/overview.md). It's important to note that VC policies, once defined, are applied globally - that is, they are enacted on all requested credentials within the scope of the verification. In a subsequent section, we'll delve into how to customize and apply specific policies to individual credentials. We will now specify these in the verification request alongside the `request_credentials` parameter (previously explained) using `vc_policies` and `vp_policies`. These represent lists of policies - either one, or both, can be provided. The structure for both is identical. VC and VP policies are formatted as lists: the policy can be represented as a string if no arguments are required, or as an object if arguments are needed. **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' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/verifier-service-api/credentials/verify' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'authorizeBaseUrl: openid4vp://authorize' \ -H 'responseMode: direct_post' \ -H 'successRedirectUri: https://example.com/success?id=$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/VerifiableDiploma", "format": "vc+sd-jwt" } ] }' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + verifier service with which to execute the credential verification (`{organizationID}.{tenantID}.{verifierServiceID}`), e.g. `waltid.tenant1.verifier1` **Header Parameters** - **authorizeBaseUrl** - is used to modify the start of the OID4VC request URL. If you are using the cross-device flow, where you will display the URL as a QR code, you can leave the value as `openid4vp://authorize` or if you don't know the wallet the user will be using to claim the credential. If you are using the same device flow, where you already know the user's wallet and want the user to be able to go directly to it, you can use the wallet URL path that is able to receive an OIDC request as a query parameter. Our wallet for example can receive OID4VC requests 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/enterprise-stack/services/verifier-service/policies/overview.md) - **vc_policies** - Policies applied to all requested credentials. A list of policies can be found [here](https://docs.walt.id/enterprise-stack/services/verifier-service/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/enterprise-stack/services/verifier-service/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 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/enterprise-stack/services/verifier-service/policies/overview.md). Here's how it looks in practice: ```json { "vp_policies": [ "signature", "expired" ], "vc_policies": [ "signature", "expired" ], "request_credentials": [ { "vct": "https://issuer.demo.walt.id/VerifiableId", "format": "vc+sd-jwt" }, { "vct": "https://issuer.demo.walt.id/ProofOfResidence", "format": "vc+sd-jwt" }, { "vct": "https://issuer.demo.walt.id/OpenBadgeCredential", "format": "vc+sd-jwt" "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/enterprise-stack/services/verifier-service/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' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/verifier-service-api/credentials/verify' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -H 'authorizeBaseUrl: openid4vp://authorize' \ -H 'responseMode: direct_post' \ -H 'successRedirectUri: https://example.com/success?id=$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": "https://issuer.demo.walt.id/VerifiableId", "format": "vc+sd-jwt" }, { "vct": "https://issuer.demo.walt.id/ProofOfResidence", "format": "vc+sd-jwt" }, { "vct": "https://issuer.demo.walt.id/OpenBadgeCredential", "format": "vc+sd-jwt" "policies": [ "signature", { "policy": "webhook", "args": "https://example.org/abc/xyz" } ] } ] }' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + verifier service with which to execute the credential verification (`{organizationID}.{tenantID}.{verifierServiceID}`), e.g. `waltid.tenant1.verifier1` **Header Parameters** - **authorizeBaseUrl** - is used to modify the start of the OID4VC request URL. If you are using the cross-device flow, where you will display the URL as a QR code, you can leave the value as `openid4vp://authorize` or if you don't know the wallet the user will be using to claim the credential. If you are using the same device flow, where you already know the user's wallet and want the user to be able to go directly to it, you can use the wallet URL path that is able to receive an OIDC request as a query parameter. Our wallet for example can receive OID4VC requests 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/enterprise-stack/services/verifier-service/policies/overview.md) - **vc_policies** - Policies applied to all requested credentials. A list of policies can be found [here](https://docs.walt.id/enterprise-stack/services/verifier-service/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/enterprise-stack/services/verifier-service/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'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": [ { "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 The `is_holder` constraint ensures that the presenter is the rightful holder of the credential. Only credentials issued to the presenter will satisfy the selected field references. ```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 Apply `same_subject` to confirm that multiple credentials describe the same entity. The verifier cross-checks the provided field IDs and requires their subjects to be identical. ```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. ## 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=waltid.tenant1.verifier1.fb94470b-bcee-4ed7-93f5-906a4f5f585d... ``` Making the call to receive the verification result ```bash curl -X 'GET' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/verifier-service-api/credentials/sessions/view' \ -H 'accept: */*' -H 'Authorization: Bearer {yourToken}' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + verifier service + sessionID to request (`{organizationID}.{tenantID}.{verifierServiceID}.{sessionID}`), e.g. `waltid.tenant1.verifier1.fb94470b-bcee-4ed7-93f5-906a4f5f585d` ## Verification Status Policies Response The response of the verification status call will contain the status of the verification policies applied to the credential(s) presented by the user. The policy results will be in the following format: ```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. -------------------------------------------------------------------------------- PAGE: Verify Mobile Driver's Licenses (ISO/IEC 18013-7 mDL) URL: https://docs.walt.id/enterprise-stack/services/verifier-service/credential-verification/mdl-oid4vc RAW: https://docs.walt.id/enterprise-stack/services/verifier-service/credential-verification/mdl-oid4vc.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Verify Mobile Driver's Licenses (ISO/IEC 18013-7 mDL) via OID4VP 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 Enterprise Verifier API. The verification process will utilize the OID4VP protocol. ## Enterprise Service Dependencies To verify credentials, you need to have the following enterprise services setup: 1. [Verifier Service](https://docs.walt.id/enterprise-stack/services/verifier-service/overview.md) - Have a running verifier service. Setup instructions can be found [here](https://docs.walt.id/enterprise-stack/services/verifier-service/setup.md). ## 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** **Endpoint:** `/v1/{target}/verifier-service-api/credentials/verify` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Verifier%20Service/post_v1__target__verifier_service_api_credentials_verify) **Example Request** ```bash curl -X 'POST' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/verifier-service-api/credentials/verify' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' \ -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", "x509SanDnsConfig": { "jarSigningKeyId": "waltid.tenant1.verifier1-kms.iso-mdl-reader-jar-signingkey", "x5Chain": [ "-----BEGIN CERTIFICATE-----\nMIIB1jCCAXygAwIBAgIUVWGgqDn/DectsPZgdM+MGZD4rlAwCgYIKoZIzj0EAwIwJjEkMCIGA1UEAwwbVGVzdCBTZWxmLVNpZ25lZCBtREwgUmVhZGVyMB4XDTI1MDkwOTA3MDgyMVoXDTQ1MDkwNDA3MDkyMVowJjEkMCIGA1UEAwwbVGVzdCBTZWxmLVNpZ25lZCBtREwgUmVhZGVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4olVtT1WWw/keXg3Bu2cIvQiBTuzuIoSo3OsJXOjQc4SznsMmVFc/b4WTuXu/uBFztn+2Op0k7MNflbAxIyVo6OBhzCBhDAPBgNVHRMBAf8EBTADAQH/MDEGA1UdEQQqMCiCG3dhbHRpZC5lbnRlcnByaXNlLmxvY2FsaG9zdIIJbG9jYWxob3N0MB0GA1UdDgQWBBS9SQQ+meofY5U4e3+nZteQeposUDAfBgNVHSMEGDAWgBS9SQQ+meofY5U4e3+nZteQeposUDAKBggqhkjOPQQDAgNIADBFAiEAp3Mlv8Xoj2aDS3rh7FyzfXRb6Oam3cLwzLznLVMyhxICIFGmhg928AE+wFXZjo+PC7GI/B/nJ3KKRWCkMLSSY3IC+R5eDcG7Zwi9CIFO7O4ihKjc6wlc6NBzhLOewyZUVz9vhZO5e7+4EXO2f7Y6nSTsw1+VsDEjJWjoxMwETAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0kAMEYCIQCR/jD5iEjY/9jZd8VStys0y4MPOKA4LIyXlePJjKFOGQIhAOrf28yStk8Evz4TOzzKgq41kX3uO8q/X75JdQ86ZvH+\n-----END CERTIFICATE-----\n" ] } }' ``` **Path Parameters** - `orgID`: Your organization ID (e.g., `test` for `test.enterprise-sandbox.waltid.dev`) - `target`: The verifier service target path (e.g., `waltid.verifier1`) **Header Parameters** - **Authorization**: Bearer token authentication is required for enterprise stack - **authorizeBaseUrl**: Should be set to `mdoc-openid4vp://` for mDL verification - **responseMode**: Must be set to `direct_post.jwt` for mDL verification - **successRedirectUri** (optional): 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): 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. - **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** ```json { "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", "x509SanDnsConfig": { "jarSigningKeyId": "waltid.tenant1.verifier1-kms.iso-mdl-reader-jar-signingkey", "x5Chain": [ "-----BEGIN CERTIFICATE-----\nMIIB1jCCAXygAwIBAgIUVWGgqDn/DectsPZgdM+MGZD4rlAwCgYIKoZIzj0EAwIwJjEkMCIGA1UEAwwbVGVzdCBTZWxmLVNpZ25lZCBtREwgUmVhZGVyMB4XDTI1MDkwOTA3MDgyMVoXDTQ1MDkwNDA3MDkyMVowJjEkMCIGA1UEAwwbVGVzdCBTZWxmLVNpZ25lZCBtREwgUmVhZGVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4olVtT1WWw/keXg3Bu2cIvQiBTuzuIoSo3OsJXOjQc4SznsMmVFc/b4WTuXu/uBFztn+2Op0k7MNflbAxIyVo6OBhzCBhDAPBgNVHRMBAf8EBTADAQH/MDEGA1UdEQQqMCiCG3dhbHRpZC5lbnRlcnByaXNlLmxvY2FsaG9zdIIJbG9jYWxob3N0MB0GA1UdDgQWBBS9SQQ+meofY5U4e3+nZteQeposUDAfBgNVHSMEGDAWgBS9SQQ+meofY5U4e3+nZteQeposUDAKBggqhkjOPQQDAgNIADBFAiEAp3Mlv8Xoj2aDS3rh7FyzfXRb6Oam3cLwzLznLVMyhxICIFGmhg928AE+wFXZjo+PC7GI/B/nJ3KKRWCkMLSSY3IC+R5eDcG7Zwi9CIFO7O4ihKjc6wlc6NBzhLOewyZUVz9vhZO5e7+4EXO2f7Y6nSTsw1+VsDEjJWjoxMwETAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0kAMEYCIQCR/jD5iEjY/9jZd8VStys0y4MPOKA4LIyXlePJjKFOGQIhAOrf28yStk8Evz4TOzzKgq41kX3uO8q/X75JdQ86ZvH+\n-----END CERTIFICATE-----\n" ] } } ``` **Body Parameters** - **request_credentials**: An array of objects detailing the credentials to be requested from the user. For mDL credentials: - **id**: A unique identifier for the credential request - **input_descriptor**: Contains the credential specification - **id**: Must be `"org.iso.18013.5.1.mDL"` for mDL credentials - **format**: Must be `"mso_mdoc"` for mDL credentials - **constraints**: Defines which fields to request and validation rules - **fields**: Array of field specifications with paths to the requested data - **limit_disclosure**: Set to `"required"` for mDL verification - **trusted_root_cas**: List of trusted IACA root certificates used to verify the authenticity of the presented mDL credential. These are PEM-encoded X.509 certificates. - **openid_profile**: Must be set to `"ISO_18013_7_MDOC"` for mDL verification. - **x509SanDnsConfig**: This object is required for mDL verification and contains parameters that are related with the `x509_san_dns` client identifier scheme, such as: - **jarSigningKeyId**: The identifier (full target path) of the KMS key that will be used to sign the JWT-secured Authorization Request (JAR). - **x5Chain**: The X509 certificate chain (provided as a list of PEM-encoded certificates) of the verifier's trust chain that will be used to populate the `x5c` JWS header of the JAR. The public key of the first (leaf) certificate of the chain must match the public key that corresponds to the private key that will be used to sign the JAR. --- **Example Response** The verification endpoint will return a presentation session with a URL that can be used to complete the verification process: ``` mdoc-openid4vp://authorize?client_id=waltid.verifier1&request_uri=https://test.enterprise-sandbox.waltid.dev/v1/waltid.verifier1/verifier-service-api/openid4vc/request&response_type=vp_token&response_mode=direct_post.jwt&scope=openid&state=1234567890 ``` ## 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. ## 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** **Option: CURL** ``` mdoc-openid4vp://authorize...state=waltid.tenant1.verifier1.fb94470b-bcee-4ed7-93f5-906a4f5f585d... ``` Making the call to receive the verification result ```bash curl -X 'GET' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/verifier-service-api/credentials/sessions/view' \ -H 'accept: */*' -H 'Authorization: Bearer {yourToken}' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + verifier service + sessionID to request (`{organizationID}.{tenantID}.{verifierServiceID}.{sessionID}`), e.g. `waltid.tenant1.verifier1.fb94470b-bcee-4ed7-93f5-906a4f5f585d` -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/services/verifier-service/policies/overview RAW: https://docs.walt.id/enterprise-stack/services/verifier-service/policies/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Policies For verification of verifiable credentials, the walt.id Enterprise 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 Enterprise verifier api offers the following verification policies: - [Static Verification Policies](https://docs.walt.id/enterprise-stack/services/verifier-service/policies/static-verification-policies.md) - [Parameterized Verification Policies](https://docs.walt.id/enterprise-stack/services/verifier-service/policies/parameterized-verification-policies.md) - [Dynamic Verification Policies](https://docs.walt.id/enterprise-stack/services/verifier-service/policies/dynamic-verification-policies.md) -------------------------------------------------------------------------------- PAGE: Static Verification Policies URL: https://docs.walt.id/enterprise-stack/services/verifier-service/policies/static-verification-policies RAW: https://docs.walt.id/enterprise-stack/services/verifier-service/policies/static-verification-policies.md STACK: Enterprise Stack (commercial) — 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 based on Verifiable Credentials Status List v2021 standard. ### 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/enterprise-stack/services/verifier-service/policies/parameterized-verification-policies RAW: https://docs.walt.id/enterprise-stack/services/verifier-service/policies/parameterized-verification-policies.md STACK: Enterprise Stack (commercial) — 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 - draft version 08](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-status-list-08) (JWT format only) **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) - type - the status credential standard (only for W3C credentials) - BitstringStatusList - StatusList2021 - RevocationList2020 - discriminator - the payload type, used in parsing - w3c - for BitstringStatusList, StatusList2021, RevocationList2020 - w3c-list - for a list of W3C payloads - 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/enterprise-stack/services/verifier-service/policies/dynamic-verification-policies RAW: https://docs.walt.id/enterprise-stack/services/verifier-service/policies/dynamic-verification-policies.md STACK: Enterprise Stack (commercial) — 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/enterprise-stack/services/verifier-service/configurations/overview RAW: https://docs.walt.id/enterprise-stack/services/verifier-service/configurations/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview The verifier, similar to other services in the Enterprise Stack, maintains a collection of configurations. These configurations include **internal configs such as the base URL**, which are used solely within the Enterprise Stack. Additionally, the verifier holds configurations that is both internally used and **externally shared, such as verifier display information**. This external information is made available through the **OpenID configuration endpoint**, which can be accessed by wallets and other external applications involved in the credential exchange process. - [OpenID Metadata](https://docs.walt.id/enterprise-stack/services/verifier-service/configurations/openid-metadata.md) - Create, update, and delete openid metadata verifier information. -------------------------------------------------------------------------------- PAGE: OpenID Metadata URL: https://docs.walt.id/enterprise-stack/services/verifier-service/configurations/openid-metadata RAW: https://docs.walt.id/enterprise-stack/services/verifier-service/configurations/openid-metadata.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # OpenID Metadata Via the openid-metadata config, the verifier service can be configured to expose details such as verifier name and logo in a standardized way with other services such as digital wallets. These services can then use this information to display a human-readable verifier name and logo to the end-user during credential exchange. Learn more about the `name` and `logo` info exposure in the OAuth 2.0 standard [here](https://www.rfc-editor.org/rfc/rfc7591.html#section-2:~:text=code%22%20response%20type.-,client_name%0A%20%20%20%20%20%20Human%2Dreadable%20string%20name%20of%20the%20client%20to%20be%20presented,this%20field%20MAY%20be%20internationalized%2C%20as%20described%20in%0A%20%20%20%20%20%20Section%202.2.,-scope%0A%20%20%20%20%20%20String%20containing). The configured info will be exposed via the public endpoint `/.well-known/openid-configuration`. ## Get OpenID Metadata **Option: CURL** Endpoint: `/v1/{target}/verifier-service-api/configuration/openid-metadata/view` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Verifier/get_v1__target__verifier_service_api_configuration_openid_metadata_view) **Example Request** ```bash curl -X 'GET' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/verifier-service-api/configuration/openid-metadata/view' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + verifier service from which to receive the configuration(`{organizationID}.{tenantID}.{verifierServiceID}`), e.g. `waltid.tenant1.verifier1` **Example Response** - `200` - metadata retrieved successfully. ```json { "name": "walt.id Enterprise Verifier", "logoUri": "http://cdn.walt.id/verifier/logo.png" } ``` - `name` _String_: Human-readable string name of the verifier. - `logoUri` _String_: URL string that references a logo for the verifier. **Note:** If no metadata is setup the body will be empty. - `401` - Invalid authentication ## Update OpenID Metadata **Option: CURL** Endpoint: `/v1/{target}/verifier-service-api/configuration/openid-metadata/update` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Verifier/get_v1__target__verifier_service_api_configuration_openid_metadata_view) **Example Request** ```bash curl -X 'PUT' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/verifier-service-api/configuration/openid-metadata/update' \ -H 'Authorization: Bearer {yourToken}' \ -H 'Content-Type: application/json' \ -d '{ "name": "walt.id Enterprise Verifier", "logoUri": "http://cdn.walt.id/verifier/logo.png" }' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + verifier service in which to update the configuration(`{organizationID}.{tenantID}.{verifierServiceID}`), e.g. `waltid.tenant1.verifier1` **Body** ```json { "name": "walt.id Enterprise Verifier", "logoUri": "http://cdn.walt.id/verifier/logo.png" } ``` **Body Parameters** - `name` (optional) _String_: Human-readable string name of the verifier. - `logoUri` (optional) _String_: URL string that references a logo for the verifier. --- **Example Response** - `200` - metadata updated successfully. ```json { "name": "walt.id Enterprise Verifier", "logoUri": "http://cdn.walt.id/verifier/logo.png" } ``` - `401` - Invalid authentication ## Delete OpenID Metadata **Option: CURL** Endpoint: `/v1/{target}/verifier-service-api/configuration/openid-metadata/delete` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Verifier/delete_v1__target__verifier_service_api_configuration_openid_metadata_delete) **Example Request** ```bash curl -X 'DELETE' \ 'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/verifier-service-api/configuration/openid-metadata/delete' \ -H 'accept: */*' \ -H 'Authorization: Bearer {yourToken}' ``` **Path Parameters** - `orgID`: - When performing operations within an organization, it is essential to use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL will be `test.enterprise-sandbox.walt.dev` when using the sandbox environment. - `target`: _resourceIdentifier_ - The target indicates the organization + tenant + verifier service in which to delete the configuration(`{organizationID}.{tenantID}.{verifierServiceID}`), e.g. `waltid.tenant1.verifier1` --- **Example Response** - `200` - metadata deleted successfully. - `401` - Invalid authentication -------------------------------------------------------------------------------- PAGE: Full API Reference URL: https://docs.walt.id/enterprise-stack/services/verifier-service/full-api-reference RAW: https://docs.walt.id/enterprise-stack/services/verifier-service/full-api-reference.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Full API Reference Find it [here](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Verifier) -------------------------------------------------------------------------------- PAGE: How to Issue and Verify PIDs with the EUDI Wallet (eIDAS2) URL: https://docs.walt.id/enterprise-stack/ecosystems/eudi-demo RAW: https://docs.walt.id/enterprise-stack/ecosystems/eudi-demo.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # How to Issue and Verify PIDs with the EUDI Wallet (eIDAS2) This comprehensive guide walks you through the complete process of issuing and verifying Person Identification Data (PID) credentials using the EU Digital Identity Reference Wallet (EUDI Wallet) under eIDAS2 with the walt.id Enterprise Stack. ## Overview The integration involves three main components: - **walt.id Enterprise Stack** - Provides the issuer2 and verifier2 services - **EUDI Reference Wallet** - The mobile wallet application for storing and presenting credentials - **Certificate Infrastructure** - X.509 certificates for trust establishment ## Prerequisites - Docker and Docker Compose - OpenSSL (for certificate generation) - Android Studio (if building the EUDI wallet from source) - An Android device or emulator with Android 8.0+ - ngrok or similar tunneling service (for local development) --- ## Part 1: Setting Up Certificates The EUDI wallet requires a proper X.509 certificate chain for trust verification. You'll need to generate certificates for both issuance and verification. ### 1.1 Generate Root Certificate Create the root CA key and self-signed certificate: ```bash # Generate root key openssl ecparam -name prime256v1 -genkey -noout -out root-key.pem # Generate self-signed root certificate (10 year validity) openssl req -new -x509 -key root-key.pem -out root-cert.pem -days 3650 -subj "/CN=CustomRoot" ``` ### 1.2 Generate Intermediate Certificate Create the intermediate CA: ```bash # Generate intermediate key openssl ecparam -name prime256v1 -genkey -noout -out intermediate-key.pem # Create CSR for intermediate CA openssl req -new -key intermediate-key.pem -out intermediate.csr -subj "/CN=CustomIntermediate" # Create extensions file for intermediate CA echo "basicConstraints=critical,CA:TRUE" > intermediate.cnf echo "keyUsage=critical,keyCertSign,cRLSign" >> intermediate.cnf # Sign intermediate certificate with root openssl x509 -req -in intermediate.csr -CA root-cert.pem -CAkey root-key.pem \ -CAcreateserial -out intermediate-cert.pem -days 365 -extfile intermediate.cnf # Verify chain openssl verify -CAfile root-cert.pem intermediate-cert.pem ``` ### 1.3 Generate Issuer Leaf Certificate Create the issuer's signing certificate: ```bash # Generate issuer key openssl ecparam -name prime256v1 -genkey -noout -out issuer-key.pem # Create CSR openssl req -new -key issuer-key.pem -out issuer.csr -subj "/CN=Issuer" # Create extensions file echo "basicConstraints=critical,CA:FALSE" > issuer-leaf.cnf echo "keyUsage=critical,digitalSignature" >> issuer-leaf.cnf # Sign issuer certificate openssl x509 -req -in issuer.csr -CA intermediate-cert.pem -CAkey intermediate-key.pem \ -CAcreateserial -out issuer-cert.pem -days 365 -extfile issuer-leaf.cnf ``` ### 1.4 Generate Verifier Leaf Certificate Create the verifier's signing certificate: ```bash # Generate verifier key openssl ecparam -name prime256v1 -genkey -noout -out verifier-key.pem # Create CSR openssl req -new -key verifier-key.pem -out verifier.csr -subj "/CN=Verifier" # Create extensions file echo "basicConstraints=critical,CA:FALSE" > verifier-leaf.cnf echo "keyUsage=critical,digitalSignature" >> verifier-leaf.cnf echo "subjectAltName=DNS:example.com" >> verifier-leaf.cnf # Sign verifier certificate openssl x509 -req -in verifier.csr -CA intermediate-cert.pem -CAkey intermediate-key.pem \ -CAcreateserial -out verifier-cert.pem -days 30 -extfile verifier-leaf.cnf # Convert to DER format (needed for x509_hash calculation) openssl x509 -in verifier-cert.pem -outform DER -out verifier-cert.der ``` ### 1.5 Convert Keys to JWK Format For both the issuer and verifier configurations, you'll need the private key in JWK format. Use a PEM to JWK conversion tool: ```bash # Using a conversion tool (example with node.js jose library or similar) # The resulting JWK should look like: { "kty": "EC", "crv": "P-256", "x": "...", "y": "...", "d": "..." } ``` You can then store these keys in the KMS service to reference later on, or use them inline in the configuration. ### 1.6 Calculate x509_hash for Verifier The EUDI wallet uses the `x509_hash` client ID scheme. Calculate it as follows: ```bash # Print SHA256 digest in Base64URL format openssl dgst -sha256 -binary verifier-cert.der \ | openssl base64 -A \ | tr '+/' '-_' \ | tr -d '=' ``` This will output something like: `QZ_TUhNLzEVo7kFDWNG42hdOk0Z40DPXn57r6y9ku54` ### 1.7 Extract x5c Chain for Configuration Extract the certificate chain in base64 format for use in configurations: ```bash # Extract base64 content from PEM files sed -e '1{/^-----BEGIN /d;}' -e '${/^-----END /d;}' verifier-cert.pem | tr -d ' ' sed -e '1{/^-----BEGIN /d;}' -e '${/^-----END /d;}' intermediate-cert.pem | tr -d ' ' sed -e '1{/^-----BEGIN /d;}' -e '${/^-----END /d;}' root-cert.pem | tr -d ' ' ``` ### 2.3 Set Up ngrok Tunnel (for Local Development) If running locally, set up an ngrok tunnel to make your issuer accessible to the mobile wallet: ```bash ngrok http 3000 # Or whatever port the enterprise stack is running on ``` Add a host alias to the enterprise stack. See more here: [Host Alias Service](https://docs.walt.id/enterprise-stack/setup/local-development.md#using-host-aliases) Update your issuer's `baseUrl` to the ngrok URL (e.g., `https://your-subdomain.ngrok-free.app`). Utilise the host for the url_host parameter when creating the verifier2 service or creating new verification sessions. ```json { "url_config": { "url_prefix": "https://your-custom-domain.com/v1/{target}/verifier2-service-api", "url_host": "haip-vp://authorize" }, } ``` --- ## Part 2: Setting Up the Issuer2 Instance ### 2.1 Configure Issuer Metadata Create an issuer2 instance configuration with support for the EUDI PID format. Here's an example metadata configuration: Please find more information regarding the issuer configuration [here](https://docs.walt.id/enterprise-stack/services/issuer2-service/setup.md). ```json { "traversable": true, "_id": "waltid.tenant.issuer22", "supportedCredentialTypes": { "eu.europa.ec.eudi.pid.1": { "format": "mso_mdoc", "scope": "eu.europa.ec.eudi.pid.1", "cryptographic_binding_methods_supported": ["jwk", "cose_key"], "credential_signing_alg_values_supported": [-7, -9], "proof_types_supported": { "jwt": { "proof_signing_alg_values_supported": ["ES256"] } }, "credential_metadata": { "display": [ { "name": "PID (MSO MDoc)", "locale": "en" } ], "claims": [ { "path": ["eu.europa.ec.eudi.pid.1", "family_name"], "mandatory": true, "display": [{ "name": "Family Name(s)", "locale": "en" }] }, { "path": ["eu.europa.ec.eudi.pid.1", "given_name"], "mandatory": true, "display": [{ "name": "Given Name(s)", "locale": "en" }] }, { "path": ["eu.europa.ec.eudi.pid.1", "birth_date"], "mandatory": true, "display": [{ "name": "Birth Date", "locale": "en" }] }, { "path": ["eu.europa.ec.eudi.pid.1", "nationality"], "mandatory": true, "display": [{ "name": "Nationality", "locale": "en" }] }, { "path": ["eu.europa.ec.eudi.pid.1", "expiry_date"], "mandatory": true, "display": [{ "name": "Expiry Date", "locale": "en" }] }, { "path": ["eu.europa.ec.eudi.pid.1", "issuing_authority"], "mandatory": true, "display": [{ "name": "Issuance Authority", "locale": "en" }] }, { "path": ["eu.europa.ec.eudi.pid.1", "issuing_country"], "mandatory": true, "display": [{ "name": "Issuing Country", "locale": "en" }] } ] }, "doctype": "eu.europa.ec.eudi.pid.1" } }, "tokenKeyId": "your-token-key-id", "kms": "waltid.tenant.kms", "baseUrl": "https://your-custom-domain.com", "authProviderConfiguration": { "name": "Keycloak", "authorizeUrl": "https://your-keycloak-domain.com/realms/your-realm/protocol/openid-connect/auth", "accessTokenUrl": "https://your-keycloak-domain.com/realms/your-realm/protocol/openid-connect/token", "clientId": "issuer_api", "clientSecret": "--", "defaultScopes": [ "openid", "profile" ] }, } ``` ### 2.2 Configure Issuance Defaults For the wallet-initiated flow, configure default issuance settings: ```json { // Follow the same configuration as above... "traversable": true, "_id": "waltid.tenant.issuer22.config1", "supportedCredentialTypes": { ... }, "tokenKeyId": "your-token-key-id", "kms": "waltid.tenant.kms", "baseUrl": "https://your-custom-domain.com", "authProviderConfiguration": { ... }, // With these additional fields: "issuanceConfigurationDefaults": { "eu.europa.ec.eudi.pid.1": { "issuerKeyId": "your-issuer-key-id", "x5Chain": [ "-----BEGIN CERTIFICATE----- ...intermediate cert... -----END CERTIFICATE----- " ], "idTokenClaimsToCredentialDataJsonPathMappingConfig": { "$.family_name": "$.['eu.europa.ec.eudi.pid.1'].family_name", "$.given_name": "$.['eu.europa.ec.eudi.pid.1'].given_name" },§ "mDocNameSpacesDataMappingConfig": { "eu.europa.ec.eudi.pid.1": { "entriesConfigMap": { "birth_date": { "type": "string", "conversionType": "stringToFullDate" }, "issue_date": { "type": "string", "conversionType": "stringToFullDate" }, "expiry_date": { "type": "string", "conversionType": "stringToFullDate" } } } }, "staticCredentialData": { "eu.europa.ec.eudi.pid.1": { "family_name": "PLACEHOLDER", "given_name": "PLACEHOLDER", "nationality": "AT", "birth_date": "1986-03-22", "issue_date": "2024-01-01", "expiry_date": "2029-01-01" } }, "authenticationMethod": "ID_TOKEN" } } } ``` --- ## Part 3: Configuring the EUDI Wallet ### Option A: Using Pre-built APKs **info:** **Coming Soon**: Pre-built APKs with walt.id integration will be available for download. These APKs will come pre-configured with the walt.id trust certificates. ### Option B: Building from Source #### 3.1 Clone the EUDI Wallet Repository ```bash git clone https://github.com/eu-digital-identity-wallet/eudi-app-android-wallet-ui cd eudi-app-android-wallet-ui ``` #### 3.2 Add Root Certificate to Trust Store Copy your root certificate to the resources folder: ```bash cp root-cert.pem resources-logic/src/main/res/raw/waltid_root_cert.pem ``` #### 3.3 Update WalletCoreConfigImpl.kt Edit the file at `core-logic/src/demo/java/eu/europa/ec/corelogic/config/WalletCoreConfigImpl.kt`: Add your certificate to the trust store configuration: ```kotlin configureReaderTrustStore( context, R.raw.pidissuerca02_cz, R.raw.pidissuerca02_ee, R.raw.pidissuerca02_eu, // ... other existing certs ... R.raw.waltid_root_cert // Add this line ) ``` #### 3.4 Configure the VCI Issuer URL (Optional) This is only required if you want to use the wallet-initiated flow. Update the `vciConfig` in the same file to include your issuer: ```kotlin override val vciConfig: List get() = listOf( OpenId4VciManager.Config.Builder() .withIssuerUrl(issuerUrl = "https://your-custom-domain.com/v1/tenant/issuer-service-api2/openid4vc/v1") .withClientAuthenticationType(OpenId4VciManager.ClientAuthenticationType.AttestationBased) .withAuthFlowRedirectionURI(BuildConfig.ISSUE_AUTHORIZATION_DEEPLINK) .withParUsage(OpenId4VciManager.Config.ParUsage.IF_SUPPORTED) .withDPoPUsage(OpenId4VciManager.Config.DPoPUsage.IfSupported()) .build(), // ... other issuers ... ) ``` #### 3.5 Build and Install Open the project in Android Studio and build the `demo` flavor. Install the resulting APK on your device. --- ## Part 4: Issuing PIDs ### 4.1 Issuer-Initiated Flow This is the simpler flow where you generate a credential offer on the server side. **Step 1**: Create an issuance session via the issuer2 API: Please find more information regarding issuance requests [here](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-issuance/sd-jwt-vc.md). ```bash curl -X POST "https://your-custom-domain.com/v1/tenant/issuer-service-api2/issue" \ -H "Content-Type: application/json" \ -d '{ "credentialConfigurationId": "eu.europa.ec.eudi.pid.1", "credentialData": { "eu.europa.ec.eudi.pid.1": { "family_name": "Doe", "given_name": "John", "birth_date": "1990-01-15", "nationality": "DE", "expiry_date": "2029-01-15", "issuing_authority": "Test Authority", "issuing_country": "DE" } } }' ``` **Step 2**: The response contains an `offerUrl`. Display this as a QR code. **Step 3**: Open the EUDI wallet and scan the QR code to receive the credential. ### 4.2 Wallet-Initiated Flow In this flow, the user initiates issuance from within the wallet. **Step 1**: Ensure your issuer is configured in the wallet's `vciConfig` (see Section 3.4). **Step 2**: In the EUDI wallet app: 1. Tap "Add Document" 2. Select your issuer from the list 3. Choose "PID (MSO MDoc)" 4. Complete any required authentication (if configured with OAuth/OIDC) 5. The credential will be issued and stored in the wallet --- ## Part 5: Verifying PIDs ### 5.1 Configure the Verifier2 Instance Create a verification session configuration: Please find more information regarding the verifier configuration [here](https://docs.walt.id/enterprise-stack/services/verifier2-service/setup.md). You can also find more information regarding the verification session creation [here](../services/verifier2-service/credential-verification/mdl-oid4vc). The example below is used to create a verification session which requests the birth date claim from the PID credential. ```json curl -X POST "https://your-custom-domain.com/v1/{TARGET}/verifier2-service-api/verification-session/create" \ -H "accept: */*" \ -H "Authorization: Bearer {yourToken}" \ -H "Content-Type: application/json" \ -d '{ "flow_type": "cross_device", "url_config": { "url_prefix": "https://your-custom-domain.com/v1/{TARGET}/verifier2-service-api", "url_host": "haip-vp://authorize" }, "core_flow": { "dcql_query": { "credentials": [ { "id": "pid", "format": "mso_mdoc", "meta": { "doctype_value": "eu.europa.ec.eudi.pid.1" }, "claims": [ { "path": ["eu.europa.ec.eudi.pid.1", "birth_date"], "intent_to_retain": false } ] } ] }, "signed_request": true, "clientId": "x509_hash:YOUR_X509_HASH_HERE", "key": { "type": "jwk", "jwk": { "kty": "EC", "crv": "P-256", "x": "YOUR_X_VALUE", "y": "YOUR_Y_VALUE", "d": "YOUR_D_VALUE" } }, "x5c": [ "BASE64_VERIFIER_CERT", "BASE64_INTERMEDIATE_CERT", "BASE64_ROOT_CERT" ] }, "redirects": { "success_redirect_uri": "https://example.com/verification-successful" } } ``` Replace the placeholder values: - `YOUR_X509_HASH_HERE` - The hash calculated in Section 1.6 - `YOUR_X_VALUE`, `YOUR_Y_VALUE`, `YOUR_D_VALUE` - Values from the verifier JWK - `BASE64_*_CERT` - Certificate chain from Section 1.7 - `{TARGET}` - The target of the verifier2 service you are using. It's important to note that in order to verify credentials from the EUDI wallet, you must use the HAIP profile, which is configured via the `url_config.url_host` parameter. As part of HAIP, you must also use signed requests, which is configured via the `signed_request` parameter, alongside providing the necessary material through the `x5c`, `clientId` and `key` parameters. --- ### 5.2 Display the QR Code The response includes a `bootstrapAuthorizationRequestUrl`. Generate a QR code from this URL: ```json { "bootstrapAuthorizationRequestUrl": "haip-vp://authorize?client_id=x509_hash%3A...&request_uri=https%3A%2F%2F..." } ``` You can use any QR code generator (e.g., [goqr.me](https://goqr.me/)) to create the QR code. ### 5.3 Scan with EUDI Wallet 1. Open the EUDI wallet 2. Tap the scan button 3. Scan the verification QR code 4. Review the requested data 5. Approve the presentation ### 5.4 Check Verification Results Query the session status: ```bash curl "https://your-custom-domain.com/v1/{TARGET}/verifier2-service-api/verification-session/{SESSION_ID}/info" ``` A successful verification returns: ```json { "id": "session-id", "status": "SUCCESSFUL", "policyResults": { "overallSuccess": true, "vp_policies": { "pid": { "mso_mdoc/device-auth": { "success": true }, "mso_mdoc/device_key_auth": { "success": true }, "mso_mdoc/issuer_auth": { "success": true }, "mso_mdoc/issuer_signed_integrity": { "success": true }, "mso_mdoc/mso": { "success": true } } } }, "presentedCredentials": { "pid": [{ "credentialData": { "docType": "eu.europa.ec.eudi.pid.1", "eu.europa.ec.eudi.pid.1": { "birth_date": "1990-01-15" } } }] } } ``` --- ## Troubleshooting ### Common Issues 1. **"Untrusted Issuer" Error** - Ensure the root certificate is properly added to the wallet's trust store - Verify the certificate chain is complete and valid 2. **"Invalid Request" on Verification** - Check that the `x509_hash` matches the verifier certificate - Verify the certificate chain order in the `x5c` array (leaf → intermediate → root) 3. **Wallet Can't Reach Issuer** - Ensure your ngrok tunnel is active (for local development) - Check that the baseUrl in the issuer configuration matches the accessible URL 4. **Certificate Validation Failed** - Verify certificates haven't expired - Check that the certificate chain is properly signed ### Debug Resources - **Demo Video**: [EUDI Wallet Issuance Demo](https://youtu.be/_agpNtzBQZE) - **EUDI Wallet Repository**: [GitHub](https://github.com/eu-digital-identity-wallet/eudi-app-android-wallet-ui) --- ## Summary This guide covered the complete flow for: 1. Generating the required X.509 certificate chain 2. Configuring the walt.id issuer2 instance for PID issuance 3. Modifying the EUDI wallet to trust your certificates and issuer 4. Issuing PIDs using both issuer-initiated and wallet-initiated flows 5. Configuring the verifier2 instance 6. Verifying PID credentials with proper policy checks -------------------------------------------------------------------------------- PAGE: Mutlipaz Wallet URL: https://docs.walt.id/enterprise-stack/ecosystems/mutlipaz-wallet RAW: https://docs.walt.id/enterprise-stack/ecosystems/mutlipaz-wallet.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Mutlipaz Wallet Create IACA and DSC Setup Issuer Import VICAL -------------------------------------------------------------------------------- PAGE: Overview URL: https://docs.walt.id/enterprise-stack/release-notes/overview RAW: https://docs.walt.id/enterprise-stack/release-notes/overview.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Overview This section contains the release notes for the Enterprise Stack. You can find our previous three versions under the releases directory, with an archive of older release notes for versions that are no longer supported. You can also find information about pre-releases that we do every so often to deliver new features and fixes ahead of time for particular customers. ## Breaking Changes We will always try to keep our breaking changes to a minimum, but sometimes we have to make changes that are not backwards compatible. You can find information about breaking changes in the release notes for the specific version you are upgrading from. We will always provide a migration guide for breaking changes, but in some cases we will also provide a migration script to help you with the migration. Generally any additional resources for helping with breaking changes can be found [here](https://github.com/walt-id/waltid-enterprise-quickstart) -------------------------------------------------------------------------------- PAGE: 0.18 URL: https://docs.walt.id/enterprise-stack/release-notes/releases/0.18.x RAW: https://docs.walt.id/enterprise-stack/release-notes/releases/0.18.x.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # 0.18 ## 0.18.2 ### Fixes & Improvements - Added a `auto-fix-enterprise-service-dependency` feature flag, which will automatically fix service dependencies when a service is called and produce a WARN log. This is to improve backwards compatibility for the breaking changes introduced in 0.18.0. We suggest updating your integrations to use the dependency system during this time for new services, as we will be removing this feature flag for 0.20.0 release. - Improved the use of MDC variables for logging. - Added some additional PUT methods for roles and api keys. ## 0.18.1 ### Fixes & Improvements - Fixed Resource Access crashing when no configuration file is present. - Moved Resource Access feature to be disabled by default. ## 0.18.0 ### Features **OIDC Integration & Role Mapping** The Enterprise Auth API now supports OIDC as an authentication flow, including external role mapping from identity providers (e.g., Keycloak) to tenant roles, deterministic fallback role assignment, and wallet-scope allow/deny validation. Session propagation and token mapping diagnostics have been improved throughout **Zero-Retention / Data Minimisation Mode** A configurable zero-retention mode has been introduced for PII data in issuance and verification sessions. When enabled, session data is not persisted beyond the immediate transaction, supporting GDPR and data minimisation requirements **TokenStatusList Draft 15 Upgrade** TokenStatusList support has been upgraded to IETF Draft 15, including Appendix C test vectors, zlib compression fixes, enforcement of application-specific status value ranges, and an end-to-end revocation test with the verifier **Extended Wallet Cryptographic Support** The wallet now supports ECDH-ES encryption with a broad range of symmetric algorithms (A128GCM, A192GCM, A256GCM, A128CBC-HS256, A192CBC-HS384, A256CBC-HS512) and asymmetric curves (secp256r1, secp384r1, secp521r1). Signed request handling with client ID prefix authentication and encrypted direct post for non-DC API flows have also been implemented. Compliance has been verified across multiple SD-JWT VC presentation combinations **Permission Enforcement on Persistence Layer** Fine-grained permission enforcement has been implemented across all major enterprise persistence services: Key Management, DID Service, DID Store, DID Registry, Certificate Store, Policy Store, and VICAL Registry **Database Profiles & Connection Pooling** Configurable database profiles and connection pooling support have been added, enabling operators to tune database behaviour per environment. DocumentDB compatibility has been improved, including `retryWrites` parameter support and configurable engine version. CosmosDB integration tests have also been added **Business Wallet Demo UI & BFF** A Business Wallet demo UI and backend-for-frontend have been introduced, featuring role-based issuance gating, Keycloak OIDC login, tenant bootstrapping for demo roles, and a default wallet key reference in the issuance form **Secured Credential Store, Policy Store, and VICAL Registry APIs** The credential store, policy store, and VICAL registry APIs are now secured with enterprise permission enforcement, consistent with the broader security initiative **X.509 Store Enhancements** The X.509 service has been significantly extended: stored certificate IDs are now returned in issuance responses, store ID collision pre-checks are enforced before issuance, VICAL entry metadata validation has been added, and certificate persistence to linked X.509 stores is now supported **AWS and Azure KMS / Storage Enhancements** Resource access configuration has been extended with support for AWS KMS, Azure Key Vault, and GCP storage. Key resolution for wallet requests now uses cloud-provider credentials where configured. Managed Azure Blob Storage support has been added **Annex C (DC API) Flow Support** `DcApiAnnexCFlowSetup` has been integrated into `EnterpriseVerifier2Service`, and the Annex C creation response has been updated to surface custom data immediately and hide unnecessary URLs **HPA Support for Data Retention** Horizontal Pod Autoscaler (HPA) support has been added for the DataRetentionService, allowing retention workloads to scale independently under load **System Config Override** Operators can now override system-level configuration at runtime without redeployment **JCA CRL Check** JCA-based Certificate Revocation List (CRL) checking has been enabled, strengthening certificate validation in X.509 flows --- ### Fixes & Improvements **HTTP Client & Timeout Stability** Enterprise HTTP client configuration has been revised: `connectTimeout` and `socketTimeout` are now explicitly set to reduce `maxEndpointIdleTime` and prevent stale connections. The Ktor CIO transitive dependency has been fixed, and the enterprise stack has been switched to OkHttp for integration tests **Integration Test Reliability** Default coroutine timeouts have been set for all tests to prevent flaky failures. Integration test reporting has been improved, and the build pipeline has been split to allow faster feedback. Test runners have been moved to self-hosted infrastructure **DataRetentionService Improvements** Cron schedule handling has been made more robust, logging has been migrated to Klogging, and the `startSchedule` function is now a suspend function for correct coroutine handling **Logging & Observability** Logging has been improved across core services, the DataRetentionService, ExternalRoleResolver, DistributedLockService, and VCT retrieval. Caught exceptions are now consistently logged. Request profiling has been added to enterprise integration tests **Issuer Service Error Handling** Error messages for expired and invalid issuance sessions have been improved. Error handling and code structure in `IssuerService` have been cleaned up for clarity **DocumentDB Fixes** Several DocumentDB compatibility issues have been resolved, including support for features not available in DocumentDB (configurable via feature flags) and `retryWrites` handling **OpenBadge Schema Reference** The Open Badges v3 schema is now referenced from the canonical IMS Global URL, ensuring schema validation uses the authoritative source **Miscellaneous Code Quality** Unused imports, debug print statements, unnecessary null assertions, and deprecated logging usages have been removed throughout the codebase. SonarQube issues have been addressed. Set-based assertions replace list-based comparisons in tests for correctness --- ### Breaking Changes - **TokenStatusList upgraded to Draft 15**: status value ranges and compression behaviour have changed. Existing status list configurations should be reviewed against the Draft 15 specification before upgrading. - **Resource Dependency Enforcement**: Some resources previously were not properly enforcing dependency permissions. This has been fixed, but may cause issues with existing setup scripts. Ensure you have properly configured dependencies for your services (e.g. that a KMS Service is attached to a DID Service when trying to create a new DID using a key managed by that KMS Service). Please refer to the [Dependency Services](../../.services/dependency-services/full-list) documentation for more details. -------------------------------------------------------------------------------- PAGE: 0.19 URL: https://docs.walt.id/enterprise-stack/release-notes/releases/0.19.x RAW: https://docs.walt.id/enterprise-stack/release-notes/releases/0.19.x.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # 0.19 ## 0.19.0 ### Features **Issuer2 and OpenID4VCI** - Overhaul of the issuer2 system, which is now enabled by default, allowing users to use the new profile system to issue credentials. - Added support for tx-code during pre-auth flow. - Added support for wallet initiated issuance. - Improved general interface for better auth code flow support. - Improved issuer2 capabilities: notifications, webhooks, SD-JWT VCT hosting, metadata coverage, and data-function mapping for W3C and SD-JWT VC profiles (#345). - OAuth `expires_in` support aligned with issuer metadata expectations (#345). - Repository-backed issuance session and profile storage with improved lookups; stored issuance session tests (#403). **VICAL, X.509 store, and X.509 service** - Replaced the legacy VICAL registry and certificate-store stack with a persisted feed model, version directories, manifest and trust-material storage, and dedicated publication vs registry controllers (#399). - Added publication-time validation, signer chain rules, registry retrieval for latest and version-specific artifacts, and OpenAPI documentation for publication and registry surfaces (#399). - Ported X.509 store and X.509 service toward the shared enterprise interface/implementation split, required key resolution, and improved hosted-certificate checks (#399). - Secured previously unauthenticated VICAL endpoints and completed integration testing hardening (WAL-825) (#399). **Credential status** - Fixed CWT encoding (binary vs hex), added `x5c`-oriented token status list support, and corrected JWT/CWT `kid` generation for status-list credentials (#402). - Added delete endpoint for status lists; removed redundant `kid` from `x5c` TSL where applicable (#402). - Configurable content-type for status-list cloud storage; MongoDB workflow and image version parameterization for integration tests (#402). **Platform** - Dependencies component for managing DID stores and KMS services during enterprise service creation (#399). - `/v2/` routing support in Caddyfile and Kubernetes deployment configurations (#399). ### Fixes and improvements - In-memory persistence adapter: nested JSON path sorting, `EncodeDefault` fixes, and calculation corrections (#403). - Swagger and documentation improvements for CWT status list surfaces (#402). - Various test harness cleanups and dependency-related test coverage for VICAL publication flows (#399). ### Breaking changes - **VICAL and X.509 certificate store**: Legacy VICAL registry and certificate-store APIs and resources were removed in favor of the new service model, storage layout, and permission mappings. Deployments and clients using the old VICAL or certificate-store endpoints must migrate to the new publication/registry APIs and updated X.509 service configuration (#399). - **Fixes to the Bitsting Status List**: Due to the changes made to fix the Bitstring Status List, you may need to update your status list configuration or republish your status lists. Please review [the accompanying breaking change guide](https://github.com/walt-id/waltid-enterprise-quickstart/breaking-changes-helper/0.19.0/BITSTRING_STATUS_LIST_MIGRATION_GUIDE.md) and select the appropriate migration path based on your existing setup. -------------------------------------------------------------------------------- PAGE: 0.20 URL: https://docs.walt.id/enterprise-stack/release-notes/releases/0.20.x RAW: https://docs.walt.id/enterprise-stack/release-notes/releases/0.20.x.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # 0.20 ## 0.20.4 ### Features - Added **authorization_details support** for OpenID4VCI flows with custom attributes in repository, updated issuer service, and Keycloak integration tests. - Fixed **custom sessionId handling** in issuer2 credential-offer creation, allowing caller-provided session IDs to work as documented. - Improved **Issuer2 UI** with better x5c certificate management, credential configuration presets, and enhanced mdoc mapping UX. ### Fixes and improvements - Fixed issuer2 service configuration deletion issue. - Replaced Selenium with Playwright for testing. - Updated Playwright config for better test stability. - Updated startupIsCompleted config. - Increased in-memory timeout. - Updated CI/CD log level. - Commented out flaky Keycloak tests temporarily. ## 0.20.3 ### Features - Added **dynamic JSON object merging** for credential profile overrides, enabling runtime merging of credential data with nested profile configurations. - Added **full credential data to issuer sessions**, providing complete credential information in session tracking. ### Fixes and improvements - Updated test to use `createW3cProfileRequest` for W3C profile consistency. ## 0.20.2 ### Highlights - Added **mTLS support** for secure mutual TLS authentication with key password validation before initialization. - Introduced **Trust Registry Services** UI page with configuration update functionality, resolve trust modal, and service creation capabilities. - Added **credential selector for VP flow** enabling users to select specific credentials during OpenID4VP presentation. - Implemented **deeplink support** for wallet Enterprise Service UI, improving mobile and cross-application integration. - Enhanced **AWS KMS support** with multi-region key capabilities and SDK integration in external KMS creation. ### Features **mTLS Support** - Added mTLS (mutual TLS) support for secure client authentication. - Ensured key password is validated before mTLS initialization. **Trust Registry UI** - Added Trust Registry Services page with service creation functionality. - Enhanced Trust Registry page with configuration update functionality. - Added Resolve Trust functionality with modal and input options. - Added Trust Registry support for verifier2. **Wallet UI Enhancements** - Added credential selector for VP flow allowing users to choose credentials during presentation. - Added support for deeplinks to wallet Enterprise Service UI. - Added masked display for `authCode` input in wallet receive page for improved security. - Updated wallet creation page to include key management and refactored dependencies handling. **Issuer2 Improvements** - Moved credential signing logic for better organization. - Fixed credential errors handling in issuer2. - Updated credential format and proof type to string. - Reverted to old display as status tests were failing. **Platform and Infrastructure** - Added AWS multi-region key support. - Renamed KMS examples for clarity. - Added functionality to delete DIDs. - Updated DID handling to use 'didId' and improved data fetching across components. ### Fixes and improvements - Fixed Wallet showing all keys of all KMS instances. - Fixed verifier2 verification sessions parsing. - Updated issuance components to use "status". - Updated paths ignored in build-deploy workflow. - Updated build-deploy-enterprise-ui workflow reference to latest version. - Updated CLI test workflow references and configurations. - Fixed formatting in .env file. - Added comments for manual branch deployment. ### Breaking Changes N/A ## 0.20.1 ### Highlights - Implemented **issuer2 SD-JWT fixes** with updated profile handling, service configuration, and examples for selective disclosure credentials. - Enhanced **security with role management fixes** and additional user permission tests covering host alias API permissions. - Added **APP_VERSION display** in Docker builds and admin navigation for better deployment visibility. ### Features **Issuer2 Enhancements** - Fixed issuer2 SD-JWT profile handling and service configuration. - Updated issuer2 examples for selective disclosure credentials. - Added Auth Provider configuration for issuer2. - Updated issuer2 client authentication config and metadata. - Updated issuer2 client verification logic. - Added `preAuthorizedGrantAnonymousAccessSupported` in the default metadata. **Platform and UI** - Added APP_VERSION to Docker builds and display version in admin navigation. - Moved X.509 service and X.509 store management pages with updated navigation links. - Removed unused certificate details and formatDate function. ### Fixes and improvements - Fixed Wallet showing all keys in all KMS instances. - Fixed move APP_VERSION ARG and ENV to the release stage in Dockerfile. - Security fixes for Role Management. - Fixed duplicate imports after merge. - Fixed keycloak test. - Fixed issuerSigningKey handling. - Fixed renaming and missing issuer2 package issues. - Removed println and made verifyCertificateChain suspend. - Simplified ServiceDependencies component. - Fixed TypeScript errors in UI components. - Added regression guard for previous fixes. ### Breaking Changes #### DID Documents response is now conforming with the (specification)[https://w3c-ccg.github.io/did-method-web]. The response was previously wrapped in a `content` object. This has been removed Before: ```json { "content": { "context": [ "https://www.w3.org/ns/did/v1", "https://w3id.org/security/suites/jws-2020/v1" ], "id": "did:web:nec.enterprise-demo.dev-quantum.necdp.com:nec.TestMobile2.test2.test2-did-registry:issuer-test-2-new", "verificationMethod": [ { "id": "did:web:nec.enterprise-demo.dev-quantum.necdp.com:nec.TestMobile2.test2.test2-did-registry:issuer-test-2-new#issuer-test-2-secp256r1-key", "type": "JsonWebKey2020", "controller": "did:web:nec.enterprise-demo.dev-quantum.necdp.com:nec.TestMobile2.test2.test2-did-registry:issuer-test-2-new", "publicKeyJwk": { "kty": "EC", "crv": "P-256", "kid": "issuer-test-2-secp256r1-key", "x": "zzM9z4mHthO8fkNZGzSzuJbFVeyElVjmCOMt38pdvbo", "y": "Dgn8HiW8NdQhjgVgE_ux8YRxAwKmdDl9FxTq0wbDewc" } } ], "assertionMethod": [ "did:web:nec.enterprise-demo.dev-quantum.necdp.com:nec.TestMobile2.test2.test2-did-registry:issuer-test-2-new#issuer-test-2-secp256r1-key" ], "authentication": [ "did:web:nec.enterprise-demo.dev-quantum.necdp.com:nec.TestMobile2.test2.test2-did-registry:issuer-test-2-new#issuer-test-2-secp256r1-key" ], "capabilityDelegation": [ "did:web:nec.enterprise-demo.dev-quantum.necdp.com:nec.TestMobile2.test2.test2-did-registry:issuer-test-2-new#issuer-test-2-secp256r1-key" ], "capabilityInvocation": [ "did:web:nec.enterprise-demo.dev-quantum.necdp.com:nec.TestMobile2.test2.test2-did-registry:issuer-test-2-new#issuer-test-2-secp256r1-key" ], "keyAgreement": [ "did:web:nec.enterprise-demo.dev-quantum.necdp.com:nec.TestMobile2.test2.test2-did-registry:issuer-test-2-new#issuer-test-2-secp256r1-key" ] } } ``` After: ```json { "context": [ "https://www.w3.org/ns/did/v1", "https://w3id.org/security/suites/jws-2020/v1" ], "id": "did:web:nec.enterprise-demo.dev-quantum.necdp.com:nec.TestMobile2.test2.test2-did-registry:issuer-test-2-new", "verificationMethod": [ { "id": "did:web:nec.enterprise-demo.dev-quantum.necdp.com:nec.TestMobile2.test2.test2-did-registry:issuer-test-2-new#issuer-test-2-secp256r1-key", "type": "JsonWebKey2020", "controller": "did:web:nec.enterprise-demo.dev-quantum.necdp.com:nec.TestMobile2.test2.test2-did-registry:issuer-test-2-new", "publicKeyJwk": { "kty": "EC", "crv": "P-256", "kid": "issuer-test-2-secp256r1-key", "x": "zzM9z4mHthO8fkNZGzSzuJbFVeyElVjmCOMt38pdvbo", "y": "Dgn8HiW8NdQhjgVgE_ux8YRxAwKmdDl9FxTq0wbDewc" } } ], "assertionMethod": [ "did:web:nec.enterprise-demo.dev-quantum.necdp.com:nec.TestMobile2.test2.test2-did-registry:issuer-test-2-new#issuer-test-2-secp256r1-key" ], "authentication": [ "did:web:nec.enterprise-demo.dev-quantum.necdp.com:nec.TestMobile2.test2.test2-did-registry:issuer-test-2-new#issuer-test-2-secp256r1-key" ], "capabilityDelegation": [ "did:web:nec.enterprise-demo.dev-quantum.necdp.com:nec.TestMobile2.test2.test2-did-registry:issuer-test-2-new#issuer-test-2-secp256r1-key" ], "capabilityInvocation": [ "did:web:nec.enterprise-demo.dev-quantum.necdp.com:nec.TestMobile2.test2.test2-did-registry:issuer-test-2-new#issuer-test-2-secp256r1-key" ], "keyAgreement": [ "did:web:nec.enterprise-demo.dev-quantum.necdp.com:nec.TestMobile2.test2.test2-did-registry:issuer-test-2-new#issuer-test-2-secp256r1-key" ] } ``` #### Client Attestation configuration in the Issuer2 API has been updated: Before: ```json clientAttestationConfig = { required: true, verificationMethod: { type: 'static-jwk', jwk: { ... }, }, clockSkewSeconds: 300, replayWindowSeconds: 300, } ``` After: ```json clientAuthenticationConfig = { supportedMethods: [ { type: "client-attestation", config: { verificationMethod: { type: 'static-jwk', jwk: { ... } }, } } ] } ``` ## 0.20.0 ### Highlights - **New Trust Registry Service** for managing ETSI trust lists (TSL, LoTE) for credential verification against official trust frameworks. - **New Client Attestation Service** for issuing and verifying wallet attestations for secure credential issuance flows. - **OpenIDVCI 1.0 Support in the Wallet Service** marking the completion of our 1.0 support across all services. - **Improved external IAM integration** to connect with external OIDC providers for authentication. - **External role mapping API** with database-backed mappings, RESTful endpoints, and demo app UI for enterprise authentication flows. Supports dynamic redirect URLs and RP-initiated logout. - **Enterprise Service security refactoring** for Issuer 1/2, Verifier 1/2, and Wallet services with interface-based implementations, automatic dependency detection, and granular permission control. Major architectural improvement enabling better testability and security isolation. - **X.509 Store and VICAL services** rebuilt with proper persistence, user-permission proxies, and service-level certificate storage with cross-store linking support. Certificate IDs are now derived from target paths with full delete support. ### Features **Enterprise Service Security** - Secured Issuer 1 and Issuer 2 Enterprise Services with interface-based implementation pattern and automatic KMS dependency detection. - Refactored Verifier 1/2 Services to follow the same secure pattern with dedicated interface and implementation classes. - Implemented Enterprise Service Wallet split into legacy and new v2 OpenID4VCI flows with mDoc support. - Added user permission proxy implementations for credential stores, policy stores, and client attesters. - Added comprehensive wallet permission tests to validate the security model. **X.509 Store and VICAL Services** - Fixed X.509 store persistence with service-side repository logic and proper certificate ID handling. - Updated X.509 service certificate creation to accept optional `storedCertificateId` and store issued certificates across linked X.509 stores. - Added delete support and completed user-permission proxy coverage for X.509 store operations. - Moved VICAL storage logic into private service-local repository and added VICAL service user-permission proxy support. - Adjusted VICAL registry/publication controllers for response status and artifact format parsing. **Issuer2 and OpenID4VCI** - Added `issuanceSessionId` to credential offer requests and access tokens for O(1) credential endpoint lookups. - Added `correlationId` (callId) to callback events for session tracking. - Enriched issuance session with id, format, and status information. - Added support for issuer state forwarding. - Improved profile update to ignore certain keys (profileId, status, version, createdAt, updatedAt) for better developer experience. **Client Attestation and EUDI Compatibility** - Implemented client attestation support with configurable validation. - Extended lenient mode to gracefully skip verification when signature validation fails. - Added issuer state in redirect for EUDI wallet compatibility. - Ensured clientAttestationConfig is properly copied during config updates. - Updated swagger example with lenient configuration for EUDI wallet demos. **Credential Status** - Added test coverage for multiple status values. - Resolved hex and binary mixed usage in CWT credentials. - Added TSL CWT to the e2e journey. - Added support for DID-based header in token status lists. **Trust Registry** - Added a trust registry service and policy allowing for credential verification against official trust frameworks. - Fixed trust registry sourceId handling by moving to target path (consistent with KMS, X.509-store patterns). - Fixed Unit body handling in documentedPost for no-body POST endpoints. - Added trust registry support aligned with OSS trust registry library. **Auth and External Role Mapping** - Implemented database-backed external role mappings with RESTful API. - Added external role mapping UI to demo app with payload editor and field documentation. - Fixed external role mapping resolution during auth. - Added allowedRedirectUrls to auth config documentation. - Added Keycloak package for Enterprise authentication with setup guides. **Wallet Service** - Added support for OpenID4VCI 1.0 to the Wallet API. - Added DID Service support in wallet creation process. - Fixed wallet init to return created resources (keyId and didId). - Added swagger examples for new wallet routes. **Platform** - Migrated HTTP clients to unified web data fetching abstraction with CIO engine. - Added load testing scripts and infrastructure improvements. ### Fixes and improvements - Fixed BSON issue in enterprise services. - Fixed credential metadata display. - Fixed assignRoleToApiKey double-stringify in demo app. - Fixed coroutine context conflict in tenant permissions listing. - Fixed old reference in credential status. - Added Swagger examples for creating plain KMS and generating keys. - Updated OpenAPI docs and examples for X.509 service and VICAL storage behavior. - Fixed integration tests workflows. ### Breaking changes - **X.509 Store API**: The X.509 store add/update flows now use service-level targets with certificate IDs derived from the target path. Clients using the old certificate store APIs must migrate to the new request models. -------------------------------------------------------------------------------- PAGE: 0.21 URL: https://docs.walt.id/enterprise-stack/release-notes/releases/0.21.x RAW: https://docs.walt.id/enterprise-stack/release-notes/releases/0.21.x.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # 0.21 ## 0.21.0 ### Features **Database Migration Framework** - Implemented cluster-aware database migration framework for safe multi-node deployments. - Added dry run and rollback capabilities for migrations. - Added node restart recovery and cluster state management. - Added progress logging for migration tracking. - Added migration for organization tree collection timestamps. - Ensured database backward compatibility with existing data. **Transaction Data Support** - Added transaction_data support in verifier2 flows. - Implemented config-driven transaction data type profiles via endpoint. - Added UI integration for transaction data with profile-driven configuration. - Validated and restricted transaction data to specific credential formats. - Enforced authentication on transaction-data-profiles endpoint. - Aligned transaction data handling with OpenID4VP specification. **Resource Timestamps** - Added `createdAt` and `updatedAt` timestamps to TreeEntry interface. - Ensured timestamps are evaluated in enterprise backend service (not client-provided). - Added index for sorting by `updatedAt` field. - Moved timestamp fields to end of resource constructors for compatibility. - Added migration for existing data to populate timestamp fields. **X.509 and CSR Support** - Added CSR support with UI integration for certificate generation. - Added support for generic X.509 certificates and CSR flows. - Added x5c support for W3C credentials. - Added integration tests for data functions. **Credential Status** - Added support for capacity monitoring and automatic rollover for status lists. - To enable auto-rollover on existing services, update the service configuration to include `capacity.autoRollover: true` ### Fixes and improvements - Fixed issuerState handling for preauthorized offers. - Updated token and credential endpoint errors. - Upgraded to Ktor 3.4 with associated fixes. - Removed openid metadata endpoint. - Fixed OIDC metadata compatibility. - Updated MongoDB connection strings for new infrastructure. - Updated ingress class from nginx to Traefik. - Updated kubeconfig secret references. - Updated helm and k8s deployment workflow references. - Fixed policy field comparison to avoid timestamp mismatch in tests. - Fixed Sonar findings and code cleanup. - Reduced code duplication across services. - Removed unnecessary try-catch blocks. - Fixed duplicate imports after merge. ### Breaking changes None -------------------------------------------------------------------------------- PAGE: 0.22.x URL: https://docs.walt.id/enterprise-stack/release-notes/releases/0.22.x RAW: https://docs.walt.id/enterprise-stack/release-notes/releases/0.22.x.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # 0.22.x ## 0.22.0 ### Highlights - Delivered **mobile wallet SDK enterprise alignment** with enterprise-backed mobile tests, wallet SDK/demo app integration, conformance updates, Wallet2 API test refactors, and mobile SDK compatibility work (WAL-1033). - Added and hardened **OIDC Bridge / IAM Bridge flows** for Ory and Keycloak use cases, including public endpoint permissions, multi-flow configuration, DC API support, verifier dependencies, demos, docs, and local scripts. - Improved **enterprise database and deployment readiness** with AWS DocumentDB migration fixes, MongoDB clustered-access handling, AWS DocumentDB profile configuration, migration 0006 repairs, issuer2 resource migrations, and Helm/Kubernetes updates (WAL-1082). - Expanded **issuer2 and verifier2 session operations** with enterprise pagination, issuer/verifier session list APIs, UI pagination migration, custom session-id handling, callback resolution by issuer state, and refresh-token support (WAL-8, WAL-963, WAL-964, WAL-1115). - Advanced **credential status and crypto support** with additional TSL states, protected COSE x5chain placement, full-scan fixes, clearer `expiresIn` naming, AWS multi-region KMS docs, Azure key rotation coverage, and JWT VC well-known metadata tests (WAL-1119, WAL-985). - Added **policy, PAR, OIDC, and OpenBadge capabilities** through PAR issuer2 work, trusted-authority / HAIP support, OpenBadge examples and ecosystem init updates, credential-profile hard delete, and richer service initialization paths (WAL-853, WAL-1062). ### Features **Mobile Wallet SDK and Wallet2** - Aligned enterprise tests and service adapters with the OSS mobile wallet SDK release, including Android/iOS mobile test coverage and enterprise companion work for WAL-1033 (#487, #486). - Updated Wallet2 integration tests to use the generated Wallet2 API and covered enterprise wallet initialization paths (#498, #478). - Added Wallet2 route and OpenAPI adjustments, credential read fallback behavior, DID type selection during wallet creation, and init-wallet endpoint coverage via the conformance branch (#478). - Added mobile SDK compatible crypto and key-flow updates required by the OSS mobile wallet SDK, including wallet adapter updates for key flows and JSON DID handling (#486). **OIDC Bridge, Ory, and Keycloak** - Added OIDC Bridge support for Ory and renamed/advanced IAM Bridge work toward the OIDC Bridge model (#496). - Added public OIDC endpoint permissions, verifier dependency overrides, IAM/OIDC service registration, multi-flow configuration, request_uri QR/deep-link flows, and DC API/web wallet support (#496, #455). - Added Keycloak local scripts, realm mapper updates, OIDC bridge docs, and Ory setup/demo-client scripts for local bridge testing (#496). **Issuer2, Sessions, and OAuth** - Added issuer2 refresh-token persistence and enterprise/keycloak refresh-grant coverage (#503). - Added PAR flow support for issuer2, aligned with RFC 9126 and WAL-853/WAL-854 work (#489). - Added hard delete of credential profiles (#499). - Added issuer2 resource migrations and regression tests for main-line migration compatibility (#520). - Added custom session-id handling and issuer-state callback resolution for issuer2 pre-authorized and auth-code flows (WAL-1115) (#523). - Added JWT VC issuer metadata well-known path coverage for tenant-scoped issuer services (WAL-985) (#524). **Pagination, UI, and Service Initialization** - Added pagination to enterprise list endpoints, issuer2 session lists, and verifier2 session lists, then completed the UI migration away from `fetchAllPages` (WAL-8, WAL-963, WAL-964) (#458, #460, #465). - Pinned Vue/reka-ui dependencies to stabilize the enterprise UI build (#505). - Added OpenBadgeCredential examples, OpenBadge ecosystem init configuration, and service/resource initialization improvements used by enterprise setup flows (#504, #455). **Credential Status, KMS, and Database Operations** - Fixed AWS DocumentDB migration behavior and default-port issuer URL handling (WAL-1082) (#500). - Fixed MongoDB clustered access, collation settings for the AWS DocumentDB 8 profile, recreate-database handling, docker-compose startup, and migration 0006 gaps (#506, #507, #508, #510). - Added profile configuration for AWS DocumentDB in deployment/config files and updated Helm values for AWS DocumentDB environments (#506, #507, #508). - Accepted additional token status list states and moved x5chain to protected COSE headers for ISO 18013-5 compliance (#516, #518). - Fixed credential status full database scans and clarified `expiresIn` naming (#517). - Added AWS multi-region KMS Swagger documentation and Azure key rotation test coverage (#515, #522). ### Fixes and improvements - Refactored enterprise wallet2 adapters and controller routes to align with OSS API changes and conformance updates (#478, #486). - Reduced duplicate Keycloak test code and updated Keycloak tests for credential issuer and nonce endpoint metadata (#478, #504). - Removed obsolete issuer initialization models and stale submodule references while keeping the new init/resource APIs current (#455, #489). - Updated deployment workflow references, Helm values, and Kubernetes service configuration for the 0.22 branch (#515, #517). - Improved status-list and issuer2 regression coverage with test vectors, custom session tests, and well-known metadata tests (#516, #523, #524). ### Breaking changes Some v2 routes for the wallet api (specifically the isolated receive endpoints) have been deprecated in favour of our finalised versions which work similarly but have different paths and slightly different payloads. You can find more details in the Swagger regarding these changes -------------------------------------------------------------------------------- PAGE: Archive Release Notes URL: https://docs.walt.id/enterprise-stack/release-notes/releases RAW: https://docs.walt.id/enterprise-stack/release-notes/releases.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Archive Release Notes This contains an archive of older release notes for versions that are no longer supported. ## 0.17 ### 0.17.3 #### Features **Data Retention - High Availability Support** - Added High Availability (HA) configuration for data retention purge jobs, enabling distributed locking to prevent concurrent execution across multiple instances in Kubernetes or load-balanced deployments. #### Fixes & Improvements **Data Retention** - Improved cron expression parsing and period calculation for more reliable scheduled purge operations. - Migrated logging to use the Klogging interface for consistent, structured logging across the service. #### Breaking Changes None ### 0.17.2 #### Features **Data Retention Service** - Enhanced logging with run IDs for audit trails and debugging, using lambda-based evaluation to ensure log messages are only constructed when the log level is enabled. #### Fixes & Improvements Resolves breaking changes introduced for DocumentDB in 0.17.1. #### Breaking Changes None ### 0.17.1 Ignore due to breaking changes for DocumentDB. Use 0.17.2 instead. ### 0.17.0 #### Features **Issuer2 and OpenID4VCI 1.0** - Added support for OpenID4VCI 1.0 through the Issuer2 API, including support for the Authorization Code Flow and Pre-Authorized Code Flow. [Learn more here](https://docs.walt.id/enterprise-stack/services/issuer2-service/overview.md) - Expanded Issuer2 capabilities with feature toggles, data retention service integration providing more flexible issuance workflows and better control over credential lifecycle. **Crypto and Key Management** - Improved crypto onboarding with Azure key-management examples and initialization to accelerate multi-cloud deployments. - Fixed AWS SDK auth parameter naming to match the expected initialization contract, ensuring reliable key management integration with AWS KMS. - Added support for Managed Identity authentication for key management with Azure Key Vault, improving security and ease of use. [Learn more here](https://docs.walt.id/enterprise-stack/services/key-management-service/azure-key-vault.md) - Added support for Tags for both key management services, allowing you to add metadata to your keys for easier organization and retrieval. **Verifier2 Interoperability and Standards Compliance** - Strengthened verifier2 interoperability with additional W3C VC verification tests, improving conformance coverage and catching regressions earlier. - Added support for 1EdTechJsonSchemaValidator2019 to broaden schema validation compatibility and support additional credential formats. - Fixed tests impacted by example updates, including DCQL query correctness and issuer schema alignment, ensuring verification flows remain stable. - Added OpenAPI examples for internationalized metadata to reduce integration guesswork and improve developer experience. **Persistence Framework and Database Support** - Introduced a new persistence framework with shared APIs and multiple backends (in-memory and MongoDB) to support scalable deployments, including a general API, data filtering, and repository-backed domain persistence used across both implementations. - Added an in-memory implementation for credential-status storage and reorganized storage strategy/configuration to make deployments easier to reason about, improving separation of concerns and configurability. **Credential Status Storage and Publishing** - Improved credential-status storage and publishing flows with clearer separation of concerns and better configurability, enabling more flexible deployment options and easier maintenance. #### Fixes & Improvements **CI/CD and Testing** - Hardened CI and test suites with self-hosted runner support, better documentation, and more readable integration assertions. - Improved mdoc integration test readability and assertions to reduce test maintenance overhead and improve developer productivity. - Updated Docker instructions and fixed an alignment warning to ensure smooth deployment experiences. **Documentation** - Removed groupBy documentation from the events endpoint to match current behavior, ensuring API documentation accurately reflects implementation. #### Breaking Changes ##### Using Issuer2 API You will need to make a minor update to your deployment files to support the new Issuer2 API. Trying to resolve the `openid4vc/v1` endpoint (required for the issuance flow) will result in a 404 error. You will need to update your deployment ingress files to change all `path: /.well-known/vct/v1/` to `​path: /.well-known/` You can always find an up to date example of our deployment files [here](https://github.com/walt-id/waltid-enterprise-quickstar/tree/main/helm) ##### Azure key management behavior - Azure key management now distinguish between SDK-based and REST-based backends. Existing deployments should review their key configuration and authentication method (SDK vs REST) before upgrading. - Keys created with one backend (SDK vs REST) are not interchangeable with the other. If you switch backends, you may need to re-provision or migrate keys accordingly. - Creating a key with the REST-based backend will now require a client ID, tenant ID, and client secret in the configuration, and uses keyType `azure-rest-api` (and no longer `azure`). - Creating a key with the SDK-based backend will now require a managed identity in the configuration, and uses keyType `azure` (previously not available). - All customers using Azure Keys must run migration scripts to update their keys to the new format. You can find resources to help you with breaking changes [here](https://github.com/walt-id/waltid-enterprise-quickstart/tree/main/breaking-changes-helper/0.17.0) #### AWS key management behavior - To better align our request bodies, a new `auth` property has been added to the `config` object for the AWS key management service created with the SDK. - If you are using the AWS key management service created with the SDK only utilising the `region` property, you will need to update your configuration to include the `auth` property. If you were previously using the Access Keys or RoleNames you will not be affected by this change. Previously: ```json { "backend": "aws", "keyType": "secp256r1", "config": { "region": "eu-central-1", } } ``` Currently: ```json { "backend": "aws", "keyType": "secp256r1", "config": { "auth": { "region": "eu-central-1", } } } ``` - If you have existing keys created with the SDK, you will need to run a migration script to update your keys to the new format. You can find resources to help you with breaking changes [here](https://github.com/walt-id/waltid-enterprise-quickstart/tree/main/breaking-changes-helper/0.17.0) ## 0.16 **Note:** You might be wondering why this release is v0.16 when the last Enterprise version was v0.7.0. Since we now release Enterprise and Community together, we’ve aligned their versions—so with the latest Community Stack at v0.15.1, the Enterprise Stack has been bumped to v0.16.0 to match going forward. ## 0.16.2 Minor fix to an underlying opensource dependency. No changes to the Enterprise Stack. ## 0.16.1 ### Features - [BETA] Introduced the X.509 certificate service for IACA/DS issuance with stricter validation and spec-aligned tests, enabling compliant certificate generation workflows. - [BETA] Added S3-compatible credential-status storage with SigV4 signing support, container validators and provisioners, and comprehensive s3mock/localstack test coverage for cloud-native deployments. - Enhanced the Enterprise UI with expanded views for accounts, policies, credentials, and KMS key details, streamlining operator workflows and improving visibility into system configuration. ### Fixes & Improvements - Fixed MongoDB and DocumentDB persistence issues for mdoc credentials and certificate stores, stabilizing integration tests and ensuring reliable data persistence across cloud database providers. - Hardened runtime security with improved security context defaults and enhanced CI filtering, while updating Kotlin and Ktor dependencies to align with the latest OSS versions. - Updated documentation and Swagger headers to accurately reflect product-type and credential-status guidance, ensuring API documentation remains current with implementation changes. ## 0.16.0 ### Features **OpenID4VP 1.0, Verifier2 and mDoc Support** - Refactored OpenID4VP wallet and verifier logic into shared libraries used by Enterprise Verifier2, providing a unified, mDoc-aware OpenID4VP 1.0 implementation across stacks. - Added full mDoc parsing, device authentication and presentation validation based on CBOR/COSE libraries, including selective disclosure handling for mDoc presentations. - Extended Verifier2 controllers to expose richer credential/session metadata and aligned Enterprise Verifier2 with the new OpenID4VP verifier package for consistent routing and session management. - Migrated Verifier2 DCQL integration tests into the Enterprise repository and expanded DCQL coverage so OpenID4VP/DCQL flows are validated against Enterprise APIs. **Credential Status Service, URLs and mDL/mDoc Status Integration** - Introduced a dedicated Credential Status microservice to manage status entries and expose them via a dedicated API, including an endpoint that returns the public status URL for a given credential. - Added configurable, signed-URL generation for status endpoints with expiry support and cloud-specific URL providers, and updated registry utilities to work with cloud-native clients. - Linked stored issuance sessions to credential status indices and refreshed status update APIs so status changes can be automated from issuance flows and traced back to the original session. - Extended issuer and verifier support for mDL/mDoc credentials using a unified status property backed by TokenStatusList/Credential Status List, including multiple status values per credential. - Introduced a Credential Status List view in the Enterprise UI and added documentation describing status features and capabilities for operators. **Enterprise Gateway, DID Registries and Certificate Management (VICAL)** - Shipped the walt.id Enterprise Gateway with documentation and deployment manifests to streamline connectivity with DID registries and other trust infrastructure in clustered deployments. - Finalized documentation for the API Gateway and public DID Web registry so operators can configure public DID hosting consistently between OSS and Enterprise environments. - Delivered the VICAL Management Service and Registry together with a Certificate Store, providing Enterprise-grade certificate distribution and management aligned with the OSS implementation. - Enabled external publishing of core cryptographic libraries (including VICAL/COSE) so the same building blocks can be reused across Enterprise and external integrations. **SD-JWT VC Issuance and Verification** - Implemented SD-JWT VC schema validation in the Enterprise Issuer, ensuring issued SD-JWT VCs conform to expected structures before they are returned to clients. - Fixed missing `_sd_alg` parameters in SD-JWT payloads and updated x5c handling to support certificate chains (with dedicated X509 parsing utilities), keeping Enterprise aligned with upstream changes. - Reworked the SD-JWT presentation pipeline for Verifier1 into a single-pass parser with robust error handling and support for multiple matching credentials per presentation. - Updated issuer responses to include cNonce plus expiry and to transcode uploaded PEM certificate chains into base64 DER for SD-JWT and W3C x5c headers, improving interoperability with external wallets/verifiers. - Tightened handling of `client_id` and `response_mode` for SD-JWT and related flows using stricter enums and validation rules to match current standards. **Authentication, Sessions and Login/Logout UX** - Adopted the updated OIDC/AuthNZ stack from the OSS libraries so Enterprise APIs follow the same authentication and session handling behavior as the Community Stack. - Added a logout endpoint, UI button and supporting Nuxt plugin to clear tokens and redirect users to the login page once sessions expire. - Implemented session-expiry detection with a modal and client-side handling so token expiry is surfaced clearly and users are guided to re-authenticate. - Improved the Enterprise login page with clearer error messages and loading states, giving admins better feedback on ongoing authentication operations. - Allowed additional authentication methods beyond email for account flows, relaxing previous constraints where only email-based methods were accepted. **Idempotent Issuer, Verifier and Resource APIs** - Made resource creation, credential issuance and verification-session endpoints idempotent, so repeated client calls (e.g. retries) do not result in duplicate resources or sessions. - Added detailed status fields and human-readable reasons to issuance and verification entities in both the backend and UI, making failed operations easier to diagnose. - Introduced timestamp fields on issuer and verifier resources to support auditing and lifecycle tracking across long-running deployments. **Telemetry, Observability and Security Scanning** - Added a first-class OpenTelemetry plugin with configuration via `telemetry.conf`, including exporter wiring, instrumentation toggles and feature flags for Enterprise services. - Hardened the telemetry stack with safer configuration parsing, singleton lifecycle management, deterministic shutdown and reduced log noise, while keeping telemetry opt-in by default. - Introduced an OWASP ZAP full-scan GitHub workflow so CI runs automated security scans and surfaces regressions early in the pipeline. **Credential Templating and Holder/Policy UX** - Added credential templating support to the Enterprise UI, allowing admins to define reusable credential templates and edit them via an improved JSON editor. - Introduced policy settings on the wallet “receive VC” screen, enabling per-wallet policy configuration when receiving credentials. - Provided UI for Verifier2 and holder policies so operators can configure and review holder-related policy behavior directly from the Enterprise console. **Admin Console, Tenant and Wallet UX Improvements** - Added tenant deletion with confirmation flows, improved configuration views and clarified “Danger Zone” messaging for sensitive operations such as credential deletion. - Updated issuer navigation so flows redirect directly to issuance-session detail pages, reducing clicks and making debugging easier. - Added API key expiration options to the API key creation form so administrators can enforce key lifetimes without manual rotation. - Enhanced DID-related screens by displaying dynamic DID Service IDs in headers, fetching dependencies automatically, improving DID store/service dependency checks and making DID formatting consistent. - Added tenant registry options, dynamic page titles and safer default selection for DID stores, improving overall admin guidance and reducing misconfiguration risk. **Session PII data retention & auto-purge** Configurable issuer/verifier session retention via `data-retention.conf` and the `data-retention` feature flag, including scheduled auto-purge, dry-run mode, and logging to limit stored PII. Lear more [here](http://docs.walt.id/enterprise-stack/setup/configurations/config-files/data-retention). ### Fixes & Improvements **Verification and Standards Compliance** - Improved OpenID4VP verifier and wallet behavior to align with the unified 1.0 implementation and mDoc handling across OSS and Enterprise stacks. - Fixed multiple SD-JWT and x5c edge cases (including missing `_sd_alg`, updated x5-chain types and parsing of x5c lists) to stay compatible with evolving community libraries. - Fixed VCT parsing during presentation and refined ISO/IEC 18013-7 profile handling so mDL/mDoc flows match the profile requirements. - Extended Verifier2 tests and presented-credential inspection coverage, including migration of DCQL integration tests, to ensure new flows remain stable. **Issuance, Credential Lifecycle and URLs** - Fixed credential-offer URL generation across issuers and cleaned up redundant handlers and tests to prevent broken issuance flows. - Updated base URL defaults and host-alias handling (including for cloud deployments) to avoid misrouted calls in gateway-based setups. - Improved logging for the Enterprise Gateway and test environments to make troubleshooting deployment-specific issues easier. - Disabled a problematic credential schema that caused build failures and added notes to highlight its status until it is fully supported. **Admin UI, Wallet and DID UX** - Fixed the Vue JSON editor onChange handling so edits to credential templates are reliably captured in the UI. - Corrected the default visibility of policy-store settings so policy configuration is hidden when no policy store is attached to a wallet. - Improved DID store/service configuration screens with clearer validation messages and UX refinements when dependencies are missing or misconfigured. - Refined navigation to issuance-session details, adjusted copy where terminology was incorrect or confusing, and clarified dangerous-action confirmations. **Docs and Developer Experience** - Updated Swagger descriptions, examples and response codes for Enterprise services, including Azure key-generation examples, to better reflect actual behavior. - Added and refined documentation for credential-status capabilities (including a feature list in the credential-status README) and removed outdated notes that no longer matched implementation. - Finalized documentation for the Enterprise Gateway, public DID Web registry, mDoc data adaptation layer and authorization-code ID token claim mapping so implementers have end-to-end guidance. - Removed legacy security-token samples from resource-service examples to avoid confusion and reduce risk in demos and training materials. - Introduced Prettier-based formatting in UI codebases to keep frontend contributions consistent. **Tests, CI/CD and Operations** - Introduced Enterprise Stack integration tests (including wallet-holder policy tests) and migrated multiple suites to JUnit, improving consistency and coverage across services. - Added remote-environment integration tests and removed obsolete or overlapping Enterprise e2e suites to reduce runtime and cut noise from brittle paths. - Temporarily disabled flaky integration tests affected by unresolved external addresses and fixed various test assertions to stabilise CI signal. - Performed general CI/CD maintenance, including Sonar-related fixes and repository clean-up (e.g. removing stale ignore rules and unnecessary files). ### Breaking Changes **Stricter SD-JWT Verifier Request Validation** - Tightened validation of `client_id` and `response_mode` for SD-JWT and related verifier flows by introducing stricter enums and request checks. - Requests that previously passed with unsupported, malformed or missing values may now be rejected; clients must ensure they send valid `client_id` and supported `response_mode` combinations. **PAR Endpoint Disabled for Enterprise Issuer** - Disabled the Pushed Authorization Request (PAR) endpoint for the Enterprise Issuer. - Integrations that relied on PAR must switch to the standard authorization flow when initiating issuance requests. **Revocation of Credentials** - Previously, revocation of credentials was done via an `index` parameter in the `/v1/{target}/credential-status-service-api/status-credential/status/update` endpoint. - Now it uses session ID instead. The session ID is available: - At issuance, by parsing the credential URL offer - During issuance, through the callback events - Post issuance, via the events exposed at `/v1/events/query` - This change was necessary to accomodate new functionality which allows the issuer to use the credential status service as a depencency to automatically update the chosen status list. It also works better with new features we are planning for the issuer such as resuable offers, deferred issuance and batch issuance. In all these cases, the index is not known to the user, whereas the session ID is through the methods described above. ## 0.7 ### Features **Holder Policies for Silent Wallet Flows** Use holder policies to allow users to set granular sharing rules for credentials, ensuring consent-first data sharing during silent issuance and verification flows. **New Status Policy in Verifier Service** A flexible status policy to validate common status types like revocation or suspension, but also custom reasons based on the BitstringStatusList, StatusList2021, RevocationList2020, TokenStatusList standards. **Support for Non-Expiring Issuance Sessions** The Issuer Service API now allows for the creation of non-expiring issuance sessions. When you call ⁠POST /issuer-service-api/credentials/issue, you can set the session to remain active indefinitely by including the parameter ⁠expiresInSeconds: -1. If you prefer a session with a specific duration, you can specify the desired length in seconds using the ⁠expiresInSeconds parameter. By default, if this parameter is omitted, the session will expire after five minutes. **mDL Issuance Simplified** The mDL issuance request no longer requires the trustedRootCAs field. Also, the OpenAPI examples have been updated. **Credential Branding Extension** Issuers can now provide a secondary image for the credential branding information when setting up an issuer service. See an updated example in the SWAGGER docs. **Enterprise UI Updates** The management of service configuration in JSON format has been implemented for the following services: DID registry, KMS, Status, Credential Store, DID Store, Verifier, and Wallet. Improved Authorization Request handling when presenting credentials via the wallet service UI. ### Fixes **SD-JWT VC Display Info Loading** When you issue an SD-JWT VC credential using the credentials/issue endpoint of the issuer service, the display information will now be correctly retrieved from the issuer service configuration. This happens if you do not provide any display information directly in your issuance request. **Wallet Service Key Enforcement** The wallet presentation flow no longer enforces that holder keys must be secp256r1. Applications can now present non‑MDOC credentials with other key types. ### Docs **OpenAPI documentation improved** Each endpoint now features clearer descriptions, and the OpenID4VCI specifications include the standardVersion as a path parameter. These updates ensure that clients generated from api.json accurately reflect the API behavior. ## 0.6.0 ### Features **Wallet API Enterprise Stack** Wallet API Enterprise Stack (Skeleton) Phase 1 implementation with default SDK authentication handling. **mDL Support** - mDL verification capabilities - mDL issuance functionality - mDoc credential layer integration - mDoc enterprise UI - mDL enhancements with removed DID requirement **DID Registry** - DID registry implementation - DID.json routing improvements **Credential Features** - Credential status compatibility check when processing credential requests - Credential display functionality - Relational Constraint support in Presentation Definition **Documentation & Configuration** - Updated Wallet Swagger documentation - Quality Gate configuration improvements - Metadata endpoints for resources **Authentication & Security** - Initial auth code flow - Recommended TLS protocol version implementation ### Fixes - Minor README.md improvements - Global log context enhancements - Conflicting import resolution - AWS Crypto module registration - Missing acceptTarget from update config endpoint - Credential status version updates - Credential configuration decoding improvements for authorization servers ## 0.5.0 ### Features **Events & Metrics** Track events like credential issuance and verification with metadata such as exchange protocol, status, session ID and more. You can also aggregate events for metrics. [Learn more](https://docs.walt.id/enterprise-stack/administration/events-and-metrics/overview.md) **Logging** The enterprise stack logs can now be adjusted to contain tracking IDs. IDs can be provided with the credentials/issue and credentials/verify endpoints. **Enterprise Quickstart CLI** Explore and learn about different Enterprise features with our CLI tool. Execute a single command like "create organization" or use the wizard to get a walkthrough of how to set up the enterprise stack end-to-end. [Learn more](https://github.com/walt-id/waltid-enterprise-quickstart) **Enterprise Stack UI** Create tenants, manage them, and utilize services such issuer, verifier, or credential status directly from your browser via the Enterprise UI. The user interface is currently designed for admin users (fain-grained access coming later) and does not yet include the complete range of features available via API. However, feature expansion will come in the following months. **Credential Status for SD-JWT VC credentials** Enable credential status for SD-JWT VCs using the Token Status List standard. [Learn more](https://docs.walt.id/enterprise-stack/services/credential-status-service/token-status-list) **Presentation Request URL Endpoint in Verifier API** Get the credential presentation request URL with the v1/{target}/verifier-service-api/credentials/sessions/presentation-request-url/view endpoint. ## 0.4.0 ### Features
**Authentication** **Login Token Security** - Provide signing and verification keys for login tokens securely stored in an external AWS Key Management Service (KMS). [Learn more here](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/auth.md) **Issuer API** **Support for OpenID4VCI Draft 11 & 13** - The Enterprise Issuer API now supports OpenID4VCI Draft 11 and Draft 13, focusing on the PreAuthorized Code Flow. Users can provide an optional `standardVersion` parameter in the issuance request to specify the required protocol version `DRAFT11` or `DRAFT13`. If omitted, `DRAFT13` will be used. **Authorization Code Flow in Issuer API** - The Issuer API now supports the authorization code flow using `ID_TOKEN` and `VP_TOKEN` as authentication methods for OpenID4VCI Draft 11 and 13. This new feature introduces an `authenticationMethod` parameter in the `/credentials/issue` request, allowing for explicit specification of the desired authentication method. ### Breaking Changes **Verifier API Presentation Definition Policy** - The implementation has been corrected to include the "vc" part in compliance with the [Presentation Exchange Specification v2.0.0.](https://identity.foundation/presentation-exchange/spec/v2.0.0/) The previous implementation was incorrect because the evaluation of JSON paths starts from the root of the JWT claims segment. In the case of JWT VC JSON for the W3C VC Data Model v1.1, this segment contains a `vc` property where the credential data exists. --- ## 0.3.0 ### Features - Issuer Service OpenID metadata display information - [Learn more](https://docs.walt.id/enterprise-stack/services/issuer-service/configurations/openid-metadata/display) - Verifier Service client display information - [Learn more](https://docs.walt.id/enterprise-stack/services/verifier-service/configurations/openid-metadata.md) - Login Token Expiration Config Options (Accounts & Super Admins) - [Learn more](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/auth.md) - API Key Expiration Options - [Learn more](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/api-keys/create-api-key.md) - Issuer callback support - [Learn more](https://docs.walt.id/enterprise-stack/services/issuer-service/credential-issuance/vc-oid4vc.md) ### Fixes - Remove MongoDB password from appearing in logs - Fix double slash issue in OIDC issuer offer URL ### Breaking Changes - Config file structure for super-admin tokens - [Find updated config file here](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/superadmin-registration.md) --- ## 0.2.0 ### Features - KMS Service: AWS integration now offers instance Auth Authentication - API Service: Added List API keys endpoint - Truststore MongoDB SSL configuration - Added list accounts for each tenant endpoint - Added list accounts for each organization endpoint ### Improvements - Automatically add basic organization endpoint information to OpenAPI docs - Showcase required permissions on SWAGGER Docs API endpoints - Import / delete key feature from waltid-crypto keys in KMS service - Improved error handling & messages ### Fixes - Fix API key role assign - Fix edge-case of NullPointer by database when the default admin role is deleted and this was the users only role - Fix illegal character issue (wrong verifier openapi docs) -------------------------------------------------------------------------------- PAGE: Pre Releases URL: https://docs.walt.id/enterprise-stack/release-notes/pre-releases RAW: https://docs.walt.id/enterprise-stack/release-notes/pre-releases.md STACK: Enterprise Stack (commercial) — version 0.21.0 -------------------------------------------------------------------------------- # Pre Releases Every so often, we release pre-releases of the Enterprise Stack. These are not yet stable releases, but they are a good way to get a feel for the new features and improvements. This page will maintain a list of the current pre-releases from the previous release **only**. Pre-releases may contain breaking changes, so please be careful when upgrading. It should mainly be used for testing and feedback purposes.