v10.3 Faster high-throughput capture and Windows compatibility fixes See what's new

How to Debug WebSocket Connections with HTTP Debugger

Inspect WebSocket frames over HTTP/1.1 and HTTP/2 — handshake, opcodes, masking, and close codes

  1. Handshake HTTP upgrade / CONNECT
  2. Open 101 / 200
  3. Frames text · binary · ping / pong
  4. Close code 1000–1015

WebSocket is a full-duplex, bidirectional protocol that keeps a persistent channel open over a single, long-lived TCP connection. A connection begins as an ordinary HTTP request and then upgrades in place to a persistent channel, after which either side can send a message at any time without waiting for the other.

Unlike a REST request/response pair — or the strictly typed remote procedure calls of gRPC — WebSocket is a raw channel that carries whatever framing your application puts on it, most often JSON, sometimes binary. Many UIs for AI agents use the WebSocket protocol for live status messages, tool-call events, and interrupts.

To debug a WebSocket connection you cannot just read a URL and a response body the way you do with REST. The useful signal is spread across two layers: the handshake that opens the channel — an HTTP/1.1 upgrade or an HTTP/2 extended CONNECT — and every frame that flows over it afterward, including data frames, keepalives, and a final close code. The sections below cover what a WebSocket connection actually looks like on the wire, the frame and close-code details that explain real failures, and how to inspect live WebSocket traffic without a proxy or browser extension.

What to inspect when you debug a WebSocket connection

A complete WebSocket debugging pass covers four layers, listed here in the order the connection builds them — though a close code, when you have one, is usually where you start:

  1. The handshake — either the HTTP/1.1 GET request with Upgrade: websocket and a 101 Switching Protocols response, or the HTTP/2 CONNECT request with :protocol set to websocket and a :status of 200. If this fails, no frames are ever exchanged.
  2. Data frames — the Text and Binary messages your application actually sends and receives, plus their direction and timing.
  3. Control framesPing/Pong keepalives and the Close frame, which explain idle drops and disconnects.
  4. The close code — the numeric reason the connection ended, which is often the single most useful clue.

How the WebSocket protocol works under the hood

Most connection bugs are easier to read once you know what the bytes mean. WebSocket is defined by RFC 6455, and compression by RFC 7692.

The HTTP/1.1 upgrade handshake

A WebSocket often begins as an ordinary HTTP/1.1 request. The client sends an Upgrade request with a random Sec-WebSocket-Key; the server confirms with 101 Switching Protocols and a matching Sec-WebSocket-Accept. The accept value is not a secret — it is the SHA-1 hash of the client key concatenated with the fixed GUID 258EAFA5-E914-47DA-95CA-C5AB0DC85B11, Base64-encoded. A mismatched or missing accept header means a proxy or server mangled the handshake.

GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Sec-WebSocket-Extensions: permessage-deflate

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

The same handshake also carries the negotiated subprotocol (Sec-WebSocket-Protocol) and extensions (Sec-WebSocket-Extensions). Reading these HTTP headers tells you whether compression was actually agreed, not just requested.

WebSocket over HTTP/2

WebSocket can also run over HTTP/2 using extended CONNECT. In that flow there is no 101 Switching Protocols response. The client opens an HTTP/2 stream with :method set to CONNECT and :protocol set to websocket; the server accepts it with :status: 200. After that, the HTTP/2 stream carries the same WebSocket frame format: text, binary, continuation, ping, pong, and close frames.

# HTTP/2 request headers
:method: CONNECT
:scheme: https
:authority: example.com
:path: /chat
:protocol: websocket

# HTTP/2 response headers
:status: 200

Frame anatomy and opcodes

After the WebSocket is established, data travels in frames rather than messages. Each frame begins with a FIN bit (is this the last fragment?), three reserved bits — RSV1 marks the first frame of a compressed message when permessage-deflate is used — a 4-bit opcode, a MASK bit, and a payload length encoded in 7, 7+16, or 7+64 bits. The opcode decides how to read the rest:

