Skip to main content

Issue a credential

Create a credential offer, display it to a user, and deliver a signed credential to their EUDI Wallet. This guide covers the minimal steps to complete an issuance flow.

Before you begin

The connector implements OID4VCI using the pre-authorized code flow under the High Assurance Interoperability Profile (HAIP). The connector's built-in Authorization Server issues access tokens to wallets using DPoP (sender-constrained tokens). You don't need to configure protocol options—the connector applies HAIP-compliant settings by default.

Set up for issuance

Generate an Issuer Signing Certificate

The Issuer Signing Certificate signs the credentials your connector issues—it must use a separate key from the access certificate.

# Generate an EC P-256 private key for credential signing
openssl ecparam -genkey -name prime256v1 -noout \
-out ./certs/signing-key.pem

# Generate a self-signed X.509 certificate (valid for 365 days)
openssl req -new -x509 -key ./certs/signing-key.pem \
-out ./certs/signing-cert.pem \
-days 365 \
-subj "/CN=My Organization Issuer/O=My Organization/C=DE"

# Configure the connector
export CONNECTOR_CERTIFICATES_SIGNING_CERT_PATH=./certs/signing-cert.pem
export CONNECTOR_CERTIFICATES_SIGNING_KEY_PATH=./certs/signing-key.pem

For production certificates, see Manage certificates.

Configure a credential type

Type Metadata defines what claims a credential contains and how wallets display it.

{
"vct": "https://issuer.example.com/oidc4vci/types/IdentityCredential",
"display": [
{ "lang": "en-US", "name": "Identity Credential" }
],
"claims": [
{ "path": ["given_name"], "sd": "allowed", "display": [{ "lang": "en-US", "label": "Given name" }] },
{ "path": ["family_name"], "sd": "allowed", "display": [{ "lang": "en-US", "label": "Family name" }] }
]
}

Save this as type-metadata/identity-credential.json, then configure the connector:

export CONNECTOR_CREDENTIAL_CONFIGURATIONS_IDENTITYCREDENTIAL_VCT_SOURCE=local
export CONNECTOR_CREDENTIAL_CONFIGURATIONS_IDENTITYCREDENTIAL_VCT_FILEPATH=./type-metadata/identity-credential.json
export CONNECTOR_CREDENTIAL_CONFIGURATIONS_IDENTITYCREDENTIAL_SCOPE=identity_credential

For the full reference, see Configure credential types.

Create a credential offer

Call POST /offers on the management API with a credential_configuration_id and the claims to include in the credential. The credential_configuration_id must match a credential type configured in the connector's Type Metadata.

note

The credential_configuration_id, claim names, and claim values in this example are illustrative. In production, use the credential_configuration_id and claims defined in your Type Metadata configuration.

curl -X POST http://connector:8081/offers \
-H "Content-Type: application/json" \
-d '{
"credential_configuration_id": "IdentityCredential",
"claims": {
"given_name": "Erika",
"family_name": "Mustermann"
}
}'

Example response (201 Created):

{
"offer_id": "abc123def456",
"credential_offer_uri": "openid-credential-offer://?credential_offer_uri=https%3A%2F%2Fissuer.example.com%2Foidc4vci%2Foffers%2Fabc123def456"
}

Store the offer_id to correlate with the issuance callback later. Render credential_offer_uri as a QR code for cross-device flows or redirect the user to it for same-device flows.

The response contains:

  • offer_id—a correlation token to match the issuance callback with this offer.
  • credential_offer_uri—an openid-credential-offer:// URI for the wallet. Render it as a QR code or use it as a deep link.

You can optionally include a tx_code object in the request to require transaction code authorization. See Use transaction codes for details.

Handle the issuance callback

Implement a callback endpoint that receives the issuance event from the connector. The event's status field indicates the issuance outcome.

# Simulate an ISSUED callback for testing
curl -X POST https://example.com:3000/callback/issuance \
-H "Content-Type: application/json" \
-d '{
"eventId": "abc123def456",
"status": "ISSUED",
"offerId": "abc123def456"
}'
# Simulate a FAILED callback for testing
curl -X POST https://example.com:3000/callback/issuance \
-H "Content-Type: application/json" \
-d '{
"eventId": "abc123def456",
"status": "FAILED",
"offerId": "abc123def456",
"errorDetails": "issuer service error: signing key validation failed"
}'

The issuance callback has four possible statuses:

  • OFFER_CREATED—the offer was created and the session is active. Use for audit logging or to start a timeout timer.
  • ISSUED—the credential was successfully issued to the wallet.
  • FAILED—issuance failed. The errorDetails field describes the reason.
  • EXPIRED—the session expired before the wallet completed the flow. Create a new offer if the user still needs the credential.

Use the offerId field to correlate the callback with the original offer. For the complete list of issuance statuses and payload fields, see the callback events reference.

Next steps

Further reading