WebTransport
WebTransport provides low latency, bidirectional communication using Centrifugo with automatic WebSocket fallback.
Open WebTransport Dashboard — See real-time streaming in action!
Overview
| Property | Value |
|---|---|
| Server | Centrifugo v6 |
| Primary Transport | WebTransport (QUIC/HTTP/3) |
| Fallback Transport | WebSocket (TCP) |
| Latency | ~10ms |
| Direction | Bidirectional |
| Scaling | Horizontal via Redis |
| Browser Support | All browsers (WebSocket fallback) |
Architecture
WebTransport is implemented using Centrifugo, a real-time messaging server that provides WebTransport with automatic WebSocket fallback:
┌─────────────────────────────────────────────────────────────────────────┐
│ WEBTRANSPORT ARCHITECTURE │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Consumer │────▶│ Centrifugo │────▶│ Clients │ │
│ │ (Publisher) │ │ (Server) │ │ (Browsers) │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │ │ │ │
│ │ HTTP API │ Redis │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ /api/publish │ │ Pub/Sub │ │ WebTransport │ │
│ │ POST + X-API- │ │ + History │ │ or WebSocket │ │
│ │ Key │ │ │ │ (automatic) │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
Why Centrifugo for WebTransport?
| Feature | Native WebTransport | Centrifugo |
|---|---|---|
| Auto Reconnection | Manual | Built-in |
| Fallback Transport | No | Yes (WT → WS) |
| History/Recovery | No | Yes |
| Horizontal Scaling | Complex | Built-in (Redis) |
| Browser Support | Chrome/Firefox only | All browsers |
| TLS/Certificates | Manual (certmagic) | Standard |
Endpoints
| Transport | URL |
|---|---|
| WebTransport | https://wt.datastream.hypetech.games/connection/webtransport |
| WebSocket (fallback) | wss://wt.datastream.hypetech.games/connection/websocket |
Client Implementation
JavaScript (Browser)
<script src="https://unpkg.com/centrifuge@5/dist/centrifuge.js"></script>
<script>
const CENTRIFUGO_WS_URL = 'wss://wt.datastream.hypetech.games/connection/websocket';
const CENTRIFUGO_WT_URL = 'https://wt.datastream.hypetech.games/connection/webtransport';
async function connect() {
const hasWebTransport = typeof WebTransport !== 'undefined';
// Configure transports with automatic fallback
const transports = [];
// Try WebTransport first if available
if (hasWebTransport) {
transports.push({
transport: 'webtransport',
endpoint: CENTRIFUGO_WT_URL
});
}
// Always add WebSocket as fallback
transports.push({
transport: 'websocket',
endpoint: CENTRIFUGO_WS_URL
});
const centrifuge = new Centrifuge(transports);
centrifuge.on('connected', (ctx) => {
console.log(`Connected via ${ctx.transport}`);
});
centrifuge.on('disconnected', () => {
console.log('Disconnected');
});
centrifuge.connect();
return centrifuge;
}
function subscribe(centrifuge, gameSlug, onMessage) {
const channel = `stream:${gameSlug}`;
const sub = centrifuge.newSubscription(channel);
sub.on('publication', (ctx) => {
onMessage(ctx.data);
});
sub.subscribe();
return sub;
}
// Usage
const centrifuge = await connect();
subscribe(centrifuge, 'crash', (data) => {
console.log('Round result:', data);
});
</script>
React Hook
import { useState, useEffect } from 'react';
import { Centrifuge } from 'centrifuge';
const CENTRIFUGO_WS_URL = 'wss://wt.datastream.hypetech.games/connection/websocket';
const CENTRIFUGO_WT_URL = 'https://wt.datastream.hypetech.games/connection/webtransport';
function useWebTransport(channels) {
const [data, setData] = useState({});
const [connected, setConnected] = useState(false);
const [transport, setTransport] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
const hasWebTransport = typeof WebTransport !== 'undefined';
const transports = [];
if (hasWebTransport) {
transports.push({ transport: 'webtransport', endpoint: CENTRIFUGO_WT_URL });
}
transports.push({ transport: 'websocket', endpoint: CENTRIFUGO_WS_URL });
const centrifuge = new Centrifuge(transports);
centrifuge.on('connected', (ctx) => {
setConnected(true);
setTransport(ctx.transport);
});
centrifuge.on('disconnected', () => {
setConnected(false);
});
centrifuge.on('error', (ctx) => {
setError(ctx.error?.message || 'Unknown error');
});
// Subscribe to channels
const subscriptions = channels.map(channel => {
const sub = centrifuge.newSubscription(channel);
sub.on('publication', (ctx) => {
setData(prev => ({
...prev,
[channel]: ctx.data
}));
});
sub.subscribe();
return sub;
});
centrifuge.connect();
return () => {
subscriptions.forEach(sub => sub.unsubscribe());
centrifuge.disconnect();
};
}, [channels.join(',')]);
return { data, connected, transport, error };
}
// Usage
function GameDashboard() {
const { data, connected, transport } = useWebTransport([
'stream:crash',
'stream:double'
]);
return (
<div>
<div>Status: {connected ? `Connected (${transport})` : 'Disconnected'}</div>
{Object.entries(data).map(([channel, result]) => (
<div key={channel}>
<h3>{channel}</h3>
<pre>{JSON.stringify(result, null, 2)}</pre>
</div>
))}
</div>
);
}
Channel Naming
Channels follow the format: stream:{game_slug}
| Channel | Description |
|---|---|
stream:crash | Crash game results |
stream:double | Double game results |
stream:aviador | Aviador game results |
stream:wall-street | Wall Street game results |
stream:fortune-tiger | Fortune Tiger game results |
Browser Support
| Browser | WebTransport | WebSocket Fallback |
|---|---|---|
| Chrome 97+ | Yes | Yes |
| Edge 97+ | Yes | Yes |
| Firefox 114+ | Yes | Yes |
| Safari | No | Yes |
| iOS Safari | No | Yes |
When WebTransport is not available (Safari, iOS, or UDP blocked), the client automatically falls back to WebSocket. No code changes required.
Performance Comparison
| Metric | WebSocket | WebTransport |
|---|---|---|
| Connection time | 1 RTT | 0-1 RTT |
| Message latency | 5-10ms | 1-5ms |
| Head-of-line blocking | Yes | No |
| Connection migration | No | Yes |
Infrastructure
WebTransport via Centrifugo is deployed on Kubernetes with:
- Centrifugo v6 with WebTransport enabled
- AWS NLB for TCP (WebSocket) and UDP (QUIC) traffic
- cert-manager for TLS certificates
- Redis for horizontal scaling
For detailed infrastructure configuration, see the Centrifugo documentation.
Troubleshooting
WebTransport Not Connecting
Cause: Browser doesn't support WebTransport or UDP is blocked
Solution: The client automatically falls back to WebSocket. Check the transport value in the connection event.
"unknown channel" Error
Cause: Channel namespace not configured in Centrifugo
Solution: Use the correct channel format: stream:{game_slug}
High Latency
Cause: Falling back to WebSocket instead of WebTransport
Solution:
- Use Chrome or Firefox
- Ensure UDP traffic is not blocked by firewall
- Check if the server supports WebTransport
When to Use WebTransport
Use WebTransport (via Centrifugo) when:
- You need automatic reconnection
- You want horizontal scaling
- You need universal browser support (with fallback)
- You want managed infrastructure
Consider native WebSocket when:
- You need maximum control
- You have simple single-server deployment
- You don't need reconnection logic