Opcode Type Purpose
0x0ContinuationNext fragment of a split message
0x1TextUTF-8 text payload
0x2BinaryRaw binary payload
0x8CloseClosing handshake, carries a close code
0x9PingKeepalive probe
0xAPongReply to a ping

Two details commonly cause confusion. First, every client-to-server frame is masked: the payload is XOR-ed with a 4-byte key, so a raw capture of an outgoing frame looks like noise until it is unmasked. Server-to-client frames are never masked. Second, a single logical message can be fragmented across one initial frame (FIN=0) and any number of Continuation frames, which is why a payload may appear to arrive in pieces.

Control frames and compression

Ping and Pong frames keep an otherwise idle connection alive; if a load balancer or proxy times out before the next ping, you get an abnormal close. A Close frame carries a 2-byte close code followed by an optional UTF-8 reason string. When permessage-deflate is negotiated, individual text and binary messages can be DEFLATE-compressed. The first frame of a compressed message sets the RSV1 bit; continuation frames inherit that compressed message state even though they do not set RSV1 themselves. The client_no_context_takeover and server_no_context_takeover parameters control whether the compression window resets between messages. A capture that does not inflate those messages shows compressed bytes instead of your JSON.

WebSocket close codes worth knowing

These are the close codes you will see most while debugging:

Code Name What it usually means
1000Normal ClosureConnection finished cleanly
1001Going AwayServer shutting down or client navigated away
1002Protocol ErrorA frame violated the protocol
1005No Status ReceivedClosed without a status code — set locally, never sent on the wire
1006Abnormal ClosureNo close frame received — network drop, crash, or proxy timeout
1009Message Too BigPayload exceeded the peer's limit
1011Internal ErrorUnhandled exception on the server
1015TLS Handshakewss:// TLS negotiation failed

Codes 1005, 1006, and 1015 are never sent on the wire — the browser or runtime sets them locally when no real close frame arrived. That is why a 1006 never has a reason string: the connection died before a Close frame could be exchanged. To find the cause, you have to look one layer down at the handshake, the last frames before the drop, and the keepalive timing — not at the close event itself.

How to debug WebSocket connections step by step

There are two ways to debug a WebSocket connection. If it already closed, you have a close code — read it first and work backward. When nothing has clearly failed, make a full pass through the steps below in order.

  1. Confirm the handshake. For HTTP/1.1, verify the request carries Upgrade: websocket and the response is 101 Switching Protocols with a valid Sec-WebSocket-Accept. For HTTP/2, verify :method: CONNECT, :protocol: websocket, and :status: 200. A redirect, forbidden response, or ordinary 200 OK HTTP/1.1 page means the WebSocket setup was blocked or rewritten upstream.
  2. Read the frames in order. Follow sent and received frames on a single timeline, checking opcode and direction so you can see which side stopped talking and when. Remember that client-to-server frames are masked, so a raw outgoing payload looks like noise until it is unmasked, and a single message can span an initial frame plus Continuation frames.
  3. Decode the payload. Inflate frames marked as compressed after permessage-deflate negotiation and pretty-print JSON so you compare application data, not compressed bytes. Confirm text frames are valid UTF-8.
  4. Check keepalive and close. Look at Ping/Pong spacing for idle drops, and read the Close code and reason to separate a clean shutdown from a network or server failure.

Ways to debug WebSocket connections

Each approach sees a different slice of the connection. Browser tooling is closest at hand but only for pages you load yourself; packet capture sees everything but at a low level; in-app logging shows decoded messages but only for code you control.

