Guide
Webhooks
Pending checks, verification.completed, verification.failed, wallet.low_balance and signatures.
Add an HTTPS endpoint in the console’s Developers page. Verath posts events to it as JSON. Reply with any 2xx status to acknowledge; failed deliveries are retried. Webhooks carry match results only, never record fields.
Events
| Event | When |
|---|---|
| verification.completed | A pending check finished, or a person finished a link (with link_id and link_kind: match or face). data is the check, or the link with its result. |
| verification.failed | A pending check gave up after 72 hours without an answer. Never billed. |
| wallet.low_balance | The available balance dropped below your alert level. At most once a day. data is the wallet. |
{
"id": "whk_01J9X…",
"type": "verification.completed",
"created_at": "2026-11-11T15:34:10.000Z",
"data": {
"reference_id": "chk_3c5e1f0a9b2d4c7e8f6a1b2c3d4e5f60",
"object": "check",
"check": "nin_basic",
"status": "completed",
"found": true,
"match_result": { "first_name": "match", "last_name": "match", "dob": "match" },
"cost": { "billed": false, "amount_kobo": 0, "currency": "NGN" },
"mode": "test"
}
}Pending checks
If no source answers within a few seconds, the call returns 424 upstream_unavailable with the check’s reference_id and status: "pending". Verath keeps the request encrypted and retries on its own for up to 72 hours, then sends verification.completed or verification.failed. You can also poll Fetch a check. Pending and failed checks are never billed.
Checking the signature
Every delivery has a Verath-Signature header: t=<unix time>,v1=<hex HMAC-SHA256>, computed over t. followed by the raw body, with your endpoint’s secret. Check it against the raw body before you parse it, and reject anything older than five minutes.
import { createHmac, timingSafeEqual } from "node:crypto";
// Verify a Verath webhook before trusting it.
export function verifyVerathWebhook(rawBody, header, secret, toleranceSec = 300) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false; // replay protection
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
const given = Buffer.from(parts.v1 ?? "", "hex");
return given.length === expected.length && timingSafeEqual(given, expected);
}