aio-libs/aiohttp · error · WSServerHandshakeError

Invalid challenge response

Error message

Invalid challenge response

What it means

Raised as `WSServerHandshakeError` (client.py:1119-1129) when the server's `Sec-WebSocket-Accept` header doesn't equal `base64(sha1(sec_key + GUID))`. The client generates a random `sec_key`, sends it in `Sec-WebSocket-Key`, and the server must echo back the SHA-1 of that key concatenated with the RFC 6455 GUID (`258EAFA5-E914-47DA-95CA-C5AB0DC85B11`). A mismatch means the server didn't follow the handshake proof — a sign of a broken/non-compliant server or an intermediary rewriting headers.

Source

Thrown at aiohttp/client.py:1123

                    message="Invalid upgrade header",
                    status=resp.status,
                    headers=resp.headers,
                )

            if not resp._upgraded:
                raise WSServerHandshakeError(
                    resp.request_info,
                    resp.history,
                    message="Invalid connection header",
                    status=resp.status,
                    headers=resp.headers,
                )

            # key calculation
            r_key = resp.headers.get(hdrs.SEC_WEBSOCKET_ACCEPT, "")
            match = base64.b64encode(hashlib.sha1(sec_key + WS_KEY).digest()).decode()
            if r_key != match:
                raise WSServerHandshakeError(
                    resp.request_info,
                    resp.history,
                    message="Invalid challenge response",
                    status=resp.status,
                    headers=resp.headers,
                )

            # websocket protocol
            protocol = None
            if protocols and hdrs.SEC_WEBSOCKET_PROTOCOL in resp.headers:
                resp_protocols = [
                    proto.strip()
                    for proto in resp.headers[hdrs.SEC_WEBSOCKET_PROTOCOL].split(",")
                ]

                for proto in resp_protocols:
                    if proto in protocols:
                        protocol = proto

View on GitHub (pinned to c0ef574e29)

Solutions

  1. Verify the server implements RFC 6455 §4.2.2 §5 (SHA-1 of `sec_key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"`, base64-encoded).
  2. Compare the client's sent `Sec-WebSocket-Key` (in `exc.request_info.headers`) against what the server received — intermediaries sometimes corrupt it.
  3. Use a known-compliant WS server (autobahn, nginx, a real framework) to isolate the fault.
  4. If testing, ensure your mock computes the Accept correctly rather than returning a placeholder.

Example fix

// before
# custom server echoes key verbatim in Sec-WebSocket-Accept
// after (server-side, Python)
import hashlib, base64
accept = base64.b64encode(
    hashlib.sha1(sec_key.encode() + b'258EAFA5-E914-47DA-95CA-C5AB0DC85B11').digest()
).decode()
Defensive patterns

Strategy: try-catch

Try / catch

from aiohttp import WSServerHandshakeError

try:
    ws = await session.ws_connect(url)
except WSServerHandshakeError as e:
    if 'challenge response' in (e.message or '').lower():
        log.error('Sec-WebSocket-Accept mismatch; server likely non-compliant')
        # verify server implements RFC 6455 SHA-1+GUID proof
    raise

Prevention

When it happens

Trigger: Server returns 101 with all the right headers but computes the Accept value incorrectly (wrong GUID, wrong hash, or echoes the Key verbatim). Some custom/broken WS servers or test mocks do this.

Common situations: Hand-rolled WS server with a buggy Accept computation; mock server that doesn't implement the handshake proof; intermediary that rewrites the Sec-WebSocket-Key and desyncs the proof; server echoing Key instead of hashing.

Related errors


AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04). Data as JSON: /data/errors/fce7217dcde46ca0.json. Report an issue: GitHub.