Skip to content

Comments

feat(v3.0.0): Introduce Binance WebSocket API, introduce multiplex WebSocket client, bump dependencies, remove some deprecated endpoints#509

Merged
tiagosiebler merged 171 commits intomasterfrom
wsapi
Jun 30, 2025
Merged

feat(v3.0.0): Introduce Binance WebSocket API, introduce multiplex WebSocket client, bump dependencies, remove some deprecated endpoints#509
tiagosiebler merged 171 commits intomasterfrom
wsapi

Conversation

@tiagosiebler
Copy link
Owner

@tiagosiebler tiagosiebler commented Jan 30, 2025

Summary

This major release brings major upgrades to the Node.js, TypeScript & JavaScript SDK for Binance's REST APIs and WebSockets. The WebSocket API is here - send orders at a lower latency over a dedicated WebSocket connection, without the complexity of managing WebSockets. Use the WebsocketAPIClient class to use the WebSocket API like a REST API, with virtually no additional complexity.

High Level Changes

  • Significant upgrades to WebSocket handling & capabilities.
    • Added integration for the WebSocket API (send orders via WS, await responses). See Add support for WebSocket API (allows placing orders, canceling orders, etc. through a WebSocket connection) #283.
    • Multiple topics now use a single WebSocket connection.
    • Breaking changes to "custom logger" method names.
    • Breaking changes to emitted WebsocketClient events:
      • error has been renamed to exception
      • reply has been renamed to response
    • WS Response events (after subscribing/unsubscribing to/from topics) are now enriched with the subscription request that was sent to the server.
    • WS heartbeats are disabled automatically if ping/pong frames are not supported in your environment
      • This usually only affects you if you are using the SDK in a frontend environment (e.g. a browser), since binance uses ping/pong control frames.
  • Introduced WebsocketAPIClient.
    • Use the WebSocket API like a REST API!
    • Looks & behaves like a promise-driven REST API class.
    • All commands:
      • Are sent over the WebSocket API.
      • Are fully typed (request & response).
      • Return a promise, so you can use promises/await to wait for the WebSocket API response.
  • Dependency updates:
    • Upgraded build version to Node LTS.
    • Misc dependency updates:
      • Axios to 1.7.9
      • @types/node to LTS
      • typescript to 5.7.3
  • Added convenient flag to enable HTTP keepAlive for REST clients.
  • Added full support for all available authentication methods on Binance: HMAC, RSA & Ed25519.
  • Added support for the Web Crypto API for request sign, both for REST & WS.
    • Node crypto is still used for request sign by default, since Ed25519 (required for WS API) is not stable yet.
  • Add customSignMessageFn() parameter to WebsocketClient and REST clients.

Major Changes

WebsocketAPIClient
  • Introduced a new WebsocketAPIClient class, wrapped around the WebsocketClient's WS API capabilities.
  • Similar to the REST API clients, this class provides one method for every available WebSocket API command.
  • Similar to the REST APIs, commands are wrapped in promises, allowing you to await WebSocket API responses with minimal complexity. The SDK handles the complexity, so for you it feels like a REST API.
Multiplex WebsocketClient
  • Introduced a newly upgraded WebsocketClient, designed around multiplex WebSocket subscriptions.
    • When subscribing to multiple topics within the same product group, they will automatically share one connection for all topics supported by that connection. This is significantly faster than the legacy WS Client, since you will no longer need thousands of connections for thousands of topics.
    • If a topic requires a different connection (e.g. a different subdomain), it will automatically handle all connectivity and event routing.
    • Detailed usage examples can be seen here: https://github.com/tiagosiebler/binance/blob/wsapi/examples/ws-public.ts#L71
  • Full support for Binance's WebSocket API capabilities. Detailed examples can be found in the examples folder & readme.
  • For backwards compatibility:
    • Existing "subscribe*" methods are available that exactly match the event behaviour seen in the legacy WS client.
    • Events behave and look the same, thanks to minimal internal processing in how events are received and handled.
WebsocketClientV1
  • The existing WebsocketClient from the previous release has been renamed to WebsocketClientV1.

