TSTier SlateAll templates

Payments & ledgers

OTP verification - six digits and a clock

How a one-time code is issued, rate limited and checked, and why the SMS bill is the attack surface.

  • System design
  • starter
  • 8 step walkthrough
  • 5 tables

Share

  • Facebook
  • X

What this board gets wrong on purpose

The tension

Every limit on this board is keyed by something the attacker chooses. Per-number limits never fire against SMS pumping because each send goes to a fresh premium number, and per-IP limits fall to a residential proxy pool, so the only control that sees the attack is the per-country spend breaker - and when it trips it blocks every legitimate user in that country too, which is an outage we cause on purpose. The attempt counter lives on the CODE, not the phone number, so asking for a new code resets it: three codes per fifteen minutes is fifteen guesses per window, roughly 1.4 in 1,000 per number per day, which is fine for a login and not fine for a bank transfer. And the code store is Redis with asynchronous replication, so a failover silently loses live codes and every user mid-login has to ask for another one - which is another paid send.

Practise this as an interview

Take OTP verification - six digits and a clock as a timed system design interview on a blank board - on your own, with the requirements to check yourself against, or with a candidate you invite.

Practise this as an interview

Start here

Why this design

A one-time passcode looks like the smallest feature in an auth system: generate six digits, text them, compare them. Interviewers like it because the obvious answer is correct about guessing and completely wrong about money. The endpoint that sends codes must be callable by somebody who is not logged in, every call costs a carrier fee, and there is an entire fraud industry - SMS pumping, also called toll fraud or IRSF - built on making other companies pay for texts to numbers the fraudster profits from.

What you will learn

  • Why a code is stored as a hash with a TTL and an attempt counter, and the arithmetic that makes six digits and five attempts safe enough for a login.
  • Why the attempt counter has to be thought about per phone number as well as per code, because re-issuing a code resets it.
  • Why per-number and per-IP rate limits do not stop SMS pumping, and why a per-country spend ledger does.
  • Why the sender is behind a queue, and what a 3 to 20 second carrier delay does to "Resend code" buttons.
  • Why the code store is allowed to be lossy, and what that costs in extra sends.

How to read this board

  1. Press Play and follow the walkthrough: REQUEST along the top left, ISSUE in the middle, DELIVERY on the right.
  2. Then CHECK on the left, which is where the code comes back - through a person, not a wire.
  3. Then COST at the bottom right, and the dashed wire that runs back from the breaker into the prefix rules.
  4. 1 · Requirements and 2 · Capacity - the numbers, including the one on the invoice.
  5. 3 · Storage, 4 · Availability, 5 · How it works, 6 · Deployment.
  6. Finally the DATA MODEL, then argue with the tension: every limit here is keyed by something the attacker picks.

1 · Requirements

Ask these before drawing anything

  • What is the code FOR - sign-up, login, or approving a payment? A login code can tolerate a 1 in 200,000 guess rate. A payment approval should bind the code to the amount and the payee, or it can be phished and replayed against a different transfer.
  • Which channels - SMS only, or also voice, email and an authenticator app? SMS is the expensive, interceptable one, and the honest long-term answer is to move users off it.
  • Which countries do we actually have users in? This decides the prefix allowlist, and it is the single most effective anti-fraud control on the board.
  • Is there a budget? Somebody owns the SMS bill. If nobody has said a number, the first pumping attack will say it for them.
  • What happens when the user does not get the code? "Resend" is a paid action and the most-clicked button on the screen.

Functional

  • POST /otp/send issues a six digit code for a phone number and a purpose (login, signup, phone_change, payment).
  • POST /otp/verify accepts the code and returns a short-lived verification token on a match.
  • A code expires after five minutes and is burned after five wrong attempts or one right one.
  • Issuing a new code invalidates the previous one for the same number and purpose.
  • Sends are limited per number, per IP, per device fingerprint and per country prefix.

Non-functional

  • Verify p99 under 50 ms. It is a hash compare; anything slower is a bug.
  • Send accepted (queued) p99 under 150 ms. Delivery to the handset is 3 to 20 seconds and not ours to promise.
  • Comparison is constant time, and the response for "wrong code" and "no such code" is identical, so the endpoint cannot be used to discover which numbers have a pending login.
  • SMS spend never exceeds 3x the trailing seven day baseline for any country for more than ten minutes.
  • Codes are never logged in plaintext - not in application logs, not in the provider webhook payloads we store.

Out of scope

  • Passwords and passkeys. This is the second factor or the passwordless step, not the account system.
  • SIM swap detection. Worth a carrier API lookup for high-value flows; a different system.

The rest of this board

Board preview

Available on Tier Slate

This page publishes the question. The answer — 5 more written pages, an 8-step narrated walkthrough and a 5-table schema — is the board itself, and it opens in Tierslate.

  • 5written pages
  • 8walkthrough steps
  • 5tables39 columns

5 pages behind this one

  • Capacity estimation
  • Storage estimation
  • Availability
  • How it works
  • Deployment plan
Open this board in TierslateBrowse every board

More boards

Systems where losing a write is losing money.

  • Double-entry ledger - the balance is not a column
  • Fraud detection - a decision that must fit in 120 milliseconds
  • Payment gateway - what a timeout means when the card may already be charged
  • Matching engine - price-time priority, and why the clock cannot decide who was first
  • Wallet transfers - the state machine, the hold that expires, and the reversal that cannot
  • Subscription billing - the invoice is computed, not typed
  • Currency exchange - a rate that is only true for a few seconds
  • Loyalty points - a currency you print yourself
All 100 templatesOpen Tierslate
Tierslate

tierslate.com

HomeTemplatesPrivacyTerms