Skip to main content

Ethereum WebSocket Guide

WebSocket connections provide real-time, bidirectional communication with the Ethereum blockchain, enabling your applications to receive instant updates for new blocks, transactions, and smart contract events without polling.

Why Use WebSockets?

Advantages over HTTP Polling

  • Real-time Updates: Instant notifications when events occur
  • Lower Latency: No polling delays
  • Reduced Bandwidth: Only receive data when it changes
  • Better Performance: Persistent connection reduces overhead
  • Event-Driven: React to blockchain events as they happen

Common Use Cases

  • Trading Applications: Real-time price feeds and order book updates
  • DeFi Dashboards: Live liquidity and yield changes
  • NFT Marketplaces: Instant sale and listing notifications
  • Wallet Applications: Balance updates and transaction confirmations
  • Analytics Platforms: Real-time blockchain metrics

Connection Setup

WebSocket Endpoints

wss://ethereum-mainnet.blockeden.xyz/<your-api-key>
wss://ethereum-sepolia.blockeden.xyz/<your-api-key>
wss://polygon-mainnet.blockeden.xyz/<your-api-key>
wss://arbitrum-mainnet.blockeden.xyz/<your-api-key>

Basic Connection

JavaScript (Browser/Node.js)

const ws = new WebSocket('wss://ethereum-mainnet.blockeden.xyz/<your-api-key>');

ws.onopen = function(event) {
console.log('WebSocket connected');
};

ws.onmessage = function(event) {
const data = JSON.parse(event.data);
console.log('Received:', data);
};

ws.onerror = function(error) {
console.error('WebSocket error:', error);
};

ws.onclose = function(event) {
console.log('WebSocket closed:', event.code, event.reason);
};

Python

import asyncio
import websockets
import json

async def connect():
uri = "wss://ethereum-mainnet.blockeden.xyz/<your-api-key>"

async with websockets.connect(uri) as websocket:
print("WebSocket connected")

# Subscribe to new heads
subscribe_msg = {
"jsonrpc": "2.0",
"method": "eth_subscribe",
"params": ["newHeads"],
"id": 1
}
await websocket.send(json.dumps(subscribe_msg))

# Listen for messages
async for message in websocket:
data = json.loads(message)
print(f"Received: {data}")

# Run the connection
asyncio.run(connect())

Go

package main

import (
"encoding/json"
"fmt"
"log"
"github.com/gorilla/websocket"
)

type SubscriptionRequest struct {
JSONRPC string `json:"jsonrpc"`
Method string `json:"method"`
Params []string `json:"params"`
ID int `json:"id"`
}

func main() {
url := "wss://ethereum-mainnet.blockeden.xyz/<your-api-key>"

conn, _, err := websocket.DefaultDialer.Dial(url, nil)
if err != nil {
log.Fatal("dial:", err)
}
defer conn.Close()

// Subscribe to new heads
req := SubscriptionRequest{
JSONRPC: "2.0",
Method: "eth_subscribe",
Params: []string{"newHeads"},
ID: 1,
}

if err := conn.WriteJSON(req); err != nil {
log.Fatal("write:", err)
}

// Read messages
for {
var msg map[string]interface{}
if err := conn.ReadJSON(&msg); err != nil {
log.Fatal("read:", err)
}
fmt.Printf("Received: %+v\n", msg)
}
}

Subscription Types

1. New Block Headers (newHeads)

Subscribe to new block headers as they're mined.

const subscribeToNewHeads = () => {
const subscription = {
jsonrpc: '2.0',
method: 'eth_subscribe',
params: ['newHeads'],
id: 1
};

ws.send(JSON.stringify(subscription));
};

// Handle new block headers
ws.onmessage = function(event) {
const data = JSON.parse(event.data);

if (data.method === 'eth_subscription' && data.params.subscription) {
const blockHeader = data.params.result;
console.log('New block:', {
number: parseInt(blockHeader.number, 16),
hash: blockHeader.hash,
timestamp: parseInt(blockHeader.timestamp, 16),
gasUsed: parseInt(blockHeader.gasUsed, 16),
gasLimit: parseInt(blockHeader.gasLimit, 16)
});
}
};

2. Pending Transactions (newPendingTransactions)

Subscribe to new pending transactions in the mempool.

const subscribeToPendingTransactions = () => {
const subscription = {
jsonrpc: '2.0',
method: 'eth_subscribe',
params: ['newPendingTransactions'],
id: 2
};

ws.send(JSON.stringify(subscription));
};

