Blog
Binance
JavaScript
Node.js
WebSockets
SDKs

Binance User Data Stream 410 Gone: Fix Spot listenKey in JavaScript

Fix Binance Spot user data stream 410 Gone errors in JavaScript by replacing the old listenKey flow with WebSocket API user data streams.

Siebly.io12 min readMarkdown

Spot user data stream now returns 410 Gone

For a long time, the user data stream for Binance spot private account events, was dependent on the listenKey mechanic, starting with a REST API call to POST /api/v3/userDataStream, but since late 2025, this endpoint returns a 410 Gone HTTP error. This failure can look like a broken network request, but in reality, it is a sign to migrate to the new spot & margin user data mechanic. It's faster, simpler, and no longer depends on a listenKey life cycle:

Common Spot listenKey failure

Text
Failed to connect to spot user data
requestUrl: https://api.binance.com/api/v3/userDataStream
body: <html><head><title>410 Gone</title></head>...

The short version is that the old Binance Spot & Margin listenKey workflow is gone for Spot & Margin. This is the issue behind reports such as tiagosiebler/binance#673, where subscribeSpotUserDataStream fails with 410 Gone, and tiagosiebler/binance#644, where the practical question is what replaces userDataStream for balance updates, order updates, and account events.

The replacement is not a raw REST workaround. The complete listenKey-centric workflow has been replaced and migrated to Binance's WebSocket API. Use the WebSocket API user data stream through the Siebly.io Binance JavaScript SDK and subscribe from WebsocketAPIClient.

What replaces Binance Spot userDataStream?

Use WebsocketAPIClient.subscribeUserDataStream(WS_KEY_MAP.mainWSAPI) for Spot user data. Under the hood, the SDK sends the WebSocket API userDataStream.subscribe request that Binance documents for the new Spot user data stream path.

That changes the startup path, not the reason you use the stream. Your app still receives private account events such as balance changes and order lifecycle updates. The event emitters are largely the same. The difference is that Spot no longer needs your system to create a REST listenKey first, keep that listenKey alive, handle listenKey expiry (every 24 hours or less), then open a stream URL with it. That workflow has been deprecated for some time now.

For a practical example, take a look at the user data section of the Binance JavaScript tutorial or the runnable WebSocket API user data example linked from the Binance JavaScript SDK QuickStart Guide.

What is a Binance user data stream?

A Binance user data stream is the private event feed for an account. For Spot, it carries events such as outboundAccountPosition, balanceUpdate, and executionReport. Those events tell your application when balances changed, when an order was accepted, rejected, filled, canceled, expired, or otherwise updated.

That stream is different from a public market stream. Public streams tell you what happened in the market. User data streams tell you what happened to your account. A trading dashboard, account-state worker, order monitor, or reconciliation service usually needs both kinds of data, but they have different permissions and failure modes. In an ideal (and highly optimised) world, your system would only rely on the REST API to fetch initial state. From here on, you would subscribe to the user data stream, which would automatically feed your system any change events (deltas) on relevant to your account. Your system can then use this to passively maintain a local state cache, that you can rely on for current account state (including balances and orders), as well as a meaningful trigger for follow-on events, such as updating orders, placing new orders (such as your Stop Loss), cancelling old ones.

Relevant references:

Why JavaScript apps should not poll REST forever

The REST API is still useful. Some commands can only be executed via the REST API. If your system doesn't yet have any initial information on the state of your account (balances, orders, positions, etc), one would typically query the initial state via the REST API and cache it locally.

Once your local cache is in sync, the user data stream can passively provide you all the data you need to keep it in sync, without relying on the REST API further.

There are some exceptions, however:

  • Initial state missing (e.g. fresh stateless restart). Fetch initial state to resync.
  • WebSocket reconnected after an unknown amount of time (can happen automatically). Use the reconnected event to trigger a state sync via REST API.
  • Unexpected internal error in your system, that suggests state may not be in sync anymore. E.g. attempted to amend order that no longer exists.

For more information, refer to the state management guidance in our glossary.

