---
title: "Issuing W3C VC Credentials via OID4VCI"
description: "Learn how to issue W3C Verifiable 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/w3c-vc
generated: 2026-10-01
---
# Issuing W3C VC Credentials via OID4VCI

This guide shows you how to issue a [W3C Verifiable Credential](https://docs.walt.id/concepts/digital-credentials/verifiable-credentials-w3c.md) (JWT-signed) 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.

[**W3C VC**](https://docs.walt.id/concepts/digital-credentials/verifiable-credentials-w3c.md): The W3C Verifiable Credentials data model, here signed as a JWT (`jwt_vc_json`).

[**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 a W3C credential configuration (format `jwt_vc_json`, with a `credential_definition`). See [Setup](https://docs.walt.id/enterprise-stack/services/issuer2-service/setup.md).
- **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 a W3C VC](https://docs.walt.id/enterprise-stack/services/wallet2-service/credential-receiving/w3c-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 `OpenBadgeCredential_jwt_vc_json` 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-def456`.

##### Example Request

```bash
curl -X 'POST' \
  'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{target}.profile-def456/issuer-service-api/credentials/profiles' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer {yourToken}' \
  -H 'Content-Type: application/json' \
  -d '{
  "name": "Open Badge Credential Profile",
  "credentialConfigurationId": "OpenBadgeCredential_jwt_vc_json",
  "issuerKeyId": "waltid.tenant1.kms1.key1",
  "issuerDid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp",
  "w3cVersion": "W3CV2",
  "credentialData": {
    "@context": [
      "https://www.w3.org/ns/credentials/v2",
      "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.3.json",
      "https://purl.imsglobal.org/spec/ob/v3p0/extensions.json"
    ],
    "type": ["VerifiableCredential", "OpenBadgeCredential"],
    "issuer": {
      "type": ["Profile"],
      "name": "Jobs for the Future (JFF)",
      "url": "https://www.jff.org/",
      "image": "https://w3c-ccg.github.io/vc-ed/plugfest-1-2022/images/JFF_LogoLockup.png"
    },
    "credentialSubject": {
      "type": ["AchievementSubject"],
      "achievement": {
        "id": "https://example.com/achievements/21st-century-skills/teamwork",
        "type": ["Achievement"],
        "criteria": {
          "narrative": "Team members are nominated for this badge by their peers and recognized upon review by Example Corp management."
        },
        "description": "This badge recognizes the development of the capacity to collaborate within a group environment.",
        "name": "Teamwork"
      }
    },
    "credentialSchema": [
      {
        "id": "https://purl.imsglobal.org/spec/ob/v3p0/schema/json/ob_v3p0_achievementcredential_schema.json",
        "type": "1EdTechJsonSchemaValidator2019"
      }
    ]
  },
  "mapping": {
    "id": "<uuid>",
    "issuer": { "id": "<issuerDid>" },
    "credentialSubject": { "id": "<subjectDid>" },
    "validFrom": "<timestamp>",
    "validUntil": "<timestamp-in:365d>"
  }
}'
```

**Body Parameters**

- **credentialConfigurationId**: _String_ (required) - Must match a W3C (`jwt_vc_json`) configuration on your issuer service.
- **issuerKeyId**: _resourceIdentifier_ (required) - A key in your KMS.
- **issuerDid**: _String_ (optional) - The issuer DID.
- **credentialData**: _Object_ (required) - The credential body (`@context`, `type`, `credentialSubject`).
- **w3cVersion**: _String_ (optional) - `W3CV11` or `W3CV2`. Controls which VCDM version is issued and whether `issuanceDate`/`expirationDate` or `validFrom`/`validUntil` are used.

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

**Note:**

  When `w3cVersion` is `W3CV2`, the service applies the correct VCDM v2 `@context` and uses `validFrom`/`validUntil` in place of `issuanceDate`/`expirationDate`, regardless of what you put in `credentialData`.

---

##### Example Response

```json
{
  "profileId": "profile-def456",
  "name": "Open Badge Credential Profile",
  "version": 1,
  "credentialConfigurationId": "OpenBadgeCredential_jwt_vc_json",
  "createdAt": 1704067200000,
  "updatedAt": 1704067200000
}
```

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

**Response Fields**

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

**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-def456/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. 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": "abc123-def456-ghi789",
  "profileId": "profile-def456",
  "profileVersion": 1,
  "authMethod": "PRE_AUTHORIZED",
  "expiresAt": 1704067500000,
  "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%3Dabc123-def456-ghi789"
}
```

**Response Fields**

- **credentialOffer**: _String_ - The OID4VCI credential offer URL. Turn it into a QR code or deep link for the wallet.

---

## 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 a W3C VC](https://docs.walt.id/enterprise-stack/services/wallet2-service/credential-receiving/w3c-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.

---

## Using the Authorization Code Flow Instead

The example above used `PRE_AUTHORIZED` — no user login. To have the holder authenticate against an external IdP 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 W3C Credentials](https://docs.walt.id/enterprise-stack/services/verifier2-service/credential-verification/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 [SD-JWT VC](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-issuance/sd-jwt-vc.md) or [mDL](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-issuance/mdl.md).
