What a WebSocket is
A connection that stays open, and either side can talk first.
HTTP works in turns. The client asks, the server answers, and that exchange is over. It suits most of what an API does: fetch an order, save a form, delete a file. It suits live data badly. A chat app needs to show a message the moment it's sent. A trading screen needs every price change. Over plain HTTP the only option is to keep asking: is there anything new? How about now?
That's called polling, and it's wasteful. Ask once a second and you send 3,600 requests an hour, most of them answered with "nothing new", and a message can still sit on the server for up to a second before anyone sees it. Ask less often and the delay grows.
A WebSocket fixes this by keeping one connection open. Once it's open, either side can send a message at any time, without being asked, until one of them closes it. The protocol was standardised in 2011 as RFC 6455 and every browser supports it. Chat, multiplayer games, shared document editors, live dashboards and notification feeds are the usual places you'll meet it.
How it compares
WebSockets aren't the only way to get live data, and you'll meet the others too:
| Technique | Who can send | What it is |
|---|---|---|
| Polling | Client asks, server answers | Ordinary requests on a timer. Simple, slow to notice changes, heavy on requests. |
| Long polling | Client asks, server answers late | The server holds each request open until it has something to say, then the client asks again. |
| Server-Sent Events | Server only | One long HTTP response that the server keeps adding events to. The browser's EventSource reconnects on its own. |
| WebSocket | Both, at any time | A separate protocol that starts as an HTTP request and then leaves HTTP behind. |
If data only flows from server to client, Server-Sent Events are often the simpler choice, and the REST tutorial's lesson on async jobs, webhooks and streams covers them. WebSockets earn their extra complexity when the client also sends a steady stream of messages, as in chat or games.
WebSocket addresses
WebSocket URLs use their own schemes: ws:// for an unencrypted connection and wss:// for one protected by TLS, the same encryption as https://. Use wss:// everywhere except your own machine. Unencrypted sockets can be read and changed by anything between client and server, and many corporate proxies break them outright.
This tutorial uses two sockets. /ws/echosign in to see sends back every message it receives. /ws/tickersign in to see ignores what you send and pushes a stock price once a second. Their reference pages are echo and ticker.
Your first connection
Press Connect below. The echo socket greets you with a JSON message as soon as the connection opens. Then type something and press Send. It comes straight back.
Messages appear here.
Notice the order of events. Nothing came back until you sent something, except the welcome, which arrived without being asked for. That's the difference from HTTP in one line: the server spoke first.
From code
Browsers have a WebSocket object, and Node.js 22 and later have the same one built in, so this script runs in a browser console or as a file with node:
const ws = new WebSocket("wss://api.testingapis.com/ws/echo");
ws.addEventListener("open", () => {
console.log("open");
ws.send("hello");
});
ws.addEventListener("message", (event) => {
console.log("received:", event.data);
if (event.data === "hello") ws.send("close");
});
ws.addEventListener("close", (event) => {
console.log("closed:", event.code, event.reason);
});open
received: {"type":"welcome","message":"Connected to TestingAPIs echo. Every text/binary message is echoed back. Send \u0022close\u0022 to disconnect."}
received: hello
closed: 1000 Bye!The script never waits for a reply the way fetch does. It registers functions for three events (open, message, close) and reacts when they happen. Sending the text close asks this particular server to hang up, and it does so with code 1000, which means a normal close, and the reason Bye!. Close codes get a lesson of their own.
A WebSocket endpoint can't be used like an ordinary URL. Send it a plain HTTP request and this API tells you so:
$ curl -i https://api.testingapis.com/ws/echo
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
{"status":400,"error":"Bad Request","message":"This endpoint only accepts WebSocket connections. Connect with a WebSocket client to ws://api.testingapis.com/ws/echo"}What changes for a tester
Several habits from HTTP testing don't carry over:
- There's no status code per message. You get one status when the connection opens and one close code when it ends. Everything in between is just messages, and their meaning depends entirely on the application.
- Replies aren't paired with requests. If you send two messages and get two back, nothing in the protocol says which answer belongs to which question. Applications that need that put an ID in each message.
- Time matters. A test has to wait for messages, and decide how long is too long. Waiting with a fixed sleep is the most common cause of flaky WebSocket tests.
- The connection has a life. Bugs cluster at its edges: opening, closing, dropping and reconnecting. A socket that works perfectly for ten seconds can still be broken.
For poking at a socket by hand, the panel above works, and so do the command-line tools wscat (from npm) and websocat. Postman can open WebSocket connections too. In a browser, open the developer tools, go to the Network tab, filter by WS, click the connection and open Messages to see every message in both directions with timestamps.
Exercise
Connect to /ws/echosign in to see and send three messages quickly, one after another. Do they come back in the order you sent them? Then send close and note how long it takes for the panel to report the connection closed. Keep the number. It comes up again in the lesson on closing.