The status message the server sends before it closes a Massive WebSocket tells you which of three common causes you hit, and the one people chase longest is the one that is not a fault at all. A disconnect with no status message is usually a slow consumer. A disconnect preceded by max_connections means the account was already at its allowance for that cluster. A disconnect at the same time every night is the daily reset, and the fix is to reconnect. An incident on our side can drop connections too, so if nothing changed on your side, check massive.com/system and massive-status.com before you rewrite your client.
Applies to
- Plans: every paid subscription.
- Feeds: every cluster under wss://socket.massive.com.
- Asset classes: stocks, options, indices, futures, forex and crypto.
How to tell the three apart
- Log every message whose ev is status, not just the ones during connect. The cause arrives as a status message, and a client that only parses data events never sees it.
- No status message before the close usually means a slow consumer. Check it: a socket receive queue that kept holding data before the close means your client fell behind, as Why is my Massive WebSocket data running behind, and is it my client or Massive? shows. If the queue stayed near empty and nothing changed on your side, look at massive.com/system and massive-status.com for an incident.
- {"ev":"status","status":"max_connections","message":"Maximum number of websocket connections exceeded..."} means the cluster's allowance was already full.
- A close at the same clock time every night, with no status message and no pattern in your own load, is the daily reset rather than either of the other two.
The nightly reset
Connections are reset shortly after midnight Eastern on the stocks, options, indices, forex and crypto clusters. Futures is not reset on weeknights, because midnight Eastern falls in the middle of a session; its connections are restarted once a week instead, early on Saturday morning Eastern. Either way this is routine rather than a fault, so a long-running client needs reconnect logic whatever else it does.
Reconnect after a pause rather than immediately. The server takes roughly 10 to 30 seconds to finish releasing a connection it has dropped, so a client that retries instantly can be refused for holding the allowance it just lost. Waiting out that window, or holding a second connection for redundancy, avoids a reconnect loop that looks like an entitlement problem.
Fixing a slow consumer
To fix a slow consumer, read packets off the network the moment they arrive, put them on a queue, and parse them on a different thread. The disconnect happens because the server buffers what you have not read and gives up when that buffer is full, so anything that blocks your read loop, JSON parsing, database writes, logging, will eventually cost you the connection.
Two things make this much more likely: subscribing to a high-volume channel such as Q.* on stocks or Options Quotes, and parsing in the same thread that reads. A single message can carry more than 25 events, so the work per read is uneven and a loop that keeps up on average can still fall behind in a burst. You can see the buffer limit on the connections tab of your dashboard.
Fixing a full allowance
Fixing a full allowance starts with knowing which connection dies, and that depends on the cluster, so do not reason from which process started last. Tested with two connections on one key: on /stocks, /futures, /indices and /forex the older connection received max_connections and was closed while the new one kept streaming; on /options the opposite happened, and the new connection was refused at its auth while the old one carried on.
The consequence is the same either way. A service that reconnects in a loop is competing with itself for one slot, and on the clusters where the newcomer wins it will keep knocking its own healthy stream offline, which looks random because the connection that dies is not the one that just started.
Count the processes using any API key on your account before changing anything else, because every key on the account shares the connection allowance. Two copies of a service, or a local script left running next to production, are the usual answer. If you genuinely need both, start a chat at massive.com/contact and ask for another connection on that cluster.
Pings and keepalive
Pings on a Massive WebSocket come from the server. Every cluster under wss://socket.massive.com, /stocks, /options, /indices, /futures, /forex and /crypto, sends a WebSocket ping frame every 5 seconds. The WebSocket standard requires the other end to answer every ping with a pong (RFC 6455, section 5.5.2), so a standards-compliant client library answers the server's pings without any code from you.
Many client libraries also send pings of their own and close the connection when the pong comes back late. The Python websockets library, which the official Massive Python client is built on, sends a ping every 20 seconds by default and closes the connection if no pong arrives within 20 seconds. Its documentation names two reasons a pong can be late: a poor network, and more traffic than the receiving side can keep up with. So when a library's keepalive closes the connection, check your receive queue first: a queue that keeps holding data means your client is behind on reading. If the queue is near empty and nothing changed on your side, send us the details listed below.
Close codes 1006, 1008 and 1011
Close codes 1006, 1008 and 1011 each mean something different on a Massive WebSocket, and your client library reports the code with the disconnect:
- 1006 is never sent by either end. RFC 6455 reserves it for a library to report a connection that closed without a close frame (RFC 6455, section 7.4.1), so a 1006 says the socket dropped, not who dropped it. Your own library's keepalive timeout can also show up as 1006, as described under 1011 below. On /options, where a connection over the allowance is the one refused, the refused connection receives max_connections and then closes without a close frame, which a library reports as 1006.
- 1008 is the policy-violation code in the same section of RFC 6455. On /forex, a connection being closed for a newer one receives the max_connections status message and then a close frame with code 1008 and an empty reason.
- 1011 with the reason keepalive ping timeout is your own client library closing the connection because its ping got no pong in time, as described under pings and keepalive. The Python websockets library sends that close frame and stops reading without waiting for a reply, so it reports the close code as 1006, and 1011 appears only in its message: sent 1011 (internal error) keepalive ping timeout; no close frame received. Search your logs for keepalive ping timeout rather than for 1011.
Reconnecting so the stream resumes
Reconnecting so the stream resumes takes more than a new socket, because subscriptions belong to a connection and do not carry over. After a subscribed /stocks connection closes, a new connection that authenticates but does not subscribe receives no data, and re-sending the same subscribe brings one acknowledgement per parameter, after which the events resume.
- Reconnect after a pause, as described under the nightly reset, so the server has finished releasing the old connection.
- Open a new socket, authenticate, and wait for auth_success before you subscribe.
- Re-send every subscription. Keep the list in your own code, because the new connection starts with none.
- Confirm one subscribed to: status per parameter. A parameter with no acknowledgement has not taken, so re-send it, and reconnect if it still gets none.
- Keep a watchdog: if a channel that should be busy delivers nothing during market hours, close the connection and reconnect rather than wait.
The official Python client re-authenticates and re-sends its subscriptions after a connection closes with an error, up to max_reconnects times (5 by default) before it raises the error. That count is a total for the life of the connect() call, not a limit per outage, because a successful reconnect does not reset it, so a long-running client raises on its sixth error close. Pass max_reconnects=None to remove the limit, or run your own reconnect loop. The client does not check the acknowledgements, so a watchdog is still worth adding.
What to send us
What to send us about a Massive WebSocket disconnect you cannot explain is what lets us find the connection. Start a chat at massive.com/contact with:
- The connection ID of the connection that dropped, from the connections tab of your WebSocket dashboard, not the ID of the one you opened afterwards.
- The time of the disconnect, in UTC.
- The close code your library reported, and the last status message before it.
If you see an error
{"ev":"status","status":"max_connections","message":"Maximum number of websocket connections exceeded. You have reached the connection limit for your account. Please contact support at https://massive.com/contact to increase your limit."} means your account is at its allowance for that cluster. On most clusters this arrives on an older connection that is being closed for a newer one; on /options it arrives on the new connection, which is refused while the existing one keeps running.
A close with no preceding status message, on a connection that had been streaming fine, is usually a slow consumer. Reconnecting works and then fails again in the same way, and a socket receive queue that keeps holding data before each close confirms it. If the queue stays near empty and nothing changed on your side, check massive.com/system and massive-status.com for an incident.
Related
- How many Massive WebSocket connections can I use at one time?
- How much data is streamed through Massive's WebSockets?
- How many tickers can you subscribe to on a single Massive WebSocket connection?
- How can I connect to a Massive WebSocket?
- Why is my Massive WebSocket data running behind, and is it my client or Massive?

