Issuer Container
sphereon/enterprise-issuer is the OpenID4VCI credential issuer. It owns the wallet-facing protocol endpoints, the multi-phase attribute pipeline that assembles credential claims, the credential-design store that defines what credentials the deployment offers, the integration registry that binds upstream attribute sources, and the webhook subsystem that fires issuance status events to tenant-configured callbacks.
The issuer is one of the two larger EDK images by surface area (the other being the AS), because it carries both the protocol layer and the credential-data layer above it. The protocol layer is shared with the IDK reference implementation; the credential-data layer (attribute pipeline, credential designs, semantic attribute sets, attribute suppliers, integration kinds) is the EDK-specific extension that this container exists to deliver.
Public Protocol Surface
The wallet-facing endpoints are the standard OID4VCI set. The IDK protocol handlers (/credential, /credential_deferred, /nonce, /notification, /.well-known/openid-credential-issuer) ship in this container unchanged from their IDK reference implementation. The EDK additions sit around them: the attribute pipeline runs before /credential returns, the deferred ingress handles the case where an upstream attribute source cannot answer in the synchronous window, and the credential offer endpoint and the various status URIs are tenant-aware through the lib-openid-oid4vci-issuer-rest-tenant overlay.
The metadata document is built dynamically from the credential designs registered for the tenant. As a tenant administrator adds or removes a credential design, the credential_configurations_supported map in the metadata picks up the change at the next cache refresh.
The Attribute Pipeline
The pipeline is the largest EDK addition relative to a vanilla OID4VCI issuer. Each issuance is an IssuancePipelineSession that carries an attribute bag, a lookup-key set, and a status. The session passes through phases (SESSION_INIT, AUTHORIZATION, TOKEN, CREDENTIAL_REQUEST, DEFERRED_POLL, POST_ISSUANCE) and at each phase the engine runs every attribute source bound to that phase, in topological order by lookup-key dependency.
A typical employee-credential pipeline binds:
invitation-contextinSESSION_INITto pre-seed anemployee_idfrom the offer.auth-session-claiminAUTHORIZATIONto pick up the authenticated user's email from the AS-issued access token.identity-resolverinTOKENto resolve email to an internal identity record.databaseinCREDENTIAL_REQUESTto read the HR record for that identity.vaultinPOST_ISSUANCEto retain the assembled attributes for re-issuance without re-running upstream lookups.
When the wallet calls /credential, the engine has the full assembled bag, applies the per-credential claim mapping declared on the design, and hands the result to the protocol handler to sign and return. Sources that cannot answer synchronously (an upstream HR system that takes thirty seconds, an async approval workflow) defer: the wallet receives 202 Accepted with a deferred-credential transaction, the upstream answers later via a callback URL, and the next /credential_deferred poll completes synchronously.
The pipeline session is persisted with attribute-bag encryption (three modes: PlaintextMode for dev, PlatformEncryptedMode AEAD under a tenant KEK, ClientBoundMode additionally bound to the session's correlationId).
For the full reference, see the dedicated OpenID4VCI section: attribute pipeline, attribute sources, REST API, persistence.
Internal Admin Surface
The admin REST under /api/v1/... covers everything a tenant administrator needs to configure the issuer:
- Credential designs.
/api/v1/credential/designsis the CRUD over designs. A design declares the credential's claims, the claim policy (which attributes are required, which optional), the rendering bindings, the issuer signing key alias, and the format (SD-JWT VC, JWT VC, mDoc). Versioning is built in: changing a design produces a new version; previous versions remain resolvable. - Semantic attribute sets.
/api/v1/data/semantic/attributesdefines reusable attribute groupings that designs reference rather than redefining attribute shapes per design. - Credential type bindings.
/api/v1/credentials/typebindingsties OID4VCI credential configuration ids to credential designs. - AS instance binding. Per-tenant binding between the issuer and the AS instance the wallet uses for this tenant's issuance. Identifies the AS issuer URI, the JWKS URI, and the supported grants.
- Attribute supplier registry. Per-tenant CRUD over the upstream systems the attribute pipeline reads from. Multiple supplier kinds are supported (
scim,hr,eidas,idv,csv-roster). - Integrations. A first-class kind-discriminated registry for
kms_provider,as_binding,issuer_attribute_supplier,webhook,did_method,trust_source, andfederation_providerbindings. - Webhooks. Per-tenant webhook configuration for issuance status events: subscription model with event-type filter, per-tenant HMAC signing key alias on KMS, durable retry queue with exponential backoff, idempotency keys, configurable timeout, circuit breaker per destination.
- Issuance session admin. List, inspect, cancel, and replay issuance sessions for debugging and operator intervention.
- Tenant admin. The shared
TenantAdminHttpAdapterfor tenant CRUD, domain registration, public-endpoint bindings, and onboarding flows.
The admin surface is tenant-scoped: an acme-tenant administrator can only see and modify resources belonging to acme. A platform-admin acting on a tenant's resources asserts the target tenant through the JWT impersonation pattern rather than through a path slug.
Tenant-Aware Metadata and Offers
The lib-openid-oid4vci-issuer-rest-tenant overlay replaces the IDK default URL resolver and the IDK default offer-creation command with EDK implementations that consult tenant_public_endpoint. The effect:
- The
credential_issuervalue in/.well-known/openid-credential-issuer/{tenant}is the tenant's public base URL, not the request host. - The
credential_endpoint,nonce_endpoint,deferred_credential_endpoint, andnotification_endpointURLs in the metadata point at the tenant's public OID4VCI backend base. POST /oid4vci/offer(the offer-creation endpoint) returns acredential_offer_urirooted at the tenant's public endpoint, and thestatus_uriin the offer body uses the same binding.
The metadata is exposed in both the spec form (/.well-known/openid-credential-issuer/{tenant-slug}) and the legacy slug-before form (/{tenant-slug}/.well-known/openid-credential-issuer).
Signing Keys
The issuer's credential signing key alias defaults to (tenant, issuer, credential-signing) on the KMS. The tenant administrator can override the alias per credential design through the signing-key binding on the design. Rotation is per-tenant and per-alias.
The webhook signing key alias (the HMAC the issuer signs outbound webhook calls with) defaults to (tenant, issuer, webhook-signing). Distinct alias, distinct rotation.
Persistence
The issuer adds the following EDK-overlay Postgres tables to the deployment's shared Postgres:
- The credential design tables (design, issuer design, verifier design, render variant, source snapshot, derived render hints, design current version) under
lib-data-store-credential-design-persistence-postgresql. - The semantic attribute and semantic vocabulary tables under their respective
persistence-postgresqlmodules. - The integration registry tables under
lib-integration-persistence-postgresql. - The webhook configuration and dispatch queue tables under
lib-webhook-persistence-postgresql. - The attribute supplier registry tables.
- The tenant IDP registry tables (shared with the AS).
- The issuance pipeline session store (KV-on-Postgres by default; promotable to dedicated session tables in future releases).
The credential offer session and the issuance pipeline session both carry sensitive intermediate state and use the encrypted session storage modes described in the OpenID4VCI persistence page.
Building and Running
The Dockerfile follows the standard EDK pattern. The fat JAR is built from services-oid4vci-issuer-rest with ./gradlew :services-oid4vci-issuer-rest:buildFatJar; the runtime image is eclipse-temurin:21-jre with the non-root appuser (uid 10001) and port 8080 exposed.
The entry point starts EnterpriseOid4vciIssuerKtorServer, which installs the tenant resolution plugin before the DI graph and then mounts the universal HTTP adapters. The Metro graph for the enterprise issuer is composed in services/oid4vci-issuer/rest/ and pulls in lib-openid-oid4vci-issuer-rest-tenant, services-tenant-rest, lib-tenant-resolution-impl, lib-tenant-persistence-postgresql, the DB-routing modules, the issuer pipeline implementation, the credential design Postgres modules, and the attribute source modules the deployment uses.
A production deployment overrides the shipped application.yaml to require admin-scoped bearer JWTs on /api/v1/..., to point at the shared Postgres, to register the KMS endpoint as a kms_provider integration on each tenant, to register the AS as the as_binding integration on each tenant, and to bind tenant public endpoints (subdomains, custom domains, path slugs) through the tenant admin REST.
Operational Notes
- Pipeline timeouts and the deferred ingress. The
syncWaitWindowon each attribute source binding controls how long the engine waits inside the synchronous/credentialwindow before falling through to a deferred response. Set it shorter than the wallet's expected/credentialtimeout; the EDK marks the source as awaiting deferred and the wallet retries via/credential_deferred. - Credential design hot-reload. Changing a credential design through
/api/v1/credential/designsinvalidates the metadata cache for the tenant via the cross-replica invalidation channel, so the next metadata fetch picks up the new design without a restart. - Webhook reliability. The outbound dispatcher uses a durable retry queue with exponential backoff and a dead-letter table. Tenant administrators can inspect delivery status through the webhook admin REST. Per-destination circuit breakers prevent a single broken consumer from saturating the dispatch pool.
- Multi-replica behaviour. The issuance pipeline session store is the shared Postgres KV-on-Postgres backing, so deferred sessions started on replica A are pollable from replica B without sticky sessions.