Treat verification as an asynchronous workflow
A verification API may return some checks quickly, but the complete process is often asynchronous. Document processing, liveness evaluation, external matching, retries, and manual review can finish after the user leaves the capture screen. Design the integration around a durable verification session with explicit states instead of assuming one request will produce a final answer.
The session should identify the user, protected action, risk tier, policy version, requested evidence, expiration, and provider references. Your backend—not the browser—creates the session and decides what a provider result means. The frontend receives a short-lived client token or redirect URL and renders status. This boundary prevents a manipulated client from declaring success and keeps policy consistent across web, mobile, and support channels.
Define an internal state machine first
Providers use different labels, and those labels can change. Create an internal state model that represents your user journey: created, in_progress, submitted, processing, verified, retry_required, additional_evidence, manual_review, unable_to_verify, expired, and canceled. Map provider events into these states. Store the provider’s raw reason in a restricted diagnostic field, but expose stable internal reason codes to product and support.
State transitions should be validated. A completed session should not return to in_progress because an event arrived late. A canceled session should ignore later success unless a reconciliation policy explicitly allows recovery. Keep transition timestamps and the actor or source—user, provider, reviewer, system, or administrator. A state machine makes race conditions visible and supports reliable retries.
| State | Product meaning | Typical user action |
|---|---|---|
| created | Session exists but evidence collection has not begun | Start verification |
| processing | Evidence was submitted and checks are running | Wait or leave safely |
| retry_required | A correctable quality or technical issue occurred | Repeat a specific step |
| manual_review | Automated evidence is insufficient for a final decision | No action unless more evidence is requested |
| verified | Policy requirements for this action were met | Continue protected journey |
| unable_to_verify | The available path could not establish the claim | Use support or an allowed alternative |
Create sessions with idempotency
Network retries, double clicks, mobile reconnection, and job reprocessing can create duplicate sessions. Accept an idempotency key tied to the intended user and action. Repeated creation calls with the same key should return the original session or a safe conflict when the request parameters differ. This prevents duplicate charges, multiple active challenges, and inconsistent user states.
Idempotency records need a defined lifetime and should store a request fingerprint, result reference, and status rather than sensitive request bodies. Use a unique business reference that does not reveal personal information. When a new session is intentionally required—after expiration or a policy step-up—create a new intent and supersede the prior session explicitly.
Protect client tokens and upload paths
The browser or mobile application should receive only the credentials required to complete the active verification. Tokens should be short-lived, scoped to one session, and unusable for administrative APIs. Avoid embedding permanent vendor secrets in client code. If evidence uploads pass through your infrastructure, use restricted signed URLs, content limits, validation, malware controls where appropriate, and immediate movement into protected storage.
Do not place names, document numbers, emails, or raw tokens in URLs because they can leak through browser history, logs, referrers, screenshots, and analytics. Use opaque identifiers. Configure content security, framing, and origin rules for embedded provider components. Treat completion messages from an iframe as user-interface events only; the backend must confirm final status independently.
- Issue short-lived, session-scoped client credentials.
- Keep provider secrets and final decision logic server-side.
- Use opaque identifiers and exclude sensitive data from URLs.
- Restrict upload type, size, destination, and lifetime.
- Verify backend state before unlocking the protected action.
Authenticate and replay-protect webhooks
Webhooks are the authoritative path for asynchronous updates, so validate their signature using the provider’s documented scheme, compare timestamps within a safe window, and protect against replay. Read the raw request body when signature algorithms require it. Rotate secrets through a controlled process and support overlapping keys during rotation if the provider allows it.
A webhook handler should respond quickly after durable receipt, then process the event through a queue. Store the event identifier and enforce uniqueness so retries are safe. Validate that the event’s account, environment, session, and expected user reference match your record. Never trust a status field without checking the event context.
Webhooks are delivery, not policy
A provider can report that its check passed. Your decision service must still verify that the result belongs to the expected session and satisfies the policy for the protected action.
Reconcile missing and out-of-order events
Webhooks can be delayed or lost, and events can arrive out of order. Build a reconciliation job that queries sessions stuck in processing beyond an expected interval. Compare provider status with the internal state machine and apply valid transitions. Record whether the update came from a webhook or reconciliation so operational teams can identify delivery issues.
Do not poll every active session aggressively. Use bounded backoff, provider rate limits, and queue prioritization. The user interface can poll your own status endpoint with backoff or receive server-sent updates, but it should not query the provider directly. A processing screen should allow the user to leave and return without losing the session.
Map provider results to stable reason codes
Provider responses may include dozens of detailed codes. Normalize them into categories that your product can act on: capture_quality, unsupported_evidence, data_mismatch, expired_evidence, authenticity_concern, liveness_uncertain, provider_error, user_canceled, and review_required. Keep diagnostic detail in restricted logs or provider consoles rather than exposing it to every service.
Each reason category needs a safe message and allowed action. Capture quality can request a retake. Unsupported evidence can offer another method. Provider error can preserve progress and retry later. A strong authenticity concern may restrict retries or route review. Avoid exposing exact anti-fraud thresholds, but give legitimate users enough information to correct ordinary problems.
Keep verification evidence out of ordinary logs
Logging middleware can accidentally record request bodies, webhook payloads, signed URLs, and provider responses. Configure redaction before production and test it. Structured logs should include opaque session ID, event ID, state transition, latency, provider, policy version, and reason category. Raw documents, images, face data, full identity fields, secrets, and tokens should not appear in logs or traces.
Observability still needs enough information to debug. Use correlation IDs, event timestamps, retry counts, queue age, and provider reference numbers that authorized staff can resolve in a restricted system. Time-bound privileged debugging should be auditable and should not turn temporary payload access into a permanent data lake.
Design for vendor portability and regional routing
An adapter layer can translate internal session requests into provider-specific calls and map responses back to the internal model. The product UI, risk policy, and data model should not depend on one vendor’s status names. Portability supports coverage by document or country, resilience during incidents, commercial flexibility, and controlled migration.
Multiple providers introduce complexity and should not be added without a reason. Define routing by capability, region, risk, or availability; avoid sending the same sensitive evidence to several providers by default. Record which provider processed each step and apply consistent retention and deletion. Test fallback to ensure it does not weaken policy or surprise the user with new data handling.
Secure manual-review and administrative APIs
Reviewer decisions and administrative overrides are high-value targets. Use strong authentication, role-based permissions, case assignment, purpose-limited views, and immutable audit events. Separate support actions such as “allow retry” from risk actions such as “approve identity.” Require additional approval for bulk changes, high-value overrides, or policy exceptions.
Every override should include a reason and actor, and the final record should preserve both automated evidence and human action. Avoid direct database edits. Reviewer APIs should limit enumeration and return only assigned cases. Session links should expire and should not be shareable outside the tool.
Test the unhappy paths
Integration testing should cover duplicate creation, expired client tokens, user cancellation, upload interruption, unsupported evidence, provider timeout, invalid signature, replayed webhook, out-of-order events, queue delay, provider outage, manual review, deletion request, and account recovery. Use synthetic or properly controlled test data and separate sandbox credentials from production.
Load tests should focus on bursts, queues, and callback processing rather than sending unnecessary sensitive data. Chaos exercises can disable webhook handling or provider access and confirm that sessions remain recoverable. Security tests should verify that one user cannot retrieve another user’s session and that client-reported success never unlocks a protected action.
- Model the internal session and state machine.
- Create server-side sessions with idempotency.
- Issue short-lived client credentials and secure uploads.
- Authenticate, deduplicate, and queue webhooks.
- Reconcile delayed sessions and normalize reasons.
- Monitor, test, delete, and preserve vendor portability.
A resilient integration preserves product control
A dependable Verification API integration is more than an SDK embedded in a page. It is a stateful, asynchronous system with authenticated events, explicit policy, privacy boundaries, operational monitoring, and safe failure handling. When these pieces are designed first, the user can leave and return, vendors can retry, and the business can explain how a decision happened.
