Migrating production infrastructure? Get up to ₹10,000 in migration credits. Apply now
voroaDocs
Networking

WebSockets on Voroa: send and receive data in real time

Run WebSockets on Voroa web services: framework setup, connecting over wss, keepalive pings, reconnecting after deploys, saving session state, and limits.

The WebSocket protocol enables real-time, two-way data streaming between a client and a server. It is commonly used for app features like text chat, live financial dashboards and AI voice assistants:

A client app subscribes to a stock and the web service streams back a new price message each time it changes

Voroa web services accept inbound WebSocket connections from the public internet. Every other service type except static sites can also open outbound WebSocket connections over the public internet.

Web service setup

In your web service code, you usually extend your existing HTTP server framework with WebSocket support. In Node.js, for example, it is common to use the ws module with the Express framework.

Below are basic examples for popular frameworks; see your framework’s documentation for details. In every case your server handles HTTP and WebSocket traffic on the same port, read from the PORT environment variable that Voroa sets for you.

Express (Node.js)

This example uses Express with the ws module:

js
// app.js
const express = require('express')
const { createServer } = require('http')
const WebSocket = require('ws')

const app = express()
const server = createServer(app)
const port = process.env.PORT || 3000

// Serves WebSocket connections at /ws (any path is fine)
const wss = new WebSocket.Server({ server, path: '/ws' })

// HTTP routes
app.get('/', (req, res) => {
  res.send('Hello over HTTP!')
})

// WebSocket connections
wss.on('connection', (ws) => {
  console.log('WebSocket client connected')

  ws.on('message', (message) => {
    console.log('Received:', message.toString())
    ws.send('Hello over WebSocket!')
  })
})

server.listen(port, () => {
  console.log(`Server listening on port ${port}`)
})

FastAPI (Python)

FastAPI supports WebSockets out of the box:

python
# main.py
from fastapi import FastAPI, WebSocket

app = FastAPI()

@app.get("/")
def index():
    return {"message": "Hello over HTTP!"}

@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()
    while True:
        message = await websocket.receive_text()
        print("Received:", message)
        await websocket.send_text("Hello over WebSocket!")

Start command: uvicorn main:app --host 0.0.0.0 --port $PORT

Django (Python)

Django handles WebSockets through Channels, which requires an ASGI server such as Daphne or Uvicorn:

python
# myapp/consumers.py
from channels.generic.websocket import WebsocketConsumer

class EchoConsumer(WebsocketConsumer):
    def connect(self):
        self.accept()

    def receive(self, text_data=None, bytes_data=None):
        print("Received:", text_data)
        self.send(text_data="Hello over WebSocket!")

Route it at ws/ in your ASGI routing, and use a start command such as daphne -b 0.0.0.0 -p $PORT myproject.asgi:application.

Rails (Ruby)

Rails uses Action Cable, which serves WebSocket connections at /cable by default:

ruby
# app/channels/echo_channel.rb
class EchoChannel < ApplicationCable::Channel
  def subscribed
    stream_from "echo"
  end

  def receive(data)
    ActionCable.server.broadcast("echo", { message: "Hello over WebSocket!" })
  end
end

Connecting from clients

After you deploy WebSocket support to your web service, you can start opening connections from client code.

To test quickly, install the websocat command-line tool and connect straight from your terminal:

bash
$ brew install websocat
$ websocat wss://example-app.onvoroa.com/ws
test test
Hello over WebSocket!

Always use wss for WebSocket connections over the public internet. If you use ws, most WebSocket clients fail, because Voroa answers their handshake request with a redirect to HTTPS. Use ws only for testing a server on your own machine.

Here is a simple Node.js client that connects to a Voroa-hosted WebSocket server:

js
// client.js
const WebSocket = require('ws')
const ws = new WebSocket('wss://example-app.onvoroa.com/ws')

ws.onopen = () => {
  ws.send('Hello from the client!')
}

ws.onmessage = (event) => {
  console.log('Received:', event.data)
}

Whatever your language or framework, all you need is your web service’s public URL (custom domains work too), including the path of your WebSocket server.

Maintaining connections

Voroa does not enforce a maximum duration for WebSocket connections. A connection can still be interrupted by a deploy or restart, a network issue, or platform maintenance.

An idle connection is closed after about 100 seconds. A connection that carries no data at all for about 100 seconds can be closed on its way through the edge network. Send a ping at least every 30 seconds and this never happens.

To keep connections alive and detect stale ones, your web service and its clients should periodically send each other keepalive messages. The WebSocket protocol defines special ping and pong control frames for exactly this. When one side sends a ping, the other side responds with a pong:

The web service sends ping "Hello!" and the client app answers pong "Hello!"

Many WebSocket libraries answer pong automatically, so you usually only need to implement the ping logic on each side.

Server-side pings

