Tradovate WebSocket Close Code 1006 Disconnect
Your Tradovate WebSocket dies with close code 1006 and no reason text at all. Here's what 1006 actually means, why it fires, and how to make reconnects a non-event.
Your Tradovate WebSocket is feeding a bot happily, and then out of nowhere it dies with close code 1006. No reason text, no close message, nothing in the logs except some flavor of “connection closed abnormally.” It is one of the most frustrating failures in Tradovate automation precisely because 1006 tells you almost nothing. The good news: you rarely need to solve the mystery. A 1006 means the connection died without a clean handshake, and the reliable fix is to make your client reconnect and re-authorize automatically the instant it happens: keep the heartbeats flowing, catch the drop, spin up a fresh socket, log back in, and re-subscribe. A bridge like PickMyTrade does exactly that under the hood, so your TradingView alerts keep reaching Tradovate even when a raw script would have gone dark.
Quick Checklist for Close Code 1006
- Treat 1006 as a symptom. It means the socket dropped without a close frame, not that one specific thing is broken.
- Send heartbeats. Push an empty
[]frame roughly every 2.5 seconds. Miss them and the server drops you. - Auto-reconnect on every close. Do not branch on the code. Reconnect on a clean
1000and a1006alike. - Re-authorize and re-subscribe. A fresh socket is unauthenticated, and quote and user-sync subscriptions do not survive a reconnect.
- Renew, do not re-login. Renewing the token keeps the socket alive; a brand-new login can boot your session and cause the drop.
- Back off with jitter. Space retries out and cap them so you do not trip Tradovate's request limits.
What “Close Code 1006” Means
WebSocket close codes are defined by the protocol itself. A clean shutdown sends code 1000 (normal closure) along with a close frame that explains why. Code 1006 is different: it is a reserved code your WebSocket library assigns when the connection vanished without a close frame ever arriving. In other words, the TCP pipe underneath the socket broke, and neither side got the chance to say goodbye properly. That is why 1006 never carries a human-readable reason. There is nothing to read, because the message that would have carried it never made it across.
It helps to know how the Tradovate socket normally talks. Every frame the server sends is tagged with a single leading character: an o frame is the open message you get the moment the socket connects and your cue to authenticate; an a frame carries an array of JSON data (quotes, order updates, sync responses); an h frame is a heartbeat; and a c frame is a graceful close. When Tradovate closes on purpose it sends c[1000,“...”] with a message telling you why. A 1006 is the opposite of that tidy c: no close frame at all, just a dead socket and the library's abnormal-closure code.
So stop hunting for a message that will never come, and ask instead what could have severed the TCP connection under a long-lived socket. That short list is where every real fix lives.
Top Causes of Close Code 1006 in 2026
1. Missed heartbeats (the 2.5-second rule)
This is the cause people overlook most. After your authorize frame is accepted, the Tradovate socket expects a steady heartbeat from your client: an empty JSON array, written literally as [], sent about every 2.5 seconds. Those tiny frames prove the connection is alive. Let them stall and the server may quietly stop sending data or tear the connection down, which lands on your side as a 1006. If your disconnects cluster after a pause in your code or a busy event loop, suspect the heartbeat first.
2. Browser tab throttling
If your socket runs in a browser, there is a nasty trap. When the tab holding your app is not the active tab, Chrome and other browsers lower the priority of setInterval and setTimeout. Your 2.5-second heartbeat slips to every several seconds, the server sees a stalled connection, and the socket closes with 1006 the moment you switch away. The fix is to run the heartbeat loop in a web worker (workers are not throttled the same way) or to run the whole thing as a Node process outside the browser entirely.

