Skip to content

Webhooks

When a verification reaches a terminal status, thibit POSTs the verdict to your configured webhook_url. Set it with POST /v1/webhook (or in the portal).

Payload

{
"event": "verification.completed",
"id": "",
"type": "kyc",
"status": "completed",
"decision": "aprovada",
"confidence": 0.92,
"result": { "verification_result": { "checks": {} } }
}

event is verification.<status> (e.g. verification.completed, verification.failed).

Headers

HeaderValue
X-Thibit-Eventverification.<status>
X-Thibit-TimestampUnix seconds when the delivery was signed
X-Thibit-SignatureHex HMAC-SHA256 (see below)

Verify the signature

The signature is HMAC_SHA256(secret, "{timestamp}.{raw_body}"), hex-encoded, where secret is the webhook_secret (whsec_…) you received at signup and raw_body is the exact bytes of the request body. Compute it over the raw body — do not re-serialize the parsed JSON.

import hmac, hashlib
def verify(secret: str, timestamp: str, raw_body: bytes, signature: str) -> bool:
mac = hmac.new(secret.encode(), f"{timestamp}.".encode() + raw_body, hashlib.sha256)
return hmac.compare_digest(mac.hexdigest(), signature)
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(secret, timestamp, rawBody, signature) {
const mac = createHmac("sha256", secret)
.update(`${timestamp}.`).update(rawBody).digest("hex");
return timingSafeEqual(Buffer.from(mac), Buffer.from(signature));
}

Receiver behavior

  • Verify the signature before trusting the payload, and reject stale timestamps.
  • Persist the event, then return 2xx. Make handlers idempotent.
  • Treat the webhook as the trigger; if your business rules need fresh state, re-fetch GET /v1/verifications/{id} before acting.
  • Today delivery is a single attempt (retries are on the roadmap), so also reconcile by polling if a webhook is critical to your flow.