On the server, periodic pings detect stale connections as early as possible, which frees up resources and keeps your service responsive.

The example below extends the Express example with a basic heartbeat using ping. The same ideas apply to other languages and frameworks.

js
// app.js
const express = require('express')
const { createServer } = require('http')
const WebSocket = require('ws')

const app = express()
const server = createServer(app)
const port = process.env.PORT || 3000

const wss = new WebSocket.Server({ server, path: '/ws' })

// Called for a connection whenever the client responds with a pong
function heartbeat() {
  this.isAlive = true
}

wss.on('connection', function connection(ws) {
  ws.isAlive = true
  ws.on('pong', heartbeat)

  ws.on('message', (message) => {
    console.log('Received:', message.toString())
    ws.send('Hello over WebSocket!')
  })
})

// Every 30 seconds, drop connections that did not answer the last ping, and ping the rest
const interval = setInterval(function ping() {
  wss.clients.forEach(function each(ws) {
    if (ws.isAlive === false) return ws.terminate()
    ws.isAlive = false
    ws.ping()
  })
}, 30000)

wss.on('close', function close() {
  clearInterval(interval)
})

server.listen(port, () => {
  console.log(`Server listening on port ${port}`)
})

Adapted from the ws README.

Client-side reconnects

Your clients should reconnect to your service after an interruption. Handle both clean disconnects (your service closing the connection because it is being replaced) and unexpected errors (the connection going stale because of a network issue).

Reconnect with exponential backoff, so a service that is struggling is not flooded with reconnects.

Clients are not guaranteed to reconnect to the same instance. If your service runs more than one instance, each new WebSocket connection is assigned to one of them, whatever it was connected to before.

The longer example below shows client-side reconnects with exponential backoff in Node.js. The same ideas apply to other languages and frameworks.

js
// client.js
const WebSocket = require('ws')

const wsUrl = 'wss://example-app.onvoroa.com/ws'
let ws = null
let attempt = 0
const maxDelay = 30000 // never wait more than 30 seconds between attempts

function connect() {
  ws = new WebSocket(wsUrl)

  ws.onopen = () => {
    console.log('Connected')
    attempt = 0 // reset the backoff after a successful connection
    ws.send('Hello from the client!')
  }

  ws.onmessage = (event) => {
    console.log('Received:', event.data)
  }

  ws.onclose = () => {
    // 1s, 2s, 4s, 8s ... up to maxDelay, plus jitter so many clients do not retry in lockstep
    const delay = Math.min(maxDelay, 1000 * 2 ** attempt) + Math.floor(Math.random() * 1000)
    attempt += 1
    console.log(`Disconnected. Reconnecting in ${delay} ms`)
    setTimeout(connect, delay)
  }

  ws.onerror = (err) => {
    console.error('WebSocket error:', err.message)
    ws.close() // onclose schedules the reconnect
  }
}

connect()

Handling deploys and restarts

Voroa replaces your web service’s running instance when you deploy a new version, when you restart the service, and during platform maintenance.

When you deploy, Voroa starts the new version first and moves traffic to it once it is ready. The previous version is then stopped, and its open WebSocket connections close at that moment. There is no extra shutdown window to drain them, so do not rely on a shutdown signal to save state: save anything a client needs as you go (see below), and let clients reconnect. With the reconnect logic above, they are back on the new version within seconds.

Saving session state

If a connection is interrupted and your service has been keeping state for that client, store that state in a Voroa Key Value instance or other shared storage:

Instance A writes the client's state to Key Value before the connection is interrupted; the client reconnects to instance B, which fetches the state from Key Value

This way, whichever instance the client reconnects to can fetch the saved state and resume the session.

If you use this pattern, set a TTL on saved session state so it expires automatically once it is no longer needed.

FAQ

Can I receive WebSocket connections on a different port from other HTTP requests?

No. All public internet traffic to your web service is routed to a single port, the one your service listens on (PORT). This includes WebSocket connections, which start as HTTP requests and are then upgraded. Serve WebSockets on a path of the same server instead, such as /ws.

How long can a WebSocket connection stay open?

Until your service is deployed or restarted. Voroa does not impose a fixed limit, as long as the connection carries some data (or pings) at least every 100 seconds. See Maintaining connections.

Do WebSocket messages count as outbound bandwidth usage?

Some of them do. Outbound WebSocket messages from your services over the public internet count as outbound bandwidth. Inbound messages do not.

Is there a limit on the number of open WebSocket connections a service can have?

No. However, a large number of connections can strain your instance’s CPU and memory and slow it down. To handle more connections, move to a larger instance type or run more instances:

  • A larger instance type has more CPU and RAM, so each instance can handle more connections.
  • Scaling your service horizontally spreads connections across several instances, reducing the load on each. Each new connection is assigned to one of your instances.
Last updated November 3, 2026