Notifications

The Issuer2 API supports webhook-based notifications for real-time updates on credential issuance events. Configure notifications at the profile level or override them per offer.

Overview

Notifications allow your backend systems to receive real-time updates when:

  • A credential offer is created or retrieved by a wallet
  • Authorization, token, nonce, or credential stages succeed or fail
  • A credential is issued
  • The issuance session status changes

This enables you to:

  • Track issuance progress
  • Update your database when credentials are claimed
  • Trigger downstream workflows
  • Audit credential issuance

Configuration

Notifications are configured in the notifications object of a credential profile or as a runtime override when creating an offer.

Basic Configuration

{
  "notifications": {
    "webhook": {
      "url": "https://your-server.com/webhook/issuance"
    }
  }
}

Configuration Properties

PropertyTypeRequiredDescription
webhook.urlStringYesThe URL to receive webhook notifications

Webhook Events

Your webhook endpoint will receive POST requests with JSON payloads for the following events. The same envelope is used for SSE.

Event Structure

{
  "target": "session-id-123",
  "event": "event_type",
  "session": { ... },
  "requestId": "3f1c2a8e-9b44-4c1d-8e2f-1a2b3c4d5e6f",
  "error": "invalid_grant",
  "error_description": "tx_code is invalid"
}
FieldDescription
targetThe session/offer ID when the request is correlated; otherwise the requestId
eventThe event type
sessionSession data when the request is correlated; {} when it is not
requestIdCall ID for this HTTP request (X-Request-ID, generated if omitted)
errorOAuth / OpenID4VCI error code on failure events
error_descriptionError description returned to the wallet on failure events

Event Types

StageEvents
Offercredential_offer_created, credential_offer_retrieved
Pushed authorizationpushed_authorization_request_succeeded, pushed_authorization_request_failed
Authorizationauthorization_request_succeeded, authorization_request_failed
Tokentoken_request_authorization_code_succeeded, token_request_authorization_code_failed, token_request_pre_authorized_code_succeeded, token_request_pre_authorized_code_failed, token_request_refresh_token_succeeded, token_request_refresh_token_failed, token_request_failed
Noncenonce_request_succeeded, nonce_request_failed
Credentialcredential_request_sd_jwt_vc_succeeded, credential_request_sd_jwt_vc_failed, credential_request_w3c_vc_succeeded, credential_request_w3c_vc_failed, credential_request_mso_mdoc_succeeded, credential_request_mso_mdoc_failed, credential_request_failed
Session lifecycleissuance_status_changed

Webhook and SSE payloads use the lowercase event strings above.

Each protocol endpoint publishes one outcome event. token_request_failed and credential_request_failed are used when the grant or credential format cannot be resolved. Events include the session only after trusted correlation. Uncorrelated failures and nonce events are published only on GET /issuer2/events.

credential_offer_created is effectively webhook-only for session subscribers: it is published before the session id is returned, so no session SSE subscriber can exist yet.

Failure Detail

Failure events carry error and error_description on the envelope, not a nested session.failure:

{
  "target": "session-id-123",
  "event": "token_request_pre_authorized_code_failed",
  "error": "invalid_grant",
  "error_description": "tx_code is invalid",
  "session": { "sessionId": "session-id-123" }
}

For terminal credential failures the same fields are persisted on the session and returned from GET /issuer2/sessions/{sessionId}. Retryable invalid_proof and invalid_nonce failures leave the session active and publish the error on the event only.

Only terminal credential endpoint failures conclude a session — earlier stages leave the grant usable so the wallet can retry.

On successful issuance the session is persisted as SUCCESSFUL before the credential success event and issuance_status_changed are published. A failure event and issuance_status_changed are always separate.

Published session payloads redact issuer signing key material (issuerKey.type = "redacted"). Credential data may still be present; use trusted webhook receivers.

Example Payload

{
  "target": "session-id-123",
  "event": "credential_offer_retrieved",
  "requestId": "3f1c2a8e-9b44-4c1d-8e2f-1a2b3c4d5e6f",
  "session": {
    "sessionId": "session-id-123",
    "credentialConfigurationId": "UniversityDegree_jwt_vc_json",
    "credentialOffer": {
      "credential_issuer": "http://localhost:7005",
      "credential_configuration_ids": ["UniversityDegree_jwt_vc_json"],
      "grants": { ... }
    }
  }
}

Profile-Level Notifications

Configure notifications in your credential profile configuration file:

# issuer2-profiles.conf

profiles = {
  "notified-credential" = {
    name = "Notified Credential"
    credentialConfigurationId = "identity_credential_dc+sd-jwt"
    issuerKey = { ... }
    credentialData = { ... }
    notifications = {
      webhook = {
        url = "https://your-server.com/webhook/issuance"
      }
    }
  }
}

Per-Offer Notifications

Override profile notifications for a specific offer using runtime overrides:

curl -X POST 'http://localhost:7005/issuer2/credential-offers' \
  -H 'Content-Type: application/json' \
  -d '{
    "profileId": "university-degree",
    "authMethod": "PRE_AUTHORIZED",
    "runtimeOverrides": {
      "notifications": {
        "webhook": {
          "url": "https://different-server.com/webhook/special-offer"
        }
      }
    }
  }'

Alternative: Server-Sent Events (SSE)

For real-time monitoring in browser-based applications, the Issuer2 API also provides a Server-Sent Events (SSE) endpoint. This allows you to subscribe to session events directly from a browser without setting up a webhook server.

Endpoint: GET /issuer2/sessions/{sessionId}/events

const sessionId = 'abc123-def456-ghi789';
const eventSource = new EventSource(
  `http://localhost:7005/issuer2/sessions/${sessionId}/events`
);

eventSource.onmessage = (event) => {
  const data = JSON.parse(event.data);
  console.log('Session event:', data.event);
  
  if (data.event === 'issuance_status_changed' && data.session.status === 'SUCCESSFUL') {
    console.log('Credential issued successfully!');
    eventSource.close();
  }
};

The session stream emits the same correlated event types and payload structure as webhooks. Uncorrelated failures and nonce events are available on GET /issuer2/events.

On connection, the SSE endpoint first sends an empty data: {} message as an initial handshake, before any session events. Guard against it in your handler (e.g. check data.event before acting), as shown in the example above where the data.event === 'issuance_status_changed' check safely ignores it.

FeatureWebhooksSSE
Best forServer-to-serverBrowser/client apps
ConnectionPush to your serverClient pulls from issuer
ReliabilityMore reliableMay disconnect
SetupRequires public endpointNo server needed

Next Steps

Last updated on August 24, 2026