// Handle pending transactions
ws.onmessage = function(event) {
const data = JSON.parse(event.data);

if (data.method === 'eth_subscription' && data.params.subscription) {
const txHash = data.params.result;
console.log('New pending transaction:', txHash);

// Optionally fetch full transaction details
fetchTransactionDetails(txHash);
}
};

3. Smart Contract Events (logs)

Subscribe to specific smart contract events using log filters.

const subscribeToContractEvents = (contractAddress, topics) => {
const subscription = {
jsonrpc: '2.0',
method: 'eth_subscribe',
params: [
'logs',
{
address: contractAddress,
topics: topics
}
],
id: 3
};

ws.send(JSON.stringify(subscription));
};

// Subscribe to ERC-20 Transfer events
const ERC20_TRANSFER_TOPIC = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef';
subscribeToContractEvents('0xA0b86a33E6c0e4A2a2a5FB1C6A6D6a30BF8b6B3a', [ERC20_TRANSFER_TOPIC]);

// Handle contract events
ws.onmessage = function(event) {
const data = JSON.parse(event.data);

if (data.method === 'eth_subscription' && data.params.subscription) {
const log = data.params.result;
console.log('Contract event:', {
address: log.address,
topics: log.topics,
data: log.data,
blockNumber: parseInt(log.blockNumber, 16),
transactionHash: log.transactionHash
});
}
};

4. Synchronized Blocks (syncing)

Subscribe to synchronization status updates.

const subscribeToSyncing = () => {
const subscription = {
jsonrpc: '2.0',
method: 'eth_subscribe',
params: ['syncing'],
id: 4
};

ws.send(JSON.stringify(subscription));
};

// Handle sync status
ws.onmessage = function(event) {
const data = JSON.parse(event.data);

if (data.method === 'eth_subscription' && data.params.subscription) {
const syncStatus = data.params.result;
if (syncStatus === false) {
console.log('Node is fully synchronized');
} else {
console.log('Sync status:', {
startingBlock: parseInt(syncStatus.startingBlock, 16),
currentBlock: parseInt(syncStatus.currentBlock, 16),
highestBlock: parseInt(syncStatus.highestBlock, 16)
});
}
}
};

Advanced Usage Patterns

Managing Multiple Subscriptions

class EthereumWebSocketManager {
constructor(endpoint) {
this.endpoint = endpoint;
this.ws = null;
this.subscriptions = new Map();
this.messageHandlers = new Map();
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
}

connect() {
return new Promise((resolve, reject) => {
this.ws = new WebSocket(this.endpoint);

this.ws.onopen = () => {
console.log('WebSocket connected');
this.reconnectAttempts = 0;
resolve();
};

this.ws.onmessage = (event) => {
this.handleMessage(JSON.parse(event.data));
};

this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
reject(error);
};

this.ws.onclose = (event) => {
console.log('WebSocket closed:', event.code, event.reason);
this.handleReconnection();
};
});
}

subscribe(type, params, handler) {
const id = Date.now();
const subscription = {
jsonrpc: '2.0',
method: 'eth_subscribe',
params: [type, ...(params || [])],
id: id
};

this.ws.send(JSON.stringify(subscription));
this.messageHandlers.set(id, handler);

return id;
}

unsubscribe(subscriptionId) {
const unsubscribe = {
jsonrpc: '2.0',
method: 'eth_unsubscribe',
params: [subscriptionId],
id: Date.now()
};

this.ws.send(JSON.stringify(unsubscribe));
this.subscriptions.delete(subscriptionId);
}

handleMessage(data) {
if (data.method === 'eth_subscription') {
// Handle subscription data
const subscriptionId = data.params.subscription;
const handler = this.subscriptions.get(subscriptionId);
if (handler) {
handler(data.params.result);
}
} else if (data.id && this.messageHandlers.has(data.id)) {
// Handle subscription response
const handler = this.messageHandlers.get(data.id);
this.messageHandlers.delete(data.id);

if (data.result) {
this.subscriptions.set(data.result, handler);
}
}
}

handleReconnection() {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
const delay = Math.pow(2, this.reconnectAttempts) * 1000;

setTimeout(() => {
console.log(`Reconnecting... Attempt ${this.reconnectAttempts}`);
this.connect().then(() => {
// Resubscribe to all previous subscriptions
this.resubscribeAll();
});
}, delay);
}
}

resubscribeAll() {
// Implementation depends on your subscription tracking
console.log('Resubscribing to all previous subscriptions');
}
}

