Web3.js: How to keep WebSocket subscriptions alive and reconnect on failures
Smart contracts are an essential part of the Ethereum ecosystem. Smart contracts emit so-called events when something meaningful happens within the smart contract that should be communicated to dapps and other smart contracts.
This guide covers some ways to keep the WebSocket connection open while listening to smart contract events. Some smart contracts don't emit events very often, so it's possible to configure your Infura WebSocket to stay alive even though the smart contract is not constantly emitting events.
Setting up our project
This tutorial makes use of Web3.js v1.x.x. Not all functionality might work with Web3.js v4.
Please create a new folder in which we can work on our project, then install Web3.js using npm.
npm install web3
Ensure you have your Infura account set up and have access to your endpoint URL. Feel free to read more about getting started with Infura.
At the top of our new JavaScript file, we can add the following to import the Web3.js library that we just installed and connect to the Infura WebSocket endpoint:
const Web3 = require('web3')
const web3 = new Web3('wss://mainnet.infura.io/ws/v3/<API_KEY>')
1. Prevent the WebSocket connection from going idle by configuring your WebSocket with ‘keepalive’ and ‘reconnect’
It's best to have a retry mechanism in place in case one of the smart contracts you're listening to does not emit any events in a 1 hour time frame (Infura WebSocket is closed after one hour of idle time).
const provider = new Web3.providers.WebsocketProvider('wss://mainnet.infura.io/ws/v3/<YOUR_API_KEY>', {
  clientConfig: {
    keepalive: true,
    keepaliveInterval: 60000,
  },
  reconnect: {
    auto: true,
    delay: 5000,
    maxAttempts: 5,
    onTimeout: false,
  },
})
2. Prevent the WebSocket connection from going idle by subscribing to ‘newBlockHeaders’
web3.eth
  .subscribe('newBlockHeaders')
  .on('data', data => {
    console.log(`Received block header for block number ${data.number}.`)
  })
  .on('error', error => {
    console.error(error)
    console.error('An error occured on the new blocks subscription.')
  })
  .on('connected', id => {
    console.log(`NewBlockHeaders subscription connected (${id})`)
  })
By having our WebSocket configured with a retry mechanism and listening to newBlockHeads, you can avoid having your WSS connection closed.
Let's track the events emitted by the UniSwap smart contract. In particular, we'll look at the pairCreated() function.
In order to listen to specific contract events, you need to get the smart contract address and the smart contract ABI from Etherscan.
Complete code overview
const Web3 = require('web3')
const factoryAbi = require('./abi.json')
const factoryAddress = '0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f'
const provider = new Web3.providers.WebsocketProvider('wss://mainnet.infura.io/ws/v3/<YOUR_PROJECT_ID', {
  clientConfig: {
    keepalive: true,
    keepaliveInterval: 60000,
  },
  reconnect: {
    auto: true,
    delay: 5000,
    maxAttempts: 5,
    onTimeout: false,
  },
})
provider.on('connect', () => {
  console.log('WebSocket connected.')
})
provider.on('close', event => {
  console.log(event)
  console.log('WebSocket closed.')
})
provider.on('error', error => {
  console.error(error)
})
const web3 = new Web3(provider)
const factoryContract = new web3.eth.Contract(factoryAbi, factoryAddress)
web3.eth
  .subscribe('newBlockHeaders')
  .on('data', data => {
    console.log(`Received block header for block number ${data.number}.`)
  })
  .on('error', error => {
    console.error(error)
    console.error('An error occured on the new blocks subscription.')
  })
  .on('connected', id => {
    console.log(`NewBlockHeaders subscription connected (${id})`)
  })
factoryContract.events
  .PairCreated()
  .on('connected', id => {
    console.log(`PairCreated subscription connected (${id})`)
  })
  .on('data', event => {
    console.log(event)
  })
  .on('error', error => {
    console.log(error)
  })