BNB Smart Chain Pub/Sub WebSocket — /ws/bsc
Subscribe over WebSocket to real-time BNB Smart Chain events — new block headers, filtered logs, and pending-transaction hashes — using the standard eth_subscribe / eth_unsubscribe JSON-RPC shape. Ordinary JSON-RPC calls and batches work on the same socket.
- Network
- BNB Smart Chain (chain ID 56)
- Scope
- bsc:rpc (subscription type then gated by tier; see below)
- Auth
- x-token: $TRIPORT_API_KEY on the upgrade request (Authorization: Bearer also accepted), an auth frame, or ?api-key= query
- Rate limit
- Per subscription type: newPendingTransactions free · logs basic · newHeads pro. Category bsc_ws_pubsub
Endpoint and plans: BNB Smart Chain RPC endpoint.
Terms on this page: JSON-RPC.
/ws/bsc is the BNB Smart Chain JSON-RPC Pub/Sub channel. Open a single
WebSocket connection, send an eth_subscribe request naming one of three
subscription types, and the server replies with a hex-encoded subscription
id. From then on the server pushes eth_subscription notification frames
carrying that id and the event payload until you send eth_unsubscribe (or the
socket closes).
You can hold multiple subscriptions on one connection; each notification frame
identifies its source via params.subscription, so match incoming events to the
id you stored at subscribe time.
The same socket also serves ordinary request/response JSON-RPC: any non-subscribe
method (for example eth_call, eth_getLogs, eth_chainId) and JSON-RPC
batch arrays are proxied to the same backend as POST /rpc/bsc, under the
same scope and rate budget. This lets a client subscribe and query over one
connection.
The three subscription types unlock at different tiers. Attempting to subscribe
to a type above your plan returns a forbidden error frame followed by a WS
close 4003 — the frame includes current_tier, required_tier, and an
upgrade_url so you can prompt for an upgrade without a second call.
Parameters
The eth_subscribe request params array holds the subscription type in
params[0] and an optional, type-specific filter object in params[1].
logsobjectaddressstring | string[]optionaltopicsarrayoptionalnull (wildcard for that position).Response
Subscribe acknowledgement — result is the hex subscription id to keep:
Notification (newPendingTransactions):
{
"jsonrpc": "2.0",
"method": "eth_subscription",
"params": {
"subscription": "0x9cef478923ff08bf67fde6c64013158d",
"result": "0x68e63ee5b6f7e2b2e9e1cf0d2f5cda0c4f3a9b1d7e8c6a5b4c3d2e1f0a9b8c7d"
}
}Notification (newHeads):
{
"jsonrpc": "2.0",
"method": "eth_subscription",
"params": {
"subscription": "0x1a2b3c4d5e6f70819a0b1c2d3e4f5061",
"result": {
"number": "0x7cb5e92",
"hash": "0x4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c",
"parentHash": "0x9e8f7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f",
"nonce": "0x0000000000000000",
"sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"logsBloom": "0x00000000000000000000000000000000",
"transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"stateRoot": "0xdeadbeef00000000000000000000000000000000000000000000000000000000",
"receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"miner": "0x0000000000000000000000000000000000000000",
"difficulty": "0x2",
"extraData": "0x",
"gasLimit": "0x8583b00",
"gasUsed": "0x1c9c380",
"timestamp": "0x66f4c3a8",
"baseFeePerGas": "0x0"
}
}
}Notification (logs):
{
"jsonrpc": "2.0",
"method": "eth_subscription",
"params": {
"subscription": "0x7f6e5d4c3b2a19080716253443526170",
"result": {
"address": "0x55d398326f99059fF775485246999027B3197955",
"topics": [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
],
"data": "0x",
"blockNumber": "0x7cb5e92",
"transactionHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"transactionIndex": "0x0",
"blockHash": "0x4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c",
"logIndex": "0x0",
"removed": false
}
}
}jsonrpcstring"2.0".idinteger | stringid.resultstringeth_unsubscribe and match against incoming params.subscription.Errors
The server may send an application-level error frame immediately before closing the socket with one of the canonical close codes, or return a JSON-RPC error on the request itself (for subscribe-time validation). The channel shares the streaming close-code set; see the streaming overview.
| Code | Where | When it happens |
|---|---|---|
-32602 | JSON-RPC error on the subscribe reply | A logs subscription was sent with an empty or all-wildcard filter. |
-32005 | JSON-RPC error on the subscribe reply | The per-process logs subscription cap is reached. |
-32601 | JSON-RPC error on the subscribe reply | A subscription type is not enabled on this deployment (for example newPendingTransactions when the mempool source is not running). |
4001 (unauthorized) | WS close | Missing or invalid credentials on the upgrade / auth frame. |
4003 (forbidden) | WS close | Subscription type requires a higher tier, or the method is unknown. Frame carries current_tier, required_tier, method, and upgrade_url. |
4029 (rate_limited) | WS close | Per-tier limit reached. Frame carries retry_after_sec and limit_rps. |
Examples
JavaScript
WebSockets are not opened with fetch; use the browser/Node WebSocket API:
const ws = new WebSocket("wss://triport.io/ws/bsc", {
headers: { Authorization: `Bearer ${process.env.TRIPORT_API_KEY}` },
});
const subs = new Map(); // subscription id -> type
ws.addEventListener("open", () => {
ws.send(JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "eth_subscribe",
params: ["newHeads"],
}));
});
ws.addEventListener("message", (ev) => {
const msg = JSON.parse(ev.data);
if (msg.id === 1 && msg.result) {
subs.set(msg.result, "newHeads"); // store the hex id
} else if (msg.method === "eth_subscription") {
console.log("block", msg.params.result.number, "from", msg.params.subscription);
}
});Notes
- One id per subscription. Store the hex
resultfrom each acknowledgement; it is the only way to correlate notifications (params.subscription) and toeth_unsubscribelater. logsneeds a non-empty filter. This is a safeguard against a per-client firehose. Supply anaddress, atopicsselector, or both.- No
removed: trueon reorg. Log notifications carryremoved: false; the channel pushes the new canonical block's logs but does not retract orphaned ones. - Tier gating is per subscription type, not per connection — a free key can
open
newPendingTransactionsbut will be closed with4003if it triesnewHeads. - Same-socket JSON-RPC. Non-subscribe methods and batch arrays are answered
on the same socket by the
/rpc/bscbackend, under the same scope and budget. - Reconnects: subscriptions end with their connection and events are not
replayed; resubscribe and backfill over HTTPS after reconnecting. Ethereum
exposes the same shape on
/ws/eth, Polygon on/ws/poly. See the streaming overview for all channels.