Hydra
Docs

How to handle disconnections and errors robustly.

Reconnection & Errors

WebSocket connections can drop for many reasons — network interruptions, server restarts, JWT expiry, rate limits. Build your client to handle these gracefully.

Use exponential backoff with jitter — don't hammer the server on reconnect.

class HydraClient {
  private ws: WebSocket | null = null
  private reconnectDelay = 1000
  private maxDelay = 60000
  private subscriptions: string[] = []

  constructor(private getJwt: () => Promise<string>) {}

  async connect(streams: string[]) {
    this.subscriptions = streams
    await this._connect()
  }

  private async _connect() {
    const jwt = await this.getJwt()
    this.ws = new WebSocket(`wss://api.hydra.app/stream?token=${jwt}`)

    this.ws.addEventListener('open', () => {
      this.reconnectDelay = 1000  // reset on successful connect
      this.ws!.send(JSON.stringify({
        action: 'subscribe',
        streams: this.subscriptions
      }))
    })

    this.ws.addEventListener('message', (e) => {
      this.handleMessage(JSON.parse(e.data))
    })

    this.ws.addEventListener('close', (e) => {
      if (e.code === 4001) {
        // JWT expired — get a new one before reconnecting
        this._reconnect(true)
      } else if (e.code !== 1000) {
        // Unexpected close — reconnect with backoff
        this._reconnect(false)
      }
    })
  }

  private async _reconnect(reauth: boolean) {
    const delay = this.reconnectDelay + Math.random() * 1000
    this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.maxDelay)
    await new Promise(r => setTimeout(r, delay))
    await this._connect()
  }

  private handleMessage(msg: HydraMessage) {
    if (msg.type === 'heartbeat') return
    // dispatch to your handlers
  }
}

JWT Expiry

When your JWT expires the server closes the connection with code 4001. Before reconnecting, fetch a fresh JWT:

ws.addEventListener('close', async (e) => {
  if (e.code === 4001) {
    // Re-authenticate — sign a new message with your wallet
    jwt = await signAndGetToken(wallet, subscriptions)
    reconnect()
  }
})

In automated setups (bots, servers), use a background job to proactively refresh the JWT before it expires:

// Refresh 5 minutes before expiry
const expiresIn = jwtPayload.exp * 1000 - Date.now()
setTimeout(refreshJwt, expiresIn - 5 * 60 * 1000)

Rate Limiting

If you send commands too quickly, the server responds with:

{
  "type": "error",
  "data": { "code": "RATE_LIMITED", "retryAfter": 5000 }
}

Honour the retryAfter value in milliseconds. Repeated violations result in a temporary ban.

Common Issues

Symptom Likely Cause Fix
Close code 4001 immediately JWT expired Re-authenticate
Close code 4002 immediately Token balance too low Top up Hydra balance
STREAM_NOT_IN_SCOPE error Stream not in JWT Re-auth with correct streams
No messages on subscribed stream Stream has no recent events Normal — streams only push on new data
Heartbeat stops arriving Network issue Reconnect

Server Maintenance

When the server is going into planned maintenance, it sends a notice before closing:

{
  "type": "maintenance",
  "ts": 1710000000000,
  "data": {
    "message": "Server restarting for maintenance",
    "resumeAt": 1710003600000
  }
}

This is followed by close code 1001. Resume connecting after resumeAt.