Verifications & Checks

A verification check is a single step in a workflow — one unit of identity verification logic. Checks are executed by the workflow engine in the order defined by your workflow graph. Each check produces an independent pass/fail result.

Check lifecycle

pending → passed
→ failed
→ error
→ skipped
→ requires_retry
StatusDescription
passedThe check completed successfully and passed.
failedThe check completed but the subject did not pass.
errorAn unrecoverable error occurred (e.g., service unavailable).
skippedThe check was skipped because prerequisites were missing (e.g., OCR data unavailable).
pendingThe check is currently executing.
requires_retryA transient failure occurred. The engine will retry automatically (up to 4 retries).

Each check attempt is stored as an immutable record with an incrementing attempt number. Retries create new records rather than overwriting the original.

Check categories

Extraction

CheckDescription
document_ocrScans the ID document front and back images. Extracts name, date of birth, address, document number, expiration date, and other fields. Validates MRZ checksums and barcode data when present.

Biometric verification

CheckDescription
face_matchCompares the selfie against the ID photo using ArcFace face embeddings. Returns a similarity score (0.0–1.0). Configurable security levels: standard (0.35 threshold), strict (0.50), very_strict (0.60). Includes liveness validation when available.
nfc_chip_authReads and validates the NFC chip in e-passports. Performs passive and active authentication, cross-references chip data against OCR, and compares the chip photo against the selfie.
face_reauthReturning User Face Verification — compares a fresh selfie 1:1 against a previously verified person’s stored facial records (matched by external_ref). Runs on document-free workflows; liveness is always enforced. Sensitivity levels: conservative, balanced, aggressive. See Returning-user re-verification.

Document evaluation

CheckDescription
document_expirationRejects documents that have expired based on the extracted expiration date.
id_document_filterFilters by country and document type matrix. Reject or allow specific combinations (e.g., only accept US passports and driver’s licenses).
proof_of_address_checkValidates proof-of-address documents against a policy you configure: accepted document types, per-type recency windows, and how closely the name and address must match. Country-aware — it normalises addresses, postcodes, and PO-box patterns per issuing country. Three-state: passed, review, or failed. See Proof of address.

Compliance screening

CheckDescription
watchlist_screeningScreens against built-in OFAC, EU, UN, and UK sanctions lists. Automatically injected into all workflows — you don’t need to add it manually.
aml_screeningEnhanced AML screening against 6,000+ global watchlists, PEP databases, and adverse media sources. Supports continuous monitoring with automatic re-screening.
email_risk_enhancedEmail risk analysis including breach history, deliverability, domain age, and fraud scoring.
phone_risk_enhancedPhone number risk analysis including SIM swap detection, porting history, VoIP detection, and carrier verification.
address_validationAddress standardization and deliverability verification. Validates against postal databases and returns geocoded coordinates.

Fraud detection

CheckDescription
duplicate_detectionSearches for the same person across all previous sessions using hashed device fingerprints, email, phone, document number, and face embeddings.
risk_assessmentAggregates 150+ fraud signals (behavioral, device, network, document forensics) into a composite risk score (0–100). Automatically injected into all workflows.
check_against_listMatches subject data against your custom allow/block/flag lists. Supports name, email, phone, IP address, device fingerprint, government ID, and geolocation matching.
identity_crossrefCross-references user-submitted data (from the capture form) against data extracted from the document. Detects mismatches in name, date of birth, and address.

Decisioning

CheckDescription
auto_approveEvaluates all prior check results. If everything passed, routes to approved. Otherwise routes to needs_review or rejected based on configuration.
external_gatePauses the workflow and waits for your server to make a decision via API callback. Useful for custom business logic or human-in-the-loop integrations. Configurable timeout (default: 24 hours).

Proof of address

proof_of_address_check is the one check with a policy of its own. Rather than a single threshold, it resolves a full proof-of-address policy and reports every finding it made along the way.

Three-state outcome

Unlike other checks, a proof-of-address result is not just pass or fail:

details.outcomeCheck statusWhat it means
passedpassedThe document is accepted, recent enough, and the name and address match.
reviewfailedPlausible, but something warrants a human look. details.action_override is set to "review" and the session routes to your review queue instead of a rejection.
failedfailedA hard failure — wrong document type, out of window, or an address that does not match.