These are exceptions though. An event-driven system should try not to rely heavily on the REST API when WebSocket streams are available. Regularly polling the REST API for order and balance changes adds request-weight pressure, increases latency between exchange state and local state, and makes busy accounts harder to follow without hitting rate-limit boundaries. WebSocket user data events give your JavaScript process account deltas or snapshots as Binance publishes them, so the local state store can react faster and then use REST only when a snapshot or repair step is needed.

Note: The SDK does not remove your responsibility for request pacing. Your application still owns rate-limit awareness, backoff behavior, queueing, and operational gates. The benefit of the SDK is that signing, WebSocket lifecycle handling, request/response matching, and product routing are centralized in the client instead of repeated across your codebase.

Spot listenKey deprecation timeline

Binance announced the Spot listenKey deprecation on April 7, 2025. The old workflow used wss://stream.binance.com with a REST-created listenKey.

On October 24, 2025, the Binance Spot changelog said documentation for listenKey usage on wss://stream.binance.com had been removed and reminded developers to use the WebSocket API user data stream instead. The same changelog entry listed the old Spot REST listenKey endpoints and older WebSocket API listenKey management methods as remaining only until a future retirement.

On January 21, 2026, Binance stated that POST /api/v3/userDataStream, PUT /api/v3/userDataStream, DELETE /api/v3/userDataStream, userDataStream.start, userDataStream.ping, and userDataStream.stop would no longer be available from February 20, 2026 at 07:00 UTC. After that date, a 410 Gone response from POST /api/v3/userDataStream is a retired Spot flow, not proof that your API key, Node.js runtime, or HTTP client is wrong.

Spot vs Margin vs Futures: what actually changed?

The easy migration mistake is to treat every Binance product group as if it changed at the same time. It did not. Only spot and margin were affected. The last we've heard, there are no plans to similarly affected how the futures user data stream works.

Product areaPreviouslyJavaScript migration note
SpotWebSocket API user data stream with userDataStream.subscribe.Replace old /api/v3/userDataStream listenKey startup with WebsocketAPIClient.subscribeUserDataStream(WS_KEY_MAP.mainWSAPI).
Cross MarginWebSocket API listen-token workflow.Use the SDK margin user data path. Support for automatic listen-token refresh and resubscribe landed in binance v3.4.0 through PR #645.
USD-M FuturesFutures listenKey workflow.Remains active, no change needed.
COIN-M FuturesFutures listenKey workflow.Remains active, no change needed.
Portfolio MarginPortfolio Margin listenKey workflow.Remains active, no change needed.

For Futures, the WebSocket API has methods around starting, pinging, and stopping a user data stream, but the stream still relies on a listenKey-backed connection. That is why the Spot deprecation should not be applied blindly to USD-M or COIN-M Futures code.

Migration with the Siebly.io Binance JavaScript SDK

For Spot, use the WebSocket API client and subscribe on the Spot WebSocket API key. For Margin, use the margin user data key. In both cases, keep your event handling close to the code that owns account state and reconcile through REST after reconnects.

Spot and margin user data over Binance WebSocket API

JavaScript
import { WebsocketAPIClient, WS_KEY_MAP } from 'binance';

const wsApi = new WebsocketAPIClient({
  api_key: process.env.BINANCE_API_KEY,
  api_secret: process.env.BINANCE_API_SECRET,
  beautify: true,
});

const ws = wsApi.getWSClient();

ws.on('formattedUserDataMessage', (event) => {
  console.log('account event', event);
});

ws.on('reconnecting', ({ wsKey }) => {
  console.log('user data reconnecting', wsKey);
});

ws.on('reconnected', ({ wsKey }) => {
  console.log('user data reconnected; reconcile account state', wsKey);
});

ws.on('exception', console.error);

await wsApi.subscribeUserDataStream(WS_KEY_MAP.mainWSAPI);

// For cross-margin user data, use the margin WebSocket API user data path:
// await wsApi.subscribeUserDataStream(WS_KEY_MAP.marginUserData);

The event side of your application should look familiar. You still route account updates, order updates, fills, rejects, and balance deltas into your local state model. The bootstrap changed from create and refresh a Spot listenKey to subscribe to user data on the WebSocket API connection.

