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) and OpenID for Verifiable Presentations (OID4VP) 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.

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.
  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.

FeatureSD-JWT VCmDoc/mDL
Standardization BodyIETF (Internet Engineering Task Force)ISO/IEC (International Organization for Standardization)
Data FormatJSON (Text-based)CBOR (Binary)
Primary Use CaseOnline / Remote VerificationIn-Person / Device-to-Device Verification
Common Transport ProtocolsHTTP-based (e.g., OID4VP)NFC, Bluetooth, Wi-Fi Aware, QR Codes
Developer EcosystemFamiliar to web developers (JWT, JSON)Requires familiarity with binary data structures
Example ApplicationVerifying identity for a web servicePresenting 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": "<uuid>",
    "iat": "<timestamp-seconds>",
    "nbf": "<timestamp-seconds>",
    "exp": "<timestamp-in-seconds:365d>"
  },
  "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 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": "vc+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, 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: 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.

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.

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 (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 — a solution for building secure, compliant and robust identity and wallet solutions or platforms at scale.

Guides

Last updated on September 18, 2025