For AI agents: a documentation index is available at the root level at /llms.txt and /llms-full.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
  • Getting Started
    • Introduction
    • How Verifa Works
    • Quickstart
    • Choosing an Integration Method
  • Use Cases
    • KYC Onboarding
    • Age Verification
    • AML Compliance
    • Fraud Prevention
    • Marketplace Trust & Safety
  • Core Concepts
    • Overview
    • Sessions
    • Verifications & Checks
    • Workflows
    • Identities
    • Cases
    • Screening & Reports
    • Lists
  • Integration Guides
    • Overview
    • JavaScript SDK
    • Web Capture Flow
    • API-Only Integration
    • Mobile SDK
    • Webhooks Guide
    • MCP Server
    • Migrating from Persona
  • API Details
    • Overview
    • Authentication
    • Pagination
    • Rate Limiting
    • Versioning
    • Errors
    • Webhooks
    • Idempotency
    • Key Inflection
    • Data Access
    • Data Retention
  • Tutorials
    • Creating Your First Verification Session
    • Creating a Workflow
    • Receiving Webhooks & Validating Signatures
    • Handling Webhook Events
    • Custom Document Types & AI Extraction
  • Best Practices
    • Testing
    • Preventing Duplicates
    • Fraud Signals
    • Changelog
  • API Reference
On this page
  • Event types
  • Payload format
  • identity.flagged
  • consortium.match_found
  • Signature verification
  • Retry behavior
  • Testing webhooks
  • Best practices
  • Related
API Details

Webhooks

Was this page helpful?
Previous

Idempotency

Next
Built with

Webhooks notify your server in real time when events occur in Verifa. Configure a webhook URL in your organization settings to start receiving events.

Event types

For the complete event catalog with payload examples, see the Webhooks Guide and Handling Webhook Events tutorial.

EventDescription
session.createdA new verification session was created.
session.startedThe user opened the capture URL and began the flow.
session.expiredThe session token expired before completion.
session.requires-reviewThe session needs manual human review.
session.requires-second-reviewThe session requires a second reviewer (dual review).
session.approvedThe verification was approved (auto or manual).
session.declinedThe verification was rejected.
session.updatedSession external_ref or metadata was updated.
session.submittedSession submitted for processing after document capture.
session.resumedSession token regenerated via resume endpoint.
session.awaiting_externalWorkflow paused at an external gate.
session.resubmission-requiredImage quality insufficient; user must re-submit.
session.redactedSession PII and documents were permanently deleted.
session.retention-expiredRetention window elapsed; data was auto-redacted.
session.link_generatedA shareable verification link was created.
check.completedA screening check finished processing.
check.matchedA screening check completed with one or more hits.
check.monitoring.new_hitsContinuous monitoring discovered new matches.
check.monitoring.clearMonitoring re-check completed with no new hits.
identity.createdA new identity record was created.
identity.updatedAn existing identity record was updated.
identity.flaggedAn identity was flagged for review.
identity.archivedAn identity was archived.
identity.restoredAn archived identity was restored.
identity.mergedTwo identity records were merged.
identity.redactedIdentity PII was permanently deleted.
identity.tag-addedA tag was added to an identity.
identity.tag-removedA tag was removed from an identity.
identity.relationship.createdA relationship was created between identities.
identity.relationship.deletedA relationship was deleted between identities.
consortium.match_foundConsortium matching detected a cross-org face match (Verifa Network).
case.createdA review case was created.
case.claimedA reviewer claimed a case.
case.approvedA case was approved.
case.rejectedA case was rejected.
case.escalatedA case was escalated to a higher reviewer.
document.uploadedA document was uploaded.
document.classifiedDocument classification completed.
document.extractedField extraction completed on a document.
document.verifiedAll verification checks completed on a document.
document.comparedA guided extraction comparison completed.
document.redactedA document was permanently deleted.
smart_qa.finding.createdSmart QA generated a finding.
smart_qa.finding.criticalSmart QA generated a critical-severity finding.

Payload format

1{
2 "event": "session.approved",
3 "data": {
4 "session_id": "session_abc123",
5 "external_ref": "user_abc123",
6 "status": "approved",
7 "is_sandbox": false,
8 "created_at": "2026-02-01T12:00:00Z"
9 },
10 "created_at": "2026-02-01T12:05:00Z"
11}

identity.flagged

Fires when an identity is flagged for review. The payload intentionally omits the reviewer email and free-text reason — fetch them from GET /identities/{identity_id} if your subscriber has the right scopes.

