Skip to main content

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

ConcernPollingWebhooks
Latency from completion to your code1-3 seconds (poll interval)<200ms
API calls per verification3-301
Reliability if your service is downYou miss itWe retry 5 times
Audit trailNoneFull delivery log in dashboard
Cost (your serverless bill)Cold start every pollOne invocation

The widget event handler still works for client-side UX feedback ("Submitted!"), but the verdict should arrive via webhook.

Setup

  1. Sign in to your Veridia dashboard
  2. Open Webhooks → Endpoints
  3. 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
  4. Copy the webhook secret — you'll need it to verify signatures. It's shown only once.
  5. 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

EventVerdictWhen
verification.approvedapprovedHigh confidence, all signals pass
verification.rejectedrejectedLow confidence or critical flag
verification.review_requiredreviewMedium 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:

AttemptWait before retry
1(immediate)
21 second
35 seconds
430 seconds
52 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 webhook
  • v1=<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 2xx within 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 + event as the dedup key
  • Log the verificationId on 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:

ToolCostNotes
ngrokFree tier availableMost popular
localtunnelFreeOpen source
Cloudflare TunnelFreeBest 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