Polygon Pub/Sub WebSocket — /ws/polygon
Real-time Polygon JSON-RPC pub/sub over WebSocket: subscribe with `eth_subscribe`, receive `eth_subscription` notifications until you `eth_unsubscribe`.
/ws/polygon is a JSON-RPC pub/sub WebSocket for Polygon. It uses the standard
eth_subscribe / eth_unsubscribe wire shape — identical to the
Ethereum WS channel. You open one WebSocket connection, send an
eth_subscribe frame naming one of four subscription types, and the server
replies with a hex subscription id. From then on the server pushes
eth_subscription notification frames carrying that id plus the event payload,
until you send eth_unsubscribe with the id (or the socket closes).
This channel is eth-base only. Polygon-specific bor_subscribe* methods are
not yet supported here — sending one is rejected as an unknown method
(forbidden, WS close 4003). The four supported subscription types are:
| Subscription type | What it pushes | Minimum tier |
|---|---|---|
newPendingTransactions | Mempool transaction hashes (or full bodies) | free |
logs | Filtered log events | basic |
newHeads | A header for every new block | pro |
syncing | Node sync-state transitions | pro |
Subscribing below the required tier closes the connection with WS code 4003
and a WSErrorFrame whose error is forbidden (the frame
carries current_tier / required_tier / upgrade_url so you can render an
upgrade prompt without another call).
Parameters
The client sends a JSON-RPC frame. eth_subscribe takes a params array of 1–2
items: the subscription type, plus an optional type-specific options object.
eth_subscribe frameobjectjsonrpcstringrequired"2.0".idinteger | stringrequiredmethodstringrequired"eth_subscribe".paramsarray (1–2 items)requiredparams[0] = subscription type; params[1] = optional filter/options.eth_unsubscribe frameobjectjsonrpcstringrequired"2.0".idinteger | stringrequiredmethodstringrequired"eth_unsubscribe".paramsarray (exactly 1)requiredparams[0] = the hex subscription id returned by the subscribe ack.Response
The subscribe ack returns the hex subscription id:
Subsequent newHeads notifications:
{
"jsonrpc": "2.0",
"method": "eth_subscription",
"params": {
"subscription": "0x9cef478923ff08bf67fde6c64013158d",
"result": {
"number": "0x3b2f1a0",
"hash": "0x8a1f...c2",
"parentHash": "0x77be...01",
"nonce": "0x0000000000000000",
"sha3Uncles": "0x1dcc...47",
"logsBloom": "0x0000...00",
"transactionsRoot": "0xab32...9f",
"stateRoot": "0xdd91...0c",
"receiptsRoot": "0x56aa...e1",
"miner": "0x0000000000000000000000000000000000000000",
"difficulty": "0x1",
"extraData": "0xd682...00",
"gasLimit": "0x1c9c380",
"gasUsed": "0x8f2c11",
"timestamp": "0x66 4f 0a",
"baseFeePerGas": "0x4a817c800"
}
}
}A newPendingTransactions notification (default mode) carries a tx hash string:
{
"jsonrpc": "2.0",
"method": "eth_subscription",
"params": {
"subscription": "0x1f2e3d4c5b6a79880123456789abcdef",
"result": "0x3a7b...e9"
}
}jsonrpcstring"2.0".idinteger | stringid.resultstringeth_unsubscribe.Errors
Before closing the connection, the server may send a WSErrorFrame whose
error field names the reason, then closes with the matching WS close code.
| Close code | error | When it happens |
|---|---|---|
4001 | unauthorized | Missing/invalid credentials on the upgrade (no Bearer / api-key / auth frame). |
4003 | forbidden | Subscription type requires a higher tier, or an unknown/unsupported method (e.g. a bor_subscribe* call — not yet supported here). |
4029 | rate_limited | Per-tier RPS exceeded; retry_after_sec / limit_rps indicate when to retry. |
4030 | trial_expired | Trial period has ended; upgrade_url points at checkout. |
Example forbidden frame (subscribing to newHeads on the free tier):
{
"error": "forbidden",
"message": "subscription 'newHeads' requires tier 'pro'",
"current_tier": "free",
"required_tier": "pro",
"method": "newHeads",
"category": "polygon_ws_pubsub",
"upgrade_url": "https://triport.io/upgrade?tier=pro"
}See the shared errors reference for the full WSErrorFrame
envelope and all fields.
Examples
JavaScript (fetch / browser WebSocket)
const ws = new WebSocket(
`wss://ws.triport.io/ws/polygon?api-key=${TRIPORT_API_KEY}`
);
ws.addEventListener("open", () => {
ws.send(JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "eth_subscribe",
params: ["newHeads"],
}));
});
let subId;
ws.addEventListener("message", (ev) => {
const msg = JSON.parse(ev.data);
if (msg.id === 1) {
subId = msg.result; // hex subscription id
return;
}
if (msg.method === "eth_subscription") {
console.log("new head", msg.params.result.number);
}
});
// later: ws.send(JSON.stringify({
// jsonrpc: "2.0", id: 2, method: "eth_unsubscribe", params: [subId],
// }));TypeScript SDK (@triport/sdk)
import { TriportClient } from "@triport/sdk";
const client = new TriportClient({ apiKey: process.env.TRIPORT_API_KEY! });
const sub = await client.polygon.ws.subscribe("newHeads");
sub.on("data", (head) => {
console.log("new head", head.number);
});
// later
await sub.unsubscribe();Python (triport-sdk)
import os
from triport import TriportClient
client = TriportClient(api_key=os.environ["TRIPORT_API_KEY"])
with client.polygon.ws.subscribe("logs", {
"address": "0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270",
}) as sub:
for log in sub:
print(log["transactionHash"], log["topics"])Notes
- Wire shape is identical to Ethereum. This channel mirrors
/ws/ethexactly — sameeth_subscribe/eth_unsubscribemethods, same four subscription types, sameeth_subscriptionnotification envelope and tier gating. Code written for one channel works on the other by swapping the path. bor_subscribe*is not yet supported. The spec marks/ws/polygonas eth-base only; Polygon's Bor-specific subscriptions will be added in a later revision. Until then, calling one returnsforbidden(close4003).- One id per subscription. A single connection can hold multiple
subscriptions; track each returned
resultid so you caneth_unsubscribeselectively. - Disconnect releases everything. Closing the socket cancels all of its subscriptions — no explicit unsubscribe is needed on teardown.
- Authentication options. Prefer the
Authorization: Bearerheader on the upgrade request. Browser WebSocket clients that cannot set headers may use?api-key=or a first-frameauthmessage instead.