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.
{
“@context”: “https://schema.org”,
“@type”: “FAQPage”,
“mainEntity”: [
{
“@type”: “Question”,
“name”: “How does the micro-deposit verification flow work for bank account verification?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Two small random deposits (e.g., $0.12 and $0.37) are sent to the account via ACH. The amounts are stored hashed in a pending_verifications table with an expiry timestamp. The user returns to the app, enters the two amounts, and the system compares the hash of the entered pair against the stored hash. On match the account transitions to VERIFIED. The deposits are then reversed or donated. The flow handles ACH's 1-3 business day settlement lag by keeping the account in PENDING state until the deposits clear.”
}
},
{
“@type”: “Question”,
“name”: “How does instant bank account verification via Plaid or Yodlee work?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “The user authenticates with their bank through the aggregator's OAuth-based Link flow. The aggregator returns an access token and account metadata (routing number, account number, account type, balance). The backend exchanges the token for a processor token scoped to the payment network, then stores it against the user's bank_accounts record. Verification is instantaneous because the aggregator confirms the account exists and the user controls it. The raw credentials never touch the application server, keeping PCI/SOC-2 scope narrow.”
}
},
{
“@type”: “Question”,
“name”: “How do attempt limits and cooling-off periods protect against abuse in bank account verification?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “A verification_attempts table records each attempt with user_id, account_id, result, and created_at. Before accepting a new attempt the system counts failures within the rolling window (e.g., 3 failures in 24 hours). On breach, the account is locked and a cooling_off_until timestamp is set (e.g., now + 72 hours). All subsequent attempts before that timestamp are rejected without hitting the aggregator or ACH network. After the window expires the counter resets. Permanent locks trigger after a higher threshold (e.g., 10 lifetime failures) and require customer support intervention.”
}
},
{
“@type”: “Question”,
“name”: “How does multi-signal risk scoring work for bank account verification?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Risk signals are collected and weighted: account age (newer accounts score higher risk), balance sufficiency relative to transaction size, velocity of recent transactions, device fingerprint and IP reputation, and whether the routing number belongs to a known high-fraud institution. Each signal contributes a score component; the weighted sum produces a composite risk score. Scores below a threshold auto-approve, scores above a hard limit auto-decline, and the middle band routes to step-up verification (e.g., require micro-deposit even if instant verification succeeded) or manual review.”
}
}
]
}
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