Skip to content

Get the result

There are two ways to learn how a verification went. Use both:

  • Webhooks (recommended): ZetaID posts to your key’s webhook URL as soon as something happens.
  • GET /api/v1/verifications/{id}: your server asks whenever it needs to — on the return page, or to catch up after an outage.

ZetaID sends a POST with a JSON body to the key’s webhook URL:

EventWhen
verification.completedThe checks have decided: verified, rejected or needs_manual_review. Carries the result (export).
verification.reviewedA manual review decided a needs_manual_review verification: verified or rejected.
verification.expiredThe person didn’t finish before the deadline, or stopped.
{
"event": "verification.completed",
"verification_id": "3f6c2a9e-7d41-4b8a-9c2e-5a1f0d7b3e64",
"status": "verified",
"reason_code": null,
"export": { "…": "see Reference → The result" },
"extra_data": { "order_id": "A-17" },
"completed_at": "2026-11-01T10:19:42Z"
}

All fields per event: Reference → Webhooks.

Answer with any 2xx within 20 seconds, then do the work. Anything else (a timeout, 4xx, 5xx) is retried up to 8 times over about two hours, with growing gaps. The same delivery can therefore arrive more than once: use the X-Webhook-Delivery header (or verification_id + event) to ignore repeats.

Your webhook URL is public, so anyone could post a fake “verified” to it. Every webhook is signed with your key’s webhook signing secret (whsec_…, shown once when the key was created):

Header
X-Webhook-TimestampUnix time (seconds) when the delivery was sent.
X-Webhook-Signaturesha256= + hex HMAC-SHA256 of timestamp + "." + raw body, with your signing secret.
X-Webhook-EventThe event name.
X-Webhook-DeliveryThe delivery’s id, the same on every retry.

Compute the HMAC over the raw request body exactly as received (before any JSON parsing), compare in constant time, and refuse timestamps older than five minutes:

import crypto from "node:crypto";
// rawBody: a Buffer of the request body as received (e.g. express.raw()).
export function validWebhook(secret, headers, rawBody) {
const ts = headers["x-webhook-timestamp"];
const sig = headers["x-webhook-signature"] ?? "";
if (!ts || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
const expected =
"sha256=" + crypto.createHmac("sha256", secret).update(`${ts}.`).update(rawBody).digest("hex");
return sig.length === expected.length && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}

Refuse a webhook whose signature doesn’t match with 401, and don’t act on it.

GET https://verify.zetaid.com/api/v1/verifications/{verification_id}
X-SDK-Key: vk_live_…

200 OK with the same fields as the completion webhook, plus where the verification stands:

{
"verification_id": "3f6c2a9e-7d41-4b8a-9c2e-5a1f0d7b3e64",
"status": "verified",
"terminal": true,
"reason_code": null,
"export": { "…": "the result, as in the webhook" },
"extra_data": { "order_id": "A-17" },
"created_at": "2026-11-01T10:15:00Z",
"updated_at": "2026-11-01T10:19:42Z"
}
  • export is null until the checks have run, and again once the key’s personal-data retention has deleted it.
  • Any key of your organization can read its verifications; another organization’s id answers 404 VERIFICATION_NOT_FOUND.
  • Call it from your server only, and not in a tight loop: every few seconds while a person waits on your return page is plenty.
statusMeaningYour site
generatedCreated; the person hasn’t opened the link.Waiting.
initiated, in_progressThe person is going through the steps.Waiting.
validatingEverything is captured; the checks are running (seconds).Waiting.
verifiedPassed.Continue.
rejectedFailed; reason_code says why.Explain, offer a new attempt or another way.
needs_manual_reviewA person at the organization reviews it (operator console).Tell the person you’ll confirm; wait for verification.reviewed.
expiredNot finished in time (COMPLETION_TIMEOUT) or abandoned (ABANDONED).Offer to start again.

terminal: true means the checks have finished. Only a manual review still changes a needs_manual_review verification, to verified or rejected (with a verification.reviewed webhook). Treat any other status you don’t know as “not decided”.

What the export contains: Reference → The result.