Notifications & Session Events
The Issuer2 Service offers two ways to observe issuance in real time:
- Webhooks (notifications) — the issuer pushes events to your server. Best for server-to-server integration and production workflows.
- Server-Sent Events (SSE) — your client streams events from the issuer. Best for live UI, such as showing issuance progress in a browser.
Both surface the same underlying issuance-session events.
| Webhooks | SSE | |
|---|---|---|
| Best for | Server-to-server, production | Browser/client apps |
| Direction | Issuer pushes to your server | Client streams from issuer |
| Reliability | More reliable for long-running processes | Connection may drop |
| Setup | Requires a public endpoint | No server needed |
Webhooks
Webhook notifications are configured in the notifications object of a credential profile or as a runtime override when creating an offer.
Configuration
{
"notifications": {
"webhook": {
"url": "https://your-server.com/webhook/issuance",
"bearer_token": "your-secret-token"
}
}
}
Configuration Properties
- webhook.url: String (required) - The URL to receive webhook notifications.
- webhook.bearer_token: String (optional) - Bearer token sent with the webhook (as
Authorization: Bearer <token>) for authentication. - webhook.basic_auth_username / webhook.basic_auth_password: String (optional) - Basic-auth credentials sent with the webhook instead of a bearer token.
Configure notifications at the profile level to apply to every offer from that profile, or as a per-offer runtime override to change them for a single offer.
Webhook Events
Your endpoint receives POST requests for the issuance-session events below. The event names are the same across webhooks and SSE:
| 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 |
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 /v2/{target}/issuer-service-api/events. Only terminal credential endpoint failures conclude a session; earlier failures and retryable invalid_proof / invalid_nonce leave the grant usable so the wallet can retry.
Failure events carry error and error_description on the envelope. Nested session.failure is not published. Terminal credential failures are also persisted on the session.
Each webhook is a POST whose JSON body is the same envelope used for SSE: target, event, session, requestId, and optional error / error_description. target is the issuance-session ID (the offerId) when the request is correlated, otherwise the requestId.
{
"target": "9cf9e1f6-dc53-48f2-8124-b160433d7728",
"event": "credential_offer_retrieved",
"requestId": "3f1c2a8e-9b44-4c1d-8e2f-1a2b3c4d5e6f",
"session": { "id": "9cf9e1f6-dc53-48f2-8124-b160433d7728", "status": "ACTIVE", "...": "..." }
}
Example Endpoint (Node.js/Express)
const express = require('express');
const app = express();
app.use(express.json());
app.post('/webhook/issuance', (req, res) => {
if (req.headers.authorization !== 'Bearer your-secret-token') {
return res.status(401).send('Unauthorized');
}
const { event, target, session } = req.body;
console.log(`Issuance event '${event}' for session ${target}, status: ${session?.status}`);
res.status(200).send('OK');
});
app.listen(3000);
Best Practices
- Secure your endpoint – Use HTTPS and verify the bearer token.
- Respond quickly – Return
200promptly; process events asynchronously if needed. - Handle retries – Implement idempotency in case of duplicate deliveries.
- Log events – Keep records of received events for debugging and auditing
Session Events (SSE)
The SSE endpoint streams issuance-session events to a connected client in real time. Use it to monitor the progress of a credential offer as it is claimed.
Endpoint: GET /v2/{target}/issuer-service-api/issuance-session/{issuance-session}/events | API Reference
Example Request
curl -N \
'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{target}/issuer-service-api/issuance-session/{offerId}/events' \
-H 'Authorization: Bearer {yourToken}' \
-H 'Accept: text/event-stream'
Path Parameters
- orgID: String (required) - Your organization ID, e.g.
test.enterprise-sandbox.waltid.dev. - target: String (required) - The resource identifier of the issuer service,
{organizationID}.{tenantID}.{issuerServiceID}. - issuance-session: String (required) - The
offerIdreturned when creating the credential offer.
Header Parameters
- Authorization: String (required) - Bearer token. Format:
Bearer {token}.
Example Response
Each event is delivered as a single SSE data: line. The event name is carried inside the JSON payload's event property (the same envelope as the webhook). The first line is an empty priming frame sent when the connection opens:
data: {}
data: {"target":"9cf9e1f6-dc53-48f2-8124-b160433d7728","event":"credential_offer_retrieved","requestId":"...","session":{ ... }}
data: {"target":"9cf9e1f6-dc53-48f2-8124-b160433d7728","event":"token_request_pre_authorized_code_succeeded","requestId":"...","session":{ ... }}
Event Types
The session SSE stream emits the same correlated issuance-session events listed under Webhook Events. Uncorrelated failures and nonce events are available on GET /v2/{target}/issuer-service-api/events.
Event Format
Every event is a single data: line whose value is the JSON envelope; there is no SSE event: line:
data: {"target":"{offerId}","event":"{eventType}","requestId":"{requestId}","session":{ ... }}
Best Practices
- Handle reconnection – SSE connections may drop; implement reconnection logic.
- Set timeouts – Close connections after the expected session duration.
- Prefer webhooks for production – For server-to-server integration, webhooks are more reliable than SSE.
- Monitor Multiple Sessions – Open separate SSE connections for each session you want to monitor
Next Steps
- Create a Profile – Add notifications to your profiles.
- Create an Offer – Override notifications per offer.
