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

PropertyTypeRequiredDescription
webhook.urlStringYesThe URL to receive webhook notifications
webhook.bearerTokenStringNoBearer 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": { ... }
}
FieldDescription
idThe offer/session ID
typeThe event type
timestampUnix timestamp of the event
dataEvent-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

  1. Secure your endpoint – Always use HTTPS and verify the bearer token
  2. Respond quickly – Return a 200 response promptly; process events asynchronously if needed
  3. Handle retries – Implement idempotency in case of duplicate deliveries
  4. Log events – Keep records of received events for debugging and auditing
  5. 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.

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 April 8, 2026