Skip to content

Webhooks

ZetaID sends POST requests with a JSON body to the webhook URL of the key that created the verification. A key without a webhook URL gets none.

Header
Content-Typeapplication/json
X-Webhook-EventThe event name, as in the body.
X-Webhook-DeliveryThe delivery id (whd_…), the same on every retry of it.
X-Webhook-TimestampUnix time in seconds when this attempt was sent.
X-Webhook-Signaturesha256=<hex>: HMAC-SHA256 with your key’s signing secret over <X-Webhook-Timestamp>.<raw body>.

How to check it: Get the result → Check the signature.

  • Answer with 2xx within 20 seconds. Anything else is retried, up to 8 attempts over about two hours, with growing gaps between them.
  • A retry carries the same body and X-Webhook-Delivery, with a new timestamp and signature.
  • Deliveries can arrive more than once and out of order. Use X-Webhook-Delivery to drop repeats, and the verification’s status (not the arrival order) to decide.

The checks decided.

FieldType
eventstringverification.completed
verification_idstring
statusstringverified, rejected or needs_manual_review
reason_codestring | nullWhy, when not plainly verified.
exportobjectThe result (The result).
extra_dataobject | nullWhat you sent at creation.
completed_atstring (time)

Someone in your organization decided a needs_manual_review verification in the operator console.

FieldType
eventstringverification.reviewed
verification_idstring
statusstringverified or rejected
reason_codestring | nullThe reviewer’s reason.
extra_dataobject | null
reviewed_atstring (time)

It carries no export: the result you received with verification.completed stays valid; read it again with GET if you need it.

The person didn’t finish: the link’s deadline passed (COMPLETION_TIMEOUT), or they started and stopped for longer than the key allows (ABANDONED, default 24 hours).

FieldType
eventstringverification.expired
verification_idstring
statusstringexpired
reason_codestringCOMPLETION_TIMEOUT or ABANDONED
extra_dataobject | null
expired_atstring (time)