Learning objectives
After reading this article you will be able to:
- Tell who starts the communication and which way the data flows in each option
- Choose between WebSocket, SSE and a webhook for your use case
- See a minimal Node.js example of all three techniques
A normal HTTP request is a question with an answer: the client asks, the server replies and the conversation ends. When you need something to arrive without asking every few seconds (a chat message, an approved payment, a price that changes), three options show up: WebSocket, Server-Sent Events (SSE) and webhooks.
They look alike in the result, but they solve different problems. The question that sorts it all out is: who talks to whom, and in which direction?
What is each one?#
WebSocket is a two-way channel over a single TCP connection. It starts as an HTTP request with Upgrade, the server answers 101 Switching Protocols, and from then on client and server send messages whenever they want. It uses the ws:// and wss:// schemes (the latter with TLS). It is defined in RFC 6455. More detail in What is a WebSocket.
SSE is an HTTP response that never ends. The browser opens the connection with EventSource, the server replies with Content-Type: text/event-stream and keeps writing text events. Data only travels from the server to the client.
A webhook is not a protocol, it is a pattern: an external service (Stripe or GitHub, for example) sends an HTTP POST to a URL of yours when an event happens. It is server-to-server communication, with no browser involved.
What are the differences?#
| WebSocket | SSE | Webhook | |
|---|---|---|---|
| Direction | Both ways | Server to client | External service to your server |
| Who starts it | The client opens the channel | The client opens the connection | The service calls your URL |
| Connection | Persistent | Persistent | One request per event |
| Format | Text or binary | UTF-8 text | Usually JSON |
| Reconnection | You write it | Automatic in the browser | Depends on the sending service |
| Typical client | Browser, app, another server | Browser | Another server |
Two practical details about SSE: over HTTP/1.1 the browser allows only 6 open connections per domain (per browser, not per tab), while over HTTP/2 the default negotiated limit is 100 streams. And since EventSource reconnects on its own, the server can tune the wait with the retry field.
When should you choose WebSocket?#
When the client also sends data often and you need low latency in both directions: chats, multiplayer games, collaborative editing, shared cursors. If only the server talks, WebSocket is more than you need.
In exchange, you lose part of what HTTP handles for you: you take care of reconnection, heartbeats (ping/pong to detect dead connections) and message authentication yourself.
// npm install ws
import { WebSocketServer } from "ws";
const wss = new WebSocketServer({ port: Number(process.env.PORT) || 8080 });
wss.on("connection", (socket) => {
socket.on("message", (data) => {
// forward to every connected client
for (const client of wss.clients) {
if (client.readyState === client.OPEN) client.send(data.toString());
}
});
});From the browser: const ws = new WebSocket("wss://your-domain.com"); ws.onmessage = (e) => console.log(e.data);. To put this server online, see how to host a WebSocket server.
When should you choose SSE?#
When you only need to push data to the browser: notifications, task progress, a live scoreboard, the reply of an AI model arriving in chunks. It is plain HTTP, needs no client library and the browser reconnects by itself.
// npm install express@5
import express from "express";
const app = express();
app.get("/events", (req, res) => {
res.set({
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
});
res.flushHeaders();
const timer = setInterval(() => {
res.write(`event: tick\ndata: ${JSON.stringify({ time: new Date() })}\n\n`);
}, 1000);
req.on("close", () => clearInterval(timer));
});
app.listen(Number(process.env.PORT) || 3000);const source = new EventSource("/events");
source.addEventListener("tick", (e) => console.log(JSON.parse(e.data)));Each message ends with a blank line. The recognized fields are event, data, id and retry. If the client needs to send something, use a separate fetch request: SSE plus REST covers many apps that seem to call for WebSocket.
When should you choose a webhook?#
When another service has to notify your backend: a confirmed payment, a push to a repository, an incoming message. You avoid polling its API every minute and get the event right away. For the Discord case, see Discord webhook vs bot.
Your endpoint has to be public, answer quickly with a 2xx and verify the signature, because anyone can send a POST to your URL. GitHub, for example, signs the body with HMAC-SHA256 in the X-Hub-Signature-256 header and recommends a constant-time comparison.
import crypto from "node:crypto";
import express from "express";
const app = express();
// the HMAC is computed over the raw body, not over the parsed JSON
app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
if (!Buffer.isBuffer(req.body)) return res.sendStatus(415); // no JSON body
const expected =
"sha256=" +
crypto.createHmac("sha256", process.env.WEBHOOK_SECRET).update(req.body).digest("hex");
const received = req.get("X-Hub-Signature-256") ?? "";
const a = Buffer.from(expected);
const b = Buffer.from(received);
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
return res.sendStatus(401);
}
const event = JSON.parse(req.body.toString());
console.debug("event received", event.action);
res.sendStatus(204);
});
app.listen(Number(process.env.PORT) || 3000);The header name and the algorithm vary by service: check its documentation.
Can you combine them?#
Yes, and that is the usual setup. A typical case: a payment provider notifies your server with a "payment completed" webhook, and your server forwards it to the user's browser with SSE or WebSocket so the screen changes without a reload. The webhook brings the data to your backend; SSE or WebSocket carries it to the client.
Short rule: if the target is a browser that only listens, SSE. If the browser also talks a lot, WebSocket. If the target is your server and the sender is an external service, webhook. There is no universal winner. If what you want is to read or change data, a REST API is still the foundation.
How to do this on Square Cloud#
All three patterns need an always-on process with a public URL, and that is what a web application on Square Cloud gives you: containers with no sleep mode and a *.squareweb.app subdomain with HTTPS on every plan. The network supports WebSockets, so the wss:// from the example works on that subdomain, and the proxy lets WebSocket and SSE through without buffering and without cutting long connections.
For a webhook, your application's URL is the one you register with the sending service. Store the signing secret as an environment variable in the dashboard, not in the code. You can use Node.js, Python, Go and others from the available runtimes.
The Hobby plan starts at R$12.49 a month with 1 GB of RAM, and the RAM is shared across all your applications. To publish a full API, start with How to host an API and go through the production checklist. The API hosting page sums up what you need.
API hosting
APIs in always-on containers, with no cold starts
FAQs
Is SSE faster than WebSocket?
There is no speed difference that should decide it. Once open, both deliver events instantly. The real difference is direction: SSE only goes from server to client, WebSocket goes both ways.
Can I use a webhook to update a page in real time?
Not directly. A webhook reaches your server, not the browser. Your server has to forward the data to the browser with SSE or WebSocket.
Does WebSocket replace normal HTTP requests?
No. To read or change resources, a REST API is still the simplest choice. WebSocket is for when you need frequent messages in both directions.
Does SSE reconnect by itself if the connection drops?
Yes. EventSource retries automatically, and the server can set the wait time with the retry field. With WebSocket you have to write the reconnection logic yourself.
How do I know a webhook really comes from the service?
Verify the signature. Many services send an HMAC of the body in a header, such as X-Hub-Signature-256 on GitHub, and you compare it with your secret using a constant-time comparison.
More in APIs
See all 15 articles- How to host an APIHosting an API means running its HTTP server around the clock at a public address with HTTPS. Step-by-step with Node.js and Python, testing and common errors.
- How to host a Node.js appTo host a Node.js app, prepare package.json, read the port and secrets from the environment, pick an LTS version and run it with node. Step-by-step guide.
- How to host a FastAPI, Flask or Django appTo host a FastAPI, Flask or Django app, run it with a production server (Uvicorn or Gunicorn) on 0.0.0.0. Commands, Django settings and common errors included.
- How to host a Python app or scriptTo host a Python app or script, pin the version, isolate dependencies in a venv, read secrets from the environment and handle SIGTERM. Step-by-step guide.