Bank Account Verification Service Low-Level Design: Micro-Deposit, Instant Verification, and Risk Scoring

Bank Account Verification Service: Overview and Requirements

A bank account verification service confirms that a user owns and controls a bank account before funds are transferred. It supports two primary verification paths — micro-deposit verification for broad coverage and instant verification via aggregator APIs for supported institutions — and applies a risk score to each verified account to gate high-value transactions.

Functional Requirements

  • Initiate micro-deposit verification by sending two small deposits to a provided account and requiring the user to confirm the exact amounts.
  • Support instant verification by redirecting the user to an account aggregator flow (Plaid, Yodlee, MX) and receiving credentials via OAuth callback.
  • Score each verified account using signals from verification method, account age, balance, and transaction history.
  • Store verified accounts and associate them with user identities for reuse across sessions.
  • Enforce attempt limits and cooling-off periods to prevent brute-force guessing of micro-deposit amounts.

Non-Functional Requirements

  • Micro-deposit initiation must complete within 2 seconds; deposits typically appear within 1-2 business days.
  • Instant verification round-trip including the aggregator OAuth flow must complete within 10 seconds for supported institutions.
  • Verification state must be durable: a server restart must not lose an in-progress verification session.
  • Risk score computation must complete within 500 ms of verification completion.
  • Account credentials received from aggregators must be encrypted at rest using envelope encryption.

Data Model

  • VerificationSession: session_id, user_id, method (micro_deposit | instant), status (initiated | pending_confirmation | confirmed | failed | expired), bank_account_id (nullable until confirmed), attempts, created_at, expires_at, updated_at.
  • MicroDepositRecord: record_id, session_id, amount_1_cents, amount_2_cents, deposited_at, confirmation_deadline, confirm_attempts, last_attempt_at.
  • InstantVerificationRecord: record_id, session_id, aggregator (plaid | yodlee | mx), oauth_state_token, access_token_encrypted, item_id, linked_at.
  • BankAccount: account_id, user_id, routing_number, account_number_encrypted, account_number_last4, account_type (checking | savings), institution_name, verification_method, verified_at, risk_score, risk_tier (low | medium | high | blocked), status (active | suspended | closed).
  • RiskSignal: signal_id, account_id, signal_type, value, source, captured_at.
  • AuditEvent: event_id, session_id, account_id, event_type, actor, metadata, occurred_at — append-only.

Micro-Deposit Verification Flow

The micro-deposit flow authenticates account ownership by proving the user can observe activity on the account:

  • The user submits routing and account numbers. The service validates the routing number against the ABA routing directory and checks the account number format.
  • Two deposits between $0.01 and $0.99 are generated using a cryptographically random generator and dispatched via the ACH originator. The amounts are stored hashed in MicroDepositRecord, not in plaintext, to prevent server-side lookup attacks.
  • The user is prompted to enter the two deposit amounts they observe in their bank statement. The service hashes the submitted amounts and compares against the stored hashes in constant time to prevent timing attacks.
  • If the amounts match, the session is confirmed. If they do not match, the attempt counter increments. After three failed attempts the session is locked and a cooling-off period of 24 hours is enforced before a new session can be initiated for the same account.
  • Unconfirmed sessions expire after 10 business days, after which the micro-deposits are reversed if not yet confirmed.

Instant Verification via Aggregator

Instant verification uses OAuth-based account aggregators to obtain read access to account data without manual entry of deposit amounts:

  • The service generates an OAuth state token and stores it in InstantVerificationRecord, then redirects the user to the aggregator authorization URL.
  • On callback, the service validates the state token to prevent CSRF, exchanges the authorization code for an access token, and fetches account identity and balance data from the aggregator API.
  • The access token is encrypted using envelope encryption: a data encryption key (DEK) generated per account is used to encrypt the token, and the DEK is encrypted by a key encryption key (KEK) stored in a hardware security module or cloud KMS.
  • Account ownership is confirmed by matching the account holder name from the aggregator response against the name on file for the user, using fuzzy name matching with a configurable confidence threshold.

Risk Scoring

After verification, the service computes a risk score from 0 to 100 using a weighted combination of signals:

  • Verification method: instant verification via aggregator with balance data scores lower risk than micro-deposit alone.
  • Account age: accounts open for less than 90 days receive a risk penalty.
  • Current balance relative to the intended transaction amount: accounts with a balance below the transaction amount score higher risk.
  • Transaction history signals from the aggregator: presence of regular payroll deposits reduces risk; frequent overdrafts increase it.
  • Institution reputation: accounts at institutions with high return rates on ACH debits (obtained from the ACH network) are scored higher risk.

Risk scores are mapped to tiers. Low-tier accounts may transact up to the platform maximum. Medium-tier accounts have per-transaction and daily limits. High-tier accounts require additional identity verification before transactions are permitted. Blocked accounts are rejected immediately.

API Design

  • POST /verifications/micro-deposit — initiate a micro-deposit session; returns session_id and expected confirmation window.
  • POST /verifications/{session_id}/confirm — submit micro-deposit amounts for confirmation.
  • POST /verifications/instant — initiate an instant verification session; returns aggregator authorization URL.
  • GET /verifications/{session_id} — poll session status and retrieve bank_account_id on confirmation.
  • GET /accounts/{account_id} — retrieve verified account details, risk score, and tier.
  • POST /accounts/{account_id}/rescore — trigger a fresh risk score computation, for example after a transaction return event.

Scalability and Observability

Verification sessions are stored in the database with all state needed for recovery on restart; no session state lives only in memory. ACH dispatch is asynchronous: the service publishes a micro-deposit job to a queue and a worker submits the ACH batch, allowing the API to respond immediately. Key metrics include: session completion rate by method, micro-deposit confirmation attempt distribution, instant verification latency by aggregator, risk score distribution across tiers, and ACH return rate per institution — the last being a leading indicator of fraud risk and a key input to score model refinement.

See also: Stripe Interview Guide 2026: Process, Bug Bash Round, and Payment Systems

See also: Scale AI Interview Guide 2026: Data Infrastructure, RLHF Pipelines, and ML Engineering

See also: Coinbase Interview Guide

Scroll to Top