[Rust] tokio_tungstenite giving 400 Bad Request with Kite WebSocket

kitedev1983
When trying to connect to Kite connect V3's websocket URL using tokio_tungstenite (0.28.0), I get a 400 Bad Request. The same code with any other WebSocket URL (say, echo.websocket.org) connects just fine. Moreover, running socat with the exact same URL connects successfully. Code snippet attached below.
let url = "wss://ws.kite.trade?api_key=<API_KEY>&access_token=<ACCESS_TOKEN>";

println!("Connecting to {}...", url);
let (ws_stream, _) = tokio_tungstenite::connect_async(url).await?;
println!("Connected!");

let (mut write, mut read) = ws_stream.split();

let read_task = tokio::spawn(async move {
while let Some(msg) = read.next().await {
match msg {
Ok(msg) => {
println!("Received: {}", msg);
}
Err(e) => {
eprintln!("Error receiving message: {}", e);
break;
}
}
}
});
  • kitedev1983
    update: I've created this simple repo, which just attempts to create a connection to Kite's WebSocket endpoint - https://github.com/ayush-sb/ws_test

    Rust code output -
    $ cargo r -- --api-key <API_KEY> --access-token <ACCESS_TOKEN>
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.16s
    Running `target/debug/ws_test --api-key <API_KEY> --access-token <ACCESS_TOKEN>`
    Connecting to wss://ws.kite.trade?api_key=<API_KEY>&access_token=<ACCESS_TOKEN>...
    Error: Http(Response { status: 400, version: HTTP/1.1, headers: {"server": "awselb/2.0", "date": "Wed, 18 Feb 2026 17:03:53 GMT", "content-type": "text/html", "content-length": "122", "connection": "close"}, body: Some([60, 104, 116, 109, 108, 62, 13, 10, 60, 104, 101, 97, 100, 62, 60, 116, 105, 116, 108, 101, 62, 52, 48, 48, 32, 66, 97, 100, 32, 82, 101, 113, 117, 101, 115, 116, 60, 47, 116, 105, 116, 108, 101, 62, 60, 47, 104, 101, 97, 100, 62, 13, 10, 60, 98, 111, 100, 121, 62, 13, 10, 60, 99, 101, 110, 116, 101, 114, 62, 60, 104, 49, 62, 52, 48, 48, 32, 66, 97, 100, 32, 82, 101, 113, 117, 101, 115, 116, 60, 47, 104, 49, 62, 60, 47, 99, 101, 110, 116, 101, 114, 62, 13, 10, 60, 47, 98, 111, 100, 121, 62, 13, 10, 60, 47, 104, 116, 109, 108, 62, 13, 10]) })
    Websocat output -
    $ websocat "wss://ws.kite.trade?api_key=<API_KEY>&access_token=<ACCESS_TOKEN>"
    {"type": "instruments_meta", "data": {"count": 171766, "etag": "W/\"69950bc7-1162\""}}
    {"type":"app_code","timestamp":"2026-02-18T22:28:35+05:30"}
  • Nivas
    Could you please verify whether the WebSocket handshake headers are being sent correctly, especially if you are using a lower-level WebSocket client that performs a minimal handshake? In some cases, differences in client handshake behavior can lead to a 400 Bad Request before the connection is established.

    For a more seamless experience and to avoid client-specific handshake nuances, we generally recommend using one of our official client libraries, which are designed to handle the WebSocket connection and protocol requirements appropriately.
  • kitedev1983
    Thanks for the reply, @Nivas

    I logged the headers being sent by the Rust client, they seem to match the ones being sent by Postman (which is able to successfully connect). Do you know of any other possible causes?

    Rust client headers -
    $ cargo r -- --api-key <API_KEY> --access-token <ACCESS_TOKEN>
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.06s
    Running `target/debug/ws_test --api-key <API_KEY> --access-token <ACCESS_TOKEN>`
    Connecting to wss://ws.kite.trade?api_key=<API_KEY>&access_token=<ACCESS_TOKEN>...
    Outgoing request headers:
    host: ws.kite.trade
    connection: Upgrade
    upgrade: websocket
    sec-websocket-version: 13
    sec-websocket-key: ZQ+9GUmebLkca9uSTUIFXw==
    origin: https://kite.trade
    user-agent: tokio-tungstenite

    Output:
    Error: Http(Response { status: 400, version: HTTP/1.1, headers: {"server": "awselb/2.0", "date": "Fri, 20 Feb 2026 18:16:21 GMT", "content-type": "text/html", "content-length": "122", "connection": "close"}, body: Some([60, 104, 116, 109, 108, 62, 13, 10, 60, 104, 101, 97, 100, 62, 60, 116, 105, 116, 108, 101, 62, 52, 48, 48, 32, 66, 97, 100, 32, 82, 101, 113, 117, 101, 115, 116, 60, 47, 116, 105, 116, 108, 101, 62, 60, 47, 104, 101, 97, 100, 62, 13, 10, 60, 98, 111, 100, 121, 62, 13, 10, 60, 99, 101, 110, 116, 101, 114, 62, 60, 104, 49, 62, 52, 48, 48, 32, 66, 97, 100, 32, 82, 101, 113, 117, 101, 115, 116, 60, 47, 104, 49, 62, 60, 47, 99, 101, 110, 116, 101, 114, 62, 13, 10, 60, 47, 98, 111, 100, 121, 62, 13, 10, 60, 47, 104, 116, 109, 108, 62, 13, 10]) })
    Headers from Postman after successful connection -
    Connected to wss://ws.kite.trade?api_key=<API_KEY>&access_token=<ACCESS_TOKEN>

    Handshake Details
    Request URL: https://ws.kite.trade/?api_key=<API_KEY>&access_token=<ACCESS_TOKEN>
    Request Method: GET
    Status Code: 101 Switching Protocols

    Request Headers
    Sec-WebSocket-Version: 13
    Sec-WebSocket-Key: vAvdU0k+A5aEC2fx8ZFWYw==
    Connection: Upgrade
    Upgrade: websocket
    Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
    Host: ws.kite.trade
    P.S. I'm aware of the official client libraries, but my current usecase requires a Rust-based client :smiley:
  • Nivas
    Since the request is receiving a 400 response from the AWS (awselb/2.0) rather than a WebSocket protocol or authentication error, the connection is likely being rejected during the initial handshake phase before reaching the Kite WebSocket service.

    Even if the visible headers appear similar, differences in client handshake characteristics such as TLS negotiation, extension headers (e.g., Sec-WebSocket-Extensions), request formatting, or underlying WebSocket stack behavior can lead to different outcomes across clients like Postman, websocat, and low-level libraries.

    Given that the same URL and credentials work with other WebSocket clients, this does not indicate a Kite Connect credential or endpoint issue, but rather a client-specific handshake or transport-level difference.
Sign In or Register to comment.