Architecture patterns
Realtime updates and long-running work
Clients need fresh state without polling hard. Push it over a live channel. Work that outlasts one request runs as a job, with a status resource and a webhook when it finishes.
Two problems look different but share a fix. Clients want live updates without asking the server every few hundred milliseconds. And some work, such as encoding a video or settling a payout, cannot finish inside one HTTP request. For live updates, push data over a channel that stays open. For long work, run it as a job and let the client check on it.
Concept
Before you start. Polling means the client asks again and again. A WebSocket is a two-way channel that stays open. Server-sent events (SSE) is a one-way stream from server to client. A webhook is a call from another system to yours when a job finishes.
The big idea
Think of a phone call versus a voicemail. For a live conversation, you need the line open, so updates arrive as they happen. For a task that takes time, you leave a ticket number and check its status later, or wait for a call back.
The analogy breaks when many clients are connected at once. Each open channel uses server resources, so a design needs to count them.
Walking through it
Choose the channel by the direction of the data.
| Case | Channel | Why |
|---|---|---|
| Chat | WebSockets | Both sides send messages |
| Notifications | Push, with SSE for in-app | Server sends, client reads |
| Ticket availability | SSE | Server sends small deltas |
| LLM token stream | SSE | Server streams tokens to one client |
Long work follows the job pattern. The client submits, gets a job ID, and checks the status.
POST /exports -> 202 Accepted, job_id = j_881
worker picks up j_881 -> status: running
GET /jobs/j_881 -> status: running
worker finishes -> status: done, result_url = ...Polling is the cost to watch. A client polling every 500 ms makes 2 requests per second. A million such clients make 2 million requests per second, before any real work happens. Push the update instead, or make the poll interval longer.
Example
A payment provider settles payouts overnight. Your service submits the payout and returns 202. When the provider finishes, it calls your webhook. You store the event by its ID, so a provider retry does not pay out twice.
Common mistakes
- Blocking a request on video encoding or a payout. The request times out, and the user sees an error for work that succeeds.
- Polling too fast across many clients. The poll load can exceed the real work.
- Accepting webhooks without deduplication. Providers resend events, so the handler must be safe to run twice.
Check yourself
- A million clients each poll every 500 ms. How many requests per second is that? 2,000,000. Each client makes 2 requests per second.
- When is SSE a better choice than WebSockets? When data flows only from server to client, such as a stream of tokens or availability updates.
Key takeaways
- Push live updates over WebSockets or SSE, not frequent polls.
- Run long work as a job with a status resource.
- Use webhooks for completion, and deduplicate them.
- Count open connections and poll rates when you size the system.
Further learning
For channel details and scaling, see realtime delivery.
For a payment flow that uses webhooks and idempotency, see the payment system case study.
The chat system case study uses WebSockets end to end.
Related
Real-time delivery
How a server pushes updates to clients that did not ask, from polling to WebSockets, and the gateway, pub/sub and reconnect design that serves millions of open connections. Start here for the real-time map.
Case study: chat system
Design a WhatsApp or Slack style messaging system. Connection gateways, per-conversation ordering, wide-column storage and large-channel fan-out are the parts that matter.
Case study: payment system
Moving money correctly, with payment intents, idempotency at every hop, a double-entry ledger, webhooks and reconciliation when the network lies. Start here for the payments map.