The same JavaScript SDK also ships with TypeScript definitions for teams that use them, but the migration does not require a TypeScript rewrite. TypeScript is strictly optional (but highly recommended!).

Why else use the WebSocket API

Traditionally for event-driven systems integrated with crypto exchange APIs such as Binance's REST APIs & WebSockets, you would rely on the REST API for commands (order management) and state queries, and on the WebSocket for state updates.

Each request sent via the REST API has a number of repetitive steps:

  1. Build request payload
  2. Sign request payload, based on desired parameters and credentials
  3. Resolve via DNS for REST API domain
  4. Open secure connection (incl TLS handshake to verify server certifiate)
  5. Send request payload
  6. Await response
  7. Close connection and return response to caller.

The WebSocket API (especially if you're using Ed25519 keys), makes a few steps here redundant. The following is done only once when using Ed25519 keys:

  1. Resolve via DNS for Websocket API domain
  2. Open secure connection (incl TLS handshake to verify server certifiate)
  3. Sign "login" payload with Ed25519 credentials
  4. Send "login" payload to authenticate WebSocket API session.

These steps are only done once when the connection first opens. From here, the WebSocket API connection should remain open and persistently ready for authenticated-requests. Should you then send a request via the same WebSocket API connection:

  1. Build request payload, based on parameters
  2. Send request payload via active WebSocket API connection.
  3. Await response and return response to caller once it arrives.

As you can see, the workflow is much simpler, giving performance optimisations that will significantly add up over a number of requests. This is especially important for latency sensitive systems such as arbitrage or other high-frequency trading systems, where every optimisation is highly valuable.

Is it complicated? Reading the above might give that impression, but no, not with our Siebly.io Binance JavaScript SDK. Our WebsocketAPIClient exposes awaitable methods for all available WebSocket API workflows, such order placement, order management, cancellation, account reads, and market data requests where Binance exposes matching WebSocket API methods. Refer to our Binance WebSocket API JavaScript examples to see how it works in practice.

Internally our WebsocketAPIClient will automatically associate tracking IDs with each outgoing WebSocket API command. This tracking ID is used to store and track an internal JavaScript promise. When the Binance WebSocket API provides a reply, this tracking ID allows us to query the internal JavaScript promise corresponding to that request, and resolve (or reject) it to let you know the request has completed.

This is completely automatic as part of the WebsocketAPIClient in our Binance JavaScript SDK. This gives you all the benefit of the WebSocket API without worrying about the complexity of asynchronous events and WebSocket life-cycle management.

Binance supports HMAC, RSA, and Ed25519 key types across WebSocket API usage. HMAC and RSA remain supported in the SDK, which matters for teams that already run those key types. For active WebSocket API usage, Ed25519 is the best-performance path because it enables WebSocket API session authentication instead of signing each private command individually.

FAQ

Does 410 Gone mean my Binance API key is wrong?

Not usually for this symptom. If the failing request is POST /api/v3/userDataStream for Spot, 410 Gone points to the retired Spot listenKey workflow. Permission errors and invalid keys have different errors.

What replaces userDataStream for balance and order updates?

For Spot, use the WebSocket API user data stream through WebsocketAPIClient.subscribeUserDataStream(WS_KEY_MAP.mainWSAPI). That gives your JavaScript app the balance and order events that used to arrive through the old Spot listenKey stream.

Should I change Futures listenKey code?

No, not because of the Spot 410 Gone error. Binance Futures listenKey workflows remain unchanged for USD-M Futures, COIN-M Futures, and Portfolio Margin.

Do I still need REST after moving to WebSocket API user data?

Yes. Use REST for startup snapshots, reconciliation after reconnects, and status checks when a request outcome is unclear. The usual pattern is snapshot plus stream deltas, not stream-only state. Refer to our exchange state guidance in our glossary.

Does the SDK handle rate-limit throttling?

No. The SDK is intentionally lean and does not automatically throttle, queue, or retry rate-limited requests. Your application should track Binance rate limits, back off after rate-limit responses, and decide how to schedule REST and WebSocket API commands.

Continue from here

Related Siebly resources

All articles