---
title: "Issuing SD-JWT VC Credentials via OID4VCI"
description: "Learn how to issue IETF SD-JWT VC credentials using the walt.id Issuer2 API."
stack: "Community Stack (open source) — version 0.21.0"
stack_version: "0.21.0"
stack_comparison: https://docs.walt.id/community-vs-enterprise.md
canonical_url: https://docs.walt.id/community-stack/issuer2/credential-issuance/sd-jwt-vc
generated: 2026-07-20
---
# Issuing SD-JWT VC Credentials via OID4VCI

This guide shows you how to issue an [IETF SD-JWT VC](https://docs.walt.id/concepts/digital-credentials/sd-jwt-vc.md) credential using the walt.id Issuer2 API. You'll issue a credential from a ready-made [profile](https://docs.walt.id/community-stack/issuer2/credential-profiles/overview.md) in seconds, then see how to define your own.

[**SD-JWT VC**](https://docs.walt.id/concepts/digital-credentials/sd-jwt-vc.md): An IETF credential format supporting selective disclosure, letting holders reveal only specific claims of a credential rather than the whole thing.

[**OID4VCI**](https://docs.walt.id/concepts/data-exchange-protocols/openid4vci.md): The protocol used to deliver the credential to a wallet.

**Note:**

Issuer2 issues credentials from **profiles** — reusable configurations declared ahead of time in a config file, with sensible defaults for the issuer key and credential data. If you're used to the original Issuer (v1), where the key and credential data were posted directly to an issuance endpoint every time, see [Getting Started](https://docs.walt.id/community-stack/issuer2/getting-started.md#core-concepts) for what changed — you can still override a profile's defaults per-offer via [`runtimeOverrides`](https://docs.walt.id/community-stack/issuer2/credential-offers/create-offer.md#runtime-overrides).

## Prerequisites

Before you begin, ensure you have:

- **Issuer2 API running** — Follow the [Setup](https://docs.walt.id/community-stack/issuer2/setup.md) guide
- **A test wallet** — Use the [walt.id Wallet](https://wallet.demo.walt.id/) for testing, or any OID4VCI-compatible wallet

**Note:**

**Local Development:** When running locally via Docker, the API is available at `http://localhost:7002` (standalone) or `http://localhost:7005` (Docker Compose). Use this as your base URL in the examples below.

---

## Quick Start: Issue Using the Default Profile

The waltid-identity repository ships with a ready-made profile called `identityCredentialSdJwt`, bound to the `identity_credential` credential type. You can issue from it immediately — no configuration file editing required.

### Step 1: Confirm the Profile Is Loaded

**Option: CURL**

**Endpoint:** `GET /issuer2/profiles/{profileId}` | [API Reference](https://issuer2.demo.walt.id/swagger/index.html)

##### Example Request

```bash
curl -X 'GET' \
  'http://localhost:7002/issuer2/profiles/identityCredentialSdJwt' \
  -H 'accept: application/json'
```

---

##### Example Response

```json
{
  "profileId": "identityCredentialSdJwt",
  "name": "Identity Credential",
  "credentialConfigurationId": "identity_credential",
  "issuerKey": {
    "type": "jwk",
    "jwk": {
      "d": "KJ4k3Vcl5Sj9Mfq4rrNXBm2MoPoY3_Ak_PIR_EgsFhQ",
      "crv": "P-256",
      "x": "G0RINBiF-oQUD3d5DGnegQuXenI29JDaMGoMvioKRBM",
      "y": "ed3eFGs2pEtrp7vAZ7BLcbrUtpKkYWAT2JPUQK4lN4E",
      "kty": "EC"
    }
  },
  "issuerDid": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6IkcwUklOQmlGLW9RVUQzZDVER25lZ1F1WGVuSTI5SkRhTUdvTXZpb0tSQk0iLCJ5IjoiZWQzZUZHczJwRXRycDd2QVo3QkxjYnJVdHBLa1lXQVQySlBVUUs0bE40RSJ9",
  "credentialData": {
    "address": {
      "region": "Anystate",
      "street_address": "123 Main St",
      "country": "US",
      "locality": "Anytown"
    },
    "is_over_18": true,
    "phone_number": "+1-202-555-0101",
    "given_name": "John",
    "family_name": "Doe",
    "is_over_21": true,
    "is_over_65": true,
    "email": "johndoe@example.com",
    "birthdate": "1940-01-01"
  },
  "mapping": {
    "exp": "<timestamp-in-seconds:365d>",
    "nbf": "<timestamp-seconds>",
    "id": "<uuid>",
    "iat": "<timestamp-seconds>"
  },
  "selectiveDisclosure": {
    "fields": {
      "birthdate": {
        "sd": true
      },
      "family_name": {
        "sd": false
      }
    },
    "decoyMode": "NONE",
    "decoys": 0
  },
  "idTokenClaimsMapping": {
    "$.given_name": "$.given_name",
    "$.family_name": "$.family_name"
  }
}
```

The profile already has selective disclosure and issuer key/DID configured — you don't need to understand every field yet to issue your first credential. We'll come back to them in [Defining Your Own Profile](#defining-your-own-profile) below.

### Step 2: Create a Credential Offer

**Option: CURL**

**Endpoint:** `POST /issuer2/credential-offers` | [API Reference](https://issuer2.demo.walt.id/swagger/index.html)

##### Example Request

```bash
curl -X 'POST' \
  'http://localhost:7002/issuer2/credential-offers' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
    "profileId": "identityCredentialSdJwt",
    "authMethod": "PRE_AUTHORIZED"
  }'
```

**Body Parameters**

- **profileId**: _String_ (required) - The profile to issue from. Here, the shipped default `identityCredentialSdJwt`.
- **authMethod**: _String_ (required) - The OID4VCI flow used to claim the offer. `PRE_AUTHORIZED` requires no user login and is the simplest way to get started. See [Using the Authorization Code Flow Instead](#using-the-authorization-code-flow-instead) for the alternative.

---

##### Example Response

```json
{
  "offerId": "48f1dc54-134c-4e10-b889-17f3c0a595bf",
  "profileId": "identityCredentialSdJwt",
  "authMethod": "PRE_AUTHORIZED",
  "expiresAt": 1782898162344,
  "credentialOffer": "openid-credential-offer://?credential_offer_uri=http%3A%2F%2Flocalhost%3A7002%2Fopenid4vci%2Fcredential-offer%3Fid%3D48f1dc54-134c-4e10-b889-17f3c0a595bf"
}
```

**Response Fields**

- **offerId**: _String_ - Unique identifier for this offer.
- **expiresAt**: _Long_ - Unix timestamp (ms) when the offer expires (default: 300 seconds after creation).
- **credentialOffer**: _String_ - The OID4VCI credential offer URL. Turn this into a QR code or deep link for the wallet.

---

## Claiming the Offer

1. **Generate a QR code** from the `credentialOffer` URL, or use it directly as a deep link
2. **Open the [walt.id Wallet](https://wallet.demo.walt.id/)** and log in
3. **Click "Request Credential"** and paste the offer URL into the field below the camera
4. **The wallet resolves the offer**, requests the credential, and stores it

🎉 You've issued an SD-JWT VC credential using OID4VCI.

To learn how to accept this credential offer programmatically instead, see [How to Accept SD-JWT VC Credentials via OID4VCI](https://docs.walt.id/community-stack/wallet/credential-exchange/guides/accept-sd-jwt-vc-oid4vci.md).

---

## Defining Your Own Profile

To issue your own SD-JWT VC credential type instead of the shipped default, two config files need an entry, and the service needs a restart to pick them up.

### Step 1: Declare the Credential Type

Add an entry to `credential-issuer-metadata.conf`:

```hocon
credentialConfigurations = {
  "my_identity_credential" = {
    format = "dc+sd-jwt"
    scope = "my_identity_credential"
    vct = "vctBaseUrl"
    cryptographic_binding_methods_supported = ["jwk", "did:key", "did:web"]
    credential_signing_alg_values_supported = ["ES256"]
    proof_types_supported = {
      jwt = {
        proof_signing_alg_values_supported = ["ES256"]
      }
    }
  }
}
```

See [Credential Issuer Metadata](https://docs.walt.id/community-stack/issuer2/configurations/config-files/credential-issuer-metadata.md) for the full field reference.

### Step 2: Define a Profile

Add a matching profile to `issuer2-profiles.conf`:

```hocon
profiles {
  myIdentityCredential {
    name = "My Identity Credential"
    credentialConfigurationId = "my_identity_credential"
    issuerKey = ${defaultIssuerKey}
    issuerDid = ${defaultIssuerDid}
    credentialData = {
      given_name = "Jane"
      family_name = "Smith"
    }
    mapping = {
      iat = "<timestamp-seconds>"
      nbf = "<timestamp-seconds>"
    }
    selectiveDisclosure = {
      fields = {
        family_name = { sd = true }
      }
    }
  }
}
```

See [Issuer Profiles Configuration](https://docs.walt.id/community-stack/issuer2/configurations/config-files/issuer-profiles.md) for every available profile field, including notifications and credential status.

### Step 3: Restart and Verify

**Error:**

Profiles are loaded once, at startup. **Restart the Issuer2 container** after editing either config file, then confirm the new profile is loaded.

```bash
curl -X 'GET' 'http://localhost:7002/issuer2/profiles/myIdentityCredential'
```

Then repeat the two steps from [Quick Start](#quick-start-issue-using-the-default-profile) above, substituting your own `profileId`.

---

## Using the Authorization Code Flow Instead

The example above used `PRE_AUTHORIZED` — no user login required. Issuer2 also supports the **Authorization Code Flow**, where the holder authenticates against an external IdP (e.g. Keycloak) before claiming the credential, and the resulting ID token claims can be mapped into the credential via `idTokenClaimsMapping`.

This requires registering an external OAuth/OIDC provider first. See [Authorization Code Flow](https://docs.walt.id/community-stack/issuer2/protocol-flows/authorization-code-flow.md) for the full sequence and setup.

## Selective Disclosure

The default profile already marks `birthdate` as selectively disclosable. To configure which claims a holder can choose to hide, see [Selective Disclosure](https://docs.walt.id/community-stack/issuer2/configurations/config-files/issuer-profiles.md#selective-disclosure) in the profile reference.

---

## Next Steps

**Info:**

The `vct` published for `identity_credential` is derived from your issuer's `baseUrl` (see [VCT handling](https://docs.walt.id/community-stack/issuer2/configurations/config-files/credential-issuer-metadata.md#vct-handling)). When verifying, use the `vct` from **your own** `/.well-known/openid-credential-issuer/openid4vci` response rather than assuming a fixed value.

- **Verify the credential you just issued** — Try [Verifying SD-JWT VC Credentials](https://docs.walt.id/community-stack/verifier2/credential-verification/sd-jwt-vc-oid4vp.md) with the walt.id Verifier2 API
- **Populate credentials dynamically** — See [Data Functions](https://docs.walt.id/community-stack/issuer2/data-functions.md) for webhooks, timestamps, and more
- **Track issuance events** — See [Notifications](https://docs.walt.id/community-stack/issuer2/notifications.md) for webhook and SSE options
- **Issue other credential types** — Try [W3C VC](https://docs.walt.id/community-stack/issuer2/credential-issuance/w3c-vc.md) or [mDL](https://docs.walt.id/community-stack/issuer2/credential-issuance/mdl.md)
