FaceVerifyDocs
Browse docsTap to expand

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).

Request bodyjson
{
  "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.

Node.js / Expressjavascript
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);
  }
);
Python / Flaskpython
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 "", 200
Always compute the HMAC over the exact raw request bytes, before any JSON parsing or re-serialization, re-stringifying a parsed object can produce a different byte sequence (key order, whitespace) and cause a valid signature to appear invalid.

Delivery 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.