review deliberately surfaces as passed: false, so integrations that only read passed stay safe: they treat a borderline document as not-yet-approved rather than silently approving it. Branch on details.outcome when you want the three-way distinction.

Reasons

Every finding is collected into details.reasons — a list of {code, severity, message} objects. Severity is fail, review, or info. The check takes the worst severity present: any fail reason fails it, otherwise any review reason routes it to review, otherwise it passes.

CodeTypical severityRaised when
unsupported_typefailThe document subtype is not in the policy’s accepted types.
unsupported_countryfailThe issuing country is excluded by a country override.
too_oldfailThe document is older than its recency window.
future_datedfailThe document is dated in the future.
date_mismatchfailThe submitted date and the date read off the document disagree by more than date_match_tolerance_days.
name_mismatch / name_partialfail / reviewThe name score fell below name_match_pass (partial) or name_match_review (mismatch).
address_mismatch / address_partialfail / reviewThe same banding for the address score. A unit-number-only difference caps at partial rather than failing.
address_not_comparedinfoUnguided flow, or no reference address was held — the address was extracted but never scored.
name_not_comparedinfoNo reference name was held — the name was extracted but never scored.
po_boxfailThe address is a PO Box and the policy does not allow one. Set allow_po_box to accept them.
business_addressreviewThe address looks like a business and the policy does not allow one.
screenshotreviewForensics identified a screen capture rather than a document. Downgraded to info when the policy sets allow_screenshots.
tampering_suspectedreviewDocument forensics flagged editing artefacts.
unreadablefail / reviewOCR could not read the document, or read too little of it to judge.
classifier_disagreementconfigurableThe classifier read a different subtype than the one submitted.
country_mismatchconfigurableThe document was issued in a different country than the ID document.
review_only_typereviewThe subtype has no official issuer (lease agreement, institution letter, other).
not_independent_sourcereviewThe document shares its issuer with the ID document.
missing_documentfailNo proof-of-address document was uploaded.

Codes are stable — pivot your UI and metrics off code, never off message.

Presets and policy precedence

A policy is resolved from four layers, each overriding the last:

built-in defaults ← regional preset ← organization settings ← workflow node config

Presets (us, uk, eu, in, mx, au, ca, za, or custom) are regional starting points — the UK preset accepts council tax and rejects PO boxes, the India preset accepts mobile phone bills and address-bearing IDs. Anything a preset omits keeps the built-in default.

Organization settings live in the dashboard under Settings → Proof of address. A workflow node either says “use organization settings” — the default — or overrides individual fields for that one workflow. Node config wins wherever it sets a value; everything else falls through to the org.

The policy that actually ran is snapshotted into details.policy, so a decision stays explainable after you change the settings.

Document types and recency windows

Each accepted subtype carries its own default window — 90 days for a utility bill or bank statement, 180 for a mobile phone bill or insurance statement, 365 for council tax, tax documents, benefits letters, and mortgage statements. Override them per type in max_age_days_by_type, and per country in country_overrides — for example, utility bills issued in India are commonly held to 60 days regardless of your preset.

Three subtypes are review-only: lease_agreement, institution_letter, and other have no official issuer, so a pass on one of them still routes to a human. id_with_address is not an independent source — it shares its issuer with the ID document — and is only accepted when the policy sets id_with_address_satisfies.

Issuing country

Pass poa_issuing_country (ISO 3166-1 alpha-2) with the upload when you know it; otherwise the check falls back to the country of the ID document on the session. The issuing country drives address normalisation (postcode formats, region names versus codes, unit tokens), PO-box detection, and any per-country window override. When it disagrees with the ID document’s country, country_mismatch_action decides whether that is ignored, sent to review, or failed.

Name and address matching

Name and address are scored separately, 0.0–1.0, and each is banded against two thresholds. At or above *_match_pass the component passes; at or above *_match_review it is partial and routes to review; below that it fails. The default bands are 0.85 / 0.60 for names and 0.80 / 0.55 for addresses.

