Credential Presentation (SD-JWT)
The vp_token for an SD-JWT VC presentation is a single string with three types of components, delimited by tildes (~):
<Issuer-Signed-JWT>~<Disclosure-1>~<Disclosure-2>~...~<Key-Binding-JWT>| Component | Description |
|---|---|
| Issuer-Signed JWT | The base SD-JWT signed by Proof. Contains hashed claim digests in the _sd array, the holder's public key in the cnf claim, and the credential type in vct. |
| Disclosures | Base64url-encoded arrays of [salt, claim_name, claim_value]. Only claims requested by the provided scope value will be returned. |
| Key Binding JWT | Signed by the holder's private key (in Proof's cloud wallet). Contains nonce, aud, and iat. Proves holder possession, prevents replay, and optionally binds the presentation to a specific set of transaction data. |
Sample Payloads: Identity Credential
The following shows a complete, decoded VP Token for a https://credentials.notarize.com/ProofCredentialV1 credential presentation where the verifier requested given_name, family_name, and birthdate. Each component is shown separately so you can see exactly what to expect.
Issuer-Signed JWT Header
{
"alg": "ES256",
"typ": "dc+sd-jwt",
"kid": "d96f1ca3-864f-49c8-92c7-17d341ae1234"
}The
kidvalue references a key in Proof's JWKs endpoint. Always fetch the key dynamically — never hardcode it, as Proof rotates signing keys periodically.
Issuer-Signed JWT Payload
{
"iss": "https://api.proof.com",
"sub": "user_8f3a92b1-47d6-4e2c-b5a0-1c9d3e7f6a84",
"vct": "https://credentials.notarize.com/ProofCredentialV1",
"iat": 1739280000,
"cnf": {
"jwk": {
"kty": "EC",
"crv": "P-256",
"x": "b28d4MwZMjw8-00CG4xfnn9SLMVMM19SlqZpVb_uNtQ",
"y": "Xv5zWwuoaTgdS6hV43yI6gBwTnjukmFQQnJ_kCxzqk8"
}
},
"_sd": [
"--NrmJe6y2u_S2dC8RKHHv3rElWsqpY8r6xslJuwaJI",
"JzYjH4svliH0R3PyEMfeZu6Jt69u5qehZo7F7EPYlSE",
"UgEtkU6-DwPQXQM-UrsaWMd7MOWMB_3iZjDeyp6gvUY",
"S4sER_VP45OHkmC7-BVsj2NqlA_0jhsJE7P0rTkkUFQ",
"bViw5OlxKJBrlXkjSBkz2MERPl1gUHnKxHmkR6Zf6Mk",
"h4JMnGjFOh9HfOB-r09jqeMxEpGELe3BDLn_fuY34vs",
"qF42T_IHyzGvTIGLH1gPMFGVLW92-Ol9HwFBaFDuGB4",
"UkVeSoup2-mIUXvkbsm2aQr6BdwyW34Tydo6PtLINQ0"
]
}Payload — What to validate
| Field | Validation |
|---|---|
iss | Must exactly match https://api.proof.com (or sandbox equivalent). Reject if different. |
vct | Must match the vct_values you specified in your DCQL query. For this example: https://credentials.notarize.com/ProofCredentialV1. |
iat | Issuance timestamp. Use for audit logging. Optionally reject credentials older than your policy allows. |
cnf.jwk | The holder's public key. You'll use this to verify the Key Binding JWT signature. Do not use this for anything else. |
_sd | Array of base64url-encoded SHA-256 digests. Each disclosure you receive must hash to one of these values. |
Disclosures (3 of 8 disclosed)
Only the three claims requested via the provided scope parameter are disclosed. The remaining five claims in the credential (address, document_number, etc.) are not included — the verifier never sees them.
Disclosure 1 – given_name (base64url decoded)
["2GLC42sKQveCfGfryNRN9w", "given_name", "Darren"]Disclosure 2 – family_name (base64url decoded)
["eluV5Og3gSNII8EYnsxA_A", "family_name", "Louie"]Disclosure 3 – date_of_birth (base64url decoded)
["6Ij7tM-a5iVPGboS5tmvVA", "birthdate", "1990-07-15"]Disclosure format
| Element | Type | Description |
|---|---|---|
[0] | string | Salt — Random value that prevents rainbow-table attacks on claim digests. You don't use this directly, but it's part of the hash input. |
[1] | string | Claim name — The name of the disclosed attribute (e.g. given_name). This is the key you'll use when extracting claims. |
[2] | any | Claim value — The actual value. Type depends on the claim: strings for names, ISO 8601 date strings for dates, objects for structured data like addresses. |
Disclosure integrity check: For each disclosure, compute
SHA-256(base64url_encoded_disclosure_string)and verify the resulting digest appears in the_sdarray. Hash the raw base64url string as received in the VP Token — not the decoded JSON.
Key Binding JWT
Header:
{
"alg": "ES256",
"typ": "kb+jwt"
}Payload:
{
"nonce": "n-0S6_WzA2Mj_6a8bRs2TU",
"aud": "camdrbpxd",
"iat": 1739280120,
"sd_hash": "fUHyO2r2Z3DZ53EsNrWBb0xWXoaNy59IiKCAqksmQEo"
}Key Binding JWT — What to validate
| Field | Validation |
|---|---|
nonce | Must exactly match the nonce you sent in the authorization request. This proves the presentation was created for your session. |
aud | Must match your client_id value`. |
iat | Presentation creation time. Reject if too far in the past (recommended: no more than 5 minutes). This limits the replay window. |
sd_hash | SHA-256 hash over the Issuer-Signed JWT concatenated with all disclosures (with ~ delimiters). Binds the KB-JWT to this specific presentation. |
| Signature | Verify using the public key from the cnf.jwk claim in the Issuer-Signed JWT. This proves the holder (Proof's cloud wallet) authorized this presentation. |
Transaction Data
The key binding JWT will, if requested, contain data from the transaction_data object passed in the authorization request. Depending on the transaction data type, data will be included in a top-level claim in the key binding JWT which will include the same claims as the payload claim in the transaction data request object. Those top-level claims are as follows:
Updated 2 days ago