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
Selective disclosure 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 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 (OID4VP) 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:
- The issuer prepares the VC payload, including context, types, schemas, and claims.
- 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.
- These hashed claim values are stored in the SD-JWT, and optional decoy hashes can be added to obscure the true number of claims.
- The issuer signs the SD-JWT using JWS (for example, EdDSA) and sends the signed credential plus all disclosures to the holder using for example the OID4VCI exchange standard.

For example, an SD-JWT VC for age verification might contain a full date of birth and other personal data, but the holder only discloses a single derived claim such as age_over_18: true to the verifier.
Verification follows a similar pattern. A verifier requests specific attributes using common protocols like OID4VP, and the holder returns only the relevant disclosures along with the SD-JWT. The verifier re-hashes each disclosure, compares it to the stored hashed value, and verifies the issuer’s signature. If everything matches, the verifier knows the claim is valid and untampered. You can refer to the OID4VP guide for further detail on how a typical verification flow looks like.
walt.id’s implementation follows the IETF specification closely and is continuously updated to align with new versions of the standard as they are published.
mDL/mdoc (ISO 18013-5 and 18013-7)
ISO mobile driving licences (mDL) and mobile documents (mdoc) implement selective disclosure very differently from SD-JWT VC (IETF). Instead of hashing selectively disclosable claims, all data elements are hidden by default. A verifier can only access requested attributes during an authenticated, device-bound session initiated through NFC, BLE, QR code scanning or via OID4VP with 18013-7.
For example, an mDL could disclose only a single attribute such as age_over_18 or driving_privilege: yes, while keeping all other personal details—like full name, address, or licence number—hidden by default.
In this model, the mdoc proves possession of its keys and authorizes the release of specific data elements during the session. It is highly optimized for offline or proximity-based interactions such as border control, airport checkpoints, police stops, and age-restricted purchases.
Characteristics of ISO mDL/mdoc:
- Selective disclosure at the data-element level
- Strong device binding (proof the data is coming from the legitimate device)
- Ephemeral sessions that protect against correlation
- Use of ISO rather than JWT/JWS cryptographic primitives
Where SD-JWT is ideal for web-based, API-driven online verification flows, mDLs excel in controlled, physical environments with secure device-to-verifier sessions, but can also support online verification through ISO 18013-7 and OID4VP.
Mono-Claim Credentials
Mono-claim credentials achieve selective disclosure through credential granularity instead of cryptography. Each credential contains exactly one attribute, meaning the holder can share or withhold individual attributes simply by choosing which credential to present. Because they rely on this structural approach rather than a specific cryptographic format, mono-claim credentials are largely standard-independent and can be issued as W3C VCs, SD-JWT VCs, or other compatible credential types.
Example of an age-verification credential:
{
"@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. This model 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:
- Salt Generation: The issuer generates a cryptographically random salt for each selectively disclosable claim.
- Disclosure Creation: The salt, claim name, and claim value are combined into a JSON array:
[salt, claim_name, claim_value]. - Base64url Encoding: This array is encoded as a base64url string, creating the "disclosure."
- Hash Computation: The disclosure is hashed using SHA-256, and the resulting hash is base64url encoded.
- Storage: The hash (not the plaintext) is stored in the credential's
_sdarray. 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:
{
"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 Issuer2 API (Open Source)
We use the walt.id Issuer2 API to create an identity credential where birthdate is selectively disclosable—meaning the holder can later choose whether to reveal it. Issuer2 issues from profiles (reusable configurations declared ahead of time) rather than a signing key and credential data posted with every request. In the shipped default profile, identityCredentialSdJwt, birthdate is the one claim marked sd: true; every other claim is issued in the clear. To make additional claims selectively disclosable, define your own profile.
You can run this directly against the hosted demo issuer or set up your own instance:
curl -X 'POST' \
'https://issuer2.demo.walt.id/issuer2/credential-offers' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"profileId": "identityCredentialSdJwt",
"authMethod": "PRE_AUTHORIZED"
}'
Response (Credential Offer URL):
{
"offerId": "48f1dc54-134c-4e10-b889-17f3c0a595bf",
"profileId": "identityCredentialSdJwt",
"authMethod": "PRE_AUTHORIZED",
"expiresAt": 1782898162344,
"credentialOffer": "openid-credential-offer://?credential_offer_uri=https%3A%2F%2Fissuer2.demo.walt.id%2Fopenid4vci%2Fcredential-offer%3Fid%3D48f1dc54-134c-4e10-b889-17f3c0a595bf"
}
Want to issue your own credential type with custom fields, rather than the shipped default profile? See Defining Your Own Profile.
Step 2: Claim the Credential in a Wallet
Product: walt.id Wallet API v2 (Open Source)
The holder's wallet claims the credential using the credentialOffer URL. Wallet API v2 is the wallet that speaks OID4VCI 1.0, and it is an API rather than a web UI, so each step below is a call. Run them against the hosted demo instance at https://wallet-api2.demo.walt.id, or run the wallet yourself.
The v1 Web Wallet does not work with credentials issued by Issuer2 — use Wallet API v2 instead.
1. Create a wallet and a holder key. A wallet is a container for keys and credentials, and the credential is bound to a key inside it — so both have to exist before the holder can claim anything. This is one-time setup: the same walletId is reused in every call that follows.
# 1. Create the wallet. The returned walletId identifies it in every call below.
curl -X 'POST' 'https://wallet-api2.demo.walt.id/wallet' \
-H 'Content-Type: application/json' -d '{}'
# -> {"walletId":"b069bf55-ed8d-40da-a8a2-31215637f810"}
# 2. Generate the holder key the credential will be bound to, and that later
# signs the presentation.
curl -X 'POST' 'https://wallet-api2.demo.walt.id/wallet/{walletId}/keys/generate' \
-H 'Content-Type: application/json' \
-d '{ "backend": "jwk", "keyType": "secp256r1" }'
2. Claim the offer. One call resolves the offer, gets an access token, signs a proof of possession, fetches the credential and stores it:
curl -X 'POST' 'https://wallet-api2.demo.walt.id/wallet/{walletId}/credentials/receive' \
-H 'Content-Type: application/json' \
-d '{
"offerUrl": "openid-credential-offer://?credential_offer_uri=https%3A%2F%2Fissuer2.demo.walt.id%2Fopenid4vci%2Fcredential-offer%3Fid%3D48f1dc54-134c-4e10-b889-17f3c0a595bf"
}'
{
"credentialIds": ["9b2be375-8fec-4464-8121-26ee4c4ae0b9"],
"deferredTransactionIds": {}
}
The credential is now stored in the wallet.
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:
{
"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:16db85c2-6a60-4339-94f8-617ef7a7852f",
"iat": 1765984477,
"nbf": 1765984477,
"exp": 1797520477,
"_sd_alg": "sha-256",
"iss": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6IkcwUklOQmlGLW9RVUQzZDVER25lZ1F1WGVuSTI5SkRhTUdvTXZpb0tSQk0iLCJ5IjoiZWQzZUZHczJwRXRycDd2QVo3QkxjYnJVdHBLa1lXQVQySlBVUUs0bE40RSJ9",
"cnf": {
"jwk": {
"kty": "EC",
"crv": "P-256",
"kid": "g5NZkDNcGXI06zy7mFLG09PvFBwdBEDO7I3bn78etlM",
"x": "woHr8cHiXFB4HYBnIqqeudpZme6XkmkwVgLg9QqlGs0",
"y": "OtX0EgOF28c6-FYpHfr5xceyGjllH75CV6S92AwBTcU"
}
},
"vct": "https://issuer2.demo.walt.id/openid4vci/identity_credential",
"_sd": [
"79NlaXRtPaElW4Pp9weAd1ck7e1jXvSgBTgDAzzibIw"
]
}
Notice that birthdate is not visible in the payload — only its hash appears in _sd. The holder (wallet) holds the corresponding disclosure and can reveal the value when needed. Every other claim, including family_name, is issued in the clear and is always visible to anyone the credential is presented to.
Step 3: Verify the Credential (Request Specific Claims)
Product: walt.id Verifier2 API (Open Source)
Now a verifier needs to confirm the holder is over 18, and would also like their date of birth on file — but is willing to proceed without it. In DCQL that is expressed with claim_sets: a list of acceptable claim combinations, most-preferred first. Below, the first set asks for both claims and the second for is_over_18 alone, which makes birthdate optional and leaves the decision to the holder.
This is the detail that puts the holder in control. A claim listed in claims without claim_sets is required — the wallet treats it as mandatory and offers no choice. Making it optional is what turns the disclosure into a decision.
You can run this against the hosted demo verifier or set up your own instance:
curl -X 'POST' \
'https://verifier2.demo.walt.id/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://issuer2.demo.walt.id/openid4vci/identity_credential"]
},
"claims": [
{ "id": "over18", "path": ["is_over_18"] },
{ "id": "dob", "path": ["birthdate"] }
],
"claim_sets": [
["over18", "dob"],
["over18"]
]
}
]
}
}
}'
Response (Verification Session):
{
"sessionId": "14d60fa2-4e7d-4a34-a291-80da0e053fab",
"bootstrapAuthorizationRequestUrl": "openid4vp://authorize?client_id=redirect_uri%3Ahttps%3A%2F%2Fverifier2.demo.walt.id%2Fverification-session%2F14d60fa2-4e7d-4a34-a291-80da0e053fab%2Fresponse&request_uri=https%3A%2F%2Fverifier2.demo.walt.id%2Fverification-session%2F14d60fa2-4e7d-4a34-a291-80da0e053fab%2Frequest",
"fullAuthorizationRequestUrl": "openid4vp://authorize?response_type=vp_token&client_id=redirect_uri%3Ahttps%3A%2F%2Fverifier2.demo.walt.id%2F...&state=60ed7c9f-38aa-43a5-84a3-fc81093ac707&response_mode=direct_post&nonce=6bec8d67-bc75-4648-ab96-b8dd9c166ab4&response_uri=...&dcql_query=...&client_metadata=..."
}
bootstrapAuthorizationRequestUrl is the short form that points the wallet at request_uri to fetch the request; fullAuthorizationRequestUrl carries every parameter inline. Either works — the short one is what you encode into a QR code.
Step 4: Present the Credential with Selective Disclosure
Product: walt.id Wallet API v2 (Open Source)
A one-call presentation would let the wallet pick the disclosures for you. Because we want the holder to decide, we drive the isolated flow: preview what would be shared, choose, then send.
1. Preview what the verifier is asking for. This resolves the request, matches the wallet's stored credentials and reports, per claim, whether the holder has a say:
curl -X 'POST' 'https://wallet-api2.demo.walt.id/wallet/{walletId}/credentials/present/preview' \
-H 'Content-Type: application/json' \
-d '{ "requestUrl": "<bootstrapAuthorizationRequestUrl from Step 3>" }'
The matched credential comes back with a disclosures list:
{
"credentialOptions": [
{
"queryId": "credential_1",
"credentialId": "9b2be375-8fec-4464-8121-26ee4c4ae0b9",
"disclosures": [
{ "path": "\"is_over_18\"", "name": "\"is_over_18\"", "value": true,
"selectivelyDisclosable": false, "required": true, "selectable": false },
{ "path": "\"birthdate\"", "name": "birthdate", "value": "1940-01-01",
"selectivelyDisclosable": true, "required": false, "selectable": true }
]
}
]
}
This is the wallet's consent screen in data form. is_over_18 is selectable: false — it sits in the signed payload and is always visible. birthdate is selectable: true, because it is selectively disclosable and the claim_sets made it optional. A wallet UI would render exactly this as a toggle.
2. Build the presentation with the holder's choice. List the disclosures to include in selectedDisclosureOptions — here, the holder agrees to share their birthdate:
curl -X 'POST' 'https://wallet-api2.demo.walt.id/wallet/{walletId}/credentials/present/build-vp-token' \
-H 'Content-Type: application/json' \
-d '{
"requestUrl": "<same requestUrl>",
"selectedCredentialOptions": [
{ "queryId": "credential_1", "credentialId": "9b2be375-8fec-4464-8121-26ee4c4ae0b9" }
],
"selectedDisclosureOptions": [
{ "queryId": "credential_1", "credentialId": "9b2be375-8fec-4464-8121-26ee4c4ae0b9", "path": "\"birthdate\"" }
]
}'
Pass "selectedDisclosureOptions": [] instead and the birthdate is withheld. Nothing else about the call changes.
3. Send it to the verifier. Pass back the vpToken from the previous step:
curl -X 'POST' 'https://wallet-api2.demo.walt.id/wallet/{walletId}/credentials/present/send-response' \
-H 'Content-Type: application/json' \
-d '{ "requestUrl": "<same requestUrl>", "vpToken": "<vpToken from step 2>" }'
{
"transmission_success": true,
"verifier_response": { "status": "received", "message": "Presentation received and is being processed." }
}
transmission_success only means the wallet delivered the presentation — the verdict comes from the verifier, which we check in Step 5.
Try it yourself: birthdate is the one claim under the holder's control:
- Disclose it — the wallet appends the
birthdatedisclosure, the verifier re-hashes it against_sd, and the value is shared. - Withhold it — the disclosure is simply left out and the verifier cannot learn your date of birth.
- What travels regardless — every claim the profile did not mark
sd: true(given_name,family_name,email,phone_number,address,is_over_21,is_over_65) is part of the signed payload and is sent whether the verifier asked for it or not. That is the practical argument for marking more claims selectively disclosable in your own profile.
What happens during presentation:
The wallet constructs the vp_token as follows:
<SD-JWT>~<disclosure>~<key_binding_jwt>
Where:
<SD-JWT>is the original signed credential, carrying the hash ofbirthdatein_sd<disclosure>is the salted["<salt>", "birthdate", "1940-01-01"]triple, sent only because you chose to reveal it<key_binding_jwt>proves the holder controls the credential, and commits to the exact set of disclosures viasd_hash
Withhold birthdate and the middle segment disappears — the credential and its signature stay valid, because the _sd hash stands on its own.
Example:
eyJhbGciOiJFUzI1NiIsImtpZCI6ImRpZDpqd2s6ZXlKcmRIa2lPaUpGUXlJc0ltTnlkaUk2SWxBdE1qVTJJaXdpZUNJNklrY3dVa2xPUW1sR0xXOVJWVVF6WkRWRVIyNWxaMUYxV0dWdVNUSTVTa1JoVFVkdlRYWnBiMHRTUWswaUxDSjVJam9pWldRelpVWkhjekp3UlhSeWNEZDJRVm8zUWt4alluSlZkSEJMYTFsWFFWUXlTbEJWVVVzMGJFNDBSU0o5IzAiLCJ0eXAiOiJkYytzZC1qd3QifQ.eyJhZGRyZXNzIjp7InJlZ2lvbiI6IkFueXN0YXRlIiwic3RyZWV0X2FkZHJlc3MiOiIxMjMgTWFpbiBTdCIsImNvdW50cnkiOiJVUyIsImxvY2FsaXR5IjoiQW55dG93biJ9LCJpc19vdmVyXzE4Ijp0cnVlLCJwaG9uZV9udW1iZXIiOiIrMS0yMDItNTU1LTAxMDEiLCJnaXZlbl9uYW1lIjoiSm9obiIsImZhbWlseV9uYW1lIjoiRG9lIiwiaXNfb3Zlcl8yMSI6dHJ1ZSwiaXNfb3Zlcl82NSI6dHJ1ZSwiZW1haWwiOiJqb2huZG9lQGV4YW1wbGUuY29tIiwiZXhwIjoxODIxMTg0MzQ3LCJuYmYiOjE3ODk2NDgzNDcsImlkIjoidXJuOnV1aWQ6MjJiZmEyNWQtNWM0Zi00ZTNhLTljNWUtODNlYzQ3Zjk5Y2UxIiwiaWF0IjoxNzg5NjQ4MzQ3LCJfc2RfYWxnIjoic2hhLTI1NiIsImlzcyI6ImRpZDpqd2s6ZXlKcmRIa2lPaUpGUXlJc0ltTnlkaUk2SWxBdE1qVTJJaXdpZUNJNklrY3dVa2xPUW1sR0xXOVJWVVF6WkRWRVIyNWxaMUYxV0dWdVNUSTVTa1JoVFVkdlRYWnBiMHRTUWswaUxDSjVJam9pWldRelpVWkhjekp3UlhSeWNEZDJRVm8zUWt4alluSlZkSEJMYTFsWFFWUXlTbEJWVVVzMGJFNDBSU0o5IiwiY25mIjp7Imp3ayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6IlNlX1JxV0VEeEdxbFB1ZUFjeEw3V1ZYR0hwaUhwY193VkZVSU1rcWNvTFEiLCJ5Ijoia0stSzljcjB5bGtTNlRHMmx3RjIzbVFWZ05tLXF1eFZqbVJ0LUhiSnQ3RSIsImtpZCI6IjRIa2tuNi1iVkZsVTRISGMxTUl2SklVNmJ4OWNXUEZ0WEw0WVEzVEhFc1UifX0sInZjdCI6Imh0dHBzOi8vaXNzdWVyMi5kZW1vLndhbHQuaWQvb3BlbmlkNHZjaS9pZGVudGl0eV9jcmVkZW50aWFsIiwiX3NkIjpbImFIS1dsVzR0WktQWFpWYnludjVCVzJvS3d1djk5TlFYNVdwR28zZUhGclEiXX0.lTVXtU4CYGhoDkO88Ps6e87N_WlfSx0ie_fvyPvMBPP01CurmeKsnXqmTKGIbZbAlDlorel8_l-Kqsleo5k1AA~WyJjWXVlWGd4ZlAxb2VVVUVZNGRXcjdRIiwiYmlydGhkYXRlIiwiMTk0MC0wMS0wMSJd~eyJhbGciOiJFUzI1NiIsInR5cCI6ImtiK2p3dCJ9.eyJpYXQiOjE3ODk2NDgzNDcsImF1ZCI6InJlZGlyZWN0X3VyaTpodHRwczovL3ZlcmlmaWVyMi5kZW1vLndhbHQuaWQvdmVyaWZpY2F0aW9uLXNlc3Npb24vN2I0M2E4YjItNmE5Ni00YWI5LWI4ZWQtZGNlODcyY2YxYjkxL3Jlc3BvbnNlIiwibm9uY2UiOiIwZjY4MDE1Yy0zOTg4LTQ3MTUtYWJlOC1jOGYyZmRiYTVhNjUiLCJzZF9oYXNoIjoiV1NocEhCTVFHdmZCci0wZVY0OHVnblVYZm9DQjBqOG1iam53NHBDQ05MbyJ9.ox_nkC2a_VMSEsXbm8qQ-DaOZCKsxmI3VKctEvw-IvAugaS9N7NxvNnQp-3Dga7H4E_GG3cMJ6bjkr1tA562pw
Step 5: Verification Result
The verifier receives the presentation and performs these checks:
- Signature verification: Confirms the SD-JWT was signed by a trusted issuer.
- Disclosure verification: Re-hashes each provided disclosure and matches against
_sd. - Key binding verification: Confirms the presenter holds the credential's private key.
curl -X 'GET' \
'https://verifier2.demo.walt.id/verification-session/{sessionId}/info' \
-H 'accept: application/json'
sessionId is the value returned as sessionId when the verification session was created in Step 3. A "status": "SUCCESSFUL" in the response confirms the verification passed. You can also use callbacks or Server-Sent Events (SSE) instead of polling — see Callbacks & SSE.
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:
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 - Issue SD-JWT VCs, W3C VCs, and mDL credentials with configurable selective disclosure
- Verifier Getting Started - Request and verify specific claims using OID4VP and DCQL
- Wallet Getting Started - Store credentials and present them with user-controlled disclosure selection
All components listed are open source and available on GitHub.
