Webhooks
Set up real-time event notifications for verifications, AML screenings, deepfake scans, and KYT alerts using webhooks.
Webhooks
Receive real-time HTTP notifications when events occur in your VeriPlus account. Webhooks eliminate the need to poll APIs for status updates.
Overview
Webhooks send HTTP POST requests to your server when:
- Verification completes
- AML screening finds matches
- Deepfake scan detects manipulation
- KYT wallet monitoring triggers alert
- Credit balance runs low
Setting Up Webhooks
Create Webhook Endpoint
/api/v3/webhooks🔒 Auth Required0Create a webhook endpoint
Register a webhook URL to receive event notifications.
const response = await fetch('https://api.veriplus.co.uk/api/v3/webhooks', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
url: 'https://yourdomain.com/webhooks/veriplus',
events: [
'verification.completed',
'aml.match_found',
'deepfake.scan_completed',
'kyt.high_risk_detected'
],
secret: 'whsec_' + crypto.randomUUID(),
description: 'Production webhook endpoint'
})
});
const data = await response.json();
console.log('Webhook ID:', data.data.webhookId);
console.log('Secret:', data.data.secret); // Save this!Response
{
"success": true,
"data": {
"webhookId": "wh_abc123xyz",
"url": "https://yourdomain.com/webhooks/veriplus",
"events": [
"verification.completed",
"aml.match_found",
"deepfake.scan_completed",
"kyt.high_risk_detected"
],
"secret": "whsec_1a2b3c4d5e6f",
"status": "ACTIVE",
"createdAt": "2024-01-15T10:30:00Z"
}
}Save Your Secret
The webhook secret is only shown once. Store it securely - you'll need it to verify webhook signatures.
Receiving Webhooks
Request Format
VeriPlus sends POST requests to your webhook URL:
POST /webhooks/veriplus HTTP/1.1
Host: yourdomain.com
Content-Type: application/json
X-Veriplus-Signature: sha256=a1b2c3d4e5f6...
X-Veriplus-Event: verification.completed
X-Veriplus-Delivery: del_xyz789
{
"event": "verification.completed",
"timestamp": "2024-01-15T10:32:14Z",
"data": {
"verificationId": "verif_abc123",
"applicantId": "app_1234567890",
"status": "COMPLETED",
"result": "APPROVED"
}
}Headers
| Header | Description |
|---|---|
X-Veriplus-Signature | HMAC-SHA256 signature for verification |
X-Veriplus-Event | Event type |
X-Veriplus-Delivery | Unique delivery ID |
Implementing Webhook Handler
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());
const WEBHOOK_SECRET = process.env.VERIPLUS_WEBHOOK_SECRET;
function verifySignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature.replace('sha256=', '')),
Buffer.from(expectedSignature)
);
}
app.post('/webhooks/veriplus', (req, res) => {
const signature = req.headers['x-veriplus-signature'];
const event = req.headers['x-veriplus-event'];
const payload = req.body;
// Verify signature
if (!verifySignature(payload, signature, WEBHOOK_SECRET)) {
console.error('Invalid webhook signature');
return res.status(401).json({ error: 'Invalid signature' });
}
// Process event
console.log('Received event:', event);
switch (event) {
case 'verification.completed':
handleVerificationCompleted(payload.data);
break;
case 'aml.match_found':
handleAmlMatch(payload.data);
break;
case 'deepfake.scan_completed':
handleDeepfakeScan(payload.data);
break;
case 'kyt.high_risk_detected':
handleKytAlert(payload.data);
break;
default:
console.log('Unknown event type:', event);
}
// Return 200 to acknowledge receipt
res.status(200).json({ received: true });
});
function handleVerificationCompleted(data) {
console.log('Verification completed:', data.verificationId);
console.log('Result:', data.result);
// Update your database, send email, etc.
if (data.result === 'APPROVED') {
// Activate customer account
} else if (data.result === 'REJECTED') {
// Notify compliance team
}
}
function handleAmlMatch(data) {
console.log('AML match found:', data.screeningId);
console.log('Risk level:', data.riskLevel);
// Alert compliance team for manual review
if (data.riskLevel === 'CRITICAL') {
// Send urgent notification
}
}
app.listen(3000, () => {
console.log('Webhook server listening on port 3000');
});Response Requirements
Your endpoint MUST:
- Return HTTP 200 status code
- Respond within 5 seconds
- Verify the signature before processing
Failure to respond: Webhook will be retried with exponential backoff (max 3 retries).
Event Types
Verification Events
| Event | Description | Payload Fields |
|---|---|---|
verification.created | Verification started | verificationId, applicantId, profileType |
verification.pending_upload | Awaiting user upload | verificationId, uploadUrl, expiresAt |
verification.completed | Verification finished | verificationId, status, result |
verification.approved | Auto-approved | verificationId, result, checks |
verification.rejected | Auto-rejected | verificationId, result, failureReasons |
verification.manual_review | Needs human review | verificationId, reviewReason |
Example Payload:
{
"event": "verification.completed",
"timestamp": "2024-01-15T10:32:14Z",
"data": {
"verificationId": "verif_abc123",
"applicantId": "app_1234567890",
"status": "COMPLETED",
"result": "APPROVED",
"checks": {
"document": "PASS",
"faceMatch": "PASS"
}
}
}AML Events
| Event | Description | Payload Fields |
|---|---|---|
aml.screening_completed | Screening finished | screeningId, matchCount, riskLevel |
aml.match_found | Match detected | screeningId, matches, riskLevel |
aml.monitoring_alert | New match in monitoring | monitoringId, newMatches |
aml.sanctions_alert | Sanctions match (critical) | screeningId, sanctionsList |
Example Payload:
{
"event": "aml.match_found",
"timestamp": "2024-01-15T10:32:14Z",
"data": {
"screeningId": "aml_abc123",
"applicantId": "app_1234567890",
"matchCount": 2,
"riskLevel": "HIGH",
"categories": ["PEP", "ADVERSE_MEDIA"]
}
}Deepfake Events
| Event | Description | Payload Fields |
|---|---|---|
deepfake.scan_completed | Analysis finished | jobId, mediaType, isDeepfake |
deepfake.deepfake_detected | High-confidence deepfake | jobId, confidence, manipulationType |
deepfake.job_failed | Analysis failed | jobId, errorCode, errorMessage |
Example Payload:
{
"event": "deepfake.deepfake_detected",
"timestamp": "2024-01-15T10:31:05Z",
"data": {
"jobId": "job_vid_xyz789",
"mediaType": "VIDEO",
"isDeepfake": true,
"confidence": 89,
"manipulationType": "FACE_SWAP",
"recommendation": "REJECT"
}
}KYT Events
| Event | Description | Payload Fields |
|---|---|---|
kyt.check_completed | Wallet check finished | checkId, address, riskScore |
kyt.high_risk_detected | Risk score above threshold | checkId, riskScore, topRisks |
kyt.sanctions_match | Sanctioned address | checkId, address, sanctionsList |
kyt.monitoring_alert | Risk change detected | monitoringId, oldRiskScore, newRiskScore |
Example Payload:
{
"event": "kyt.high_risk_detected",
"timestamp": "2024-01-15T10:30:00Z",
"data": {
"checkId": "kyt_abc123",
"address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
"blockchain": "BTC",
"riskScore": 72,
"riskLevel": "HIGH",
"topRisks": ["MIXER", "DARKNET_MARKETS"]
}
}System Events
| Event | Description | Payload Fields |
|---|---|---|
credits.low_balance | Credit balance low | currentBalance, threshold |
credits.depleted | No credits remaining | organizationId |
Example Payload:
{
"event": "credits.low_balance",
"timestamp": "2024-01-15T10:00:00Z",
"data": {
"organizationId": "org_abc123",
"currentBalance": 50,
"threshold": 100,
"message": "Your credit balance is running low. Purchase more credits to avoid service interruption."
}
}Managing Webhooks
List Webhooks
/api/v3/webhooks🔒 Auth Required0List all webhook endpoints
const response = await fetch('https://api.veriplus.co.uk/api/v3/webhooks', {
headers: {
'Authorization': `Bearer ${apiKey}`
}
});
const data = await response.json();
console.log('Webhooks:', data.data);Update Webhook
/api/v3/webhooks/:id🔒 Auth Required0Update webhook configuration
const response = await fetch(
`https://api.veriplus.co.uk/api/v3/webhooks/${webhookId}`,
{
method: 'PATCH',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
events: [
'verification.completed',
'aml.match_found',
'deepfake.scan_completed',
'kyt.high_risk_detected',
'credits.low_balance'
]
})
}
);Delete Webhook
/api/v3/webhooks/:id🔒 Auth Required0Delete webhook endpoint
const response = await fetch(
`https://api.veriplus.co.uk/api/v3/webhooks/${webhookId}`,
{
method: 'DELETE',
headers: {
'Authorization': `Bearer ${apiKey}`
}
}
);
if (response.ok) {
console.log('Webhook deleted');
}Test Webhook
/api/v3/webhooks/:id/test🔒 Auth Required0Send test event to webhook
Send a test event to verify your webhook is working.
const response = await fetch(
`https://api.veriplus.co.uk/api/v3/webhooks/${webhookId}/test`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
event: 'verification.completed'
})
}
);
const data = await response.json();
console.log('Test delivery ID:', data.data.deliveryId);
console.log('Response code:', data.data.responseCode);Webhook Deliveries
View delivery logs for debugging:
/api/v3/webhooks/:id/deliveries🔒 Auth Required0List webhook delivery attempts
const response = await fetch(
`https://api.veriplus.co.uk/api/v3/webhooks/${webhookId}/deliveries`,
{
headers: {
'Authorization': `Bearer ${apiKey}`
}
}
);
const data = await response.json();
data.data.forEach(delivery => {
console.log(`${delivery.event}: ${delivery.responseCode} (${delivery.duration}ms)`);
});Response:
{
"success": true,
"data": [
{
"deliveryId": "del_xyz789",
"event": "verification.completed",
"responseCode": 200,
"duration": 234,
"success": true,
"timestamp": "2024-01-15T10:32:14Z"
},
{
"deliveryId": "del_abc123",
"event": "aml.match_found",
"responseCode": 500,
"duration": 5023,
"success": false,
"retryCount": 3,
"timestamp": "2024-01-15T09:15:00Z"
}
]
}Retry Logic
Failed deliveries are retried automatically:
| Attempt | Delay | Total Elapsed |
|---|---|---|
| 1 | Immediate | 0s |
| 2 | 30 seconds | 30s |
| 3 | 5 minutes | 5m 30s |
After 3 failed attempts, the webhook is marked as failed and no further retries occur.
Best Practices
- Verify Signatures: Always verify
X-Veriplus-Signaturebefore processing - Return 200 Quickly: Process events asynchronously, respond within 5 seconds
- Handle Duplicates: Same event may be delivered multiple times (use delivery ID for deduplication)
- Log Deliveries: Keep audit log of webhook events received
- Use HTTPS: Webhook URLs must use HTTPS (HTTP will be rejected)
- Monitor Failures: Set up alerts for failed webhook deliveries
- Test Thoroughly: Use test endpoint before going live
Security
- HTTPS Only: All webhook URLs must use HTTPS
- Signature Verification: Required to prevent spoofing
- IP Allowlist: Optionally restrict to VeriPlus IPs (Enterprise)
- Secret Rotation: Rotate webhook secrets every 90 days
VeriPlus Webhook IPs (for allowlisting):
203.0.113.10
203.0.113.11
203.0.113.12
Debugging
Webhook not receiving events?
- Check webhook is ACTIVE:
GET /api/v3/webhooks - Verify URL is publicly accessible
- Check firewall allows VeriPlus IPs
- Review delivery logs:
GET /api/v3/webhooks/:id/deliveries - Test webhook:
POST /api/v3/webhooks/:id/test
Common Issues:
- SSL certificate invalid → Fix SSL configuration
- Timeout after 5 seconds → Process events asynchronously
- Signature verification fails → Check secret matches
Next Steps
Ready to get started?
Start with our free plan. No credit card required.