// Usage
const wsManager = new EthereumWebSocketManager('wss://ethereum-mainnet.blockeden.xyz/<your-api-key>');

wsManager.connect().then(() => {
// Subscribe to new blocks
wsManager.subscribe('newHeads', null, (blockHeader) => {
console.log('New block:', blockHeader);
});

// Subscribe to USDC transfers
wsManager.subscribe('logs', [{
address: '0xA0b86a33E6c0e4A2a2a5FB1C6A6D6a30BF8b6B3a',
topics: ['0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef']
}], (log) => {
console.log('USDC transfer:', log);
});
});

Real-time Trading Dashboard

class TradingDashboard {
constructor(wsEndpoint) {
this.wsManager = new EthereumWebSocketManager(wsEndpoint);
this.priceFeeds = new Map();
this.orderBookUpdates = new Map();
}

async start() {
await this.wsManager.connect();

// Subscribe to DEX events
this.subscribeToUniswapV3();
this.subscribeToSushiSwap();
this.subscribeToNewBlocks();
}

subscribeToUniswapV3() {
// Uniswap V3 Swap events
const SWAP_TOPIC = '0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67';

this.wsManager.subscribe('logs', [{
address: '0x1F98431c8aD98523631AE4a59f267346ea31F984', // Uniswap V3 Factory
topics: [SWAP_TOPIC]
}], (log) => {
this.handleUniswapSwap(log);
});
}

subscribeToSushiSwap() {
// SushiSwap Swap events
const SWAP_TOPIC = '0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822';

this.wsManager.subscribe('logs', [{
topics: [SWAP_TOPIC]
}], (log) => {
this.handleSushiSwap(log);
});
}

subscribeToNewBlocks() {
this.wsManager.subscribe('newHeads', null, (blockHeader) => {
this.updateBlockInfo(blockHeader);
});
}

handleUniswapSwap(log) {
// Parse Uniswap V3 swap data
const poolAddress = log.address;
const data = this.parseSwapData(log.data);

this.updatePriceFeed('uniswap', poolAddress, data);
}

handleSushiSwap(log) {
// Parse SushiSwap swap data
const poolAddress = log.address;
const data = this.parseSwapData(log.data);

this.updatePriceFeed('sushiswap', poolAddress, data);
}

updatePriceFeed(exchange, pool, swapData) {
const key = `${exchange}:${pool}`;
const currentFeed = this.priceFeeds.get(key) || { prices: [], volume: 0 };

currentFeed.prices.push({
price: swapData.price,
timestamp: Date.now(),
volume: swapData.amount
});

// Keep only last 100 prices
if (currentFeed.prices.length > 100) {
currentFeed.prices.shift();
}

currentFeed.volume += swapData.amount;
this.priceFeeds.set(key, currentFeed);

// Emit update to UI
this.emitPriceUpdate(exchange, pool, currentFeed);
}

parseSwapData(data) {
// Implementation depends on the specific DEX
// This is a simplified example
return {
price: parseFloat(data.slice(0, 66)),
amount: parseFloat(data.slice(66, 130))
};
}

emitPriceUpdate(exchange, pool, feed) {
// Send to frontend/dashboard
console.log(`Price update for ${exchange}:${pool}`, feed);
}

updateBlockInfo(blockHeader) {
const blockInfo = {
number: parseInt(blockHeader.number, 16),
timestamp: parseInt(blockHeader.timestamp, 16),
gasUsed: parseInt(blockHeader.gasUsed, 16),
gasLimit: parseInt(blockHeader.gasLimit, 16)
};

console.log('New block:', blockInfo);
}
}

// Usage
const dashboard = new TradingDashboard('wss://ethereum-mainnet.blockeden.xyz/<your-api-key>');
dashboard.start();

NFT Marketplace Monitor