1{
2 "event": "identity.flagged",
3 "identity_id": "idn_abc123",
4 "session_id": "ses_def456",
5 "has_reason": true,
6 "is_sandbox": false
7}
FieldTypeDescription
identity_idstringThe identity that was flagged
session_idstring or nullThe session that triggered the flag, if any
has_reasonbooleantrue when a reason was recorded; the reason itself is no longer included for privacy
is_sandboxbooleanWhether the identity is in sandbox mode

consortium.match_found

Fires when consortium matching detects a face match for the same individual across two organizations participating in the Verifa Network. Both involved organizations receive the event.

1{
2 "event": "consortium.match_found",
3 "consortium_id": "cgrp_abc123",
4 "matched_session_id": "ses_def456",
5 "primary_session_id": "ses_abc123",
6 "similarity": 0.94,
7 "ghost_student_score": 78,
8 "ghost_student_tier": "high",
9 "is_sandbox": false
10}
FieldTypeDescription
consortium_idstringThe consortium where the match was found
matched_session_idstringThe previously seen session that matched
primary_session_idstringThe session being verified that triggered the match
similarityfloatFace-embedding cosine similarity (0–1)
ghost_student_scoreintegerGhost-student risk score (0–100)
ghost_student_tierstringOne of low, medium, high, critical
is_sandboxbooleanWhether either session is in sandbox mode

Signature verification

Every webhook request includes an X-Verifa-Signature header in the Stripe-style format t=<unix_ts>,v1=<hex_hmac>:

  • t — Unix timestamp the signature was generated at
  • v1 — hex HMAC-SHA256 of f"{t}.{raw_request_body}", signed with your endpoint’s whsec_* secret

Always verify the signature and reject deliveries with a t= timestamp more than 5 minutes from your current clock to prevent replay attacks. See Tutorial: Receiving Webhooks & Validating Signatures for the full reference recipe with end-to-end code samples.

The legacy bare-hex HMAC over the raw body is still accepted by the verification helper (src/core/security.py:verify_webhook_signature) for in-flight deliveries during the deprecation window. New outbound deliveries always use the timestamped format.

Node.js
Python
1const crypto = require("crypto");
2const TOLERANCE = 300; // 5-minute replay window
3
4function verifyWebhookSignature(body, header, secret) {
5 const parts = Object.fromEntries(
6 (header || "").split(",").map((p) => {
7 const i = p.indexOf("=");
8 return i > 0 ? [p.slice(0, i), p.slice(i + 1)] : [p, ""];
9 })
10 );
11 const ts = parseInt(parts.t || "0", 10);
12 const v1 = parts.v1 || "";
13 if (!ts || !v1) return false;
14 if (Math.abs(Math.floor(Date.now() / 1000) - ts) > TOLERANCE) return false;
15
16 const signed = Buffer.concat([Buffer.from(`${ts}.`), body]);
17 const expected = crypto
18 .createHmac("sha256", secret)
19 .update(signed)
20 .digest("hex");
21 const a = Buffer.from(expected);
22 const b = Buffer.from(v1);
23 return a.length === b.length && crypto.timingSafeEqual(a, b);
24}
25
26// In your webhook handler:
27const isValid = verifyWebhookSignature(
28 req.rawBody,
29 req.headers["x-verifa-signature"],
30 process.env.VERIFA_WEBHOOK_SECRET
31);

Retry behavior

If your endpoint returns a non-2xx status code or does not respond within 30 seconds, Verifa retries the delivery with exponential backoff:

AttemptDelay
1st retry~30 seconds
2nd retry~2 minutes
3rd retry~8 minutes
4th retry~32 minutes
5th retry~2 hours

After 5 failed retries, the event is marked as failed. Failed events can be viewed in the dashboard.

Testing webhooks

Use the test endpoint to send a test event to your configured URL:

$curl -X POST https://api.withverifa.com/api/v1/webhooks/endpoints/webhook_abc123/test \
> -H "X-API-Key: vk_live_your_key_here"
1{
2 "success": true,
3 "http_status": 200,
4 "url": "https://your-app.com/webhooks/verifa"
5}

Best practices

  • Respond quickly. Return a 200 status code as soon as you receive the event. Process the event asynchronously.
  • Handle duplicates. In rare cases (network issues, retries), you may receive the same event more than once. Use the session_id to deduplicate.
  • Verify signatures. Always validate the X-Verifa-Signature header to prevent spoofed events.

Related

  • Webhooks Guide — Comprehensive setup guide with code examples in 7 languages
  • Errors — Error codes returned by webhook API endpoints
  • Sessions — Session events and lifecycle