3. Idle proxy, NAT, or firewall timeouts
Anything sitting between your machine and Tradovate can drop a connection it thinks has gone quiet. Corporate proxies, load balancers, NAT tables, and home routers commonly cut idle TCP connections after 30 to 120 seconds, and they rarely send a close frame when they do it. That is textbook 1006, and a properly paced heartbeat is what keeps the link looking active enough to survive it.
4. Token expiry or a second login stealing your session
A Tradovate access token lives for about 90 minutes, and letting it expire while the socket is connected can drop the connection. Worse, Tradovate allows only two concurrent sessions per account, and creating a third closes the oldest. If another script, a manual test, or a fresh login flow requests a new token for the same account, it can boot the session your socket is riding on, again showing up as a 1006. The safe pattern is to renew the token on a timer rather than logging in again, which keeps the existing session, and the socket, alive.
5. Network instability and reconnect storms
Plain network drops cause 1006 too: a flaky Wi-Fi link, a VPS with a noisy neighbor, a brief ISP hiccup. That much is unavoidable. The trap is how you respond. If your reconnect logic fires in a tight loop the moment it sees a close, you can bury Tradovate's endpoint in connection attempts, trip its request penalty, and turn a two-second blip into a multi-minute lockout. Backoff is not optional here.
How to Fix Close Code 1006: Step-by-Step
Wrap the socket and reconnect on every close
Own the whole lifecycle
Put your WebSocket inside a class or function that owns the whole lifecycle: connect, authorize, subscribe, heartbeat, and teardown.
Attach a single close handler
Attach a single close handler that fires on any close, clean or abnormal. Do not branch on the code. A 1000 and a 1006 both mean “the socket is gone, rebuild it.”
Clear the dead socket and schedule a reconnect
On close, drop all references to the dead socket, clear its heartbeat timer, and schedule a reconnect. Never reuse a closed socket object.
Save your state before it tears down
Before you tear it down, save the state you need to restore: which contracts you were subscribed to, whether a user-sync request was open, and your current token.
Keep the heartbeat alive
Start the heartbeat once authorized
As soon as your authorize frame is accepted, start sending an empty [] frame every 2.5 seconds.
Move browser timers into a web worker
If you are in a browser, move that timer into a web worker so a background tab cannot throttle it. On Node, a plain interval is fine.
Watch the far side too
Watch the far side too. If you go more than a few seconds with no frame at all from the server, treat the connection as dead and force a reconnect rather than waiting.

Re-authorize and re-subscribe the new socket
Wait for the open frame
Wait for the o open frame on the fresh socket. Only then is it ready to authenticate.
Send the authorize frame again
Send the authorize frame again with a valid access token, formatted exactly as the current API docs specify (the request keyword, an id, then the token, with the correct blank-line separators).
Replay every subscription
Once authorization succeeds, replay every subscription from your saved state: quote subscriptions, user-sync requests, and anything else the old socket was carrying.
Reconcile after the gap
Reconcile after the gap. Pull current positions and working orders so your view matches reality, since events may have fired while you were disconnected.
Renew the token on a timer, do not spawn a new session
Store expiration and set a renew timer
When you first log in, store the token's expiration and set a timer to renew it roughly 15 minutes before it lapses.
Renew through the renew endpoint
Renew through the renew endpoint, which extends your session without a fresh login. The connected socket keeps working with nothing further required.
Never spawn a second login
Never kick off a brand-new login for an account that already has a live socket. That can push you past the two-session limit and drop the socket you are trying to protect.