Name matching ignores titles and middle names, folds accents, understands common nicknames, and accepts any one of several joint holders printed on the document. Address matching compares components — line 1, city, region, postcode, unit — and treats a region code and its spelled-out name as equivalent.

By default the reference is the applicant’s declared address; set address_source to id_document to compare against the address extracted from the ID instead. The other source is still compared and reported under details.secondary_cross_reference — a divergence there is a fraud signal rather than a check failure. In an unguided flow (require_declared_address: false) there is no reference at all: the address is extracted, no score is produced, and address_not_compared is reported.

Example result

1{
2 "status": "failed",
3 "details": {
4 "outcome": "review",
5 "passed": false,
6 "action_override": "review",
7 "reasons": [
8 {
9 "code": "address_partial",
10 "severity": "review",
11 "message": "Address matches except for the unit number."
12 }
13 ],
14 "reason_code": "poa_review",
15 "document_type": "utility_bill",
16 "document_type_canonical": "utility_bill",
17 "document_type_detected": "utility_bill",
18 "document_date": "2026-07-02",
19 "period_start": "2026-06-01",
20 "period_end": "2026-06-30",
21 "age_days": 56,
22 "max_age_days": 90,
23 "issuing_country": "GB",
24 "address_type": "residential",
25 "cross_reference": {
26 "overall_score": 0.82,
27 "name_score": 0.97,
28 "address_score": 0.72,
29 "partial": true,
30 "partial_reasons": ["unit_mismatch"],
31 "component_scores": {
32 "first_name": 1.0,
33 "last_name": 0.95,
34 "line1": 0.78,
35 "city": 1.0,
36 "postcode": 1.0,
37 "unit": 0.0
38 }
39 },
40 "policy": {
41 "preset": "uk",
42 "address_match_pass": 0.8,
43 "address_match_review": 0.55
44 }
45 }
46}

details.issuer, details.holder_names, and details.extracted_address are personal data. They are returned only on PII-bearing surfaces, are subject to your organization’s sensitive-data access window, and are never exposed through the MCP server.

Auto-injected checks

Some checks are automatically added to every workflow run regardless of your workflow configuration:

  • watchlist_screening — Required for compliance. Always runs.
  • risk_assessment — Required for fraud scoring. Always runs.
  • identity_crossref — Runs for certain plan tiers to detect form/document mismatches.

You cannot remove auto-injected checks, but you can configure how their results affect the workflow outcome.

Check results in the session result

After a session completes, each check’s result is available in the check_results object:

1{
2 "check_results": {
3 "document_ocr": {
4 "status": "passed",
5 "details": {
6 "fields_extracted": 12,
7 "mrz_valid": true,
8 "barcode_valid": true
9 }
10 },
11 "face_match": {
12 "status": "passed",
13 "score": 0.94,
14 "liveness_passed": true
15 },
16 "age_verification": {
17 "status": "passed",
18 "extracted_age": 28,
19 "minimum_age": 18
20 },
21 "watchlist_screening": {
22 "status": "passed",
23 "hits": 0
24 },
25 "risk_assessment": {
26 "status": "passed",
27 "risk_score": 12,
28 "risk_level": "low",
29 "triggered_signals": 2
30 }
31 }
32}

Configuring checks in workflows

Each check can be configured with parameters specific to its type. For example:

1{
2 "type": "policy",
3 "policy": "face_match",
4 "config": {
5 "security_level": "strict"
6 },
7 "on_pass": "next_step",
8 "on_fail": "review_terminal"
9}

See Workflows for how to build verification graphs with checks, conditionals, and routing logic.

Standalone checks via API

You can also run checks independently of a session through the checks API:

$curl -X POST https://api.withverifa.com/api/v1/checks \
> -H "X-API-Key: vk_live_your_key_here" \
> -H "Content-Type: application/json" \
> -d '{
> "check_type": "sanction",
> "external_ref": "user_abc123",
> "name_first": "Jane",
> "name_last": "Doe",
> "birthdate": "1997-06-15"
> }'

This is useful for:

  • Running AML screening on existing customers
  • Adding phone or email risk checks after onboarding
  • Re-screening identities on a schedule

See the API reference for the full list of check endpoints.