Back to API Design Best Practices
Q22Question 22 of 22

What is the WebSocket handshake

Key Concepts
  • The handshake starts as a standard HTTP GET with Upgrade: websocket and Connection: Upgrade headers.
  • The client sends a random Sec-WebSocket-Key; the server computes Sec-WebSocket-Accept by hashing it with a fixed GUID.
  • The server responds with 101 Switching Protocols; after this the connection speaks WebSocket frames, not HTTP.
  • Authentication happens during the handshake via cookies or Authorization headers, because there is no per-message HTTP auth after the upgrade.
  • WebSocket frames have minimal overhead (2-14 bytes); both sides send text or binary messages freely.
  • Load balancers must be configured to recognize the Upgrade header and keep the long-lived connection open.

Definition

The WebSocket handshake is an HTTP upgrade request answered with 101 Switching Protocols, after which the same connection carries free two-way WebSocket frames instead of HTTP.
  • Upgrade request – the client sends an HTTP GET with Upgrade: websocket, Connection: Upgrade, a random Sec-WebSocket-Key, and Sec-WebSocket-Version: 13.
  • 101 Switching Protocols – the server's acceptance response; it includes Sec-WebSocket-Accept computed 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 Authorization headers, 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.

TEXT
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: websocket and Connection: Upgrade signal the upgrade intent.
  • Sec-WebSocket-Key is a random base64 value the client generates.
  • Sec-WebSocket-Version: 13 states the protocol version.
  • Authorization header 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.

TEXT
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.

JAVA
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=="));
    }
}
TEXT
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.

DIAGRAM
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.

JAVA
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.

JAVA
// 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.

TEXT
// 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.

JAVA
// 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.

JAVA
// 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

DIAGRAM
  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.

---

Share
Use ← → arrow keys to navigate