Back off with jitter
Start at one to two seconds
On the first reconnect, wait one to two seconds. On each further failure, roughly double the delay.
Add a small random offset
Add a small random offset to every delay so many clients do not all retry on the same tick and hammer the endpoint together.
Cap the maximum
Cap the maximum at 30 to 60 seconds. A capped, jittered backoff recovers fast from a blip without becoming a self-inflicted rate-limit lockout.
Troubleshooting Table
| Symptom | Likely cause | Fix |
|---|---|---|
| 1006 within seconds of switching browser tabs | Throttled heartbeat timer in a background tab | Run the [] heartbeat in a web worker or on Node |
| 1006 after a code pause or busy loop | Heartbeat stalled past ~2.5 seconds | Send [] on a reliable 2.5s interval, plus on incoming frames |
| 1006 after 30 to 120 seconds of quiet | Proxy, NAT, or firewall dropped an idle TCP link | Keep heartbeats flowing so the link never looks idle |
| 1006 right around the 90-minute mark | Access token expired | Renew the token ~15 minutes before it lapses |
| 1006 the moment you log in elsewhere | New session bumped you past the 2-session limit | Renew instead of re-logging in; one session per account |
| 1006 storms, then a long lockout | Tight reconnect loop tripped the request penalty | Exponential backoff with jitter, capped at 30 to 60s |
Prevent This with PickMyTrade
A reconnect layer, a throttle-proof heartbeat, a token renewer, and a subscription replay are a lot of plumbing to get right by hand, and one missed edge case drops your orders at the worst moment. PickMyTrade sits between TradingView and Tradovate and handles all of it for you:
- Managed WebSocket connections with automatic reconnect and re-authorization, so a 1006 becomes a blip instead of an outage.
- Always-on heartbeats that run server-side, well clear of browser tab throttling.
- Automatic token renewal and one clean session per account, so a 90-minute expiry or a stray second login never knocks you offline.
- Rate-limit-safe reconnects with sensible backoff, so recovery never turns into a request-penalty lockout.
The payoff: your TradingView alerts reach Tradovate reliably without you writing or babysitting a single line of WebSocket lifecycle code.
Never Let a 1006 Take You Offline
PickMyTrade manages the reconnect, re-authorization, and heartbeat logic for you, so a dropped Tradovate WebSocket costs you a second, not a session.
Start Your Free 5-Day TrialFrequently Asked Questions
Code 1006 is an abnormal closure. The underlying TCP connection dropped without either side sending a proper WebSocket close frame, so your library reports 1006 with no reason text. It is a symptom, not a root cause: something below the WebSocket layer (a timeout, a reset, a missed heartbeat, or a dropped network) killed the pipe before a clean 1000 close could happen.
The usual triggers are missed heartbeats (Tradovate expects an empty array frame roughly every 2.5 seconds), an idle proxy or firewall timing out the TCP connection, the access token expiring or being invalidated by a second login, or plain network instability on a VPS or home connection. Because 1006 hides the exact reason, the practical fix is to reconnect and re-authorize automatically instead of chasing one cause.
About every 2.5 seconds. Once your authorize frame is accepted, the client should send an empty JSON array frame written as [] on that interval. If those heartbeats stall, the server can stop sending data or close the connection outright, which often surfaces as a 1006 on your end.
Yes. A brand-new socket starts unauthenticated. After it opens you have to send the authorize frame with a valid access token again, then re-send every subscription (quotes, user sync, and so on). Subscriptions and authorization do not carry over from the socket that just died.
Renewing does not. Calling the renew endpoint keeps your existing session valid, and the already-connected socket stays up with nothing further to do. What does drop it is requesting a brand-new token with a fresh login: Tradovate allows only two concurrent sessions, so a new session can boot the older one your socket is riding on and you see a 1006.
Chrome and other browsers throttle setInterval and setTimeout in inactive tabs to save power. That throttling delays your 2.5-second heartbeat, the server sees a stalled connection, and the socket closes with 1006. Running the heartbeat in a web worker or on a Node process, rather than a background tab, avoids the throttle.
Use exponential backoff with a little random jitter. Start around one to two seconds, roughly double the delay on each failed attempt, add a small random offset so many clients do not retry in lockstep, and cap the maximum at 30 to 60 seconds. Reconnecting in a tight loop can trip Tradovate's request penalty and make the outage last longer.
No connection is immune to 1006, because networks, proxies, and servers occasionally drop long-lived sockets. The goal is not to eliminate it but to make it a non-event: keep heartbeats flowing, detect the close instantly, reconnect and re-authorize on a backoff, and re-subscribe. Done right, a 1006 costs you a second or two instead of taking your bot offline.
This guide is for educational and informational purposes only and is not financial, investment, or trading advice. Trading futures and other leveraged products carries a substantial risk of loss and is not suitable for every investor. PickMyTrade is an independent third-party automation platform and is not affiliated with, endorsed by, or sponsored by Tradovate, Inc. or Bookmap. All related names, logos, and trademarks are the property of their respective owners. Platform features and steps change over time, so always confirm the current process in the official platform documentation before acting.