Skip to main content

Webhooks

Webhooks deliver verification results to your server in real time. When a verification completes, is approved, is rejected, or triggers a fraud alert, Kora IDV sends an HTTP POST to your configured endpoint.

Setup

You can configure webhooks in two ways:

  1. Global endpoint — Set a webhook URL in your dashboard settings to receive all events for your tenant
  2. Per-verification callback — Pass a callbackUrl when creating a verification to receive events for that specific verification

If both are configured, events are delivered to both endpoints.

Payload format

All webhook payloads follow the same structure:

{
"id": "evt_def456",
"eventType": "verification.completed",
"resourceType": "verification",
"resourceId": "ver_abc123",
"tenantId": "your-tenant-uuid",
"timestamp": "2025-01-15T10:35:00Z",
"data": {
"verificationId": "ver_abc123",
"externalId": "user-123",
"status": "verified",
"decision": "auto_approve",
"decisionReason": "All checks passed",
"overallScore": 96.3
}
}

Signature verification

Every webhook includes an X-Kora-Signature header containing an HMAC-SHA256 hash of the request body. Always verify this signature before processing the payload.

const crypto = require("crypto");

function verifyWebhookSignature(body, signature, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(typeof body === "string" ? body : JSON.stringify(body))
.digest("hex");

return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}

// In your Express handler
app.post("/webhooks/koraidv", express.json(), (req, res) => {
const signature = req.headers["x-kora-signature"];
if (!verifyWebhookSignature(req.body, signature, WEBHOOK_SECRET)) {
return res.status(401).json({ error: "Invalid signature" });
}

// Process the webhook...
res.status(200).json({ received: true });
});
warning

Always use timing-safe comparison functions (crypto.timingSafeEqual, hmac.compare_digest, hmac.Equal) to prevent timing attacks.

Event types

EventDescriptionTrigger
verification.createdNew verification session createdPOST /verifications
verification.completedAll verification steps processedPOST /verifications/{id}/complete
verification.verifiedVerification approved (auto or manual)Score above threshold or manual approval
verification.rejectedVerification rejectedCritical failure, sanctions hit, or manual rejection
verification.expiredSession expired before completion24-hour timeout
document.uploadedDocument image uploaded and processedPOST /verifications/{id}/document
document.verifiedDocument authenticity checks completedAfter document processing
liveness.completedLiveness session finishedAll challenges submitted
fraud_alert.createdFraud signal detectedDuplicate document, spoof, tampering

Example payloads

verification.completed

{
"id": "evt_def456",
"eventType": "verification.completed",
"resourceType": "verification",
"resourceId": "ver_abc123",
"tenantId": "your-tenant-uuid",
"timestamp": "2025-01-15T10:35:00Z",
"data": {
"verificationId": "ver_abc123",
"externalId": "user-123",
"status": "verified",
"decision": "auto_approve",
"decisionReason": "All checks passed",
"overallScore": 96.3
}
}

fraud_alert.created

{
"id": "evt_yza567",
"eventType": "fraud_alert.created",
"resourceType": "fraud_alert",
"resourceId": "fa_ghi789",
"tenantId": "your-tenant-uuid",
"timestamp": "2025-01-15T10:32:00Z",
"data": {
"alertId": "fa_ghi789",
"verificationId": "ver_abc123",
"alertType": "document_tampered",
"severity": "critical",
"description": "Document tampering detected: PHOTO_REPLACED"
}
}

Retry behavior

Failed deliveries (non-2xx response or timeout) are retried up to 5 times with exponential backoff:

AttemptDelay
11 minute
25 minutes
330 minutes
42 hours
524 hours

After 3 consecutive failures, you'll receive an email notification. Failed deliveries can also be manually retried from the webhook logs in your dashboard.

Best practices

  • Return 200 quickly — Process webhooks asynchronously. Return a 200 status immediately, then process the event in a background job.
  • Handle duplicates — Use the event id field for idempotency. The same event may be delivered more than once.
  • Verify signatures — Always verify the X-Kora-Signature header before trusting the payload.
  • Log payloads — Store raw webhook payloads for debugging and audit trails.
  • Use HTTPS — Webhook endpoints must use HTTPS in production.