# Sec-WebSocket-Key

> Learn how the Sec-WebSocket-Key header provides a random key for WebSocket handshake validation. Understand the upgrade process and security implications.

Source: https://howhttpworks.com/headers/sec-websocket-key
Last reviewed: 2026-10-05

> **TL;DR:** `Sec-WebSocket-Key` is a random 16-byte value, base64-encoded, that the client sends when opening a WebSocket. The server hashes it with a fixed GUID and sends the result back in `Sec-WebSocket-Accept`, proving it understood the upgrade. It's not a password or an encryption key, and browsers set it for you.

## What The Header Is Doing

This example HTTP/1.1 request uses the sample key from RFC 6455:

```http
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
```

Real clients pick a new random value for every connection. Decoded from base64, it has to be exactly 16 bytes, as required by [RFC 6455 §§4.1–4.2.1](https://www.rfc-editor.org/rfc/rfc6455#section-4.1).

Because the accept value depends on this exact key, an unrelated HTTP response can't satisfy the handshake by accident. That's all it does. It doesn't authenticate anyone or tell you the peer is trustworthy; TLS and your application's own authentication handle those jobs.

## The Important Practical Consequence

In the browser, you open a socket with `new WebSocket(url)`; you can't build the upgrade yourself with `fetch()`. The browser generates the key. The [Fetch Standard](https://fetch.spec.whatwg.org/#forbidden-request-header) reserves headers beginning with `Sec-`, and the [WebSockets Standard](https://websockets.spec.whatwg.org/#opening-handshake) defines handshake generation.

## What Server Code Has To Do

Validate the whole opening request before accepting it. To compute the accept value, append the public GUID `258EAFA5-E914-47DA-95CA-C5AB0DC85B11` to the **base64 text**, not to the decoded 16 bytes, then SHA-1 hash and base64-encode the result.

Generate a diagnostic key in Node:

```bash
node --input-type=module -e 'import { randomBytes } from "node:crypto"; console.log(randomBytes(16).toString("base64"))'
```

That only prints a value; no request goes anywhere. A real client also has to validate the upgrade response and handle framing, masking, and closing the connection.

## What To Check During Debugging

Look at the request and response side by side. Check that the key decodes to 16 bytes and that the response has status `101`, `Upgrade: websocket`, `Connection: Upgrade`, and the matching accept value. If the accept value is missing or wrong, the client has to fail the handshake under [RFC 6455 §4.1](https://www.rfc-editor.org/rfc/rfc6455#section-4.1).

Generate a fresh key per connection; the RFC sample is for testing only. And if a proxy rewrites the key, the server's answer won't match what the client expects, so the handshake fails.

For a captured key, check the decoded length in Node:

```bash
node -e 'console.log(Buffer.from("dGhlIHNhbXBsZSBub25jZQ==", "base64").length)'
```

The RFC sample prints `16`. That's a sanity check rather than full validation: [Node's base64 decoder](https://nodejs.org/api/buffer.html#buffers-and-character-encodings) accepts some inputs the WebSocket grammar doesn't allow. Leave real handshake validation to your WebSocket library.

## Related Headers

- [Sec-WebSocket-Accept](https://howhttpworks.com/headers/sec-websocket-accept)
- [Upgrade](https://howhttpworks.com/headers/upgrade)
- [101 Switching Protocols](https://howhttpworks.com/status-codes/101)
