Definition
- Upgrade request – the client sends an HTTP GET with
Upgrade: websocket,Connection: Upgrade, a randomSec-WebSocket-Key, andSec-WebSocket-Version: 13. - 101 Switching Protocols – the server's acceptance response; it includes
Sec-WebSocket-Acceptcomputed from the client's key and a fixed magic GUID. - Protocol switch – after the 101, the same TCP connection stops speaking HTTP and carries lightweight WebSocket frames.
- Authentication – happens during the handshake via cookies or
Authorizationheaders, because once upgraded there is no per-message HTTP auth. - Frames – minimal overhead (2-14 bytes), text or binary, with ping/pong for keepalive and close for clean shutdown.
A trader at National Bank of India walks up to the market desk and, using the normal written request slip, asks "can we switch to an open intercom?" The desk checks the request, agrees, and stamps back "approved, intercom is now on." From that moment the written-note system is put away and both just talk over the intercom whenever they like. The handshake is the one polite written request to switch channels.
Understanding the Concepts
What does the client's upgrade request look like?
The handshake begins as a standard HTTP/1.1 GET request. This is why WebSocket works over ports 80 and 443 and passes through most firewalls.
GET /ws/trading HTTP/1.1
Host: api.bank.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Authorization: Bearer eyJhbGciOi...
Upgrade: websocketandConnection: Upgradesignal the upgrade intent.Sec-WebSocket-Keyis a random base64 value the client generates.Sec-WebSocket-Version: 13states the protocol version.Authorizationheader carries the auth token for the handshake.
What does the server's response look like?
If the server accepts, it responds with 101 Switching Protocols and a computed Sec-WebSocket-Accept.
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
The server computes Sec-WebSocket-Accept by concatenating the client's key with the fixed magic GUID 258EAFA5-E914-47DA-95CA-C5AB0DC85B11, taking the SHA-1 hash, and base64-encoding the result. The client verifies this value to confirm the server genuinely understood the WebSocket protocol.
package com.bank.websocket;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Base64;
public class WebSocketAccept {
private static final String MAGIC_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
public static String compute(String clientKey) throws Exception {
String concat = clientKey + MAGIC_GUID;
byte[] sha1 = MessageDigest.getInstance("SHA-1")
.digest(concat.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(sha1);
}
public static void main(String[] args) throws Exception {
System.out.println(compute("dGhlIHNhbXBsZSBub25jZQ=="));
}
}
s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
What happens after the 101?
The connection is no longer HTTP. The same TCP connection now carries WebSocket frames: a lightweight binary framing where each message has a small header indicating type (text, binary, ping, pong, close) and length. Both sides send frames at any time. There is no per-message HTTP header overhead.
WebSocket frame structure: ┌──────┬──────┬──────────────────────────────┐ │ FIN │ Type │ Payload (text or binary) │ │ 1bit │ 4bit │ 2-14 byte header + data │ └──────┴──────┴──────────────────────────────┘ Types: 0x1 = text, 0x2 = binary, 0x9 = ping, 0xA = pong, 0x8 = close
Why does authentication happen during the handshake?
Once upgraded, you cannot re-run HTTP auth per message. So the client sends a cookie, an Authorization header, or a token query parameter during the handshake, and the server validates it before returning 101. An invalid token gets a normal HTTP error response and no upgrade.
package com.bank.websocket;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
import java.util.Map;
public class AuthHandshakeInterceptor implements HandshakeInterceptor {
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler handler, Map<String, Object> attributes) {
String token = extractToken(request);
boolean valid = tokenService.isValid(token);
if (!valid) {
response.setStatusCode(HttpStatus.UNAUTHORIZED);
} else {
attributes.put("traderId", tokenService.getSubject(token));
}
return valid; // false → no upgrade, connection stays HTTP with 401
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler handler, Exception exception) { }
}
What about origin checking?
Origin checking during the handshake defends against cross-site WebSocket hijacking. The server verifies the Origin header matches allowed domains.
// In the handshake interceptor:
String origin = request.getHeaders().getOrigin();
if (!allowedOrigins.contains(origin)) {
response.setStatusCode(HttpStatus.FORBIDDEN);
return false; // reject hijack attempt
}
What about load balancers and proxies?
Load balancers must be configured to recognize the Upgrade header and keep the connection open. Without this, the upgrade fails or the connection is cut after a short timeout.
// Nginx configuration for WebSocket proxying:
location /ws/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s; # keep alive for 1 hour
}
How does keepalive work?
Ping and pong control frames detect dead connections. The server sends a ping; the client must respond with a pong. If no pong arrives within the timeout, the connection is considered dead.
// Spring WebSocket: configure ping interval
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(tradingHandler, "/ws/trading")
.addInterceptors(authInterceptor)
.setAllowedOrigins("https://app.bank.com");
}
// Application-level heartbeat:
@Scheduled(fixedRate = 30000)
public void heartbeat() {
sessions.forEach(session -> {
try { session.sendMessage(new PingMessage()); }
catch (IOException e) { sessions.remove(session); }
});
}
What are common mistakes?
The most common mistake is not authenticating during the handshake, leaving the WebSocket open to anyone. The second mistake is not configuring the load balancer for WebSocket, causing connections to drop. The third is not implementing keepalive, so dead connections consume resources.
// Wrong – no auth check, anyone can connect:
public boolean beforeHandshake(...) { return true; } // open door!
// Correct – validate token before upgrade:
public boolean beforeHandshake(...) {
return tokenService.isValid(extractToken(request));
}
What should a senior engineer know?
The handshake is the security checkpoint. After the 101, the connection is a long-lived session. The server must track each connection for its entire lifetime, which is a different resource model from stateless HTTP. Over TLS (wss://), the handshake and all frames are encrypted. A senior engineer also knows that WebSocket connections cannot be load-balanced per-request like HTTP; they are sticky to one server instance, so the architecture must account for connection distribution and graceful shutdown.
Visual Overview
WebSocket Handshake Flow:
Client Server
│ │
│ GET /ws/trading HTTP/1.1 │
│ Upgrade: websocket │
│ Sec-WebSocket-Key: abc123 │
│ Authorization: Bearer token │
│ ─────────────────────────────→ │
│ │ Validate token
│ │ Compute Accept = SHA1(key + GUID)
│ │
│ HTTP/1.1 101 Switching │
│ Sec-WebSocket-Accept: xyz789 │
│ ←───────────────────────────── │
│ │
│ === Protocol switched === │
│ │
│ ←──── WebSocket frames ────→ │ (both directions, freely)
│ text, binary, ping, pong │
│ │
Key Takeaway
The WebSocket handshake is a one-time negotiation that upgrades an ordinary HTTP connection into a persistent full-duplex WebSocket connection. The client sends a normal HTTP GET carrying Upgrade: websocket, a random Sec-WebSocket-Key, and the protocol version. Because it starts as HTTP on standard web ports, it passes through most firewalls. The server responds with 101 Switching Protocols and a computed Sec-WebSocket-Accept that the client verifies.
After the 101, the connection is no longer HTTP. The same TCP connection carries lightweight WebSocket frames with minimal overhead, and both sides send text or binary messages at any time. The crucial point is that authentication happens during the handshake, because once upgraded there is no per-message HTTP auth. The server validates the token before returning 101 and stores the identity on the session for the connection's lifetime.
At National Bank of India, the live trading terminal relies on this. The handshake interceptor validates the trader's token before the upgrade, stashes the identity on the session, and checks the origin to defend against cross-site hijacking. The load balancer is configured to recognize the Upgrade header and keep the long-lived connection open. Ping-pong keepalive detects dead connections.
The three phases are: the client's HTTP upgrade request, the server's 101 response (the moment to authenticate), and the open full-duplex phase where messages flow freely with keepalive. Understanding that the handshake is HTTP that becomes WebSocket on acceptance is what lets you place security at the handshake and plan the long-lived connection correctly.
Remember one sentence: the WebSocket handshake is the last HTTP exchange, and everything that matters for security must happen before the 101.
---