System Design Library

Venmo / P2P Payments

Send money between users instantly, with a social feed of transactions.

Open the interactive version → diagrams, practice & more

Requirements

Functional

  • Send/request money
  • Balance/ledger
  • Social feed
  • Bank linking

Non-functional

  • Consistent balances
  • Idempotent
  • Auditable

Scale

Millions of transfers

The approach

Double-entry ledger for transfers (atomic debit/credit) with idempotency keys; a separate denormalized social feed fans out transaction events; bank rails (ACH) are async with state machines.

Key components

Transfer service (ledger) · feed fan-out · bank-rail adapters

Numbers that matter

Senior deep-dive

The ledger is the source of truth — the social feed is a read projection on top of double-entry accounting; the money movement and the social post are separate concerns sharing one event.

Idempotency keys on every write are non-negotiable: a user who taps "Pay" twice on a flaky connection must not send money twice, and the defense is server-side key dedup, not client-side retry suppression.

Bank rails (ACH) are async and slow — Venmo balances are instant because they're internal ledger moves; the real ACH settlement to a bank account takes 1–3 business days and is the actual money-movement risk surface.

Double-entry ledger: the immutable core

Every transaction records both a debit from the sender's account and a credit to the recipient's account atomically — no single-row balance updates. The ledger is append-only: you never UPDATE a row, you INSERT a reversal entry to correct errors. This gives you a full audit trail, simplifies reconciliation, and means account balance = SUM of all ledger entries for that account (expensive, so you maintain a running-balance cache).

Idempotency keys: the double-spend guard

Every payment request carries a client-generated idempotency key (UUID). On arrival, the server does: Redis GET(key) — if found, return the cached response immediately; if not, process the payment, store the result under the key with a 24h TTL, then return. The check-then-act must be atomic (Redis SET NX + Lua) or two simultaneous requests with the same key can both pass the check and both process. After 24h the key expires from Redis but the ledger remains the definitive duplicate check.

Social feed: fan-out on write

After a payment completes, an async worker fans out a social event to the timelines of the sender, the recipient, and their mutual friends (if the payment is public or friends-only). This is a write fan-out (like Twitter's model) into a Cassandra timeline table keyed by user_id + timestamp. Reading the home feed is a simple range scan. The social graph (who is friends with whom) is stored in a separate graph store; the fan-out worker joins these at write time to avoid joins at read time.

Bank rail integration: ACH and debit card

Moving money in or out of Venmo means touching real bank rails. ACH (Automated Clearing House) is batch-settled overnight — cheap but slow. Debit card instant transfer (Visa/MC push-to-card) settles in minutes but costs ~1.75%. Venmo's internal transfers are instant because they never leave the ledger — two Venmo users exchanging money is a ledger journal entry, not an ACH transaction. Plaid or direct bank APIs authenticate the bank account during onboarding and retrieve the routing/account number needed for ACH initiation.

Fraud detection on the instant-credit model

Venmo allows instant transfers between users even before ACH settles — they're extending short-term unsecured credit on every internal transaction. A fraud model must score each payment in <200ms using velocity features (payments in last hour/day), device fingerprint, network graph signals (is this a new connection?), and amount anomaly detection. High-risk transactions are flagged for manual review or blocked; the cost of a wrong allow is a direct P&L hit because Venmo can't claw back an ACH debit after the recipient withdraws.

What breaks at scale

Hot accounts (a popular creator receiving thousands of simultaneous Venmo payments) create a write hotspot on the recipient's ledger partition — mitigated by sharding the ledger by account_id and batching balance cache updates. Reconciliation drift happens when the in-memory balance cache diverges from the ledger sum (due to a mid-crash write) — periodic ledger reconciliation jobs recompute the authoritative balance and alert on discrepancies. The subtlest failure: idempotency key collisions if the client reuses UUIDs (birthday paradox at billions of transactions) — mandate v4 random UUIDs and accept a tiny collision risk is lower than the cost of longer keys.

In production

Venmo (under PayPal) runs on a double-entry ledger backed by a sharded relational DB, with idempotency keys stored in Redis for the hot window (24h) and archived to the ledger DB. The social feed is a denormalized fan-out into a per-user timeline store (Cassandra-style) — the same transaction creates a ledger entry and a social event. The real challenge is the float and risk layer: Venmo extends instant credit to users before ACH settles, taking on bank-transfer risk; their fraud models must decide in milliseconds whether to allow a payment before the real money has moved, and a wrong call is a direct dollar loss, not just a bad UX.

Common mistakes

Related System Design Library

Part of System Design Library on SystemLore — system design interview prep with 148 deep topics, interactive diagrams, and a practice game. Practice this one →