Logo
New RPC users get 35% off their first monthView the offer
OnFinality Learn
Network & Protocol Guides12 min read

Polygon WebSocket RPC: WSS Endpoints, eth_subscribe, and Reconnection

Learn how to connect to Polygon PoS WebSocket RPC endpoints, subscribe to newHeads and logs, handle reconnections, and avoid common pitfalls.

TL;DR

This guide explains how to use Polygon WebSocket RPC for real-time data on Polygon PoS, covering WSS endpoints, eth_subscribe methods, connection lifecycle, and a runnable Node.js example with reconnection logic.

Direct Answer: What You Need to Know About Polygon WebSocket RPC

Polygon WebSocket RPC (WSS) lets you receive real-time blockchain events from the Polygon PoS network. The primary WSS endpoint for Polygon mainnet is wss://polygon-bor-rpc.publicnode.com, and for the Amoy testnet it's wss://polygon-amoy-bor-rpc.publicnode.com. You can also use provider-specific endpoints from services like OnFinality, Ankr, or Dwellir. With eth_subscribe, you can listen for new blocks, pending transactions, and logs matching specific filters. This guide covers the mechanics, a runnable example, and troubleshooting for common disconnection issues.

Important: This article is about the Polygon blockchain (MATIC/POL). It is not about polygon.io, a separate stock-market data provider. If you're looking for financial market data, that's a different service.

  • Mainnet WSS: wss://polygon-bor-rpc.publicnode.com
  • Testnet WSS (Amoy): wss://polygon-amoy-bor-rpc.publicnode.com
  • Methods: eth_subscribe with newHeads, logs, newPendingTransactions, syncing

Polygon Architecture: Bor and Heimdall

Polygon PoS uses a two-layer architecture: Bor (the block producer layer, EVM-compatible) and Heimdall (the validator layer, based on Tendermint). Bor produces blocks at a fast cadence (approximately 2 seconds), while Heimdall periodically checkpoints these blocks to Ethereum. This means that when you subscribe to newHeads on a Polygon WSS endpoint, you'll receive a notification for every Bor block, but the data may not be immediately final until a Heimdall checkpoint is confirmed. For most use cases, this is fine, but be aware of potential reorgs on Bor.

The block time on Polygon PoS is around 2 seconds, so you can expect a high volume of newHeads notifications. This is important for rate limiting and connection stability.

  • Bor: produces blocks every ~2 seconds
  • Heimdall: checkpoints blocks to Ethereum, providing finality
  • Data freshness: newHeads is real-time but not final until checkpoint

WSS Endpoints for Polygon Mainnet and Testnet

You can use public endpoints or provider endpoints. Public endpoints are free but often have rate limits and may drop idle connections. Provider endpoints (like OnFinality) offer higher reliability and dedicated support. Always check the latest documentation for up-to-date URLs. The authoritative list of Polygon RPC endpoints, including WebSocket URLs, is maintained in the official Polygon documentation.

Here are some commonly used WSS endpoints (documented by the respective providers):

  • PublicNode: wss://polygon-bor-rpc.publicnode.com (mainnet), wss://polygon-amoy-bor-rpc.publicnode.com (Amoy testnet)
  • Ankr: wss://rpc.ankr.com/polygon (mainnet), wss://rpc.ankr.com/polygon_amoy (testnet)
  • Dwellir: wss://polygon-rpc.dwellir.com (mainnet)
  • OnFinality: available via your API key, see API service

eth_subscribe: Methods and Payloads

The eth_subscribe method allows you to subscribe to real-time events. The standard subscriptions are:

newHeads: Notifies you of new block headers. The payload includes the block number, hash, parent hash, timestamp, and other header fields.

logs: Notifies you of logs that match a filter (address and topics). This is useful for tracking contract events.

newPendingTransactions: Notifies you of transaction hashes that enter the transaction pool. Note that this can be high-volume and may not be supported by all providers.

syncing: Notifies you when the node starts or stops syncing. Rarely used.

  • Request format: {"jsonrpc":"2.0","id":1,"method":"eth_subscribe","params":["newHeads"]}
  • Response: {"jsonrpc":"2.0","id":1,"result":"0x9cef..."} (subscription ID)
  • Notification: {"jsonrpc":"2.0","method":"eth_subscription","params":{"subscription":"0x9cef...","result":{...}}}
// Example subscription request for logs
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "eth_subscribe",
  "params": ["logs", {"address": "0x...", "topics": ["0x..."]}]
}

Connecting with ethers.js and web3.js

You can connect to a Polygon WSS endpoint with either ethers.js or web3.js. In ethers.js, create a WebSocketProvider and register handlers for 'open', 'message', 'error', and 'close' so you can react to the connection lifecycle instead of silently losing events. With web3.js 1.x, the equivalent is new Web3.providers.WebsocketProvider(url) exposed through a Web3 instance. Both libraries expose eth_subscribe through a provider-level on/subscription API, and both expect the JSON-RPC notification format shown in the previous section.

A common mistake is treating a WebSocket provider like a stateless HTTP provider and not handling reconnects. The example below subscribes to newHeads and to a logs filter for a specific contract, and it re-establishes every subscription when the socket closes. Keep the filter narrow: subscribe to only the addresses and topics you actually consume, because each log notification that matches is delivered on every matching block. The notification and subscription formats follow the Ethereum JSON-RPC WebSocket specification, which Polygon implements.

const { ethers } = require('ethers');

const WSS_URL = 'wss://polygon-bor-rpc.publicnode.com';
let provider;
let subscriptionIds = [];

