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

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.

Prerequisites

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

Import Methods

There are two ways to import credentials into your wallet:

  1. Web Wallet UI – Use the "Import credential (JWT)" page in the wallet web interface
  2. Wallet API – Call the POST /credentials/import endpoint directly

Both methods perform the same validation checks to ensure credential integrity.

Import via Web Wallet UI

The wallet web interface provides an intuitive page for importing credentials:

Steps

  1. Navigate to Import Page
    In the wallet header, click the "Import credential (JWT)" button to access the import page.
  2. 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).
  3. 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.
  4. 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.
  5. Clear and Retry (if needed)
    Use the "Clear" button to reset the form if you need to import a different credential.

UI Features

  • 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

Import via API

Use the Wallet API to programmatically import credentials into a wallet.

CURL

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"
}'

Request Body Parameters

ParameterTypeRequiredDescription
jwtstringYesThe signed Verifiable Credential in JWT or SD-JWT format
associated_didstringYesThe DID in your wallet to associate the credential with

Response Codes

StatusDescription
201Credential imported successfully
400Invalid JWT format, VC structure, or validation failed
401Invalid or missing authentication
409Credential already exists in the wallet (duplicate)

Validation and Security

The import process performs comprehensive validation to ensure credential integrity and prevent unauthorized imports:

Validation Checks

  1. JWT/SD-JWT Format Validation
    The credential must be a valid JWT or SD-JWT with proper structure (header, payload, signature).
  2. VC Structure Validation
    The JWT payload must contain valid Verifiable Credential claims according to the W3C specification.
  3. Signature Verification
    The issuer's signature is cryptographically verified to ensure the credential hasn't been tampered with.
  4. 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.
  5. Time Window Validation
    The credential's validity period is checked:
    • iat (issued at) – Must not be in the future
    • exp (expiration) – Must not be expired (if present)
  6. 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.

Error Messages

When validation fails, the API returns descriptive error messages to help identify the issue:

ErrorDescription
Invalid JWT formatThe provided string is not a valid JWT
Invalid VC structureThe JWT payload doesn't contain valid VC claims
Signature verification failedThe credential's signature is invalid
Credential subject does not match wallet DIDThe sub claim doesn't match the associated_did
Credential is expiredThe credential's exp claim is in the past
Credential already existsA credential with the same ID is already stored

Example JWT Payload

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"
}

Key Claims

ClaimDescription
issThe DID of the credential issuer
subThe DID of the credential subject (must match a wallet DID)
vcThe Verifiable Credential payload containing the credential data
iatIssued at timestamp (Unix epoch)
expExpiration timestamp (Unix epoch, optional)
jtiJWT ID for unique identification

SD-JWT Support

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:

  1. The full credential (including all disclosures) is stored in the wallet
  2. The signature is verified against the SD-JWT's key binding
  3. All validation checks apply as with regular JWTs

SD-JWTs follow the format: <issuer-signed-jwt>~<disclosure1>~<disclosure2>~...~<optional-key-binding-jwt>

Best Practices

  1. Always verify the source – Only import credentials from trusted issuers
  2. Check expiration dates – Expired credentials will be rejected
  3. Use the correct DID – Ensure the associated_did matches the credential's subject
  4. Handle errors gracefully – Implement proper error handling for all response codes
  5. Avoid duplicate imports – Check if a credential exists before attempting to import
Last updated on April 16, 2026