The eth_unsubscribe JSON-RPC method allows developers to cancel an active WebSocket subscription on the Hyperliquid EVM blockchain. This method is used to stop receiving notifications for subscriptions created with eth_subscribe, helping to manage resources and control data flow in applications.
Get your own node endpoint todayStart for free and get your app to production levels immediately. No credit card required.You can sign up with your GitHub, X, Google, or Microsoft account.

Parameters

  1. subscription ID (string, required): The subscription identifier returned by eth_subscribe when the subscription was created

Response

The method returns a boolean value indicating whether the unsubscribe operation was successful.

Response structure

  • true — the subscription was successfully cancelled
  • false — the subscription was not found or could not be cancelled

Usage example

Basic implementation

Note that subscriptions require a WebSocket connection. Install WebSocket cat for testing:
npm install -g wscat
wscat
$ wscat -c wss://hyperliquid-mainnet.core.chainstack.com/4f8d8f4040bdacd1577bff8058438274/evm
# Wait for the connection to be established

Connected (press CTRL+C to quit)

# First create a subscription
> {"id":1,"jsonrpc":"2.0","method":"eth_subscribe","params":["newHeads"]}
< {"jsonrpc":"2.0","id":1,"result":"0x1234567890abcdef"}

# Now unsubscribe using the subscription ID
> {"id":2,"jsonrpc":"2.0","method":"eth_unsubscribe","params":["0x1234567890abcdef"]}
< {"jsonrpc":"2.0","id":2,"result":true}

JavaScript implementation with subscription management

const WebSocket = require('ws');

const CHAINSTACK_WSS_URL = 'wss://hyperliquid-mainnet.core.chainstack.com/YOUR_ENDPOINT/evm';

class SubscriptionManager {
  constructor(wsUrl) {
    this.wsUrl = wsUrl;
    this.ws = null;
    this.subscriptions = new Map();
    this.requestId = 0;
  }

  connect() {
    return new Promise((resolve, reject) => {
      this.ws = new WebSocket(this.wsUrl);
      
      this.ws.on('open', () => {
        console.log('Connected to Hyperliquid EVM WebSocket');
        resolve();
      });
      
      this.ws.on('message', (data) => {
        this.handleMessage(JSON.parse(data));
      });
      
      this.ws.on('error', (error) => {
        console.error('WebSocket error:', error);
        reject(error);
      });
      
      this.ws.on('close', () => {
        console.log('WebSocket connection closed');
        this.subscriptions.clear();
      });
    });
  }

  async subscribe(type, params = []) {
    const id = ++this.requestId;
    
    return new Promise((resolve) => {
      const request = {
        id,
        jsonrpc: '2.0',
        method: 'eth_subscribe',
        params: [type, ...params]
      };
      
      // Store resolver to handle response
      this.subscriptions.set(`request_${id}`, {
        type: 'request',
        resolve,
        subscriptionType: type
      });
      
      this.ws.send(JSON.stringify(request));
      console.log(`Subscribing to ${type}...`);
    });
  }

  async unsubscribe(subscriptionId) {
    const id = ++this.requestId;
    
    return new Promise((resolve) => {
      const request = {
        id,
        jsonrpc: '2.0',
        method: 'eth_unsubscribe',
        params: [subscriptionId]
      };
      
      // Store resolver to handle response
      this.subscriptions.set(`request_${id}`, {
        type: 'unsubscribe',
        resolve,
        subscriptionId
      });
      
      this.ws.send(JSON.stringify(request));
      console.log(`Unsubscribing from ${subscriptionId}...`);
    });
  }

  handleMessage(message) {
    // Handle subscription/unsubscription responses
    if (message.id) {
      const request = this.subscriptions.get(`request_${message.id}`);
      if (request) {
        if (request.type === 'request') {
          // Store the subscription ID
          this.subscriptions.set(message.result, {
            type: 'subscription',
            subscriptionType: request.subscriptionType,
            handler: null
          });
          console.log(`Subscribed with ID: ${message.result}`);
          request.resolve(message.result);
        } else if (request.type === 'unsubscribe') {
          // Clean up subscription
          if (message.result === true) {
            this.subscriptions.delete(request.subscriptionId);
            console.log(`Successfully unsubscribed from ${request.subscriptionId}`);
          } else {
            console.log(`Failed to unsubscribe from ${request.subscriptionId}`);
          }
          request.resolve(message.result);
        }
        this.subscriptions.delete(`request_${message.id}`);
      }
    }
    
    // Handle subscription notifications
    if (message.method === 'eth_subscription') {
      const subscription = this.subscriptions.get(message.params.subscription);
      if (subscription && subscription.handler) {
        subscription.handler(message.params.result);
      }
    }
  }

