Overview
Webhooks provide real-time notifications when verifications complete, eliminating the need for polling and enabling event-driven workflows.
How Webhooks Work
Configure Webhook
Provide webhook URL when creating verification
Verification Processes
AnyCheck processes your verification asynchronously
Webhook Delivered
When complete, POST request sent to your webhook URL
Process Results
Your endpoint processes the webhook payload
Configuring Webhooks
During Verification Creation
curl -X POST https://api.anycheck.id/verifications \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"service_id": "service-uuid",
"folder_id": "folder-uuid",
"configuration": {...},
"webhook_url": "https://your-app.com/webhooks/anycheck",
"webhook_metadata": {
"customer_id": "cust-123",
"order_id": "order-456",
"internal_ref": "ref-789"
}
}'
Include custom data in webhook_metadata to identify the verification in your system:
{
"webhook_metadata" : {
"customer_id" : "cust-123" ,
"order_id" : "order-456" ,
"application_id" : "app-789" ,
"user_email" : "[email protected] " ,
"custom_field" : "any value"
}
}
This metadata is included in the webhook payload.
Webhook Payload
When verification completes, your endpoint receives:
{
"event" : "verification.completed" ,
"verification_id" : "verification-uuid" ,
"result_id" : "result-uuid" ,
"status" : "COMPLETED" ,
"service" : {
"id" : "service-uuid" ,
"name" : "KTP Verification"
},
"output" : {
"verified" : true ,
"match_score" : 0.95 ,
"data" : {
"nik" : "1234567890123456" ,
"name" : "JOHN DOE" ,
"birth_date" : "1990-01-01"
}
},
"metadata" : {
"customer_id" : "cust-123" ,
"order_id" : "order-456" ,
"internal_ref" : "ref-789"
},
"execution_time" : 5.2 ,
"completed_at" : "2024-01-15T10:05:30Z" ,
"credit_used" : 1
}
Payload Fields
Event type: verification.completed, verification.failed, verification.needs_review
Unique verification identifier
Final status: COMPLETED, FAILED, NEED_REVIEW
Verification results (only if COMPLETED)
Your custom metadata from webhook_metadata
Implementing Webhook Handler
Node.js/Express Example
const express = require ( 'express' );
const crypto = require ( 'crypto' );
const app = express ();
app . use ( express . json ());
app . post ( '/webhooks/anycheck' , async ( req , res ) => {
try {
// 1. Verify webhook signature (if configured)
const signature = req . headers [ 'x-webhook-signature' ];
if ( ! verifySignature ( req . body , signature )) {
return res . status ( 401 ). send ( 'Invalid signature' );
}
// 2. Respond quickly
res . status ( 200 ). send ( 'OK' );
// 3. Process asynchronously
await processWebhook ( req . body );
} catch ( error ) {
console . error ( 'Webhook error:' , error );
res . status ( 500 ). send ( 'Error processing webhook' );
}
});
async function processWebhook ( payload ) {
const { verification_id , status , output , metadata } = payload ;
// Update your database
await db . verifications . update ({
id: verification_id ,
status: status ,
results: output ,
completed_at: new Date ()
});
// Trigger business logic
if ( status === 'COMPLETED' && output . verified ) {
await approveCustomerApplication ( metadata . customer_id );
await sendNotificationEmail ( metadata . customer_id );
} else if ( status === 'NEED_REVIEW' ) {
await addToReviewQueue ( verification_id );
} else if ( status === 'FAILED' ) {
await handleFailedVerification ( verification_id );
}
}
Python/Flask Example
from flask import Flask, request
import hmac
import hashlib
app = Flask( __name__ )
@app.route ( '/webhooks/anycheck' , methods = [ 'POST' ])
def webhook_handler ():
try :
# 1. Verify signature
signature = request.headers.get( 'X-Webhook-Signature' )
if not verify_signature(request.data, signature):
return 'Invalid signature' , 401
# 2. Respond quickly
payload = request.json
# 3. Process asynchronously
process_webhook.delay(payload) # Using Celery
return 'OK' , 200
except Exception as e:
print ( f 'Webhook error: { e } ' )
return 'Error' , 500
def process_webhook ( payload ):
verification_id = payload[ 'verification_id' ]
status = payload[ 'status' ]
output = payload[ 'output' ]
metadata = payload[ 'metadata' ]
# Update database
db.verifications.update(
id = verification_id,
status = status,
results = output
)
# Business logic
if status == 'COMPLETED' and output[ 'verified' ]:
approve_application(metadata[ 'customer_id' ])
elif status == 'NEED_REVIEW' :
add_to_review_queue(verification_id)
Webhook Events
verification.completed
Verification successfully completed:
{
"event" : "verification.completed" ,
"status" : "COMPLETED" ,
"output" : {
"verified" : true ,
"data" : { ... }
}
}
verification.needs_review
Verification requires manual review:
{
"event" : "verification.needs_review" ,
"status" : "NEED_REVIEW" ,
"review_reason" : "Low confidence score" ,
"output" : {
"verified" : null ,
"confidence" : 0.65
}
}
verification.failed
Verification failed:
{
"event" : "verification.failed" ,
"status" : "FAILED" ,
"error" : {
"code" : "PROVIDER_ERROR" ,
"message" : "External verification service unavailable"
}
}
Security
Webhook Signature Verification
Verify webhook authenticity using HMAC signature (if webhook secret is configured):
function verifySignature ( payload , signature ) {
const secret = process . env . WEBHOOK_SECRET ;
const hmac = crypto
. createHmac ( 'sha256' , secret )
. update ( JSON . stringify ( payload ))
. digest ( 'hex' );
return hmac === signature ;
}
Best Practices
Always use HTTPS endpoints for webhooks: ✅ https://your-app.com/webhooks/anycheck
❌ http://your-app.com/webhooks/anycheck
Return 200 OK within 5 seconds: app . post ( '/webhook' , async ( req , res ) => {
res . status ( 200 ). send ( 'OK' ); // Respond first
await processWebhook ( req . body ); // Process async
});
Handle duplicate webhook deliveries: const processedWebhooks = new Set ();
async function processWebhook ( payload ) {
const webhookId = payload . verification_id ;
if ( processedWebhooks . has ( webhookId )) {
return ; // Already processed
}
// Process...
processedWebhooks . add ( webhookId );
}
Validate webhook structure: function validateWebhook ( payload ) {
if ( ! payload . verification_id ) {
throw new Error ( 'Missing verification_id' );
}
if ( ! payload . status ) {
throw new Error ( 'Missing status' );
}
// More validations...
}
Log webhook failures for debugging: app . post ( '/webhook' , async ( req , res ) => {
try {
await processWebhook ( req . body );
res . status ( 200 ). send ( 'OK' );
} catch ( error ) {
await logWebhookError ( req . body , error );
res . status ( 500 ). send ( 'Error' );
}
});
Retry Mechanism
AnyCheck automatically retries failed webhook deliveries:
Retry Count : Up to 3 attempts
Retry Delay : Exponential backoff (5s, 25s, 125s)
Failure Condition : Non-2xx response or timeout
If all retries fail, webhook is marked as failed and can be manually retriggered.
Testing Webhooks
Local Development
Use tools like ngrok to expose local server:
# Start ngrok
ngrok http 3000
# Use ngrok URL as webhook
https://abc123.ngrok.io/webhooks/anycheck
RequestBin : Capture and inspect webhooks
Webhook.site : Test webhook URLs
Postman : Mock webhook requests
Manual Webhook Trigger
For testing, manually trigger webhook:
curl -X POST https://your-app.com/webhooks/anycheck \
-H "Content-Type: application/json" \
-d '{
"event": "verification.completed",
"verification_id": "test-uuid",
"status": "COMPLETED",
"output": {...}
}'
Webhook vs Polling
Use Webhooks When:
✅ Real-time processing needed
✅ High volume of verifications
✅ Event-driven architecture
✅ Reducing server load
Use Polling When:
❌ Cannot expose public endpoint
❌ Behind strict firewall
❌ Low verification volume
❌ Simple integration needed
Troubleshooting
Possible Causes:
Webhook URL not accessible
Firewall blocking requests
SSL certificate issues
Endpoint returning non-2xx
Solution:
Verify URL is publicly accessible
Check firewall rules
Ensure valid SSL certificate
Return 200 OK from endpoint
Cause:
Retry mechanism or network issuesSolution:
Implement idempotency using verification_id
Cause:
Synchronous processing in webhook handlerSolution:
Process webhooks asynchronously:res . status ( 200 ). send ( 'OK' ); // Respond first
await queueJob ( payload ); // Process async
Next Steps