Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.chainstack.com/llms.txt

Use this file to discover all available pages before exploring further.

TLDR:
  • WebSocket close code 1009 (MESSAGE_TOO_BIG) fires when the inbound frame exceeds the client’s payload cap — common on Solana blockSubscribe because mainnet blocks can be tens of MiB.
  • Python websockets defaults to a 1 MiB cap (max_size=2**20). Raise or disable it on the connect() call.
  • Node ws defaults to a 100 MiB cap (maxPayload). Usually fine, but disable it (Infinity) for full blocks.
  • Raising the cap stops 1009, but blockSubscribe itself is unstable upstream. For production, use Yellowstone gRPC Geyser.
blockSubscribe is marked unstable by the Solana team. The fixes below address client-side 1009 only — for production, use Yellowstone gRPC Geyser instead.

What close code 1009 means

Per RFC 6455 §7.4.1, close code 1009 (MESSAGE_TOO_BIG) signals that an endpoint received a frame or message larger than it is willing or able to process. It is a defensive cap meant to protect the client from resource exhaustion or a hostile server flooding it with oversized frames. On Solana, blockSubscribe pushes the full block payload — transactions, inner instructions, log messages, balance deltas. On Mainnet this is routinely well above 1 MiB and can climb past 10 MiB during heavy activity. A library default of 1 MiB will drop the connection on the first large block. The symptom differs by library:
  • Python websockets raises PayloadTooBig, then closes with code 1009.
  • Node ws emits a close event with code === 1009.
Code 1006 (“abnormal closure”) often shows up on the same workload after the 1009 fix — that one is the upstream instability, not a payload-size issue. Raising the client cap will not fix 1006; switching to Geyser will.

Python: websockets default limit

The websockets library sets max_size = 2**20 bytes (1 MiB) per message and max_queue = 32 buffered messages. Any inbound frame larger than max_size triggers:
websockets.exceptions.PayloadTooBig: over size limit (length > max_size)
ConnectionClosedError: sent 1009 (message too big)

Fix

Pass max_size to connect() — raise it, or set None to disable the cap:
import asyncio
import json
import websockets

WS_URL = "wss://solana-mainnet.core.chainstack.com/AUTH_KEY"

async def main():
    async with websockets.connect(WS_URL, max_size=None) as ws:
        await ws.send(json.dumps({
            "jsonrpc": "2.0",
            "id": 1,
            "method": "blockSubscribe",
            "params": ["all", {"encoding": "json", "transactionDetails": "full", "showRewards": False, "maxSupportedTransactionVersion": 0}],
        }))
        async for msg in ws:
            print(msg)

asyncio.run(main())
Guidance:
  • max_size=None — disable the cap entirely.
  • max_size=10 * 1024 * 1024 — 10 MiB ceiling, a reasonable headroom for blockSubscribe.
Keep max_queue at its default unless you have a slow consumer; raising it just hides backpressure.

Node.js: ws default limit

The ws library defaults to maxPayload = 100 * 1024 * 1024 (100 MiB), which usually covers Solana blocks. If you ever see code 1009, raise the cap or disable it:
const WebSocket = require("ws");

const WS_URL = "wss://solana-mainnet.core.chainstack.com/AUTH_KEY";

const ws = new WebSocket(WS_URL, {
  maxPayload: Infinity, // disable cap
  // or: maxPayload: 10 * 1024 * 1024 // 10 MiB
});

ws.on("open", () => {
  ws.send(JSON.stringify({
    jsonrpc: "2.0",
    id: 1,
    method: "blockSubscribe",
    params: ["all", { encoding: "json", transactionDetails: "full", showRewards: false, maxSupportedTransactionVersion: 0 }],
  }));
});

ws.on("message", (data) => console.log(data.toString()));
ws.on("close", (code, reason) => console.error("closed", code, reason.toString()));
Guidance:
  • maxPayload: 0 or Infinity — no cap.
  • maxPayload: <bytes> — custom cap. Set it to your worst-case block size with headroom.

Trimming the payload instead

If you can live with less detail, narrow what blockSubscribe returns. This also makes the stream noticeably more stable — our internal load testing showed transactionDetails: "signatures" survives 1,000+ concurrent clients while "full" started disconnecting after ~40 seconds at a single client.
  • transactionDetails: "signatures" — drop full transaction bodies, keep signatures only. Most reliable option if you don’t need the full body.
  • transactionDetails: "none" — block metadata only, no transactions.
  • showRewards: false — skip the rewards array.
  • Filter to a single program via {"mentionsAccountOrProgram": "<pubkey>"} in params[0] — only blocks containing that program’s transactions are pushed.
See the blockSubscribe reference for the full parameter list.

Production path: Yellowstone gRPC Geyser

Even after raising the client cap, blockSubscribe itself is upstream-flagged as unstable on busy programs. Our load tests show connections drop within ~40 seconds even at 1 concurrent client when transactionDetails: "full" is used. Symptoms include 1006 (abnormal closure) closes and silently dropped notifications — not just the 1009 case the fixes above address. The recommended production path is Yellowstone gRPC Geyser — a Chainstack add-on that delivers the same data (blocks, transactions, account updates, slots) over a gRPC stream, with no RFC-6455 payload caps and without the upstream blockSubscribe instability. Existing blockSubscribe consumers migrate to a gRPC subscribe call against the Geyser endpoint. See:

Billing note

Each blockSubscribe push counts as 1 request unit. On Mainnet that is one RU per slot — roughly two per second when the network is healthy. Factor the subscription cost into your plan before pointing a long-running consumer at blockSubscribe.

See also

Last modified on May 22, 2026