Guide · System design
Design WhatsApp: Delivery Receipts, Groups and Encryption
25 min readUpdated By Saket Bhatnagar
Researched and drafted with AI, reviewed and edited by Saket Bhatnagar.
In short
- WhatsApp system design comes down to four jobs. Keep an open connection to every phone, hold messages until they arrive, report ticks honestly, and encrypt so only the receiver can read.
- Ticks are acknowledgements, small "got it" messages sent back to the sender. Here, one tick means the server saved the message, two mean the receiver's device saved it, and blue means it was read.
- When the receiver is offline, the server keeps the sealed message in an inbox for that device and sends a push notification, a wake-up alert through Apple's or Google's service. It deletes its copy only after the device confirms.
- End-to-end encryption means only the chat members' devices hold the keys, so servers relay bytes they cannot read. Groups use sender keys: each member shares one key once, then uploads each message once for the server to copy (fan-out).
- Beginners can stop after the section on what end-to-end encryption means. Engineers then get how a switched-off phone can still receive a sealed first message and the two-line hash ratchet with output you can check. They also see why one exit from a 250-member group can cost about 62,000 key messages once everyone sends again.
What goes wrong when you build a chat app the obvious way?
Four things break: ticks lie, messages arrive twice, big groups crawl, and servers end up able to read chats. Fixing those four is the core of any WhatsApp system design question.
Picture a common setup. A regional chat app has about 4 million daily users, mostly families and small business groups. The setup is made up, but the problems are real. One festival week, traffic doubled and four complaints arrived.
- A shop owner saw two ticks on an order. The customer's phone had been switched off all evening, and the message never reached it.
- Some people got the same message twice after their phone switched from mobile data to Wi-Fi.
- In a 250-member family group, one message took almost a minute to reach everyone. The sender's phone was encrypting and uploading 249 separate copies.
- An engineer found message text in a debug log on a chat server. Anyone with access to those logs could read private chats.
The rest of this guide redesigns the app part by part: connections, delivery, ticks, groups and encryption. At each step it compares the design with what WhatsApp has published about its own system.
What does WhatsApp system design need to handle?
A WhatsApp-style design must deliver short messages quickly to phones that are often offline. It must also show honest ticks, support groups and several devices per account, and keep content readable only by the people in the chat.
Start with the features, called functional requirements because they say what the system does.
- One-to-one chats with text, photos and voice notes.
- Groups of up to 1,024 members (our design target).
- Ticks for sent, delivered and read, including in groups.
- One account on a phone, plus linked laptops and tablets.
- End-to-end encryption for every message, group and photo.
Non-functional requirements say how well it must work. Messages reach an online receiver in under a second, none are lost, and each chat keeps one order. Servers never hold readable text.
Now some rough maths. Each user sends about 30 messages a day, so 4 million users send 120 million messages a day. Divided by 86,400 seconds, that is about 1,400 messages a second. Evening peaks run five times higher, so plan for 7,000 a second.
For a chat app, open connections matter more than message counts. Say 1.5 million phones stay connected at the peak, and one chat server holds 100,000 connections. You need 15 chat servers, plus spares for failures and deploys.
Those numbers are small next to WhatsApp. In a January 2012 blog post, WhatsApp reported one server holding over 2 million TCP connections, the long-lived links between two machines. The post showed Erlang, a language built for many small parallel tasks, running on FreeBSD.
How does a message travel from one phone to another?
The sender's phone keeps one open connection to a chat server. That server first saves the message in the receiver's per-device inbox and confirms to the sender. Then it forwards the message to the server holding the receiver's connection, or sends a push notification if the receiver is offline.
The phone opens this connection, not the server, because a phone moving between networks has no stable address the server could dial. The phone keeps the line open with a heartbeat, a tiny "still here" packet sent every few minutes.
In this design the connection is a WebSocket, a standard web connection where either side can send at any moment. WhatsApp's 2016 white paper says its apps secure their long-running connection to the server with Noise Pipes, from the Noise Protocol Framework, a published recipe for encrypted connections.
In the diagram, start at the sender's phone. Every message is saved to the inbox first, then forwarded if the receiver is online, or announced by push if not.
The main parts of the chat app
Drag to exploreNotice that chat servers only route sealed messages, and the inbox holds only what no device has confirmed yet.
Diagram connections:
- Sender's phone to Key server (Fetch public keys)
- Sender's phone to Media blob store (Upload encrypted photo)
- Sender's phone to Connection balancer
- Connection balancer to Chat servers
- Chat servers to Presence registry (Where is the receiver?)
- Chat servers to Receiver's phone (Forward if online)
- Chat servers to Per-device inbox (Save every message first)
- Per-device inbox to Push notification service (Receiver offline)
- Push notification service to Receiver's phone (Wake up and reconnect)
- Sender's phone: Encrypts the message before it leaves
- Key server: Public keys for every device
- Media blob store: Encrypted photos, videos and voice notes
- Connection balancer: Spreads open connections across servers
- Chat servers: Hold open connections and route messages
- Presence registry: Which chat server holds each device
- Per-device inbox: Sealed messages waiting, per device
- Push notification service: Wakes a sleeping phone, carries no text
- Receiver's phone: Decrypts and shows the message
The presence registry is a fast in-memory store that maps each device to the chat server holding its connection. An entry expires when heartbeats stop, so a dead connection does not swallow messages.
The per-device inbox is a database of sealed messages that no device has confirmed yet. It is partitioned by device id. Partitioning means splitting one big table across many machines by a key, so one device's waiting messages sit together and load fast.
Photos skip the chat path. WhatsApp's 2016 white paper describes the flow. The sender encrypts the file with fresh random keys and uploads it to a blob store. Then it sends those keys, a SHA-256 hash (a short fingerprint) of the encrypted file and a pointer, inside a normal encrypted message.
How do delivery receipts work in a chat app?
Delivery receipts are small acknowledgement messages that flow back to the sender. Each tick changes only after the next party confirms it has safely saved the message, not when someone merely sends it onward.
Picture a wedding where you cannot reach the bride yourself. You hand a sealed shagun envelope, the cash gift, to a relative near the stage. The table maps each part of that handover to the chat system.
| In the wedding | In the chat system | Tick |
|---|---|---|
| You hand the sealed envelope to a relative | The sender's phone sends the encrypted message to a chat server | None yet |
| The relative says they have it | The server saves it and confirms to the sender | One grey tick |
| The bride's mother says it is in the bride's bag | The receiver's device saves it and confirms | Two grey ticks |
| The bride opens it | The receiver opens the chat | Two blue ticks |
Where the picture breaks: a relative cannot copy an envelope, but a server can and does resend a message when a confirmation goes missing. So the bride's bag can end up with two copies. That is the duplicate problem below.
The diagram shows the same handover as messages. Each note says who talks to whom.
How one message earns its ticks
Drag to exploreNotice that each tick waits for a confirmation from whoever now holds the message, and the server deletes its copy only after the receiver confirms.
Diagram connections:
- Send message m42 to Save to inbox, confirm
- Save to inbox, confirm to Deliver m42
- Deliver m42 to Store on phone, confirm
- Store on phone, confirm to Delete copy, two ticks
- Delete copy, two ticks to Chat opened, read receipt
- Chat opened, read receipt to Turn ticks blue
- Send message m42: Sender's phone → Chat server
- Save to inbox, confirm: Chat server → Sender's phone (one tick)
- Deliver m42: Chat server → Receiver's phone
- Store on phone, confirm: Receiver's phone → Chat server
- Delete copy, two ticks: Chat server → Sender's phone
- Chat opened, read receipt: Receiver's phone → Chat server
- Turn ticks blue: Chat server → Sender's phone
The fourth box fixes the shop owner's complaint. The receiver's phone sends the delivered receipt only after saving the message. The old code set two ticks the moment the server pushed the message out.
Why messages arrive twice, and how to stop it
Duplicates come from lost acknowledgements. Say the phone saves m42, but its receipt is lost when the phone jumps to Wi-Fi. The server never heard back, so it sends m42 again, because resending beats losing.
This is at-least-once delivery: a message may arrive twice but never zero times. So every message carries a unique id made by the sender's phone, and the receiver drops ids it has already seen. The guide on delivery guarantees covers this pattern.
Receipts arrive late and out of order too
Receipts travel over the same shaky networks, so a read receipt can overtake a delivered one. If the code stores whatever arrives last, blue ticks can turn grey again. So a status may only move forward.
In the code below, ticks is a dictionary that maps each message id to the status of every receiving device. The last three lines reuse it for a group message, g7, sent to three devices.
RANK = {"sent": 1, "delivered": 2, "read": 3}
def apply_receipt(ticks, msg_id, device_id, status):
per_device = ticks.setdefault(msg_id, {})
old = per_device.get(device_id, "sent")
if RANK[status] > RANK[old]: # only ever move forward
per_device[device_id] = status
return per_device
def ticks_to_show(per_device, all_devices):
states = [per_device.get(d, "sent") for d in all_devices]
return min(states, key=RANK.get) # the slowest device decides
ticks = {}
apply_receipt(ticks, "m42", "phone-b", "read")
apply_receipt(ticks, "m42", "phone-b", "delivered") # late, ignored
print(ticks_to_show(ticks["m42"], ["phone-b"])) # read
group = ["phone-b", "laptop-b", "phone-c"]
apply_receipt(ticks, "g7", "phone-c", "delivered")
print(ticks_to_show(ticks["g7"], group)) # sentRun it with Python 3 and it prints two lines: read, then sent. Any Python 3 version works, because the code uses no libraries.
What just happened? The RANK comparison ignored the late delivered receipt, so the status stayed at read. In the group, one device never answered, so min picked the slowest status and the sender still sees one tick.
How does a chat app deliver messages to an offline phone?
It keeps the sealed message in the receiver's per-device inbox, sends a push notification to wake the phone, and hands over everything waiting when the phone reconnects. The inbox copy is deleted only after the phone confirms.
WhatsApp describes the same rule for its own servers. Its 2021 engineering post on multi-device says messages are not stored on the server after they are delivered.
The server gives each message in an inbox a sequence number, a counter that goes up by one. The phone remembers the highest number it has stored. Here is what happens after a night switched off.
Phone reconnects
The phone opens its connection and says the last sequence number it stored was 1,041.
Server sends what is waiting
The chat server reads the inbox from 1,042 onwards and sends the messages in order, in batches of 100.
Phone stores, then confirms
The phone decrypts and saves each batch, drops any ids it has already seen, then acknowledges the highest number in the batch.
Server cleans up
The server deletes everything up to that number and sends delivered receipts back to each sender.
Groups need one more rule. Every group message passes through a group fan-out server, which stamps a group sequence number before copying it out. So all members see the group's messages in the same order, even if two people typed in the same second.
In this design, push notifications carry no message text, only a nudge that something is waiting. The phone wakes, fetches the sealed message and decrypts it locally.
What does end-to-end encryption mean in a chat app?
It means a message is locked on the sender's phone and unlocked only on the receiver's devices. Every server in between carries ciphertext, scrambled bytes it has no key to read.
Go back to the wedding. The envelope passes through several relatives, but it is sealed and has one name on it. End-to-end encryption seals the message on the sender's phone, so every server is just another relative passing it along.
One term first. A key pair is two linked numbers: a public key you can share with anyone, and a private key that never leaves the device.
Two phones can each mix their own private key with the other's public key and get the same secret. Someone who sees only the public keys cannot feasibly work it out with today's computers. This is a Diffie-Hellman exchange, the building block of the whole protocol.
WhatsApp uses the Signal protocol, an open design published by the Signal project. It turns Diffie-Hellman into a fresh key for every message, as the next section shows.
How does end-to-end encryption work in WhatsApp?
Phones agree on a secret using public keys parked on the server, then derive a new key for every message. The server relays only ciphertext. WhatsApp's 2016 white paper describes this as its use of the Signal protocol.
Starting a chat when the other phone is offline
The receiver may be switched off when the first message is sent, so it cannot take part in a live exchange. The fix is prekeys: public keys a phone uploads in advance. WhatsApp's 2016 white paper says each phone uploads three kinds at registration.
- An identity key: a long-term public key that identifies the device.
- A signed prekey: a medium-term key, signed with the identity key and replaced on a schedule.
- A batch of one-time prekeys: the server hands each one out once and then deletes it.
The sender fetches one bundle, checks the signature, and runs four Diffie-Hellman mixes to get a shared master secret. This step is called X3DH. Mix 1 pairs the sender's identity key with the receiver's signed prekey. Mixes 2 to 4 pair a fresh throwaway key from the sender with the receiver's identity key, signed prekey and one-time prekey. If the one-time prekeys have run out, mix 4 is skipped.
The protocol still works without mix 4, but with less protection. The X3DH specification warns that a first message sent without a one-time prekey may be replayed, that is, sent again by an attacker and accepted.
Follow the diagram from the receiver's upload to the server's relay. Notice that the key server and the chat server only ever hold public keys and sealed bytes.
Starting an encrypted chat with an offline phone
Drag to exploreNotice that the receiver does its part in advance by uploading public keys, so the first message is sealed without it being online.
Diagram connections:
- Receiver uploads prekeys to Key server stores them
- Key server stores them to Sender fetches a bundle
- Sender fetches a bundle to Signature valid?
- Signature valid? to Work out shared secret (Yes)
- Signature valid? to Stop, send nothing (No)
- Work out shared secret to Seal with a fresh key
- Seal with a fresh key to Server relays ciphertext
- Receiver uploads prekeys: Identity, signed and one-time public keys
- Key server stores them: Public halves only, one-time keys used once
- Sender fetches a bundle: Gets one one-time prekey, then it is deleted
- Signature valid?: Signed prekey checked against identity key
- Stop, send nothing: Someone may have tampered with the keys
- Work out shared secret: X3DH: three or four key mixes, no reply
- Seal with a fresh key: Double Ratchet: a new key for each message
- Server relays ciphertext: Sees who and when, never the text
A new key for every message
After X3DH, the Double Ratchet takes over. It gives forward secrecy, which means a key stolen today cannot decrypt earlier messages. It also gives break-in recovery: once the phones swap fresh public keys again, a thief who copied keys stops reading new messages.
The core step is a hash ratchet. A chain key is a secret that moves forward one step per message. Each step uses HMAC-SHA256, a keyed one-way hash that is easy to compute and infeasible to reverse. WhatsApp's 2016 white paper and Signal's Double Ratchet specification give the same recipe, sketched below with Python's standard hmac and hashlib modules.
import hashlib
import hmac
def step(chain_key):
message_key = hmac.new(chain_key, b"\x01", hashlib.sha256).digest()
next_chain = hmac.new(chain_key, b"\x02", hashlib.sha256).digest()
return message_key, next_chain
chain = bytes(32) # 32 zero bytes, only so you can check the output
for text in ["Pickup at 6", "Bring the banner"]:
key, chain = step(chain) # the old chain key is overwritten
print(text, "->", key.hex()[:16])Check it: with Python 3 this prints "Pickup at 6 -> 3d7afb663124ecbf" and "Bring the banner -> e43cd1fae55cf944". Each value is the first 8 of 32 bytes. A real app starts from a secret agreed during X3DH, never from zeros.
What just happened? Both outputs come from the current chain key, but you cannot run them backwards. Once the old chain key is overwritten, nobody can recompute earlier message keys from what the phone still holds.
A real app does not encrypt with this 32-byte output directly. The Signal specification recommends stretching it with HKDF, a standard key-derivation function, into 80 bytes. WhatsApp's 2016 white paper describes the same 80-byte message key: a key for the AES-256 cipher, a key for checking tampering, and a 16-byte IV, the cipher's starting value.
You can see the same two constants in open-source code. Take python-axolotl 0.2.3, a Python port of the Signal library. Its one-to-one chain and its group sender chain both use 0x01 for message keys and 0x02 for the next chain key.
What the server can still see
End-to-end encryption hides content, not activity. To route a message, the server must know which device it is for, and it sees when it was sent and roughly how big it is. The Noise channel between phone and server hides those details from network outsiders, not from the server itself.
A dishonest server could also hand out its own public keys and sit in the middle. WhatsApp's 2016 white paper lets two users compare a 60-digit number or scan a QR code to confirm the keys match. Your design needs the same screen, plus a notice when a contact's keys change.
In April 2023, Meta's engineering blog described key transparency for WhatsApp. It is a directory of public keys with public audit records, so the app can check a contact's key automatically.
The post says this complements the code check rather than replacing it. An update on the same post says key transparency is now fully available on Android and iOS.
Signal's own specifications have moved on since 2016. PQXDH, a Signal specification dated 24 May 2023, adds protection against future quantum computers to the first key exchange. Revision 4 of the Double Ratchet specification, dated 4 November 2025, adds a Triple Ratchet for the same reason.
Which of these WhatsApp ships is a fact that changes, so check its current white paper rather than this guide. This guide explains the classic X3DH and Double Ratchet design, as of 19 October 2026.
How do group chats work with end-to-end encryption?
WhatsApp's groups use sender keys, according to its 2016 white paper and its 2021 multi-device post. Each member makes its own sender key for the group and shares it once over private one-to-one sessions. After that it encrypts each message once, and the server copies that one ciphertext to every member.
This fixes the slow family group. The old app uploaded 249 separately encrypted copies from one phone. With sender keys, the phone uploads one ciphertext and the server does the copying. The 2016 white paper calls this server-side fan-out.
The 2016 white paper lists the first-message steps. The sender makes a random 32-byte chain key and a signing key pair, packs them into a sender key, and sends it to each member over one-to-one encryption.
Every later message steps the chain key forward, as in the ratchet code above, and is signed so members know who sent it. In the diagram, notice the slow path used once per sender and the fast path used for every message.
Group messages with sender keys
Drag to exploreNotice that the sender key travels once over one-to-one sessions, while each later message is uploaded once and copied by the server.
Diagram connections:
- Sender's phone to One-to-one sessions (First message only)
- One-to-one sessions to Member devices
- Sender's phone to Group fan-out server (Every message, one copy)
- Group fan-out server to Group membership (Who is in it?)
- Group fan-out server to Member devices (Copy to each device)
- Group membership to Did a member leave?
- Did a member leave? to Everyone makes new keys (Yes)
- Everyone makes new keys to One-to-one sessions (Shared again)
- Sender's phone: Makes one sender key for this group
- One-to-one sessions: Carry the sender key to each member device
- Group fan-out server: Copies one ciphertext into every inbox
- Group membership: Members and their devices
- Member devices: Decrypt with the sender's key
- Did a member leave?: Checked on every membership change
- Everyone makes new keys: Old sender keys are cleared
Pause and predict: one member leaves the 250-member group. The departed phone still holds every sender key, so all of them must go. How many key messages could that cost?
The 2016 white paper says all participants then clear their sender keys and start over. That leaves 249 members. Each one who sends again must share a new key with the 248 others: 249 times 248 is 61,752, about 62,000 small key messages.
That count assumes one device per member. Linked laptops and tablets each need their own copy, so the real number grows with devices.
What MLS changes for big groups
There is a newer open standard for this problem. RFC 9420, published in July 2023 by the IETF (the internet's main standards body), defines Messaging Layer Security (MLS). It arranges group keys in a tree, and the RFC says key updates cost work that scales with the log of the group size.
In a full tree of 1,024 members, one update path has 10 levels, so about 10 encryptions instead of 1,022 one-to-one key shares. Blank spots left by departed members can raise that count, because the RFC then encrypts to more keys at those levels.
| Separate copy per member | Sender keys | MLS (RFC 9420) | |
|---|---|---|---|
| Uploads per message | 249 ciphertexts from the phone | 1 ciphertext, server copies it | 1 ciphertext, server copies it |
| Work when someone leaves | Nothing extra | Every sender shares a new key with everyone | One tree update, grows with log of group size |
| Forward secrecy | Yes, per pair | Yes, through the hash ratchet | Yes |
| Recovery after a key theft | Yes, through the Double Ratchet | Weak until keys are replaced | Yes, a stated goal of the RFC |
| Best fit | Very small groups | Most chat groups | Large groups that change often |
Group receipts can flood the sender. In a group of 1,024 with one device each, one message triggers 1,023 delivered and 1,023 read receipts, 2,046 in all. So each chat server collects receipts for a second, then sends one combined update.
One account on several devices
Each linked device has its own identity key and its own sessions, according to WhatsApp's July 2021 engineering post. The sending phone encrypts a one-to-one message separately for each of the receiver's devices, and for the sender's own other devices.
WhatsApp calls this client-fanout: N devices means N encryptions. Groups still use sender keys across devices. A new laptop is linked by scanning a QR code with the main phone.
What goes wrong in a chat system, and how do you guard against it?
Most failures come from dropped networks, restarting servers and keys running out. Each has a guard.
- A chat server restarts and 100,000 phones reconnect at once. Guard: each phone waits a random few seconds before reconnecting, so the load spreads out.
- The presence registry points at a dead server. Guard: entries expire when heartbeats stop, and messages stay in the inbox until a device confirms them.
- A phone's one-time prekeys run out. Guard: the server tells the phone when its stock is low, as the X3DH specification describes, and limits how fast anyone can fetch bundles.
- One huge group gets busy, and copying blocks other chats. Guard: fan-out jobs go on a message queue, a waiting line of work that separate workers take in turn.
- Someone reinstalls the app, so their keys change. Guard: a clear notice in each chat, and the 60-digit number to check again.
How do you explain WhatsApp system design in a system design interview?
Explain it as the path a message takes, adding one guarantee at a time: connection, routing, storage, ticks, groups and encryption.
Pin down the scope
Ask about groups, maximum group size, devices per account, media and encryption. These change the design more than the user count does.
Do quick numbers
Estimate messages per second and peak open connections, then size the chat servers from connections.
Draw the connection layer
Phones keep one long-lived connection open with heartbeats. A presence registry maps each device to its server.
Trace one message
Save it to the receiver's inbox first. Forward it at once if the receiver is online, or send a push notification without text if not.
Make ticks honest
Tie each tick to a confirmation from whoever stored the message, and keep receipts forward-only.
Handle duplicates and order
Drop repeats using sender-made message ids, and keep order with sequence numbers per inbox and per group.
Add encryption and groups
Explain prekeys, X3DH and the Double Ratchet, then sender keys, server-side fan-out and what a member leaving costs.
Questions people ask
- Can WhatsApp read my end-to-end encrypted messages?
- Not the content, if the system works as its 2016 white paper describes. Messages are sealed on the sender's device with keys the server never holds. The server can still see metadata, such as who messaged whom, when, and roughly how much. Comparing the 60-digit security number with a contact checks that nobody has swapped keys.
- What encryption protocol does WhatsApp use?
- WhatsApp says it uses the Signal protocol. X3DH sets up a shared secret, even when the receiver is offline, using prekeys stored on the server. The Double Ratchet then gives every message a new key. Groups use sender keys, and the phone-to-server link adds a separate channel built with the Noise Protocol Framework.
- Why do my group message ticks stay grey?
- In the design in this guide, group ticks show the slowest member. Two ticks need every member's device to store the message, and blue needs every member to read it. One member with a switched-off phone keeps the ticks grey. A per-member info screen lets the sender see who is holding things up.
- Where are messages stored when the receiver is offline?
- They wait on the server in an inbox for each receiving device, still encrypted. The server sends a push notification without the text, and the phone collects everything when it reconnects. The server deletes each message only once the device confirms it has stored it.
- What is a sender key in group chats?
- A sender key is a group key that one member creates and shares once with every other member over private one-to-one sessions. After that, the member encrypts each group message once with it, and the server copies the ciphertext to everyone. When someone leaves, members clear their sender keys and share new ones.
- Which database fits a chat app in a system design interview?
- Pick a store that partitions well by a key, such as a wide-column or key-value database partitioned by device id or chat id. The inbox workload is simple: append messages, read from a sequence number, then delete. Keep the presence registry in a fast in-memory store, because it changes every time a phone connects.
Sources and further reading
- WhatsApp Encryption Overview, technical white paper (April 4, 2016 version, DocumentCloud copy)
- How WhatsApp enables multi-device capability, Engineering at Meta (July 14, 2021)
- The X3DH Key Agreement Protocol, Signal specification, revision 1 (PDF)
- The Double Ratchet Algorithm, Signal specification, revision 4 (2025-11-04)
- The PQXDH Key Agreement Protocol, Signal specification, revision 3 (2023-05-24)
- RFC 9420: The Messaging Layer Security (MLS) Protocol, IETF, July 2023
- Deploying key transparency at WhatsApp, Engineering at Meta (April 13, 2023)
- 1 million is so 2011, WhatsApp blog (January 6, 2012)
- Setting the priority of a message, Firebase Cloud Messaging docs
- python-axolotl 0.2.3 on PyPI (chainkey.py and senderchainkey.py in the sdist)