Breaking / Potentially Breaking Changes

  • WebSockets:
    • For the few places that somewhat supported testnet, renamed the { useTestnet: true } config to { testnet: true }
    • Previous WebsocketClient has been renamed to WebsocketClientV1.
      • You can keep using this if you're not ready to transition, until you're ready.
      • See below for details.
    • Introduced NEW multiplex WebsocketClient:
      • Capable of live subscribing/unsubscribing (on shared websockets)
      • Supports all userData streams available on Binance.
      • Supports all available WS APIs available on Binance.
    • Consolidated the internal DefaultLogger methods, in line with my other SDKs
      • Into 3 key levels: trace | info | error.
      • Similar to the silly level before this release, the trace level is disabled/silent by default.
    • The "wsKey" will no longer uniquely identify the topic an event is for, since it represents the connection the event came from (and one connection may have multiple subscribed topics).
    • Within the on('open') ws event handler, any references to event.ws.target.url should use event.wsUrl instead.
  • REST Clients:
    • For the few places that somewhat supported testnet, renamed the { useTestnet: true } config to { testnet: true }
    • Deprecated 3rd boolean parameter for testnet. Use { testnet: true } as part of the REST client options instead.
      • Affects USDMClient
      • Affects COINMClient
    • Removed deprecated endpoints
      • get24hrChangeStatististics(), use get24hrChangeStatistics() instead.
      • getAccountComissionRate(), use getAccountCommissionRate() instead.
      • updateAutoInvestmentPlanOld(), use updateAutoInvestmentPlan() instead.
      • submitAutoInvestmentPlanOld(), use submitAutoInvestmentPlan() instead.
    • Removed old SpotClient export
      • Use MainClient instead (it is the same).

High level checklist

  • Announce public beta
  • Run final batch HFT stress testing via WS API

@socket-security
Copy link

socket-security bot commented Jan 30, 2025

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updatedtypescript@​4.9.5 ⏵ 5.7.3100 +110089100100
Updatedts-jest@​29.1.1 ⏵ 29.2.59710093 +193100
Updatedaxios@​1.7.7 ⏵ 1.8.399100 +1610093100

View full report

@tiagosiebler
Copy link
Owner Author

However, when I used the websocket API of the @binance/spot library before, if I requested public data, the auth is not required,why?🤔

import { Spot } from "@binance/spot";

// @ts-ignore
const client = new Spot({ configurationWebsocketAPI: {}, });

client.websocketAPI
  .connect()
  .then((connection) => {
    return connection.tickerPrice({ symbol: "BTCUSDT" });
  })
  .then((rs) => {
    console.log(rs.data);
  });

I was largely thinking about account-level commands (submitting orders, changing leverage, etc), which will always require auth, naturally. Part of the motivation for using the WS API is latency, and naturally for market data if latency is important you should be consuming events (as a subscriber), not polling data at intervals (even if the ws api makes it faster than pure REST API).

I also misunderstood what you were doing when I first read your question. I thought you were subscribing to market data as a consumer (which should be via the WebsocketClient). Perhaps auth should remain optional for WebSocket API commands that don't require it (market data). Should be straight forward to implement around how the WebsocketAPIClient sends each command... I'll look into it. In the meantime I've also improved internal checks to make sure there's a clearer error message if API keys are missing when trying to authenticate the WS API.

…require auth. add better message when keys are required but missing
@tiagosiebler
Copy link
Owner Author

Published binance@3.0.0-beta.16:

  • Added flag for public WSAPI commands that do not require auth. Thanks @aweiu for pointing that out.
  • Added better error message when API keys are required by WS API but missing.

Thank you to everyone involved in testing & feedback, it's been extremely helpful! If you notice anything else that doesn't look normal, please keep opening issues.

@aweiu
Copy link
Contributor

aweiu commented May 27, 2025