  setHandler(subscriptionId, handler) {
    const subscription = this.subscriptions.get(subscriptionId);
    if (subscription) {
      subscription.handler = handler;
    }
  }

  async disconnect() {
    // Unsubscribe from all active subscriptions
    const activeSubscriptions = Array.from(this.subscriptions.entries())
      .filter(([_, sub]) => sub.type === 'subscription')
      .map(([id, _]) => id);
    
    for (const subId of activeSubscriptions) {
      await this.unsubscribe(subId);
    }
    
    this.ws.close();
  }
}

// Example usage
async function main() {
  const manager = new SubscriptionManager(CHAINSTACK_WSS_URL);
  
  try {
    await manager.connect();
    
    // Subscribe to new blocks
    const blockSubId = await manager.subscribe('newHeads');
    manager.setHandler(blockSubId, (block) => {
      console.log(`New block: ${parseInt(block.number, 16)}`);
    });
    
    // Subscribe to logs
    const logSubId = await manager.subscribe('logs', [{
      address: '0x5555555555555555555555555555555555555555'
    }]);
    manager.setHandler(logSubId, (log) => {
      console.log(`New log from ${log.address}`);
    });
    
    // Run for 30 seconds then clean up
    setTimeout(async () => {
      console.log('\nCleaning up subscriptions...');
      
      // Unsubscribe from blocks
      const blockResult = await manager.unsubscribe(blockSubId);
      console.log(`Block unsubscribe result: ${blockResult}`);
      
      // Unsubscribe from logs
      const logResult = await manager.unsubscribe(logSubId);
      console.log(`Log unsubscribe result: ${logResult}`);
      
      // Disconnect
      await manager.disconnect();
      process.exit(0);
    }, 30000);
    
  } catch (error) {
    console.error('Error:', error);
  }
}

main();

Python implementation with context manager

import json
import asyncio
import websockets
from typing import Dict, Any, Optional
from contextlib import asynccontextmanager

class SubscriptionManager:
    def __init__(self, ws_url: str):
        self.ws_url = ws_url
        self.websocket = None
        self.subscriptions: Dict[str, Any] = {}
        self.request_id = 0
        self.pending_requests = {}
        
    async def connect(self):
        self.websocket = await websockets.connect(self.ws_url)
        print("Connected to Hyperliquid EVM WebSocket")
        
    async def disconnect(self):
        if self.websocket:
            await self.websocket.close()
            print("Disconnected from WebSocket")
    
    async def subscribe(self, subscription_type: str, params: Optional[Dict] = None) -> str:
        self.request_id += 1
        request = {
            "id": self.request_id,
            "jsonrpc": "2.0",
            "method": "eth_subscribe",
            "params": [subscription_type] + ([params] if params else [])
        }
        
        # Create a future to wait for response
        future = asyncio.Future()
        self.pending_requests[self.request_id] = future
        
        await self.websocket.send(json.dumps(request))
        print(f"Subscribing to {subscription_type}...")
        
        # Wait for subscription ID
        subscription_id = await future
        self.subscriptions[subscription_id] = {
            "type": subscription_type,
            "params": params
        }
        print(f"Subscribed with ID: {subscription_id}")
        return subscription_id
    
    async def unsubscribe(self, subscription_id: str) -> bool:
        self.request_id += 1
        request = {
            "id": self.request_id,
            "jsonrpc": "2.0",
            "method": "eth_unsubscribe",
            "params": [subscription_id]
        }
        
        # Create a future to wait for response
        future = asyncio.Future()
        self.pending_requests[self.request_id] = future
        
        await self.websocket.send(json.dumps(request))
        print(f"Unsubscribing from {subscription_id}...")
        
        # Wait for unsubscribe result
        result = await future
        
        if result:
            del self.subscriptions[subscription_id]
            print(f"Successfully unsubscribed from {subscription_id}")
        else:
            print(f"Failed to unsubscribe from {subscription_id}")
        
        return result
    
    async def listen(self, handler):
        """Listen for messages and handle them"""
        async for message in self.websocket:
            data = json.loads(message)
            
            # Handle subscription/unsubscription responses
            if "id" in data:
                request_id = data["id"]
                if request_id in self.pending_requests:
                    self.pending_requests[request_id].set_result(data.get("result"))
                    del self.pending_requests[request_id]
            
            # Handle subscription notifications
            elif data.get("method") == "eth_subscription":
                subscription_id = data["params"]["subscription"]
                if subscription_id in self.subscriptions:
                    await handler(subscription_id, data["params"]["result"])
    
    async def cleanup(self):
        """Unsubscribe from all active subscriptions"""
        print("\nCleaning up subscriptions...")
        for sub_id in list(self.subscriptions.keys()):
            await self.unsubscribe(sub_id)

