---
title: "Issuing SD-JWT VC Credentials via OID4VCI"
description: "Learn how to issue IETF SD-JWT VC credentials end to end using the walt.id Enterprise Issuer2 Service."
stack: "Enterprise Stack (commercial) — version 1.1.1"
stack_version: "1.1.1"
stack_comparison: https://docs.walt.id/community-vs-enterprise.md
canonical_url: https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-issuance/sd-jwt-vc
generated: 2026-10-01
---
# 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 end to end with the Enterprise Issuer2 Service. You'll create a minimal [credential profile](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-profiles/overview.md), turn it into a [credential offer](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-offers/overview.md), and claim it in a wallet.

[**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 rather than the whole credential.

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

## Prerequisites

Before you begin, ensure you have:

- **An Issuer2 service** with an SD-JWT VC credential configuration (format `dc+sd-jwt`, with a `vct`). See [Setup](https://docs.walt.id/enterprise-stack/services/issuer2-service/setup.md). The `vct` can be an external URL or a self-hosted `{vctBaseURL}` placeholder — see [VCT Handling](https://docs.walt.id/enterprise-stack/services/issuer2-service/configurations/credential-types.md#vct-handling).
- **A KMS key** for signing, in a [KMS Service](https://docs.walt.id/enterprise-stack/services/key-management-service/overview.md) under the same tenant.
- **An issuer DID** created via the [DID Service](https://docs.walt.id/enterprise-stack/services/did-service/overview.md).
- **A wallet to receive into** — an Enterprise Stack [Wallet2 Service](https://docs.walt.id/enterprise-stack/services/wallet2-service/overview.md) (see [Receiving an SD-JWT VC](https://docs.walt.id/enterprise-stack/services/wallet2-service/credential-receiving/sd-jwt-vc.md)), or any OID4VCI-compatible wallet app.

Throughout this guide:

- **orgID** is your organization ID, e.g. `test.enterprise-sandbox.waltid.dev`.
- **target** is your issuer service, `{organizationID}.{tenantID}.{issuerServiceID}`, e.g. `waltid.tenant1.issuer1`.

---

## Flow Overview

1. **Create a profile** — Bind the `identity_credential_vc+sd-jwt` type to your key, DID, and a data template.
2. **Create an offer** — Generate an OID4VCI offer URL from the profile.
3. **Claim the offer** — Hand the URL to a wallet.

---

## Step 1: Create a Profile

**Option: CURL**

**Endpoint:** `POST /v2/{target}.{profileId}/issuer-service-api/credentials/profiles` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html)

The new profile's own ID is the **last path segment** of the target (like service creation), i.e. `{target}.{profileId}` — here `waltid.tenant1.issuer1.profile-abc123`.

##### Example Request

```bash
curl -X 'POST' \
  'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{target}.profile-abc123/issuer-service-api/credentials/profiles' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer {yourToken}' \
  -H 'Content-Type: application/json' \
  -d '{
  "name": "Identity Credential Profile",
  "credentialConfigurationId": "identity_credential_vc+sd-jwt",
  "issuerKeyId": "waltid.tenant1.kms1.key1",
  "issuerDid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp",
  "credentialData": {
    "given_name": "John",
    "family_name": "Doe",
    "birthdate": "1990-01-15"
  },
  "selectiveDisclosure": {
    "fields": {
      "birthdate": { "sd": true }
    }
  },
  "mapping": {
    "iat": "<timestamp-seconds>",
    "exp": "<timestamp-in-seconds:365d>"
  }
}'
```

**Body Parameters**

- **credentialConfigurationId**: _String_ (required) - Must match an SD-JWT VC (`dc+sd-jwt`) configuration on your issuer service.
- **issuerKeyId**: _resourceIdentifier_ (required) - A key in your KMS, e.g. `waltid.tenant1.kms1.key1`.
- **issuerDid**: _String_ (optional) - The issuer DID.
- **credentialData**: _Object_ (required) - The claims to issue.
- **selectiveDisclosure**: _Object_ (optional) - Which claims the holder can selectively disclose. Here, `birthdate` is made disclosable.

See [Create a Profile](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-profiles/create-profile.md) for every field.

---

##### Example Response

```json
{
  "profileId": "profile-abc123",
  "name": "Identity Credential Profile",
  "version": 1,
  "credentialConfigurationId": "identity_credential_vc+sd-jwt",
  "createdAt": 1704067200000,
  "updatedAt": 1704067200000
}
```

The example above is shortened — the response also echoes your submitted `issuerDid`, `issuerKeyId`, `credentialData`, `mapping`, and `selectiveDisclosure`.

**Response Fields**

- **profileId**: _String_ - The profile's own ID (the last path segment you chose), e.g. `profile-abc123`. The offer in Step 2 targets the **full** path `{target}.{profileId}`, e.g. `waltid.tenant1.issuer1.profile-abc123`.

**Response Codes**

- `201` — Profile created.
- `400` — Invalid request body.
- `401` — Invalid or missing authentication token.

---

## Step 2: Create a Credential Offer

Create the offer against the **profile** as the target — the `profileId` from Step 1.

**Option: CURL**

**Endpoint:** `POST /v2/{profileTarget}/issuer-service-api/credentials/offers` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html)

##### Example Request

```bash
curl -X 'POST' \
  'https://{orgID}.enterprise-sandbox.waltid.dev/v2/waltid.tenant1.issuer1.profile-abc123/issuer-service-api/credentials/offers' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer {yourToken}' \
  -H 'Content-Type: application/json' \
  -d '{
  "authMethod": "PRE_AUTHORIZED"
}'
```

**Body Parameters**

- **authMethod**: _String_ (required) - `PRE_AUTHORIZED` requires no user login and is the simplest way to start. See [Protocol Flows](https://docs.walt.id/enterprise-stack/services/issuer2-service/protocol-flows/overview.md) for the alternative.

See [Create an Offer](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-offers/create-offer.md) for PIN, expiration, delivery mode, and runtime overrides.

---

##### Example Response

```json
{
  "offerId": "48f1dc54-134c-4e10-b889-17f3c0a595bf",
  "profileId": "profile-abc123",
  "profileVersion": 1,
  "authMethod": "PRE_AUTHORIZED",
  "expiresAt": 1782898162344,
  "credentialOffer": "openid-credential-offer://?credential_offer_uri=https%3A%2F%2Fwaltid.enterprise-sandbox.waltid.dev%2Fv2%2Fwaltid.tenant1.issuer1%2Fissuer-service-api%2Fopenid4vci%2Fcredential-offer%3Fid%3D48f1dc54-134c-4e10-b889-17f3c0a595bf"
}
```

**Response Fields**

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

---

## Claiming the Offer

Hand the `credentialOffer` URL to a wallet to receive the credential. 

With the Enterprise Stack [Wallet2 Service](https://docs.walt.id/enterprise-stack/services/wallet2-service/overview.md) this is a single API call — see **[Receiving an SD-JWT VC](https://docs.walt.id/enterprise-stack/services/wallet2-service/credential-receiving/sd-jwt-vc.md)**, which passes the offer URL to the wallet's OID4VCI receive endpoint (Wallet2), resolves the offer, requests the credential, and stores it.

To claim it in any other wallet: generate a QR code from the `credentialOffer` URL which the wallet can scan or open it as a deep link.

---

## Selective Disclosure

The profile above marks `birthdate` as selectively disclosable via `selectiveDisclosure.fields`. Any claim you don't mark is always disclosed. To make more claims optional, add them under `fields` with `{ "sd": true }`. See [Create a Profile](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-profiles/create-profile.md) for the full selective-disclosure and decoy options.

## Using the Authorization Code Flow Instead

The example above used `PRE_AUTHORIZED` — no user login. To have the holder authenticate against an external IdP (e.g. Keycloak) and map ID-token claims into the credential via `idTokenClaimsMapping`, use `authMethod: "AUTHORIZED"`. See [Authorization Code Flow](https://docs.walt.id/enterprise-stack/services/issuer2-service/protocol-flows/authorization-code-flow.md).

---

## Next Steps

- **Verify the credential you just issued** — Try [Verifying SD-JWT VC Credentials](https://docs.walt.id/enterprise-stack/services/verifier2-service/credential-verification/sd-jwt-vc-oid4vp.md).
- **Populate credentials dynamically** — See [Data Functions](https://docs.walt.id/enterprise-stack/services/issuer2-service/data-functions.md).
- **Track issuance events** — See [Notifications & Session Events](https://docs.walt.id/enterprise-stack/services/issuer2-service/notifications.md).
- **Issue other credential types** — Try [W3C VC](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-issuance/w3c-vc.md) or [mDL](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-issuance/mdl.md).
