DEV Community

Cover image for How to Test WebSocket Connections With curl and Other Tools
Hassann
Hassann

Posted on • Originally published at apidog.com

How to Test WebSocket Connections With curl and Other Tools

WebSocket gives you a persistent, two-way channel between client and server over a single TCP connection. Once the connection is open, either side can send a message at any time. That is why WebSocket is common in live chat, trading feeds, multiplayer games, and dashboards. Testing it is different from testing a request-response API because you are not inspecting one response. You are observing a stream.

Try Apidog today

This guide shows how to test WebSocket endpoints from the command line and with a GUI client. You will use curl for handshake checks, websocat for interactive and scriptable message testing, and Apidog when you need a visual timeline.

Why WebSocket testing is not like REST testing

A REST test is usually a single transaction:

  1. Send one request.
  2. Receive one response.
  3. Assert the response.
  4. Finish the test.

A WebSocket test is a conversation:

  1. Open a connection.
  2. Keep it alive.
  3. Send one or more messages.
  4. Receive replies and server-pushed messages.
  5. Validate close behavior.

That changes what you need to verify:

  • The HTTP connection upgrades successfully.
  • The server accepts your initial message.
  • Expected replies arrive with the right payload shape.
  • Server-pushed messages arrive without another request.
  • The connection closes with the expected close code.

A tool built for one-shot HTTP requests can only cover part of this workflow. That is why curl is useful for quick checks, but not ideal for full WebSocket testing. For broader test planning, the distinction between a test scenario and a test case maps well to WebSocket work: the whole conversation is the scenario, while each message check is a test case.

The WebSocket handshake

Every WebSocket connection starts as an HTTP request that asks the server to upgrade the protocol.

The client sends headers like:

Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: <base64-value>
Enter fullscreen mode Exit fullscreen mode

If the server accepts, it returns:

HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: websocket
Enter fullscreen mode Exit fullscreen mode

After that, the connection is no longer normal HTTP. It uses the WebSocket frame protocol defined in RFC 6455.

This is the core limitation of classic curl. It can send HTTP headers, but WebSocket messages after the handshake must be framed and unframed correctly. To test beyond the upgrade, you need a tool that understands WebSocket frames.

Testing WebSocket with curl

curl 7.86 and later includes experimental native WebSocket support. It is useful for basic reachability and handshake checks.

First, confirm your curl version:

curl --version
Enter fullscreen mode Exit fullscreen mode

If you are on 7.86 or newer, you can try connecting to a WebSocket endpoint.

Example handshake check against a public echo server:

curl --include --no-buffer \
  --header "Connection: Upgrade" \
  --header "Upgrade: websocket" \
  --header "Sec-WebSocket-Version: 13" \
  --header "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
  https://echo.websocket.org
Enter fullscreen mode Exit fullscreen mode

Use:

  • --include to print response headers.
  • --no-buffer to stream output immediately instead of buffering it.
  • wss:// for secure WebSocket endpoints, similar to how you use https://.

What you want to see is an HTTP 101 Switching Protocols response.

curl is best for quick checks like:

# Is the endpoint reachable?
# Does the server accept the upgrade?
# Are the required headers accepted?
Enter fullscreen mode Exit fullscreen mode

It is not ideal for long interactive sessions where you need to send multiple messages, receive pushed events, and inspect a timeline. For CI/CD usage, you can still wrap simple command-line checks into a pipeline. See this guide on automating API tests in CI/CD.

Testing WebSocket with websocat

For most command-line WebSocket testing, use websocat.

It is purpose-built for WebSocket, understands frames, supports interactive sessions, and behaves like netcat for WebSocket connections.

Install it with your package manager:

brew install websocat
Enter fullscreen mode Exit fullscreen mode

Or with Cargo:

cargo install websocat
Enter fullscreen mode Exit fullscreen mode

Connect to a WebSocket endpoint:

websocat wss://echo.websocket.org
Enter fullscreen mode Exit fullscreen mode

This opens an interactive session. Type a line, press Enter, and websocat sends it as a WebSocket message. Replies are printed as they arrive.

Send one message and exit

For a one-off test, pipe a message into websocat:

echo '{"action":"subscribe","channel":"prices"}' | websocat wss://stream.example.com/feed
Enter fullscreen mode Exit fullscreen mode