@asynccontextmanager
async def managed_subscription(ws_url: str):
    """Context manager for automatic subscription cleanup"""
    manager = SubscriptionManager(ws_url)
    try:
        await manager.connect()
        yield manager
    finally:
        await manager.cleanup()
        await manager.disconnect()

# Example usage
async def main():
    ws_url = 'wss://hyperliquid-mainnet.core.chainstack.com/YOUR_ENDPOINT/evm'
    
    async with managed_subscription(ws_url) as manager:
        # Subscribe to different events
        block_sub = await manager.subscribe("newHeads")
        log_sub = await manager.subscribe("logs", {
            "address": "0x5555555555555555555555555555555555555555"
        })
        sync_sub = await manager.subscribe("syncing")
        
        # Handler for notifications
        async def handle_notification(sub_id: str, result: Any):
            if sub_id == block_sub:
                print(f"New block: {int(result['number'], 16)}")
            elif sub_id == log_sub:
                print(f"New log from: {result['address']}")
            elif sub_id == sync_sub:
                if result is False:
                    print("Node is synchronized")
                else:
                    print(f"Syncing: current block {int(result['currentBlock'], 16)}")
        
        # Listen for 30 seconds
        listen_task = asyncio.create_task(manager.listen(handle_notification))
        await asyncio.sleep(30)
        
        # Manual unsubscribe example (optional, cleanup will handle it anyway)
        await manager.unsubscribe(block_sub)
        
        listen_task.cancel()
    
    print("All subscriptions cleaned up automatically")

# Run the example
asyncio.run(main())

Subscription lifecycle management

// Complete subscription lifecycle with error handling
class RobustSubscriptionManager {
  constructor(wsUrl, maxReconnectAttempts = 5) {
    this.wsUrl = wsUrl;
    this.maxReconnectAttempts = maxReconnectAttempts;
    this.reconnectAttempts = 0;
    this.subscriptions = new Map();
    this.handlers = new Map();
  }
  
  async manageSubscription(type, params, duration, handler) {
    console.log(`Starting ${type} subscription for ${duration}ms`);
    
    try {
      // Connect and subscribe
      await this.connect();
      const subId = await this.subscribe(type, params);
      
      // Set up handler
      this.handlers.set(subId, handler);
      
      // Run for specified duration
      await new Promise(resolve => setTimeout(resolve, duration));
      
      // Cleanup
      console.log(`Time's up, unsubscribing from ${type}...`);
      const success = await this.unsubscribe(subId);
      
      if (success) {
        console.log(`✅ Successfully completed ${type} subscription lifecycle`);
      } else {
        console.log(`⚠️ Failed to cleanly unsubscribe from ${type}`);
      }
      
    } catch (error) {
      console.error(`Error in subscription lifecycle: ${error.message}`);
    } finally {
      this.disconnect();
    }
  }
  
  // ... rest of implementation similar to previous example
}

// Example: Monitor blocks for 1 minute
const manager = new RobustSubscriptionManager(CHAINSTACK_WSS_URL);
await manager.manageSubscription(
  'newHeads',
  {},
  60000,  // 1 minute
  (block) => console.log(`Block ${parseInt(block.number, 16)}`)
);

Use cases

The eth_unsubscribe method is essential for:
  • Resource management: Free up server and client resources by canceling unused subscriptions
  • Dynamic subscription control: Change subscription parameters by unsubscribing and resubscribing
  • Application lifecycle management: Clean up subscriptions when components unmount or disconnect
  • Rate limiting compliance: Manage the number of active subscriptions to stay within limits
  • Cost optimization: Reduce bandwidth and processing costs by removing unnecessary subscriptions
  • Error recovery: Cancel problematic subscriptions that are causing issues
  • Testing and development: Clean up test subscriptions during development
  • User preference changes: Update subscriptions when users change monitoring preferences
  • Performance optimization: Remove subscriptions that are no longer needed to improve performance
  • Connection management: Properly clean up before reconnecting or switching endpoints
Proper use of eth_unsubscribe ensures efficient resource utilization and clean application shutdown, preventing memory leaks and unnecessary network traffic.