aio-libs/aiohttp · error · HTTPBadRequest

Unsupported version

Error message

Unsupported version: {version}

What it means

_handshake() checks the Sec-WebSocket-Version header against the supported set {13, 8, 7} (RFC 6455 and its two drafts). Any other value (missing, empty, '12', etc.) returns HTTP 400. Version 13 is the standard (RFC 6455); 8 and 7 are legacy drafts kept for backward compatibility.

Solutions

  1. Ensure the client sends 'Sec-WebSocket-Version: 13' (all modern browsers and the aiohttp client do this by default).
  2. Upgrade client libraries to one supporting RFC 6455.
  3. If supporting legacy drafts is unnecessary, treat this 400 as expected behaviour for malformed clients.

Example fix

// before — hand-built request missing version
// headers: {Upgrade: websocket, Connection: Upgrade, ...}
// after
// headers: {Upgrade: websocket, Connection: Upgrade, 'Sec-WebSocket-Version': '13', 'Sec-WebSocket-Key': '<base64 16 bytes>'}
Defensive patterns

Strategy: validation

Validate before calling

SUPPORTED_WS_VERSIONS = {'13', '8', '7'}

def is_supported_ws_version(request) -> bool:
    return request.headers.get('Sec-WebSocket-Version', '') in SUPPORTED_WS_VERSIONS

if not is_supported_ws_version(request):
    return web.Response(status=400, text='unsupported WS version')

Type guard

def has_valid_ws_version(request) -> bool:
    return request.headers.get('Sec-WebSocket-Version', '') == '13'

Try / catch

ws = web.WebSocketResponse()
try:
    await ws.prepare(request)
except web.HTTPBadRequest as e:
    if 'Unsupported version' in (e.text or ''):
        log.info('client sent unsupported WS version, rejecting')
    return

Prevention

When it happens

Trigger: A client sends an outdated draft version (e.g. '6' or 'hixie-76'); the header is missing entirely (defaults to ''); a malformed/forged request with a non-numeric value; an extremely old browser library.

Common situations: Legacy client libraries from the draft era; misbehaving bots/scanners sending probe requests; intermediaries that strip the version header; tests with hand-built handshake requests that forget the version.

Related errors


AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11). Data as JSON: /api/errors/c0094f0d3d238708. Report an issue: GitHub.

Appendix: source

Thrown at aiohttp/web_ws.py:311

            ]

            for proto in req_protocols:
                if proto in self._protocols:
                    protocol = proto
                    break
            else:
                # No overlap found: Return no protocol as per spec
                ws_logger.warning(
                    "%s: Client protocols %r don’t overlap server-known ones %r",
                    request.remote,
                    req_protocols,
                    self._protocols,
                )

        # check supported version
        version = headers.get(hdrs.SEC_WEBSOCKET_VERSION, "")
        if version not in ("13", "8", "7"):
            raise HTTPBadRequest(text=f"Unsupported version: {version}")

        # check client handshake for validity
        key = headers.get(hdrs.SEC_WEBSOCKET_KEY)
        try:
            if not key or len(base64.b64decode(key)) != 16:
                raise HTTPBadRequest(text=f"Handshake error: {key!r}")
        except binascii.Error:
            raise HTTPBadRequest(text=f"Handshake error: {key!r}") from None

        accept_val = base64.b64encode(
            hashlib.sha1(key.encode() + WS_KEY).digest()
        ).decode()
        response_headers = CIMultiDict(
            {
                hdrs.UPGRADE: "websocket",
                hdrs.CONNECTION: "upgrade",
                hdrs.SEC_WEBSOCKET_ACCEPT: accept_val,
            }

View on GitHub (pinned to d041d4d0fd)