iLive Docs
Authentication

Webhooks

Receive asynchronous event callbacks from iLive.

Webhooks let iLive push verification results to your backend as soon as a user finishes a managed session. You don't have to poll — iLive calls you when the verdict is final.

When webhooks fire

A webhook is delivered exactly when a managed session reaches a final verdict (pass, fail, or retry). Webhooks are not sent for direct sessions or for in-flight session state changes.

Register a receiver by setting webhook_url when you create the session:

curl -X POST https://api.iliveauth.com/api/v1/managed-session \
  -H "Authorization: Bearer iksk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "redirect_url": "https://your-app.com/verified",
    "webhook_url":  "https://your-app.com/webhooks/ilive",
    "reference_id": "user-12345"
  }'

The webhook URL must be HTTPS and must not resolve to an internal or private IP. Non-HTTPS URLs are rejected at session creation and never delivered if somehow set.

Payload

iLive POSTs a JSON body:

{
  "event": "liveness.verdict",
  "session_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "verdict": "pass",
  "confidence": 0.8732,
  "reference_id": "user-12345",
  "camera_metadata": { "...": "..." },
  "location": { "...": "..." },
  "timestamp": "2026-04-11T09:30:12.456789+00:00"
}

The session_id is the canonical identifier. Pair it with the result endpoint (GET /api/v1/session/{id}/result) or the photo endpoint (GET /api/v1/session/{id}/photo) to fetch the full record including the ICAO-compliant photo.

Delivery semantics

iLive delivers each verdict with up to two attempts: if the first POST fails with a 5xx or a network error, we retry once after a short delay. If both attempts fail the webhook is dropped and the failure is logged on our side.

Because retries exist but are capped, design your handler to be idempotent keyed on session_id. Receiving the same verdict twice should be a no-op.

Verifying authenticity

Every outbound webhook is signed with HMAC-SHA256 using a per-account secret. iLive attaches three headers to each delivery:

HeaderDescription
X-iLive-Signaturesha256=<hex> — HMAC-SHA256 of the signed payload
X-iLive-TimestampUnix seconds at signing time — use for replay protection
X-iLive-Webhook-IdRandom UUID per delivery — use for idempotency

The signed payload is:

"{timestamp}.{raw_body}"

where raw_body is the exact bytes of the request body as delivered. Do not re-parse and re-serialise JSON before verifying — whitespace and key ordering would change the bytes and the signature would not match.

Your signing secret is created when your tenant account is provisioned and is shown once — both on creation and when you rotate it from the iLive admin console or tenant portal. Store it alongside your other backend secrets; a rotation invalidates the previous secret immediately.

When verifying, always:

  1. Read X-iLive-Signature and X-iLive-Timestamp from the request.
  2. Recompute sha256=hex(HMAC_SHA256(secret, f"{timestamp}.{raw_body}")).
  3. Compare using a constant-time comparison.
  4. Reject the request if |now - timestamp| > 300 seconds.

Receiver examples

import hmac, hashlib, time
from flask import Flask, request, abort
 
SECRET = b"whsec_..."  # from iLive portal
 
app = Flask(__name__)
 
@app.post("/webhooks/ilive")
def ilive_webhook():
    signature = request.headers.get("X-iLive-Signature", "")
    timestamp = request.headers.get("X-iLive-Timestamp", "")
    raw_body = request.get_data()  # DO NOT re-parse + re-serialise
 
    try:
        ts = int(timestamp)
    except ValueError:
        abort(400)
    if abs(int(time.time()) - ts) > 300:
        abort(400, "stale timestamp")
 
    signed = f"{ts}.".encode() + raw_body
    expected = "sha256=" + hmac.new(SECRET, signed, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, signature):
        abort(400, "bad signature")
 
    # Safe to handle the verified event.
    return "ok", 200

Defence in depth

Signatures prove the payload came from iLive and hasn't been tampered with, but you can layer additional controls:

  • IP allowlist. If your edge supports it, restrict the webhook route to iLive's published egress ranges (available from support on request).
  • Confirm via pull. Before granting access based on a webhook, call GET /api/v1/session/{id}/result with your server-side API key. That endpoint is always authoritative.

Handling the event

Once the signature has been verified, persist the event keyed on session_id (or X-iLive-Webhook-Id for exact-duplicate detection) before acting on it. A minimal Python handler:

if body["verdict"] == "pass":
    mark_user_verified(body["reference_id"], body["confidence"])
elif body["verdict"] == "fail":
    flag_failed_verification(body["reference_id"])

Respond 2xx as soon as you have persisted the event. iLive treats any non-2xx response (or a timeout past ten seconds) as a delivery failure and will retry once.

Related: Managed sessions · Liveness overview · Error codes.

On this page