class NFTMarketplaceMonitor {
constructor(wsEndpoint) {
this.wsManager = new EthereumWebSocketManager(wsEndpoint);
this.marketplaces = {
opensea: '0x00000000006c3852cbEf3e08E8dF289169EdE581',
looksrare: '0x59728544B08AB483533076417FbBB2fD0B17CE3a',
x2y2: '0x74312363e45DCaBA76c59ec49a7Aa8A65a67EeD3'
};
}

async start() {
await this.wsManager.connect();

Object.entries(this.marketplaces).forEach(([name, address]) => {
this.subscribeToMarketplace(name, address);
});
}

subscribeToMarketplace(name, address) {
// Subscribe to all events from marketplace contract
this.wsManager.subscribe('logs', [{
address: address
}], (log) => {
this.handleMarketplaceEvent(name, log);
});
}

handleMarketplaceEvent(marketplace, log) {
const eventSignature = log.topics[0];

switch (eventSignature) {
case '0x9d9af8e38d66c62e2c12f0225249fd9d721c54b83f48d9352c97c6cacdcb6f31': // Sale
this.handleSale(marketplace, log);
break;
case '0x3cbb63f144840e5b1b0a38a7c19211d2e89de4d7c5faf8b2d3c1776c302d1d33': // Listing
this.handleListing(marketplace, log);
break;
default:
console.log(`Unknown event from ${marketplace}:`, eventSignature);
}
}

handleSale(marketplace, log) {
// Parse sale data
const saleData = this.parseSaleData(log);

console.log(`🎉 NFT Sale on ${marketplace}:`, {
collection: saleData.collection,
tokenId: saleData.tokenId,
price: saleData.price,
buyer: saleData.buyer,
seller: saleData.seller,
txHash: log.transactionHash
});

// Send notification to users
this.sendSaleNotification(marketplace, saleData);
}

handleListing(marketplace, log) {
// Parse listing data
const listingData = this.parseListingData(log);

console.log(`📝 New Listing on ${marketplace}:`, {
collection: listingData.collection,
tokenId: listingData.tokenId,
price: listingData.price,
seller: listingData.seller
});
}

parseSaleData(log) {
// Implementation depends on marketplace contract
return {
collection: log.topics[1],
tokenId: parseInt(log.topics[2], 16),
price: parseInt(log.data.slice(0, 66), 16),
buyer: `0x${log.data.slice(66, 106)}`,
seller: `0x${log.data.slice(106, 146)}`
};
}

parseListingData(log) {
// Implementation depends on marketplace contract
return {
collection: log.topics[1],
tokenId: parseInt(log.topics[2], 16),
price: parseInt(log.data.slice(0, 66), 16),
seller: `0x${log.data.slice(66, 106)}`
};
}

sendSaleNotification(marketplace, saleData) {
// Send to Discord, Telegram, email, etc.
console.log(`Sending notification for ${marketplace} sale`);
}
}

// Usage
const nftMonitor = new NFTMarketplaceMonitor('wss://ethereum-mainnet.blockeden.xyz/<your-api-key>');
nftMonitor.start();

Error Handling & Reconnection

Robust Error Handling

class RobustWebSocketClient {
constructor(endpoint, options = {}) {
this.endpoint = endpoint;
this.options = {
reconnectInterval: 5000,
maxReconnectAttempts: 10,
heartbeatInterval: 30000,
...options
};

this.ws = null;
this.reconnectAttempts = 0;
this.heartbeatTimer = null;
this.subscriptions = new Map();
this.isConnected = false;
}

connect() {
return new Promise((resolve, reject) => {
try {
this.ws = new WebSocket(this.endpoint);

this.ws.onopen = () => {
console.log('WebSocket connected');
this.isConnected = true;
this.reconnectAttempts = 0;
this.startHeartbeat();
this.resubscribeAll();
resolve();
};

this.ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
this.handleMessage(data);
} catch (error) {
console.error('Error parsing message:', error);
}
};

this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
this.isConnected = false;
};

this.ws.onclose = (event) => {
console.log('WebSocket closed:', event.code, event.reason);
this.isConnected = false;
this.stopHeartbeat();

if (event.code !== 1000) { // Not a normal closure
this.handleReconnection();
}
};

} catch (error) {
reject(error);
}
});
}

startHeartbeat() {
this.heartbeatTimer = setInterval(() => {
if (this.isConnected && this.ws.readyState === WebSocket.OPEN) {
this.ping();
}
}, this.options.heartbeatInterval);
}

stopHeartbeat() {
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer);
this.heartbeatTimer = null;
}
}

ping() {
// Send a simple request to check connection
const ping = {
jsonrpc: '2.0',
method: 'net_version',
params: [],
id: 'ping'
};

this.send(ping);
}

send(data) {
if (this.isConnected && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(data));
return true;
} else {
console.warn('WebSocket not connected, queueing message');
return false;
}
}

handleReconnection() {
if (this.reconnectAttempts < this.options.maxReconnectAttempts) {
this.reconnectAttempts++;
const delay = Math.min(
this.options.reconnectInterval * Math.pow(2, this.reconnectAttempts - 1),
30000 // Max 30 seconds
);

console.log(`Reconnecting in ${delay}ms... Attempt ${this.reconnectAttempts}`);

setTimeout(() => {
this.connect().catch((error) => {
console.error('Reconnection failed:', error);
});
}, delay);
} else {
console.error('Max reconnection attempts reached');
}
}

