Webhooks
Webhooks are how Veridia pushes verification results to your backend the moment they're ready. Push, not poll. Signed, not anonymous. Retried, not best-effort.
This is the recommended way to consume verdicts in production.
Why webhooks beat polling
| Concern | Polling | Webhooks |
|---|---|---|
| Latency from completion to your code | 1-3 seconds (poll interval) | <200ms |
| API calls per verification | 3-30 | 1 |
| Reliability if your service is down | You miss it | We retry 5 times |
| Audit trail | None | Full delivery log in dashboard |
| Cost (your serverless bill) | Cold start every poll | One invocation |
The widget event handler still works for client-side UX feedback ("Submitted!"), but the verdict should arrive via webhook.
Setup
- Sign in to your Veridia dashboard
- Open Webhooks → Endpoints
- Add a new endpoint:
- URL:
https://yourapp.com/webhooks/veridia(must be HTTPS in production) - Events: select which events to receive (or "All")
- Description: optional internal label
- URL:
- Copy the webhook secret — you'll need it to verify signatures. It's shown only once.
- Save
That's it. The next verification that completes triggers a POST to your URL.
What you receive
Every webhook is a POST request with:
POST /webhooks/veridia HTTP/1.1
Host: yourapp.com
Content-Type: application/json
Veridia-Signature: t=1714604000,v1=4f8a3b9c...
Veridia-Event: verification.approved
User-Agent: Veridia-Webhooks/1.0
{
"event": "verification.approved",
"verificationId": "vf_AG07CDWRRFQV4T05ZXG2",
"tenantId": "tn_default_demo",
"userRef": "customer-12345",
"verdict": "approved",
"confidence": 87.4,
"scores": {
"ocrConfidence": 78.0,
"faceMatch": 96.2,
"liveness": 91.5,
"docQuality": 85.0
},
"flags": [],
"metadata": {},
"submittedAt": "2026-05-01T18:39:05Z",
"completedAt": "2026-05-01T18:39:08Z"
}
The body is compact JSON with sorted keys — required for reproducible signature verification.
Event types
| Event | Verdict | When |
|---|---|---|
verification.approved | approved | High confidence, all signals pass |
verification.rejected | rejected | Low confidence or critical flag |
verification.review_required | review | Medium confidence, manual review recommended |
Full schemas: Event types.
Required: respond 2xx quickly
Your endpoint must respond with 2xx within 10 seconds. That's the timeout we use before considering the delivery failed.
If your webhook handler does heavy work (database writes, sending emails, calling other APIs), do it after responding:
// Express
app.post('/webhooks/veridia', async (req, res) => {
// 1. Verify signature (fast)
if (!verifySignature(req)) {
return res.status(401).send('Invalid signature');
}
// 2. Acknowledge immediately
res.status(200).send('ok');
// 3. Heavy work happens after the response
await processVerificationAsync(req.body);
});
This is the standard pattern for any webhook system (Stripe, GitHub, etc.).
Retry policy
If your endpoint returns a non-2xx status, times out, or is unreachable, Veridia retries with exponential backoff:
| Attempt | Wait before retry |
|---|---|
| 1 | (immediate) |
| 2 | 1 second |
| 3 | 5 seconds |
| 4 | 30 seconds |
| 5 | 2 minutes |
| (final) | 10 minutes |
After 5 failed attempts, the delivery is marked failed in the dashboard. You can manually re-queue it from there.
A 4xx response (other than 408 Request Timeout and 429 Too Many Requests) is treated as a permanent failure — we don't retry, because retrying won't help.
Verifying signatures
Always verify the Veridia-Signature header before trusting webhook content. Otherwise an attacker who knows your URL could forge events.
The format is:
Veridia-Signature: t=1714604000,v1=4f8a3b9c01ee...
Two parts:
t=<unix_timestamp>— when we sent the webhookv1=<hex_hmac>— HMAC-SHA256 of<timestamp>.<raw_body>using your webhook secret
Full algorithm + code samples: Signature verification.
Idempotency
We deliver the same event at most once under normal conditions, but during retries it's possible to receive a duplicate. Make your handler idempotent:
async function handleWebhook(payload) {
// Use verificationId + event as the dedup key
const key = `${payload.verificationId}:${payload.event}`;
if (await wasAlreadyProcessed(key)) {
return; // Already handled
}
await processVerification(payload);
await markAsProcessed(key);
}
Best practices
- Verify the signature before trusting the body. Always.
- Respond
2xxwithin 10 seconds even if you queue the work for later - Validate the timestamp is within 5 minutes (replay protection)
- Make your handler idempotent using
verificationId + eventas the dedup key - Log the
verificationIdon every webhook — easier to trace later - Use HTTPS in production (we'll deliver to HTTP only in test mode for localhost)
- Whitelist Veridia IPs if you have egress rules (contact support for the current list)
Test webhooks locally
Webhooks can't reach localhost directly. Use a tunneling service:
| Tool | Cost | Notes |
|---|---|---|
| ngrok | Free tier available | Most popular |
| localtunnel | Free | Open source |
| Cloudflare Tunnel | Free | Best for production-grade dev |
Quick example with ngrok:
ngrok http 3000
# Forwarding https://abc123.ngrok.io -> localhost:3000
# Then use https://abc123.ngrok.io/webhooks/veridia in your dashboard
What's next
- Signature verification — exact algorithm with code samples
- Event types — full schemas for each event
- Examples — complete handler implementations in Node.js, Python, PHP