Hi Community,
I’ve been attempting to load test a WebSocket endpoint that uses the STOMP protocol over WSS. Despite exhaustive testing and replicating a known working Python client, I am consistently hitting a websocket: close 1002 (protocol error) immediately after the WebSocket connection opens.
This issue seems to be a deep-rooted incompatibility between the underlying GO WebSocket client used by k6 and the API Gateway.
Background and working client
I have a working Python client using the websocket-client library that successfully connects, sends the STOMP CONNECT frame, and receives the CONNECTED response.
# This builds the STOMP CONNECT frame
connect_frame = f"""CONNECT
accept-version:1.2
X-Vault-Session:{session_key}
X-Vault-Endpoint:{endpoint_url}
\x00
"""
ws = None
recv_thread = None
try:
print(f"Connecting to {url}...")
ws = websocket.create_connection(url, sslopt={"cert_reqs": ssl.CERT_NONE})
print("WebSocket tunnel OPEN.")
print("Sending STOMP CONNECT frame...")
print("RAW STOMP CONNECT BYTES:\n", connect_frame.encode())
ws.send(connect_frame)
print("Waiting for CONNECTED response...")
response = ws.recv()
print("\n--- Server Response ---")
print(response)
print("-----------------------\n")
if "CONNECTED" in response:
print("✅ SUCCESS! You are connected and authenticated.")
else:
print("❌ STOMP CONNECT FAILED. Server did not send 'CONNECTED'.")
Summary of Attempts (all failed with 1002 error)
I tested 2 methods: The 3rd-party extension (k6/x/stomp) and the native k6/ws module, meticulously replicating the successful Python client’s behavior.
- Using k6/x/stomp custom extension
import stomp from "k6/x/stomp";
export function connectWebsocket(vaultEndpoint, vaultSession, xChatEndpoint, vcrmVersion = "main") {
const headers = {
"X-Vault-Endpoint": vaultEndpoint,
"X-Vault-Session": vaultSession,
};
const path = `/${vcrmVersion}/api/xxx/xxx`;
const connAddr = `${xChatEndpoint}:443`;
return stomp.connect({
protocol: "wss",
host: xChatEndpoint,
addr: connAddr,
path: path,
verbose: true,
});
}
Below is from the verbose log
INFO[0000] 2025/12/03 16:41:38 WRITE: CONNECT
INFO[0000] host:xxx.com
INFO[0000] heart-beat:60000,60000
INFO[0000] accept-version:1.0,1.1,1.2
INFO[0000]
INFO[0000]
INFO[0000] 2025/12/03 16:41:38 READ-ERR: websocket: close 1002 (protocol error): Protocol error
ERRO[0000] GoError: websocket: close 1002 (protocol error): Protocol error
running at reflect.methodValueCall (native)
- Using native k6/ws by manually implementing STOMP protocol
// EXACT Python frame, sent as TEXT
function buildStompConnectFrame() {
return (
"CONNECT\n" +
"accept-version:1.2\n" +
`X-Vault-Session:${vaultSession}\n` +
`X-Vault-Endpoint:${vaultEndpoint}\n` +
"\n" +
"\x00\n"
);
}
export default function () {
ws.connect(
url,
{
timeout: "20s",
insecureSkipTLSVerify: true,
},
function (socket) {
socket.on("open", () => {
console.log("OPEN → sending STOMP CONNECT as TEXT");
socket.send(buildStompConnectFrame()); // TEXT FRAME
});
socket.on("error", (e) => console.log("ERROR:", e));
socket.on("close", () => console.log("CLOSED"));
}
);
}
INFO[0000] OPEN → sending STOMP CONNECT as TEXT source=console
INFO[0000] ERROR: websocket: close 1002 (protocol error): Protocol error source=console
INFO[0000] CLOSED source=console
Has anyone else encountered this level of incompatibility with specific API gateways? Are there any hidden k6 environment variables or global options that can influence the low-level handshake format?
From ChatGPT, it says the main issue is that k6 does not support WebSocket RFC 7692 permessage-deflate