This is useful for scripts where you want to send a known payload and inspect the reply.

Add authentication headers

Many WebSocket APIs require authentication during the handshake.

Pass headers like this:

websocat \
  --header "Authorization: Bearer your-token-here" \
  wss://api.example.com/socket
Enter fullscreen mode Exit fullscreen mode

You can also use query parameters if your API expects tokens in the URL:

websocat "wss://api.example.com/socket?token=your-token-here"
Enter fullscreen mode Exit fullscreen mode

Capture a response in a script

A minimal shell check might look like this:

#!/usr/bin/env bash

set -euo pipefail

response=$(
  echo '{"type":"ping"}' | websocat -1 wss://api.example.com/socket
)

echo "$response"

if echo "$response" | grep -q '"type":"pong"'; then
  echo "WebSocket check passed"
else
  echo "WebSocket check failed"
  exit 1
fi
Enter fullscreen mode Exit fullscreen mode

Use this pattern when you need a lightweight CI check:

  1. Connect.
  2. Send a known message.
  3. Capture the reply.
  4. Assert on expected content.
  5. Exit non-zero on failure.

For payload validation, the same ideas from writing useful API assertions apply to WebSocket message bodies.

Testing WebSocket with a GUI tool

Command-line tools are good for scripts and quick checks. A GUI is better when you need to explore, debug, and share a WebSocket flow.

Apidog includes a dedicated WebSocket client. You can:

  • Enter a ws:// or wss:// URL.
  • Connect and keep the session open.
  • View sent and received messages in a timeline.
  • Send structured JSON messages.
  • Set headers and query parameters for authentication.
  • Save connections for reuse.
  • Test WebSocket alongside REST, GraphQL, and SOAP APIs.

Use a GUI client when you are:

  • Exploring an unfamiliar WebSocket API.
  • Debugging why a message is not arriving.
  • Checking server-pushed events.
  • Sharing a reproducible test with a teammate.
  • Comparing multiple messages in a single timeline.

Download Apidog to test WebSocket endpoints with a visual timeline.

Use the command line when the check needs to run unattended. Most teams use both: GUI clients for exploration, command-line tools for automation. For more GUI options, see this roundup of free online API testing tools.

A simple WebSocket test checklist

Use this checklist when validating a WebSocket endpoint.

1. Confirm the upgrade

The server should return HTTP 101 Switching Protocols.

If it does not:

  • Check the URL path.
  • Check the scheme: ws:// or wss://.
  • Check required headers.
  • Check authentication.

2. Check authentication

Many WebSocket servers expect a token in a header:

websocat \
  --header "Authorization: Bearer your-token-here" \
  wss://api.example.com/socket
Enter fullscreen mode Exit fullscreen mode

Or in a query parameter:

websocat "wss://api.example.com/socket?token=your-token-here"
Enter fullscreen mode Exit fullscreen mode

If the connection opens and immediately closes, authentication is often the cause.

3. Send a known valid message

Use a real payload your API understands:

{
  "action": "subscribe",
  "channel": "prices"
}
Enter fullscreen mode Exit fullscreen mode

Then verify that the server returns the expected response shape.

4. Verify server-pushed messages

After subscribing, wait for messages without sending another request.

For example:

echo '{"action":"subscribe","channel":"prices"}' | websocat wss://stream.example.com/feed
Enter fullscreen mode Exit fullscreen mode

The key behavior to test is that messages arrive from the server without further client input.

5. Test close behavior

Close the connection and check the close code.

Common codes include:

  • 1000: Normal closure.
  • 1006: Abnormal closure.
  • 1011: Server error.

A clean close should usually return 1000.

6. Test failure paths

Send malformed or invalid payloads and confirm the server responds predictably.

Example invalid payload:

{
  "action": "subscribe"
}
Enter fullscreen mode Exit fullscreen mode

Expected behavior might be an error message, not a silent disconnect.

For organizing these checks into repeatable groups, see this guide on building API test suites.

Debugging a WebSocket connection that will not work

When a WebSocket connection fails, debug it in this order.

1. Check the URL scheme

Use:

  • ws:// for unencrypted WebSocket.
  • wss:// for encrypted WebSocket over TLS.

