Webhooks
Receive real-time notifications when events occur in your vouchID integration. Webhooks allow your application to react immediately to verification completions, compliance actions, and monitoring alerts.
One Endpoint, All Events
Configure a single webhook URL per application. All event types are sent to the same endpoint, and you filter by the eventType field in the payload.
Setting Up Webhooks
Configure your webhook URL in the Developer Console under your application settings:
1. Set Webhook URL
Enter your HTTPS endpoint in the application settings
2. Generate Secret
Click "New Secret" to generate your signing key
3. Verify Signatures
Validate HMAC signatures to ensure authenticity
Event Types
vouchID sends webhooks for the following events:
| Event Type | Description |
|---|---|
| verification.completed | A user has completed age or identity verification |
| monitoring.alert.created | A monitoring alert was generated (sanctions hit, PEP match, etc.) |
| case.created | A compliance case was created (manual or automatic) |
| case.resolved | A compliance case was resolved by a compliance officer |
| case.status_changed | A case status changed (OPEN → IN_REVIEW → ESCALATED, etc.) |
| vid.fraud_status_changed | A user was added to or removed from the fraud list |
Payload Format
All webhook payloads follow a consistent structure:
| Field | Description |
|---|---|
| apiVersion | API version for payload format (currently 2024-01-01) |
| eventId | Unique identifier for deduplication (use this to avoid processing duplicates) |
| eventType | The type of event (see Event Types above) |
| timestamp | ISO 8601 timestamp when the event occurred |
| data | Event-specific data (varies by event type) |
Verifying Webhook Signatures
Always Verify Signatures
Always verify the webhook signature before processing. This ensures the request came from vouchID and hasn't been tampered with.
Each webhook request includes two headers for signature verification:
| Header | Description |
|---|---|
| X-VouchID-Signature | HMAC-SHA256 signature (prefixed with sha256=) |
| X-VouchID-Timestamp | ISO 8601 timestamp when the webhook was sent (used in signature) |
Signature Verification Steps
- 1. Extract the
X-VouchID-Timestampheader - 2. Validate the timestamp is within ±5 minutes of current time (replay protection)
- 3. Read the raw request body as a string
- 4. Compute:
HMAC_SHA256(secret, "{timestamp}.{rawBody}") - 5. Compare with the signature from
X-VouchID-Signature(strip thesha256=prefix)
const crypto = require('crypto');
// Middleware to verify webhook signatures
function verifyWebhookSignature(req, res, next) {
const signature = req.headers['x-vouchid-signature'];
const timestamp = req.headers['x-vouchid-timestamp'];
const rawBody = req.rawBody; // You need raw body - see note below
if (!signature || !timestamp) {
return res.status(401).json({ error: 'Missing signature headers' });
}
// 1. Check timestamp is within 5 minutes (replay protection)
const timestampDate = new Date(timestamp);
const now = new Date();
const diffMinutes = Math.abs(now - timestampDate) / (1000 * 60);
if (diffMinutes > 5) {
return res.status(401).json({ error: 'Timestamp too old' });
}
// 2. Compute expected signature
const signatureInput = `${timestamp}.${rawBody}`;
const expectedSignature = 'sha256=' + crypto
.createHmac('sha256', process.env.VOUCHID_WEBHOOK_SECRET)
.update(signatureInput)
.digest('hex');
// 3. Compare signatures (timing-safe)
if (!crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
)) {
return res.status(401).json({ error: 'Invalid signature' });
}
next();
}
// IMPORTANT: Preserve raw body for signature verification
app.use('/webhooks/vouchid', express.json({
verify: (req, res, buf) => {
req.rawBody = buf.toString();
}
}));
app.post('/webhooks/vouchid', verifyWebhookSignature, (req, res) => {
const { eventType, eventId, data } = req.body;
// TODO: Check eventId for deduplication
switch (eventType) {
case 'case.created':
console.log('New compliance case:', data.caseId);
// Handle case creation
break;
case 'vid.fraud_status_changed':
console.log('Fraud status changed:', data.scopedVid, data.action);
// Handle fraud status change
break;
// ... handle other event types
}
// Always return 200 quickly to acknowledge receipt
res.status(200).json({ received: true });
});Best Practices
Return 200 Quickly
Acknowledge receipt immediately and process asynchronously. We timeout after 30 seconds and will retry on failure (up to 5 attempts).
Deduplicate Events
Store processed eventIds and skip duplicates. We guarantee at-least-once delivery.
Handle Retries
We retry failed deliveries with exponential backoff (1s, 2s, 4s, 8s, 16s). Make your handler idempotent.
Validate Timestamps
Reject webhooks with timestamps older than 5 minutes to prevent replay attacks.
Event Payload Examples
case.created
vid.fraud_status_changed
monitoring.alert.created
Need Help?
If you have questions about webhook integration or need assistance debugging, contact our support team at vouchid.co/support.