resubscribeAll() {
// Resubscribe to all active subscriptions
this.subscriptions.forEach((handler, subscription) => {
// Implementation depends on your subscription tracking
});
}

disconnect() {
this.stopHeartbeat();
if (this.ws) {
this.ws.close(1000, 'Client disconnecting');
}
}
}

Performance Optimization

Efficient Message Processing

class OptimizedWebSocketProcessor {
constructor(endpoint) {
this.ws = new RobustWebSocketClient(endpoint);
this.messageQueue = [];
this.processing = false;
this.batchSize = 100;
this.batchTimeout = 100; // ms
}

async start() {
await this.ws.connect();
this.ws.onMessage = (data) => {
this.queueMessage(data);
};

this.startBatchProcessor();
}

queueMessage(message) {
this.messageQueue.push({
message,
timestamp: Date.now()
});

// Process immediately if queue is full
if (this.messageQueue.length >= this.batchSize) {
this.processBatch();
}
}

startBatchProcessor() {
setInterval(() => {
if (this.messageQueue.length > 0 && !this.processing) {
this.processBatch();
}
}, this.batchTimeout);
}

async processBatch() {
if (this.processing || this.messageQueue.length === 0) {
return;
}

this.processing = true;
const batch = this.messageQueue.splice(0, this.batchSize);

try {
await this.processBatchAsync(batch);
} catch (error) {
console.error('Error processing batch:', error);
} finally {
this.processing = false;
}
}

async processBatchAsync(batch) {
// Process messages in parallel where possible
const promises = batch.map(item => this.processMessage(item.message));
await Promise.allSettled(promises);
}

async processMessage(message) {
// Your message processing logic
if (message.method === 'eth_subscription') {
const subscription = message.params.subscription;
const result = message.params.result;

// Handle based on subscription type
this.handleSubscriptionMessage(subscription, result);
}
}

handleSubscriptionMessage(subscription, result) {
// Route to appropriate handler
console.log(`Processing subscription ${subscription}:`, result);
}
}

Testing WebSocket Connections

Unit Testing

// Mock WebSocket for testing
class MockWebSocket {
constructor(url) {
this.url = url;
this.readyState = WebSocket.CONNECTING;
this.messages = [];

// Simulate connection
setTimeout(() => {
this.readyState = WebSocket.OPEN;
if (this.onopen) this.onopen();
}, 100);
}

send(data) {
this.messages.push(JSON.parse(data));
}

close() {
this.readyState = WebSocket.CLOSED;
if (this.onclose) this.onclose({ code: 1000, reason: 'Normal closure' });
}

simulateMessage(data) {
if (this.onmessage) {
this.onmessage({ data: JSON.stringify(data) });
}
}
}

// Test example
describe('WebSocket Manager', () => {
let wsManager;
let mockWs;

beforeEach(() => {
global.WebSocket = MockWebSocket;
wsManager = new EthereumWebSocketManager('wss://test.example.com');
});

test('should connect and subscribe', async () => {
await wsManager.connect();

const handler = jest.fn();
wsManager.subscribe('newHeads', null, handler);

// Simulate subscription confirmation
mockWs.simulateMessage({
id: 1,
result: 'subscription-id-123'
});

// Simulate new block
mockWs.simulateMessage({
method: 'eth_subscription',
params: {
subscription: 'subscription-id-123',
result: { number: '0x123', hash: '0xabc...' }
}
});

expect(handler).toHaveBeenCalledWith({ number: '0x123', hash: '0xabc...' });
});
});

Best Practices

1. Connection Management

  • Always implement reconnection logic
  • Use heartbeat/ping to detect connection issues
  • Handle network interruptions gracefully
  • Limit concurrent connections

2. Resource Management

  • Unsubscribe from unused subscriptions
  • Implement proper cleanup on disconnect
  • Monitor memory usage for long-running connections
  • Use connection pooling for multiple subscriptions

3. Error Handling

  • Implement exponential backoff for reconnections
  • Log errors for debugging
  • Handle malformed messages gracefully
  • Set connection timeouts

4. Performance

  • Batch process messages when possible
  • Use appropriate buffer sizes
  • Implement message filtering client-side
  • Monitor subscription overhead

5. Security

  • Validate all incoming data
  • Use secure WebSocket connections (wss://)
  • Implement rate limiting
  • Keep API keys secure

Next Steps

Resources