Skip to main content

WebSocket

WebSocket provides bidirectional, full-duplex communication over a single TCP connection. It's the most widely supported real-time protocol.

Try it Online

Open WebSocket Client — See WebSocket streaming in action!

Overview

PropertyValue
ProtocolTCP (WebSocket)
DirectionBidirectional
Latency1-5ms
ReconnectionManual
Browser SupportUniversal

Endpoints

EndpointDescription
ws://host/ws/:gameStream by game slug
ws://host/ws/type/:typeStream by game type

Examples

ws://localhost:3000/ws/crash
ws://localhost:3000/ws/double
ws://localhost:3000/ws/type/multiplier
wss://datastream.hypetech.games/ws/crash

Message Format

Incoming Messages (Server → Client)

{
"round_id": 512,
"game_id": 1,
"game_slug": "crash",
"game_type": "multiplier",
"finished_at": "2026-01-17T00:45:42.735266-03:00",
"extras": "{\"point\": \"11.72\"}",
"timestamp": 1768621542123
}

Field Descriptions

FieldTypeDescription
round_idintegerUnique round identifier
game_idintegerGame ID in database
game_slugstringURL-friendly game name
game_typestringGame category
finished_atstring (ISO 8601)Round completion time
extrasstring (JSON)Game-specific data
timestampintegerUnix timestamp in milliseconds

Client Implementation

JavaScript (Browser)

class WebSocketClient {
constructor(game) {
this.game = game;
this.ws = null;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
this.reconnectDelay = 3000;
}

connect() {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const url = `${protocol}//${window.location.host}/ws/${this.game}`;

this.ws = new WebSocket(url);

this.ws.onopen = () => {
console.log('WebSocket connected');
this.reconnectAttempts = 0;
};

this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
this.handleMessage(data);
};

this.ws.onclose = () => {
console.log('WebSocket disconnected');
this.reconnect();
};

this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
}

handleMessage(data) {
console.log('New round:', data.round_id);
console.log('Result:', JSON.parse(data.extras));
}

reconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.error('Max reconnect attempts reached');
return;
}

this.reconnectAttempts++;
console.log(`Reconnecting in ${this.reconnectDelay}ms...`);

setTimeout(() => {
this.connect();
}, this.reconnectDelay);
}

disconnect() {
if (this.ws) {
this.ws.close();
}
}
}

// Usage
const client = new WebSocketClient('crash');
client.connect();

Go Client

package main

import (
"encoding/json"
"log"
"time"

"github.com/gorilla/websocket"
)

type Round struct {
RoundID int64 `json:"round_id"`
GameSlug string `json:"game_slug"`
GameType string `json:"game_type"`
FinishedAt time.Time `json:"finished_at"`
Extras json.RawMessage `json:"extras"`
Timestamp int64 `json:"timestamp"`
}

func main() {
url := "ws://localhost:3000/ws/crash"

c, _, err := websocket.DefaultDialer.Dial(url, nil)
if err != nil {
log.Fatal("dial:", err)
}
defer c.Close()

for {
_, message, err := c.ReadMessage()
if err != nil {
log.Println("read:", err)
return
}

var round Round
if err := json.Unmarshal(message, &round); err != nil {
log.Println("unmarshal:", err)
continue
}

log.Printf("Round %d: %s", round.RoundID, round.Extras)
}
}

Python Client

import asyncio
import json
import websockets

async def connect():
uri = "ws://localhost:3000/ws/crash"

async with websockets.connect(uri) as websocket:
print("Connected to WebSocket")

async for message in websocket:
data = json.loads(message)
print(f"Round {data['round_id']}: {data['extras']}")

if __name__ == "__main__":
asyncio.run(connect())

Server Implementation

The WebSocket handler is implemented using Fiber's WebSocket middleware:

// internal/adapters/inbound/websocket/handlers.go

func (h *Handlers) StreamByGame(c *websocket.Conn) {
game := c.Params("game")
ctx := context.Background()

// Get last result and send immediately
if latest, err := h.roundRepo.GetLatest(ctx, game); err == nil {
data, _ := json.Marshal(latest)
c.WriteMessage(websocket.TextMessage, data)
}

// Subscribe to Redis Pub/Sub
channel := fmt.Sprintf("stream:%s", game)
sub, err := h.subscriber.Subscribe(ctx, channel)
if err != nil {
return
}

// Stream messages
for round := range sub {
data, _ := json.Marshal(round)
if err := c.WriteMessage(websocket.TextMessage, data); err != nil {
return
}
}
}

Best Practices

Connection Management

  1. Always implement reconnection logic - WebSocket doesn't auto-reconnect
  2. Use exponential backoff - Prevent server overload during outages
  3. Handle connection errors gracefully - Show user-friendly messages
  4. Close connections properly - Call ws.close() when done

Message Handling

  1. Parse JSON safely - Handle malformed messages
  2. Validate message structure - Check required fields
  3. Process messages asynchronously - Don't block the message loop
  4. Buffer messages if needed - Queue for UI updates

Security

  1. Use WSS (WebSocket Secure) in production
  2. Validate origin header - Prevent cross-site attacks
  3. Implement rate limiting - Protect against abuse
  4. Authenticate connections - Use tokens in query string or first message

Troubleshooting

Connection Refused

WebSocket connection to 'ws://localhost:3000/ws/crash' failed

Solutions:

  • Check if backend is running
  • Verify the port is correct
  • Check firewall settings

Connection Closes Immediately

WebSocket closed with code 1006

Solutions:

  • Check server logs for errors
  • Verify game slug exists
  • Check Redis connection

Messages Not Received

Solutions:

  • Verify Redis Pub/Sub is working
  • Check consumer is running
  • Ensure CDC pipeline is active

Performance Issues

Solutions:

  • Implement message batching
  • Use binary protocol for high volume
  • Consider connection pooling

Comparison with Other Protocols

FeatureWebSocketSSEWebTransport
BidirectionalYesNoYes
Auto-reconnectNoYesNo
Binary dataYesNoYes
Multiple streamsNoNoYes
Proxy-friendlySometimesYesNo

When to Use WebSocket

Use WebSocket when:

  • Client needs to send data to server
  • Latency < 50ms is critical
  • Many small messages per second
  • Long-lived connections

Consider alternatives when:

  • Server-only push (use SSE)
  • Ultra-low latency needed (use WebTransport)
  • Behind restrictive proxies (use SSE)