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.
Recommended Reconnection Strategy
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.