Redirect & webhooks
When a verification session completes, we notify you two ways at once: a browser redirect (for the end user) and a signed server-to-server webhook (for your backend). Use the webhook, or the session-check endpoint, as your source of truth, never the redirect alone.
Webhook payload
Sent as POST to your account's callback_url (set in Settings).
{
"event": "verification.completed",
"status": "verified",
"external_user_id": "user_8841",
"confidence": 97.4,
"livenessPassed": true,
"duplicate": false,
"risk": "LOW",
"reason": null,
"frontImageUrl": "https://...signed, expires in 15m"
}status is either verified or failed.
Verifying the signature
Every webhook request includes an X-Webhook-Signature header, an HMAC-SHA256 hex digest of the raw request body, keyed with your webhook signing secret (found in Settings). Recompute it on your end and compare before trusting the payload.
const crypto = require("crypto");
app.post(
"/webhooks/verify",
express.raw({ type: "application/json" }),
(req, res) => {
const signature = req.headers["x-webhook-signature"];
const expected = crypto
.createHmac("sha256", process.env.FACEVERIFY_WEBHOOK_SECRET)
.update(req.body)
.digest("hex");
if (signature !== expected) {
return res.status(401).send("Invalid signature");
}
const payload = JSON.parse(req.body.toString("utf8"));
// payload.status is "verified" or "failed"
res.sendStatus(200);
}
);import hmac, hashlib, os
from flask import request
@app.route("/webhooks/verify", methods=["POST"])
def verify_webhook():
signature = request.headers.get("X-Webhook-Signature", "")
expected = hmac.new(
os.environ["FACEVERIFY_WEBHOOK_SECRET"].encode(),
request.get_data(), # raw bytes, not parsed JSON
hashlib.sha256,
).hexdigest()
if not hmac.compare_digest(signature, expected):
return "Invalid signature", 401
payload = request.get_json()
# payload["status"] is "verified" or "failed"
return "", 200Delivery behavior
Webhook delivery is best-effort and fire-and-forget, a failed delivery (your endpoint down, timing out, etc.) does not affect the verification result itself, and there is currently no automatic retry. Use GET /v1/sessions/:token to poll for the result if you need a guaranteed-delivery fallback.