Browsers block ws:// connections from HTTPS pages because that mixes secure and insecure content. In production, prefer wss://.

2. Check the handshake response

If you do not see HTTP 101, the server did not upgrade the connection.

Common responses:

  • 400: Missing or malformed upgrade headers.
  • 401: Authentication missing or invalid.
  • 403: Authenticated but not allowed.
  • 404: Wrong endpoint path.
  • 500: Server-side failure.

With curl, use:

curl --include --no-buffer \
  --header "Connection: Upgrade" \
  --header "Upgrade: websocket" \
  --header "Sec-WebSocket-Version: 13" \
  --header "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
  https://example.com/socket
Enter fullscreen mode Exit fullscreen mode

With websocat, use verbose output:

websocat -v wss://example.com/socket
Enter fullscreen mode Exit fullscreen mode

3. Check idle timeouts

If the handshake succeeds but the connection drops later, check idle timeout behavior.

Possible causes:

  • The server expects ping/pong frames.
  • A proxy or load balancer closes idle connections.
  • The client stops reading from the socket.
  • The server closes unauthenticated or unsubscribed sessions.

4. Read the close code

Close codes are defined in RFC 6455.

Useful examples:

  • 1000: Normal closure.
  • 1001: Endpoint is going away.
  • 1002: Protocol error.
  • 1003: Unsupported data.
  • 1006: Abnormal closure with no clean close handshake.
  • 1008: Policy violation.
  • 1011: Internal server error.

The close code usually tells you which side ended the connection and why.

Automating WebSocket checks

Manual testing confirms the endpoint works right now. Automation helps catch regressions later.

A useful automated WebSocket test should stay small and deterministic. Avoid trying to validate every message from a long-lived stream.

A practical automated check should assert:

  1. The connection upgrades successfully.
  2. A known request receives the expected response.
  3. A subscribed channel receives at least one pushed message within a timeout.

Example script structure:

#!/usr/bin/env bash

set -euo pipefail

endpoint="wss://api.example.com/socket"
payload='{"type":"ping"}'

response=$(echo "$payload" | websocat -1 "$endpoint")

if echo "$response" | grep -q '"type":"pong"'; then
  echo "Pass"
else
  echo "Fail: unexpected response"
  echo "$response"
  exit 1
fi
Enter fullscreen mode Exit fullscreen mode

Add it to CI like any other test command:

steps:
  - name: Test WebSocket endpoint
    run: ./scripts/test-websocket.sh
Enter fullscreen mode Exit fullscreen mode

A GUI tool with a scenario runner, such as Apidog, can also save a WebSocket flow with sent messages and assertions, then replay it from a schedule or pipeline trigger.

Keep each WebSocket test focused. The same principle that keeps a test case reliable applies here: test one clear behavior and test it well.

Frequently asked questions

Can curl test WebSocket connections?

Partly. curl 7.86 and later has experimental native WebSocket support. It can complete the handshake and exchange basic messages, which is enough for a quick reachability check. For interactive testing with multiple messages, use websocat or a GUI client like Apidog.

What is the difference between ws and wss?

ws:// is an unencrypted WebSocket connection. wss:// is WebSocket over TLS.

Use wss:// outside local development because ws:// sends messages in plain text. Tools usually treat both schemes the same apart from encryption.

Why does my WebSocket connection open and then immediately close?

The most common cause is authentication. The server may accept the initial connection and then close it after rejecting a missing or invalid token.

Check:

  • The close code.
  • The token value.
  • Whether the token should be sent as a header or query parameter.
  • Whether the token is expired.
  • Whether the user has permission for the requested channel.

Is websocat better than curl for WebSocket testing?

Yes, for WebSocket-specific testing. websocat is built for WebSocket, understands the frame protocol, supports interactive sessions, custom headers, and piping messages in and out.

Use curl for a quick upgrade or reachability check. Use websocat for real command-line WebSocket testing.

How do I test that a server pushes messages without a request?

Open the connection, subscribe to the relevant channel if required, then wait.

With websocat, pushed messages print as they arrive:

websocat wss://stream.example.com/feed
Enter fullscreen mode Exit fullscreen mode

With a GUI client like Apidog, pushed messages appear in the message timeline.

The important assertion is that messages arrive without another request from the client.

Top comments (0)