Polling vs. webhooks vs. SSE vs. WebSockets
How to choose between polling, webhooks, Server-Sent Events, and WebSockets: latency, infrastructure complexity, directionality, scalability, and which pattern fits each use case.
TL;DR
| Pattern | Best for | Direction | Latency | Complexity |
|---|---|---|---|---|
| Polling | Simple read-heavy checks, low-frequency updates | Client β Server | Seconds to minutes | Low |
| Webhooks | Server-to-server event notifications | Server β Server | Near-instant | Medium |
| SSE | One-way live feeds to browser clients | Server β Client | Sub-second | Low-medium |
| WebSockets | Bidirectional real-time (chat, gaming, collaboration) | Bidirectional | Sub-100ms | High |
Default answer: SSE for server-to-client pushes. WebSockets only when the client needs to send data back. Webhooks for server-to-server. Polling only when you genuinely can't do better.
30-Second Explanation
Polling is the simplest option: the client asks repeatedly, but it can waste requests and adds interval-sized delay. Webhooks notify another server and need retries, signatures, and delivery tracking. SSE keeps a one-way browser stream open and is a good fit for live updates; WebSockets support two-way interaction but require more connection and recovery machinery. Choose by client type, directionality, latency, and how much delivery control you need.
5-Minute Explanation
- Define the client and direction. Browser-only server-to-client updates favor SSE; server-to-server events favor webhooks; interactive client-to-server traffic favors WebSockets.
- Set the freshness and delivery target. Polling can be enough for low-frequency status checks; persistent connections are justified only when the latency benefit matters.
- Design recovery: reconnect with backoff, heartbeat and timeout rules, event IDs or cursors, replay windows, duplicate handling, and a fallback when the stream is unavailable.
- For webhooks, sign requests, make consumers idempotent, retry with limits, and expose delivery status. For polling, use conditional requests, jitter, and a rate limit.
- Validate the fleet impact: concurrent connections, fan-out, slow clients, load-balancer timeouts, and regional failover are part of the cost of push.
The Framing
Your team ships a dashboard that shows order status. Version one polls the API every 5 seconds. It works. Then the product manager wants real-time updates. Then Finance notices the API bill: 90% of those polling requests return "no change." You're burning bandwidth, database queries, and money on empty responses.
This is the core tension: who initiates the communication, and how often?
Polling puts the client in control. Simple, but wasteful. Push-based approaches (webhooks, SSE, WebSockets) put the server in control. More efficient, but more infrastructure. The right choice depends on directionality, latency requirements, and what your client actually is: browser, mobile app, or another server.
How Each Works
Polling: Client Pulls on a Timer
The simplest approach. The client sends HTTP requests at a fixed interval and checks for new data.
// Short polling: fixed interval
const poll = async () => {
while (true) {
const res = await fetch('/api/orders/123/status');
const data = await res.json();
updateUI(data);
await sleep(5000); // 5-second interval
}
};
// Long polling: server holds connection until data arrives
const longPoll = async () => {
while (true) {
// Server holds this request open up to 30 seconds
const res = await fetch('/api/orders/123/status?wait=30');
const data = await res.json();
updateUI(data);
// Immediately reconnect
}
};
Short polling fires requests at a fixed interval (1s, 5s, 30s). Most responses are empty. At 5-second intervals with 10,000 clients, that's 2,000 requests/second, even when nothing changes.
Long polling is smarter. The server holds the connection until new data arrives (or a 30-second timeout). This simulates push over HTTP, but each "event" still requires a full request/response cycle. Both work through any HTTP infrastructure with zero special configuration.
For your interview: if someone asks about real-time and you mention polling, immediately follow with "but polling wastes bandwidth, so I'd prefer SSE or WebSockets depending on the direction of data flow."
Webhooks: Server POSTs to Your URL
Webhooks flip the model: instead of the client asking for updates, the server sends an HTTP POST to a pre-registered URL when an event occurs.
// Registration: tell the provider where to send events
await stripe.webhookEndpoints.create({
url: 'https://api.myapp.com/webhooks/stripe',
enabled_events: ['payment_intent.succeeded', 'charge.failed'],
});
// Receiver: handle incoming events
app.post('/webhooks/stripe', (req, res) => {
// Step 1: Verify signature (CRITICAL)
const sig = req.headers['stripe-signature'];
const event = stripe.webhooks.constructEvent(
req.body, sig, process.env.STRIPE_WEBHOOK_SECRET
);
// Step 2: Process idempotently (webhooks retry)
await processEvent(event.id, event.type, event.data);
// Step 3: Respond 200 quickly (provider times out at ~5-15s)
res.status(200).json({ received: true });
});
The webhook receiver must be a publicly-reachable server. This makes webhooks ideal for server-to-server integrations (Stripe, GitHub, Twilio) but useless for browser or mobile clients.
Delivery guarantees matter. Webhook providers retry failed deliveries (typically exponential backoff over 24-72 hours). Your receiver must be idempotent because you will receive the same event multiple times. Always deduplicate by event ID.
Security is non-negotiable. Every webhook payload must include an HMAC signature. Without verification, anyone can POST fake events to your endpoint. Stripe uses HMAC-SHA256 with a shared secret. GitHub uses the X-Hub-Signature-256 header.
SSE: Server Streams Events Over HTTP
Server-Sent Events use a persistent HTTP connection where the server pushes text-based event frames to the client. One direction only: server to client.
Continue Reading with Premium
Unlock this article and every other in-depth system design guide on the platform with SDEpedia Premium.