Approach What it shows Sees non-browser traffic? Decrypts wss://?
Browser DevTools (WS filter) Decoded, inflated frames for pages you load yourself, in a Messages view No — only the page's own connections, and not ones opened before DevTools attached Yes — the browser already holds the TLS keys
Packet capture (Wireshark) Raw WebSocket frames at the network layer, with manual reassembly Yes, but with heavy setup Only with a TLS key-log file from the client
Client-side / library logging Messages your own code sends and receives, already decoded Only apps you own and can instrument N/A — it reads payloads in-process, above TLS
System-wide capture (HTTP Debugger) Live frames from any process, decoded and inflated, beside the rest of the session Yes — any process on the machine Yes — via a local root certificate, no proxy

Browser DevTools is the fastest path for a page you control, and a packet capture is the most thorough at the network layer. The gap each one leaves is the WebSocket you cannot load in a tab or instrument yourself.

From the shell, the fastest first check is to open the handshake directly with a CLI client:

wscat -c wss://api.example.com/chat

If wscat (or websocat) completes the handshake and echoes messages but your app does not, the difference is on the client — headers, subprotocol, or auth. If the handshake fails here too, the problem is the server or a hop in between. A CLI client confirms the upgrade and prints decoded messages, but it does not show frame internals like opcodes, masking, or compression — for that you need a capture.

Debug WebSocket connections with HTTP Debugger

HTTP Debugger captures WebSocket traffic and keeps it visible in the same grid as the rest of the session. Each connection shows its method, URL, status, type, size, and timing, then opens into a dedicated Messages pane for per-frame inspection.

  1. Reproduce the WebSocket issue or workflow in the client application. HTTP Debugger shows the captured WebSocket setup request in the grid with its method, URL, status, type, size, timing, and session metadata. If the grid contains heavy mixed traffic, click on All Types in the toolbar and select WS to see only WebSocket rows.
    HTTP Debugger main grid with a WebSocket upgrade request selected, status 101, and a message-count badge HTTP Debugger type filter dropdown with DATA, GENERAL, and STREAMING groups, including a WS entry
  2. Click the message-count badge in the WebSocket row. HTTP Debugger opens the Messages pane on the right, with sent and received frames for the selected connection. You can filter the messages by type, direction, or payload text. Selecting a frame shows its details: the Info tab displays protocol and frame metadata, while Text, Raw, and Hex tabs show the payload in the available formats.
    WebSocket Messages pane Info tab showing direction, opcode, final flag, masking, compression, payload chunks, and wire size for a selected frame WebSocket Messages pane Text tab showing a decoded text frame payload formatted as JSON
  3. The Info tab shows metadata for the selected WebSocket frame. For WebSocket rows, HTTP Debugger groups these details as Frame plus Sent or Received fields, depending on the frame direction.

    Frame

    • Type and Opcode — the frame kind, such as Text, Binary, Close, Ping, Pong, or Continuation, plus the numeric opcode.
    • Offset — when timing is available, the frame's time offset within the captured session.
    • Final and Fragmented — whether this frame completes the message and whether the logical message spans continuation frames.
    • Close code and Close reason — for Close frames, the shutdown code and optional reason string that explain why the connection ended.

    Sent or Received

    • Direction and Bytes — whether the frame was sent by the client or received from the server, and the captured payload byte count shown for that frame.
    • Masked — whether the payload was masked on the wire. Client-to-server frames are masked; server-to-client frames are not.
    • Compressed, Payload decoded, and Compression — whether the frame is marked as compressed, whether the delivered payload is already decoded, and the compression mode when HTTP Debugger can determine it.
    • Decoded and Payload text — whether the selected payload can be shown as readable text, plus any text-decoding status such as invalid UTF-8 or a compressed payload that is not decoded.

The same connection's setup headers and frames sit next to the rest of your captured session, alongside the HTTP traffic DevTools cannot attach to, so one capture covers both.

Common WebSocket debugging scenarios

"The connection closes immediately with 1006"

A 1006 right after opening usually means the handshake never really succeeded or was torn down before any frame arrived. Confirm a 101 (or HTTP/2 200) actually came back; a redirect, a 200 OK HTML page, or a stripped Sec-WebSocket-Accept means a proxy or gateway intercepted the upgrade. For wss://, a failure during TLS surfaces locally as 1015.

