WebSocket vs HTTP: What's the Difference? (2026)
WebSocket vs HTTP, explained plainly — request/response vs a persistent two-way connection — and whether your SaaS actually needs realtime (usually not yet).
Table of contents7 sections
- 01The Live Cursor Nobody Asked For
- 02HTTP Is You Asking. WebSocket Is a Line Left Open.
- 03The Handshake: A WebSocket Starts Life as HTTP
- 04When You Actually Need a WebSocket
- 05The Cheaper Middle: Polling and Server-Sent Events
- 06The Infra Tax: Persistent Connections Need an Always-On Box
- 07The Bottom Line
The Live Cursor Nobody Asked For
It was late 2024. I was building a little collaborative tool — think a shared board where two people could edit the same thing — and I got it into my head that it needed live cursors. You know the ones: little colored arrows with names floating around, moving in real time, the thing Figma made everyone want. I had zero users. I hadn’t even shown it to a friend. But I spent a weekend wiring up a WebSocket server so a cursor I was the only person to see could glide across a screen nobody else was looking at.
That’s the Broken Engineer disease in one sentence. I reached for the most technically exciting option before checking whether the product needed it. WebSockets are exciting — a live, two-way pipe between browser and server is genuinely cool. But cool isn’t the same as necessary, and this toy came with an infrastructure bill I’ll get to.
So this post is what I wish I’d understood before that weekend. What HTTP actually is, what a WebSocket actually is, how they’re related (closer than you’d think), and — the part that matters for your wallet — whether your SaaS needs realtime at all. Spoiler I’ll defend later: usually not. Not yet.
HTTP Is You Asking. WebSocket Is a Line Left Open.
Here’s the whole distinction, and everything else is detail.
HTTP is request/response. Your browser opens a connection, asks for something — GET /dashboard, POST /links — the server answers, and then the conversation is over. The connection closes (or gets reused for the next request, but conceptually the exchange is done). Nothing else comes back unless you ask again. The server can’t tap you on the shoulder later. It has no way to reach you; it doesn’t even know you’re still there. This is the workhorse of the entire web. Every page you load, every form you submit, every API call your app makes — that’s HTTP, asking and answering, over and over. It’s also stateless: each request stands alone, carrying its own auth and context, and the server forgets you the moment it responds.
If that “you ask, they answer, nothing arrives unless you asked” model sounds familiar, it’s the same split I drew between an API and a webhook — pull versus push. HTTP is the pull side of the web.
A WebSocket is a line you dial once and leave open. The browser and server agree to keep a single connection alive, and then either side can send a message at any time, for as long as it lasts. The server can push you data at 3 AM without you asking. You can fire something up without starting a new request. Both directions, same open pipe, no re-handshaking for every message. That’s “full-duplex” — both people can talk at once, like a phone call, not a walkie-talkie.
The phone metaphor is what finally made it click for me. HTTP is texting: send a message, get a reply, each one self-contained, no connection persisting between texts. A WebSocket is calling someone and staying on the line — expensive to hold open, but instant in both directions once it is.
The Handshake: A WebSocket Starts Life as HTTP
Here’s the part that surprises people, and it’s the reason “WebSocket vs HTTP” is a slightly misleading framing: a WebSocket connection begins as an HTTP request. They’re not rival worlds. One grows out of the other.
When the browser wants a WebSocket, it doesn’t invent some exotic new protocol out of nowhere. It sends a perfectly ordinary HTTP GET request — but with a couple of special headers that say “hey, I’d like to upgrade this to a WebSocket.” The key ones are Upgrade: websocket, Connection: Upgrade, and a random Sec-WebSocket-Key. If the server speaks WebSocket and agrees, it replies with the wonderfully specific 101 Switching Protocols and a matching Sec-WebSocket-Accept header. From that instant, the same TCP connection stops behaving like HTTP and both sides start speaking the lightweight WebSocket framing format. The line is open.
Why start with HTTP at all? Because the early WebSocket experiments that tried custom raw-TCP handshakes died in the real world. Corporate firewalls block unknown protocols on weird ports, and proxies refuse to forward traffic they don’t recognize. By dressing the handshake up as a normal HTTP request, WebSockets ride through the same ports 80 and 443, the same proxies, load balancers, and CDNs as ordinary web traffic. It snuck in through the door the web already trusts.
So the mental model isn’t “HTTP or WebSocket.” It’s “HTTP, and then — if both sides opt in — the connection graduates into a WebSocket.”
When You Actually Need a WebSocket
Let me be the friend who talks you down off the ledge, because I needed one. You reach for a WebSocket when the product itself is genuinely live — when the core experience breaks if updates aren’t instant and two-way. The honest list is short:
- Chat and messaging. Two humans typing at each other. If a message takes ten seconds to appear, it’s not chat, it’s email. This is the textbook case.
- Live collaboration. Multiple cursors on the same doc, simultaneous edits, the Figma/Google-Docs feeling. Everyone’s changes have to land on everyone’s screen right now, both directions.
- Multiplayer and games. Anything where two clients are reacting to each other in fractions of a second.
- Live cursors and presence. The “3 people viewing this” dot, who’s-online indicators.
Notice the pattern: these all need low-latency, bidirectional flow where the client is also constantly sending, not just receiving. That’s the job a WebSocket is uniquely good at. If your feature genuinely looks like one of these, wire up the socket and don’t feel bad about it.
But here’s the trap I fell into — a lot of features that feel realtime aren’t, or don’t need a two-way pipe to fake it. A notification bell. A dashboard that updates “live.” A progress bar for a background job. A status page. Product-brain wants to build these the exciting way. Almost none of them need a persistent full-duplex connection. Which brings us to the cheaper middle ground everyone skips.
The Cheaper Middle: Polling and Server-Sent Events
Before you open a socket, ask: does the client actually need to send things continuously, or does it just need to hear about changes? Because if it’s only listening, you have two much cheaper options, and they run on plain old HTTP.
Option one: just poll. Have the browser ask “anything new?” every few seconds with a normal HTTP request. Yes, I spent a whole webhook post mocking myself for polling a payments API four hundred times — but that was server-to-server at scale, where events must never be missed. A browser fetching your notification count every 15 seconds is fine. It’s boring, stateless, works on every host on earth including a dumb static frontend, and costs you nothing to reason about. A tiny variant: poll a Postgres table for a “last updated” timestamp and only pull the full payload when it changed. Unglamorous. Ships today.
Option two: Server-Sent Events (SSE). The criminally underused one. SSE is a standard where the server holds an HTTP connection open and streams updates down to the browser as they happen — real push, no polling lag — but it’s one-way, server-to-client only, and plain HTTP. No upgrade handshake, no ws://, and it auto-reconnects if the connection drops. For a live dashboard, a notification feed, a “your export is ready” ping, an AI response streaming in token by token — SSE is often exactly right, and far simpler than a WebSocket because there’s no two-way protocol to manage.
The rule I use now, that would’ve saved me that weekend: if the client only needs to listen, use SSE or polling. Reach for a WebSocket only when the client also needs to constantly talk back. Live cursors need a socket because your cursor position is going up as fast as everyone else’s is coming down. A notification bell does not. My toy actually deserved its WebSocket — the mistake was never the tech, it was building the feature at all with zero users.
The Infra Tax: Persistent Connections Need an Always-On Box
Here’s the cost nobody mentions in the tutorials, and it’s the whole reason I say “not yet” to most people. An HTTP request is cheap because it’s ephemeral — it shows up, gets served, and vanishes, which is exactly why serverless is so magical for normal web apps. A function spins up, answers, and dies. Nothing to keep running, nothing to pay for while idle.
A WebSocket is the opposite. It stays open, sometimes for hours, and something server-side has to hold it the whole time. That means an always-on process — a real server that never sleeps. This is where serverless and realtime fundamentally clash, and it’s why Vercel has historically been the wrong place for it. Vercel’s serverless functions terminate the moment they return a response; there’s no long-lived process to keep a socket alive, so classic WebSocket servers (Socket.IO, ws, and friends) simply couldn’t run there. (Vercel added native WebSocket support in a public beta in June 2026, but read the fine print: connections are pinned to a single function instance, there’s no built-in cross-instance broadcast, no presence, and a duration cap measured in minutes. Real feature, but not “run your chat app here and forget it.” I’d still host the socket elsewhere.) It’s the same reason a long-running Python API doesn’t belong on Vercel — persistent processes and serverless are oil and water.
So you’ve got two sane paths, and they map cleanly onto how broke you are:
Path one — rent an always-on box. Put the socket server on a real, flat-fee instance. A $5 Railway box, a Fly.io machine, or a Koyeb instance will happily hold thousands of open connections for a predictable monthly price, no per-request roulette. This is the exact same “move the long-running thing off serverless onto a cheap box” pattern from my deployment tier list — the workload changed, the fix didn’t. If you want to own the stack and you’re comfortable running a process, this is the cheapest way to do realtime.
Path two — let someone else hold the connections. Managed realtime services exist because keeping millions of sockets open, scaling them, and handling reconnections is genuinely hard. Pusher has a free Sandbox tier (200 concurrent connections, 200K messages/day) and jumps to $49/month on Startup. Ably is consumption-based with a fairly generous free tier (around 6M messages/month). And if you’re already on Supabase for your database, its built-in Realtime feature pushes Postgres changes straight to the browser over a WebSocket with almost no server code from you — the closest thing to a free lunch here. Either way, you’re paying to make the persistent-connection problem someone else’s job.
The point stands: realtime is not free the way an HTTP endpoint basically is. It’s a standing cost — a box that’s always awake, or a service metering your connections. For a product with real users who need live collaboration, worth every rupee. For a solo founder with $0 MRR building a “live” dashboard nobody’s watching yet? That’s the $90/month tax I paid, dressed up in a new outfit.
The Bottom Line
HTTP and WebSocket aren’t enemies — one literally grows out of the other. HTTP is the ask-answer-hang-up workhorse that powers basically everything, stateless and cheap because each exchange stands alone and then it’s gone. A WebSocket is a line you leave open so both sides can talk instantly, forever, which is powerful and expensive to keep alive, and which starts its life as an HTTP request that gets upgraded mid-handshake.
The practical version, the one I’d tattoo on my past self: most SaaS features don’t need realtime yet. If the client only needs to hear about changes, poll a Postgres table or stream it with SSE — plain HTTP, ships today, runs anywhere. Reach for a WebSocket only when the product genuinely is live and two-way: chat, multiplayer, real collaboration. And when you do, remember it needs an always-on home — a cheap Railway or Fly box, or a managed service like Pusher, Ably, or Supabase Realtime — never a serverless function pretending it can hold the line.
I built live cursors for an audience of one. Don’t be like me. Build the boring HTTP version first, get actual users, and let them tell you the day the product needs to go live. That day is later than you think.
This is the Broken Engineer Guide — I over-engineer everything, open sockets nobody’s listening on, and hand you the scars so you can ship the boring thing instead. Now go poll a database like an adult.