{"id":1,"status":401,"error":{"code":-2015,"msg":"Invalid API-key, IP, or permissions for action."},"rateLimits":[{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":6000,"count":4}],"wsMarket":"spot","wsKey":"mainWSAPI","isWSAPIResponse":true}

[
  'Websocket connection closed',
  { category: 'binance-ws', wsKey: 'mainWSAPI' }
]
[
  'Reconnecting to websocket with delay...',
  ...

I have two questions about the wsAPI execution log above:

  1. Can you include WSAPIOperation (such as time, ticker.price, etc.) in the error log to quickly locate the location of the exception call

  2. This error was actually caused by me intentionally manually calling wsClient. sendWSAPIRequest (...'time'...) without setting {authIsOptional: true}. Normally, it should not cause the ws connection to be disconnected (currently tested, it seems that as long as the log outputs ws exception the ws connection will be disconnected)

@aweiu
Copy link
Contributor

aweiu commented May 27, 2025

What is the difference between the two methods of the WebsocketClient class: connect and connectWSAPI? At present, I have noticed that the method comments of connectWSAPI have stated that it can be called before calling wsAPI, but testing has found that:

  1. ConnectWSAPI always requires api_key and api_secret
  2. It always fails when api_key and api_secret are passed in:
ws exception:  {"id":1,"status":401,"error":{"code":-2015,"msg":"Invalid API-key, IP, or permissions for action."},"rateLimits":[{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":6000,"count":4}],"wsMarket":"spot","wsKey":"mainWSAPI","isWSAPIResponse":true}

I am currently calling connect and there are no issues mentioned above (referring to the internal implementation, it seems that all calls connect)

@aweiu
Copy link
Contributor

aweiu commented May 28, 2025

import { WebsocketAPIClient } from "binance";

const ws = new WebsocketAPIClient({});

ws.getFuturesSymbolPriceTicker({ symbol: "BTCUSDT" }).then((rs) =>
  console.log(rs.result),
);

=>

[
  'Websocket connected',
  { category: 'binance-ws', wsKey: 'usdmWSAPI' }
]
2025-05-28T10:09:18.044Z ws connected usdmWSAPI
[
  Error: API key and/or secret missing, unable to prepare WS auth event without valid API keys.
      at WebsocketClient.<anonymous>
      ....
]
{ symbol: 'BTCUSDT', price: '108500.90', time: 1748426958317 }

getFuturesSymbolPriceTicket correctly returned priceTicket but does not seem to handle authentication logic correctly (getSpotSymbolPriceTicket worked well).

Even though I passed in api_key and api_secret:

import { WebsocketAPIClient } from "binance";

const ws = new WebsocketAPIClient({
  api_key: "*",
  api_secret: "*",
});

ws.getFuturesSymbolPriceTicker({ symbol: "BTCUSDT" }).then((rs) =>
  console.log(rs.result),
);

The execution result is still abnormal:

[
  'Websocket connected',
  { category: 'binance-ws', wsKey: 'usdmWSAPI' }
]
2025-05-28T10:14:31.751Z ws connected usdmWSAPI
2025-05-28T10:14:31.918Z ws exception:  {"id":1,"status":400,"error":{"code":-1022,"msg":"Signature for this request is not valid."},"rateLimits":[{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":2400,"count":7}],"wsMarket":"usdm","wsKey":"usdmWSAPI","isWSAPIResponse":true}
[
  'Websocket connection closed',
  { category: 'binance-ws', wsKey: 'usdmWSAPI' }
]
[
  'Reconnecting to websocket with delay...',
  {
    category: 'binance-ws',
    wsKey: 'usdmWSAPI',
    connectionDelayMs: 500
  }
]
2025-05-28T10:14:31.923Z ws automatically reconnecting....  usdmWSAPI
[
  'Reconnecting to websocket now',
  { category: 'binance-ws', wsKey: 'usdmWSAPI' }
]
[
  'Websocket reconnected',
  { category: 'binance-ws', wsKey: 'usdmWSAPI' }
]
...

@tiagosiebler
Copy link
Owner Author

I have two questions about the wsAPI execution log above:

  1. Can you include WSAPIOperation (such as time, ticker.price, etc.) in the error log to quickly locate the location of the exception call
  1. This error was actually caused by me intentionally manually calling wsClient. sendWSAPIRequest (...'time'...) without setting {authIsOptional: true}. Normally, it should not cause the ws connection to be disconnected (currently tested, it seems that as long as the log outputs ws exception the ws connection will be disconnected)

I've tried this on my end and I don't see any exceptions from this, are you on the latest beta?
Screenshot 2025-05-29 at 10 28 14
Screenshot 2025-05-29 at 10 28 21

@tiagosiebler
Copy link
Owner Author

What is the difference between the two methods of the WebsocketClient class: connect and connectWSAPI? At present, I have noticed that the method comments of connectWSAPI have stated that it can be called before calling wsAPI, but testing has found that:

  1. ConnectWSAPI always requires api_key and api_secret
  2. It always fails when api_key and api_secret are passed in:
ws exception:  {"id":1,"status":401,"error":{"code":-2015,"msg":"Invalid API-key, IP, or permissions for action."},"rateLimits":[{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":6000,"count":4}],"wsMarket":"spot","wsKey":"mainWSAPI","isWSAPIResponse":true}

I am currently calling connect and there are no issues mentioned above (referring to the internal implementation, it seems that all calls connect)

This connectWSAPI() was originally built around the assumption that the WS API is for account-level functionality, thus auth is always required. Not the case here, so a question how to anticipate that without diverging from similar logic on other exchanges. I could keep it as is by default but expose an optional boolean? Thoughts?

  public connectWSAPI(wsKey: WSAPIWsKey, skipAuth?: boolean): Promise<unknown> {
    if (skipAuth) {
      return this.assertIsConnected(wsKey);
    }

    /** This call automatically ensures the connection is active AND authenticated before resolving */
    return this.assertIsAuthenticated(wsKey);
  }

Regarding the difference, as you can see here, the connectWSAPI call is wrapped around the assert methods. Those methods are special in that they're promise-wrapped, although at this point the assertIsConnected() call is almost the same as the connect() call you're referring to - it resolves a promise when the connection is confirmed to be open (including if it was already opened before that call).

@tiagosiebler
Copy link
Owner Author

ws.getFuturesSymbolPriceTicker({ symbol: "BTCUSDT" }).then((rs) =>
console.log(rs.result),
);

I missed these, thanks for pointing them out! @JJ-Cro pushed a fix. Will include it with the next beta release. Would love your thoughts on the other comments when you have a moment @aweiu

@aweiu
Copy link
Contributor

aweiu commented May 29, 2025

I have two questions about the wsAPI execution log above:
...

I've tried this on my end and I don't see any exceptions from this, are you on the latest beta?

You may misunderstood me:

This error was actually caused by me intentionally manually calling wsClient.sendWSAPIRequest (...'time'...) without setting {authIsOptional: true}. Normally, it should not cause the ws connection to be disconnected (currently tested, it seems that as long as the log outputs ws exception the ws connection will be disconnected)

wsClient.sendWSAPIRequest (WS_API_WS_KEY, 'time') // Intentionally not passing in the {authIsOptional: true} parameter

So, my question is actually why a failed request from a certain ws_api leads to the disconnection of the ws connection?

@tiagosiebler
Copy link
Owner Author

tiagosiebler commented Jun 4, 2025

I have two questions about the wsAPI execution log above:
...

I've tried this on my end and I don't see any exceptions from this, are you on the latest beta?

You may misunderstood me:

This error was actually caused by me intentionally manually calling wsClient.sendWSAPIRequest (...'time'...) without setting {authIsOptional: true}. Normally, it should not cause the ws connection to be disconnected (currently tested, it seems that as long as the log outputs ws exception the ws connection will be disconnected)

wsClient.sendWSAPIRequest (WS_API_WS_KEY, 'time') // Intentionally not passing in the {authIsOptional: true} parameter

So, my question is actually why a failed request from a certain ws_api leads to the disconnection of the ws connection?

Apologies for the delay. At first I thought it was related to not handling the promise returned by the sendWSAPIRequest method, but I actually can't reproduce the issue with the latest changes. I think it might've been related to sending an invalid sign request when no keys are present - I've recently added a guard to prevent even trying it if credentials aren't available, so perhaps that fixed your issue as a side effect. Perhaps already with the changes in the .16 beta release. I've just published another beta release with the latest code, can you give it a try? Thanks for all the issues you've reported!

Published binance@3.0.0-beta.17:

  • add optional skipAuth flag for connectWSAPI method
  • remove excessive trace log around resolveEmittable. This wasn't meant to be included in the release.

Thank you to everyone involved in testing & feedback, it's been extremely helpful! If you notice anything else that doesn't look normal, please keep opening issues.

@aweiu
Copy link
Contributor

aweiu commented Jun 7, 2025

the keepAlive option may override httpsAgent: https://github.com/tiagosiebler/binance/blob/wsapi/src/util/BaseRestClient.ts#L83

I found this by accident while looking through the binance-connector-js codes: https://github.com/binance/binance-connector-js/blob/master/common/src/utils.ts#L294

@tiagosiebler
Copy link
Owner Author

Hey @aweiu apologies for the delay, had some time away. Thanks for pointing that out. Yes, it was meant initially as a convenience flag for those that didn't have more advanced needs, while it was already possible to inject your own https agent in the constructor of the REST clients (second parameter, passed through to axios). Might be a bit misleading though that it wasn't documented and I've been meaning to improve it.

I've revised the logic a bit to consider an existing https agent. Also added a test to validate that it's working as expected. Just a simple check that certificate pinning correctly rejects the request when using an incorrect public key hash, while keep alive is active (confirming that both agent configurations were merged, not overwritten). Thanks for all the feedback!

Let me know if there's anything else! Will look to release this branch this week, since it seems most of the important issues have been ironed out!

@tiagosiebler tiagosiebler merged commit 525b06e into master Jun 30, 2025
5 checks passed
@tiagosiebler tiagosiebler deleted the wsapi branch June 30, 2025 16:07
@tiagosiebler tiagosiebler mentioned this pull request Jul 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants