Notifications
The Issuer2 Service 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 resolved by a wallet
- An access token is requested
- A credential is issued
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"
}
}
}
With Authentication
{
"notifications": {
"webhook": {
"url": "https://your-server.com/webhook/issuance",
"bearerToken": "your-secret-token"
}
}
}
Configuration Properties
| Property | Type | Required | Description |
|---|---|---|---|
webhook.url | String | Yes | The URL to receive webhook notifications |
webhook.bearerToken | String | No | Bearer token for authentication |
Webhook Events
Your webhook endpoint will receive POST requests with JSON payloads for the following events:
Event Structure
{
"id": "offer-id-123",
"type": "event_type",
"timestamp": 1704067200000,
"data": { ... }
}
| Field | Description |
|---|---|
id | The offer/session ID |
type | The event type |
timestamp | Unix timestamp of the event |
data | Event-specific payload |
Event Types
resolved_credential_offer
Triggered when a wallet resolves the credential offer.
{
"id": "offer-id-123",
"type": "resolved_credential_offer",
"timestamp": 1704067200000,
"data": {
"credentialOffer": { ... }
}
}
requested_token
Triggered when a wallet requests an access token.
{
"id": "offer-id-123",
"type": "requested_token",
"timestamp": 1704067210000,
"data": {
"tokenRequest": { ... }
}
}
sdjwt_issue
Triggered when an SD-JWT VC credential is issued.
{
"id": "offer-id-123",
"type": "sdjwt_issue",
"timestamp": 1704067220000,
"data": {
"sdjwt": "eyJ..."
}
}
jwt_issue
Triggered when a W3C JWT credential is issued.
{
"id": "offer-id-123",
"type": "jwt_issue",
"timestamp": 1704067220000,
"data": {
"jwt": "eyJ..."
}
}
generated_mdoc
Triggered when an mDoc credential is generated.
{
"id": "offer-id-123",
"type": "generated_mdoc",
"timestamp": 1704067220000,
"data": {
"mdoc": "a26..."
}
}
Webhook Implementation
Example Endpoint (Node.js/Express)
const express = require('express');
const app = express();
app.use(express.json());
app.post('/webhook/issuance', (req, res) => {
const authHeader = req.headers.authorization;
// Verify bearer token
if (authHeader !== 'Bearer your-secret-token') {
return res.status(401).send('Unauthorized');
}
const { id, type, timestamp, data } = req.body;
console.log(`Event: ${type} for offer ${id}`);
switch (type) {
case 'resolved_credential_offer':
console.log('Offer resolved by wallet');
break;
case 'requested_token':
console.log('Token requested');
break;
case 'sdjwt_issue':
case 'jwt_issue':
case 'generated_mdoc':
console.log('Credential issued');
// Update your database, trigger workflows, etc.
break;
}
res.status(200).send('OK');
});
app.listen(3000);
Example Endpoint (Python/Flask)
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/webhook/issuance', methods=['POST'])
def handle_webhook():
auth_header = request.headers.get('Authorization')
# Verify bearer token
if auth_header != 'Bearer your-secret-token':
return 'Unauthorized', 401
event = request.json
event_type = event.get('type')
offer_id = event.get('id')
print(f"Event: {event_type} for offer {offer_id}")
if event_type in ['sdjwt_issue', 'jwt_issue', 'generated_mdoc']:
# Credential was issued - update your systems
pass
return 'OK', 200
if __name__ == '__main__':
app.run(port=3000)
Profile-Level Notifications
Configure notifications in your credential profile to apply to all offers created from that profile:
{
"name": "Notified Credential Profile",
"credentialConfigurationId": "identity_credential_vc+sd-jwt",
"issuerKeyId": "waltid.tenant1.kms1.key1",
"credentialData": { ... },
"notifications": {
"webhook": {
"url": "https://your-server.com/webhook/issuance",
"bearerToken": "profile-secret-token"
}
}
}
Per-Offer Notifications
Override profile notifications for a specific offer using runtime overrides:
{
"authMethod": "PRE_AUTHORIZED",
"runtimeOverrides": {
"notifications": {
"webhook": {
"url": "https://different-server.com/webhook/special-offer",
"bearerToken": "offer-specific-token"
}
}
}
}
Best Practices
- Secure your endpoint – Always use HTTPS and verify the bearer token
- Respond quickly – Return a 200 response promptly; 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
- Monitor failures – Set up alerts for webhook delivery failures
Error Handling
If your webhook endpoint is unreachable or returns an error:
- The issuance process will continue
- Events may be lost if not delivered successfully
- Consider implementing a fallback mechanism (e.g., polling the session events endpoint)
Alternative: Session Events (SSE)
For real-time monitoring in browser-based applications, consider using Server-Sent Events instead of webhooks.
| 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
- Create a Profile – Add notifications to your profiles
- Session Events – Real-time monitoring via SSE
