#How to Import Verifiable Credentials (JWT/SD-JWT) with walt.id
TL;DR
Learn how to import signed Verifiable Credential JWTs (including SD-JWTs) into your walt.id wallet using both the web wallet UI and the Wallet API, with built-in validation, signature verification, and duplicate prevention.
What you'll learn
- Import signed JWT and SD-JWT credentials via the web wallet UI
- Use the Wallet API import endpoint to store credentials programmatically
- Associate imported credentials with specific DIDs in your wallet
- Understand the validation process including signature verification and duplicate checking
- Handle import responses and error codes
Relevant concepts
This guide walks you through importing signed Verifiable Credential JWTs (including SD-JWTs) into your walt.id wallet. You can import credentials either through the web wallet UI or programmatically via the API.
Before importing credentials, ensure you have:
- A walt.id wallet set up and running. If you haven't set up a wallet yet, follow the wallet setup guide.
- A signed Verifiable Credential in JWT or SD-JWT format that you want to import.
- A DID in your wallet to associate the credential with (the credential's subject must match a DID owned by your wallet).
There are two ways to import credentials into your wallet:
- Web Wallet UI – Use the "Import credential (JWT)" page in the wallet web interface
- Wallet API – Call the
POST /credentials/import endpoint directly
Both methods perform the same validation checks to ensure credential integrity.
The wallet web interface provides an intuitive page for importing credentials:
- Navigate to Import Page
In the wallet header, click the "Import credential (JWT)" button to access the import page. - Paste Your Credential
Enter your signed JWT or SD-JWT credential in the text input field. The credential should be in the standard JWT format (three Base64URL-encoded parts separated by dots). - Select Associated DID
Choose which DID in your wallet should be associated with this credential. The wallet auto-selects your default DID, but you can manually override this selection. Note that the credential's subject (sub claim) must match a DID owned by your wallet. - Submit the Import
Click the "Import" button to process the credential. The UI will disable the buttons during processing and display either a success message or an error description. - Clear and Retry (if needed)
Use the "Clear" button to reset the form if you need to import a different credential.
- Auto-select default DID – The wallet automatically selects your default DID for convenience
- Manual DID override – Choose any DID from your wallet to associate with the credential
- Real-time feedback – Success and error messages are displayed immediately after import
- Input validation – The form validates that a JWT is provided before submission
Use the Wallet API to programmatically import credentials into a wallet.
Endpoint:
POST /wallet-api/wallet/{wallet_id}/credentials/import | API Reference
Example Request:
curl -X 'POST' \
'https://wallet.demo.walt.id/wallet-api/wallet/{wallet_id}/credentials/import' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <your_access_token>' \
-d '{
"jwt": "eyJraWQiOiJWeng3bDVmaDU2RjNQZjlhUjNERUNVNUJ3ZnJZNlpKZTA1YWlXWVd6YW44IiwiYWxnIjoiRWREU0EifQ.eyJpc3MiOiJkaWQ6a2V5Ono2TWtqb1JocTFqU05KZExpcnVTWHJGRnhhZ3FyenRaYVhIcUhHVVRLSmJjTnl3cCIsInN1YiI6ImRpZDprZXk6ejZNa2pvUmhxMWpTTkpkTGlydVNYckZGeGFncXJ6dFphWEhxSEdVVEtKYmNOeXdwIiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy9leGFtcGxlcy92MSJdLCJpZCI6Imh0dHA6Ly9leGFtcGxlLmdvdi9jcmVkZW50aWFscy8zNzMyIiwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIlVuaXZlcnNpdHlEZWdyZWVDcmVkZW50aWFsIl0sImlzc3VlciI6eyJpZCI6ImRpZDp3ZWI6dmMudHJhbnNtdXRlLndvcmxkIn0sImlzc3VhbmNlRGF0ZSI6IjIwMjAtMDMtMTBUMDQ6MjQ6MTIuMTY0WiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmV4YW1wbGU6ZWJmZWIxZjcxMmViYzZmMWMyNzZlMTJlYzIxIiwiZGVncmVlIjp7InR5cGUiOiJCYWNoZWxvckRlZ3JlZSIsIm5hbWUiOiJCYWNoZWxvciBvZiBTY2llbmNlIGFuZCBBcnRzIn19fX0.PureDss8Z-w3fyrraVEds0-qftaadS6i7JqF1zxjxAUpFY2qUNGNiiQUaQ5pACFVRmJv7bw1SQZqnkZgstDlAA",
"associated_did": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiaDRDcmJUYnc1NG8wTFMtLWtMTUUzSFZ4ZFpTczNHa0YyS1BUSEl0R0lqbyIsIngiOiJWZ0JFc0hwQmp3aE8waXZQWlpuS0MtY3dfeHNDYjZEbEhxUnd6SFRkN0RZIn0"
}'
| Parameter | Type | Required | Description |
|---|
jwt | string | Yes | The signed Verifiable Credential in JWT or SD-JWT format |
associated_did | string | Yes | The DID in your wallet to associate the credential with |
| 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 (duplicate) |
The import process performs comprehensive validation to ensure credential integrity and prevent unauthorized imports:
- JWT/SD-JWT Format Validation
The credential must be a valid JWT or SD-JWT with proper structure (header, payload, signature). - VC Structure Validation
The JWT payload must contain valid Verifiable Credential claims according to the W3C specification. - Signature Verification
The issuer's signature is cryptographically verified to ensure the credential hasn't been tampered with. - DID Binding Validation
The credential's subject (sub claim) must match a DID owned by your wallet. This ensures you can only import credentials that were issued to you. - Time Window Validation
The credential's validity period is checked:iat (issued at) – Must not be in the futureexp (expiration) – Must not be expired (if present)
- Duplicate Prevention
The wallet checks if the credential already exists to prevent duplicate imports. If a duplicate is detected, the import is rejected with a 409 Conflict response.
When validation fails, the API returns descriptive error messages to help identify the issue:
| Error | Description |
|---|
Invalid JWT format | The provided string is not a valid JWT |
Invalid VC structure | The JWT payload doesn't contain valid VC claims |
Signature verification failed | The credential's signature is invalid |
Credential subject does not match wallet DID | The sub claim doesn't match the associated_did |
Credential is expired | The credential's exp claim is in the past |
Credential already exists | A credential with the same ID is already stored |
Here's an example of a valid JWT payload structure for a W3C Verifiable Credential:
{
"iss": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp",
"sub": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp",
"vc": {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1"
],
"id": "http://example.gov/credentials/3732",
"type": ["VerifiableCredential", "UniversityDegreeCredential"],
"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"
}
}
},
"iat": 1618884473,
"exp": 1672531199,
"jti": "urn:uuid:123e4567-e89b-12d3-a456-426614174000"
}
| Claim | Description |
|---|
iss | The DID of the credential issuer |
sub | The DID of the credential subject (must match a wallet DID) |
vc | The Verifiable Credential payload containing the credential data |
iat | Issued at timestamp (Unix epoch) |
exp | Expiration timestamp (Unix epoch, optional) |
jti | JWT ID for unique identification |
The import endpoint also supports Selective Disclosure JWTs (SD-JWTs). SD-JWTs allow for selective disclosure of credential attributes during presentation.
When importing an SD-JWT:
- The full credential (including all disclosures) is stored in the wallet
- The signature is verified against the SD-JWT's key binding
- All validation checks apply as with regular JWTs
SD-JWTs follow the format: <issuer-signed-jwt>~<disclosure1>~<disclosure2>~...~<optional-key-binding-jwt>
- Always verify the source – Only import credentials from trusted issuers
- Check expiration dates – Expired credentials will be rejected
- Use the correct DID – Ensure the
associated_did matches the credential's subject - Handle errors gracefully – Implement proper error handling for all response codes
- Avoid duplicate imports – Check if a credential exists before attempting to import