Credential Formats & Selective Disclosure
What We Are Doing:
Choosing between the two Verifiable Credential formats the platform issues: SD-JWT VC and JWT-VC-JSON.
Understanding credential configuration ids of the form
name@version:format, and how a single schema can expose more than one of them.Configuring selective disclosure for SD-JWT VC credentials with a
disclosureFrame, so a holder can reveal individual claims without exposing the rest.
Why: The format you issue in determines what a credential looks like on the wire and what a holder can do with it when presenting. JWT-VC-JSON is a familiar W3C Verifiable Credential serialized as a JWT — the whole credential is revealed when presented. SD-JWT VC adds selective disclosure: each disclosable claim is individually salted and hashed, so the holder can present only the claims a Verifier asks for (for example, age without first_name or last_name). Picking the right format up front means your schema, your credential offers, and your verification requests all line up.
This page assumes you have already created and assigned a schema (see Uploading the Credential Schema). The same environment variables apply:
ISSUER_BASE_URL=https://your-issuer.evdi.app
ISSUER_DID=did:web:your-issuer.evdi.app:8f3c... # your Issuer's DID
TOKEN=<your OIDC access token> # JWT with the admin roleEvery call below requires a valid OIDC Bearer token.
Local development: with
AUTH_DISABLED=trueyou can omit theAuthorizationheader — see Authenticating to the API.
The two credential formats
The Issuer signs credentials in one of two formats. You select the format at the schema level (which formats a schema enables) and again per credential offer (which configuration id you offer).
SD-JWT VC — a Selective Disclosure JWT Verifiable Credential. In schema configuration this format is
sd-jwt; in the direct-signing API request you sendsd-jwt-vc. At runtime the platform identifies these credentials with the format tokendc+sd-jwt— that is the value the signing and verification responses carry, and the value DCQL queries use (the IETF media type for the credential isapplication/dc+sd-jwt). Each disclosable claim is salted and hashed individually, so the holder can present a subset of claims. SD-JWT VC credentials carry avct(Verifiable Credential Type) value that identifies the credential type.JWT-VC-JSON — a W3C Verifiable Credential serialized as a JWT. In schema configuration this format is
jwt-vc-json; in the direct-signing API it is identified asjwt_vc_json. The credential'stypearray (for example["VerifiableCredential", "KYCCredential"]) describes what it is. When presented, the credential is revealed in full — there is no per-claim selective disclosure.
Schema formats value
sd-jwt
jwt-vc-json
Signing API request format value
sd-jwt-vc
jwt_vc_json
Runtime response format token
dc+sd-jwt
jwt_vc
Type identifier
vct (a single string)
jwtVcTypes (a type array)
Selective disclosure
Yes, via disclosureFrame
No (whole credential is revealed)
Use SD-JWT VC when you want holders to share only specific claims (the most common choice, and the one used throughout this tutorial). Use JWT-VC-JSON when you need a classic W3C VC and full-credential disclosure is acceptable.
Credential configuration ids: name@version:format
A schema is identified by its name and version (for example [email protected]). A schema combined with a single format produces a credential configuration id of the form name@version:format. This is the value you reference when creating credential offers and what wallets discover in the Issuer metadata.
If a schema enables both formats, it exposes two configuration ids — one per format. For a [email protected] schema with formats: ["sd-jwt", "jwt-vc-json"]:
[email protected]:sd-jwt[email protected]:jwt-vc-json
The formats field controls which configuration ids exist:
The schema above enables only sd-jwt, so it yields a single configuration id, [email protected]:sd-jwt. Omitting formats enables both formats by default, giving you both [email protected]:sd-jwt and [email protected]:jwt-vc-json.
A few format-specific schema fields are worth calling out:
vct— used for the SD-JWT VC type. It identifies what the credential is and is the value a Verifier matches against when requesting SD-JWT credentials.jwtVcTypes— used for JWT-VC-JSON. It populates the credential'stypearray (for example["VerifiableCredential", "KYCCredential"]). If omitted, sensible defaults are derived from the schema name.disclosureFrame— used for SD-JWT VC selective disclosure (explained next). It is ignored for JWT-VC-JSON.
Selective disclosure with disclosureFrame
Selective disclosure is the defining feature of SD-JWT VC. When a credential is issued, every claim listed in its disclosure frame is encoded as a separate, salted disclosure. The signed credential body only contains the hashes of those disclosures, so the holder's wallet can choose, at presentation time, which disclosures to reveal. Claims not listed in the disclosure frame are always present in the credential and are not individually hide-able.
There are two places a disclosure frame appears, depending on how you issue.
In a schema (the OpenID4VCI flow)
When you issue through a credential offer, the disclosure frame comes from the schema. Set disclosureFrame to the list of claim keys that should be selectively disclosable:
With this configuration, a holder who has claimed a [email protected]:sd-jwt credential can later present, say, only age to a Verifier and keep first_name and last_name hidden. If you leave disclosureFrame out, every claim defined in the schema's properties becomes selectively disclosable by default.
Once the schema is created and assigned, you issue exactly as in Issuing Credentials — there is nothing extra to send per offer, because the disclosure frame is already baked into the configuration. The claim values still travel through issuanceMetadata.claimsByConfigurationId:
In a direct signing call
For one-off credentials that do not need a wallet/offer flow, the Issuer can sign a credential directly. Here you provide the disclosure frame inline, in the shape { "_sd": [ ... ] }.
POST {ISSUER_BASE_URL}/issuers/{issuerDid}/credentials/sign
Signs a single credential and returns it in compact serialized form. No issuance session or wallet is involved.
Request Body (application/json):
format(string, required) —sd-jwt-vcorjwt_vc_json.payload(object, required) — the claims to sign. For SD-JWT VC, include avctvalue.subjectDid(string, optional) — the DID of the credential subject; used as the subject identifier in the credential.disclosureFrame(object, optional) — SD-JWT only. An object{ "_sd": [ ... ] }whose_sdarray lists the claim keys that should be selectively disclosable.
Response Body:
credential— the signed credential in compact serialized form (for SD-JWT VC, the JWT followed by~-separated disclosures).format— the resolved, canonical credential-format token (for an SD-JWT VC,dc+sd-jwt). Note this is not identical to theformatenum you send in the request (sd-jwt-vc); for a JWT-VC-JSON credential the response token isjwt_vc.
Example request:
Example response:
The trailing ~-separated segments are the disclosures for age, first_name, and last_name. To sign a JWT-VC-JSON credential instead, set format to jwt_vc_json and omit disclosureFrame — that format does not support per-claim disclosure.
Inspecting what was disclosed
You can decode any credential string — compact JWT or SD-JWT — with the Verifier's direct verification endpoint to confirm which claims it carries.
POST {VERIFIER_BASE_URL}/credentials/verify
Request Body:
credential— the compact JWT or SD-JWT string to check.
Response Body (when valid):
isValid—true.format— the detected, canonical format token (for exampledc+sd-jwtfor an SD-JWT VC,jwt_vcfor a JWT-VC-JSON credential).payload— the decoded credential payload.prettyClaims— for SD-JWT credentials, all disclosed claims merged into a single readable object. (When invalid, the response is{ "isValid": false, "error": "..." }, plus the detectedformatwhenever the credential could be parsed.)
For an SD-JWT VC, prettyClaims reflects exactly the disclosures attached to the credential string you passed in — so it is a quick way to verify that selective disclosure is working as expected.
Matching formats during verification
The format you issue must match the format your Verifier asks for. When you build an OpenID4VP authorization request (see Creating a Verification Endpoint), the credential query targets a specific format and can request individual claim paths — which is what makes selective disclosure usable end to end. For example, as a DCQL query:
A Presentation Exchange definition expresses the same targeting with a vc+sd-jwt format entry and limit_disclosure: "required" — that is the form the tutorial's verification endpoint sends, because it can also constrain claim values (e.g. age >= 18). Because the credential was issued as SD-JWT VC with age in its disclosure frame, the wallet can answer either request by revealing only age. Had the same credential been issued as JWT-VC-JSON, the wallet would have to present the entire credential to satisfy the request.
With formats and selective disclosure understood, you can pick sd-jwt for privacy-preserving credentials, scope each credential's disclosureFrame to the claims holders should control, and reference the resulting name@version:format configuration ids in your offers and verification requests.
Last updated