async function connect() {
  console.log('Connecting to Polygon WSS...');
  provider = new ethers.WebSocketProvider(WSS_URL);

  provider.on('error', (error) => {
    console.error('WebSocket error:', error);
    reconnect();
  });

  provider.on('close', () => {
    console.log('WebSocket closed. Reconnecting...');
    reconnect();
  });

  // Subscribe to newHeads
  const headSub = await provider.send('eth_subscribe', ['newHeads']);
  subscriptionIds.push(headSub);
  provider.on('newHeads', (head) => {
    console.log('New head:', head.number);
  });

  // Subscribe to logs (example: USDT transfer events)
  const logSub = await provider.send('eth_subscribe', ['logs', {
    address: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F', // USDT on Polygon
    topics: [ethers.id('Transfer(address,address,uint256)')]
  }]);
  subscriptionIds.push(logSub);
  provider.on('logs', (log) => {
    console.log('Log:', log.transactionHash);
  });

  console.log('Subscriptions active:', subscriptionIds);
}

function reconnect() {
  if (provider) {
    provider.removeAllListeners();
    provider.destroy();
  }
  setTimeout(connect, 5000); // wait 5 seconds before reconnecting
}

connect();

// Expected output: logs of new block numbers and transaction hashes
// The script will keep running and reconnect on disconnection.

// Expected output on each new Bor block (~2s):
// { type: 'newHeads', number: 57800000, hash: '0x...', parentHash: '0x...', ... }
// For each matching log: { type: 'logs', logIndex: '0x0', address: '0x...', topics: [...], data: '0x...' }

Reconnection and Resubscription Strategies

Public WSS endpoints often drop idle connections after a timeout (e.g., 60 seconds) or when the client exceeds rate limits. To maintain a reliable stream, you need to implement a heartbeat and reconnection logic. The example above reconnects on close, but you should also send a ping frame periodically to keep the connection alive. Many libraries do this automatically, but you can also use a timer to send eth_subscribe again if you haven't received any notifications within a certain interval.

When reconnecting, you must resubscribe to all subscriptions because the previous subscription IDs are invalid. Also, be prepared to handle duplicate notifications: if you reconnect and resubscribe, you might receive the same block or log twice. Use idempotent processing (e.g., track processed block numbers or transaction hashes) to avoid double-counting.

Decide your tolerance to missed events before reconnecting. For a newHeads feed, a reorg-safe strategy is to track the produced block number, and on reconnect, rewind to head - 1 and replay any gap using eth_getBlockByNumber before resuming the live subscription. For logs, re-fetch the recent window with eth_getLogs and de-duplicate by transaction hash and log index. This catch-up step is what separates a monitor that recovers cleanly from one that silently loses a spike.

  • Heartbeat: send a ping every 30-60 seconds (e.g., provider.send('eth_subscribe', ['newHeads']) as a keepalive, or use a WebSocket ping frame)
  • Resubscribe: on reconnect, re-issue all subscriptions
  • Duplicate handling: maintain a set of seen block numbers or log hashes
  • Backoff: use exponential backoff for reconnection attempts to avoid overwhelming the server

Common Failures and Troubleshooting

Here are common issues you might encounter and how to fix them.

Subscription excess codes: Some providers return error codes like -32005 (limit exceeded) when you have too many subscriptions. Reduce the number of subscriptions or use a single filter with multiple addresses/topics.

Per-IP connection limits: Public endpoints often limit the number of concurrent connections per IP. If you hit this, use a provider with higher limits or rotate endpoints.

Block height lag: If your node is behind, you might receive stale data. Check your provider's sync status and consider using a dedicated endpoint.

  • Error -32005: Too many subscriptions. Consolidate filters.
  • Connection limit: Use a provider with higher limits or multiple endpoints.
  • Lag: Monitor block height and compare with a reference (e.g., Polygonscan).
  • Idle disconnects: Implement heartbeat and reconnection.
  • Rate limiting: Use a provider with higher rate limits or reduce request frequency.
  • If notifications stop but the socket stays open, assume the subscription died silently: send eth_subscribe again and confirm a new subscription id.
  • If the socket closes after ~60s of inactivity, it is an idle timeout: implement a heartbeat (a lightweight eth_blockNumber ping every 30s) rather than reconnecting blindly.
  • If you get -32005 (limit exceeded) on logs, you have too many subscriptions: consolidate into one filter with multiple addresses, or page by topic.
  • If you receive duplicate blocks after a reconnect, de-duplicate using the block number you last processed plus a replay of head - 1.
  • If data looks stale, compare your node's latest eth_blockNumber with the live Polygon value on Polygonscan before assuming the WSS endpoint is healthy.

Tradeoffs and Limitations

WebSocket RPC is ideal for real-time applications, but it has limitations. Public endpoints may not support newPendingTransactions due to high volume. Also, the logs subscription can be heavy if you subscribe to a popular contract like USDT. Use filters to narrow down the data.

For high-throughput production use, consider using a dedicated provider like OnFinality, which offers RPC pricing and API service with guaranteed uptime. Also, be aware that WebSocket connections are stateful, so you need to handle reconnections gracefully.

For more on disconnection fixes, see our generic WebSocket RPC disconnection fixes. For performance considerations, check Polygon RPC latency and performance.

Next Steps and Further Reading

Now that you understand Polygon WebSocket RPC, you can build real-time applications like transaction monitors, DEX arbitrage bots, or NFT mint trackers. For more details, refer to the official Polygon documentation at docs.polygon.technology.

Explore more guides on the OnFinality Learn hub. If you need help with Polygon RPC, see our Polygon RPC guidance (RPC Assistant). For general Polygon network info, visit Polygon network page.

Never Worry about Infrastructure Again

OnFinality takes away the heavy lifting of DevOps so you can build smarter and faster.

Get Started