"Messages stop arriving after a while"

Look at the Ping/Pong spacing leading up to the silence. When an idle connection sends no keepalives, a load balancer or reverse proxy will close it on its own timeout — often surfacing as 1001 or a bare 1006 with no reason. The gap between the last data frame and the drop tells you whether it was idle.

"The handshake returns 200 or a redirect instead of 101"

The request never reached the WebSocket endpoint as an upgrade. An authenticating proxy, a CDN, or a gateway handled it as plain HTTP and stripped the Upgrade and Connection headers, or routing sent it to a default handler. Compare the request headers that left the client with the response: if Upgrade: websocket went out but did not come back acknowledged, the hop in between is the suspect.

"The payload looks like binary garbage"

Either the message is genuinely binary (opcode 0x2), or permessage-deflate was negotiated and the frame is DEFLATE-compressed with RSV1 set on the first frame. Inflate it before judging the contents; only then can you tell corrupt data from compressed-but-valid JSON.

"It works locally but breaks behind a proxy"

Reverse proxies and load balancers are the most common cause of WebSocket failures that never reproduce on localhost. They may not forward the Upgrade header, may buffer frames instead of streaming them, or may apply an idle timeout shorter than your keepalive interval. Capture the connection through the proxy and compare its handshake headers and frame timing against a direct connection.

Limitations

System-wide capture shows exactly what crossed the wire, but it does not rewrite it. WebSocket traffic in HTTP Debugger is read-only: it decodes and inspects frames rather than injecting or modifying them, and the Auto-Reply and HTTP Modifier rules apply to regular HTTP/HTTPS traffic, not WebSocket frames. Frame decoding also has size and format limits, so very large or unusual payloads may show as raw bytes rather than fully decoded text. Capture is Windows-only and reads the connection at the protocol layer, not your application's message schema. For everything around the frames — headers, timing, sizes, and the raw HTTP protocol exchange — the HTTP analyzer and the system-wide HTTP sniffer cover the rest. The same workflow applies to the rest of the stack — see the guides on debugging the HTTP/2 traffic a connection can ride on, plus gRPC calls and SSE streams.

FAQ

How do I see the messages in a WebSocket connection?

Open a frame timeline for the connection — the Messages view in DevTools, or the Messages view in a system-wide capture tool — and read sent and received frames in order, with their opcode, direction, size, and decoded payload.

Can I debug secure wss:// WebSocket traffic?

Yes. After the TLS layer is decrypted — via the browser's TLS key log for packet captures, or a local root certificate in HTTP Debugger — wss:// frames are inspected exactly like plaintext ws:// frames.

Can I debug WebSockets outside the browser?

Browser DevTools only sees WebSockets from pages it is attached to. For desktop apps, background services, CLI tools, or emulators, use a system-wide capture tool such as HTTP Debugger Pro that intercepts traffic from any process on the machine.

Does WebSocket work over HTTP/2?

Yes, through extended CONNECT. Instead of an HTTP/1.1 101 Switching Protocols upgrade, the client opens an HTTP/2 stream with :method: CONNECT and :protocol: websocket, and the server accepts it with :status: 200. After that the stream carries the same WebSocket frame format — text, binary, continuation, ping, pong, and close.

What does WebSocket close code 1006 mean?

1006 is an abnormal closure: the connection dropped without a Close frame, so the browser or runtime set the code locally and there is no reason string. It usually points at a network drop, a crash, or a proxy or load-balancer idle timeout. To find the cause, look at the handshake, the last frames before the drop, and the Ping/Pong spacing rather than the close event itself.

HTTP Debugger Pro

Windows 11 / 10

A proxy-less HTTP/HTTPS sniffer for Windows that captures traffic system-wide from any process.

Download free 7-day trial

No registration required · Trusted since 2007