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
| Property | Type | Required | Description |
|---|---|---|---|
webhook.url | String | Yes | The 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"
}
| Field | Description |
|---|---|
target | The session/offer ID when the request is correlated; otherwise the requestId |
event | The event type |
session | Session data when the request is correlated; {} when it is not |
requestId | Call ID for this HTTP request (X-Request-ID, generated if omitted) |
error | OAuth / OpenID4VCI error code on failure events |
error_description | Error description returned to the wallet on failure events |
Event Types
| Stage | Events |
|---|---|
| Offer | credential_offer_created, credential_offer_retrieved |
| Pushed authorization | pushed_authorization_request_succeeded, pushed_authorization_request_failed |
| Authorization | authorization_request_succeeded, authorization_request_failed |
| Token | token_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 |
| Nonce | nonce_request_succeeded, nonce_request_failed |
| Credential | credential_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 lifecycle | issuance_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.
| Feature | Webhooks | SSE |
|---|---|---|
| Best for | Server-to-server | Browser/client apps |
| Connection | Push to your server | Client pulls from issuer |
| Reliability | More reliable | May disconnect |
| Setup | Requires public endpoint | No server needed |
Next Steps
- Issuer Profiles Configuration – Add notifications to your profiles
