WebTransport API — Next-Gen Communication
In this tutorial, you'll learn about WebTransport API. We cover key concepts, practical examples, and best practices.
WebTransport is a next-generation browser API for low-latency, bidirectional communication between clients and servers using the QUIC protocol and HTTP/3.
In this tutorial, you'll learn the WebTransport API — how to establish connections, send and receive data via streams and datagrams, handle connection lifecycle, and choose between reliable and unreliable delivery. WebTransport offers lower latency than WebSockets and higher throughput than HTTP, making it ideal for real-time gaming, live streaming, collaborative editing, and IoT applications. By the end, you'll build a low-latency threat alert system.
Real-world use: Doda Browser's real-time sync feature uses WebTransport for low-latency bookmark and tab synchronization. Durga Antivirus Pro uses WebTransport for real-time threat intelligence feeds where sub-100ms delivery matters.
flowchart LR A[WebTransport] --> B[QUIC / HTTP/3] B --> C[Client] B --> D[Server] C --> E[Datagrams] C --> F[Streams] E --> G[Unreliable, unordered] E --> H[Lowest latency] F --> I[Reliable, ordered] F --> J[Stream per message] G & H --> K[Real-time gaming] G & H --> L[Live positions] I & J --> M[File transfer] I & J --> N[Chat messages]
Connecting to a WebTransport Server
WebTransport requires a server that supports QUIC and HTTP/3. Connect using the server's URL.
async function connectWebTransport(url) {
if (!navigator.webtransport) {
console.error('WebTransport not supported');
return null;
}
const transport = new WebTransport(url);
try {
await transport.ready;
console.log('WebTransport connection established');
return transport;
} catch (error) {
console.error('Connection failed:', error);
return null;
}
}
// Connect to a local WebTransport server
const transport = await connectWebTransport('https://localhost:4433');
console.log('Ready state:', transport.ready);
// Expected: Promise fulfilled
console.log('Closed state:', transport.closed);
// Expected: Promise pending (until close)
Sending and Receiving Datagrams
Datagrams provide unreliable, unordered delivery — perfect for real-time data where speed matters more than guaranteed delivery.
// Sending datagrams
function sendDatagram(transport, data) {
const encoder = new TextEncoder();
const buffer = encoder.encode(JSON.stringify(data));
transport.datagrams.writable.getWriter().write(buffer);
}
// Receiving datagrams
async function receiveDatagrams(transport, onMessage) {
const reader = transport.datagrams.readable.getReader();
const decoder = new TextDecoder();
try {
while (true) {
const { value, done } = await reader.read();
if (done) break;
const text = decoder.decode(value);
const data = JSON.parse(text);
onMessage(data);
}
} catch (error) {
console.error('Datagram read error:', error);
}
}
// Usage: send real-time positions
setInterval(() => {
sendDatagram(transport, {
type: 'position',
x: Math.random() * 100,
y: Math.random() * 100,
timestamp: Date.now()
});
}, 50); // 20 updates per second
receiveDatagrams(transport, (data) => {
console.log('Received datagram:', data);
});
Using Bidirectional Streams
Streams provide reliable, ordered delivery. Each stream is independent — a slow stream doesn't block others.
// Open a bidirectional stream
async function sendStream(transport, payload) {
const stream = await transport.createBidirectionalStream();
const writer = stream.writable.getWriter();
const encoder = new TextEncoder();
await writer.write(encoder.encode(JSON.stringify(payload)));
await writer.close();
// Read response from server
const reader = stream.readable.getReader();
const decoder = new TextDecoder();
const { value } = await reader.read();
return JSON.parse(decoder.decode(value));
}
// Usage: send a threat alert with guaranteed delivery
async function sendThreatAlert(transport, alert) {
const response = await sendStream(transport, {
type: 'threat_alert',
threat: alert.threat,
severity: alert.severity,
fileHash: alert.fileHash,
timestamp: Date.now()
});
console.log('Server acknowledged:', response);
return response;
}
Unidirectional Streams
For scenarios where only the client or server needs to send data (e.g., log streaming).
// Client → Server unidirectional stream
async function sendUnidirectionalStream(transport, data) {
const stream = await transport.createUnidirectionalStream();
const writer = stream.getWriter();
const encoder = new TextEncoder();
await writer.write(encoder.encode(JSON.stringify(data)));
await writer.close();
}
// Read incoming unidirectional streams from server
async function receiveServerStreams(transport) {
const reader = transport.incomingUnidirectionalStreams.getReader();
const decoder = new TextDecoder();
try {
while (true) {
const { value: stream, done } = await reader.read();
if (done) break;
// Process each incoming stream in parallel
handleIncomingStream(stream, decoder);
}
} catch (error) {
console.error('Stream read error:', error);
}
}
async function handleIncomingStream(stream, decoder) {
const reader = stream.getReader();
while (true) {
const { value, done } = await reader.read();
if (done) break;
const message = JSON.parse(decoder.decode(value));
console.log('Server pushed:', message);
}
}
Connection Management
Handle connection lifecycle events for resilient applications.
class WebTransportClient {
constructor(url) {
this.url = url;
this.transport = null;
this.reconnectDelay = 1000;
}
async connect() {
try {
this.transport = new WebTransport(this.url);
await this.transport.ready;
console.log('Connected to', this.url);
// Handle close
this.transport.closed.then(() => {
console.log('Connection closed');
this.reconnect();
});
// Handle connection errors via monitor
this.monitorConnection();
return true;
} catch (error) {
console.error('Connection error:', error);
this.reconnect();
return false;
}
}
monitorConnection() {
// Poll for connection stats
setInterval(() => {
if (!this.transport) return;
const stats = this.transport.stats;
console.log('RTT:', stats.rtt, 'ms');
console.log('Packets lost:', stats.packetsLost);
console.log('Estimated bandwidth:', stats.estimatedBandwidth);
if (stats.packetsLost > 50) {
console.warn('High packet loss, reconnecting...');
this.close();
this.connect();
}
}, 5000);
}
reconnect() {
setTimeout(() => {
console.log('Attempting reconnection...');
this.connect();
}, this.reconnectDelay);
// Exponential backoff
this.reconnectDelay = Math.min(this.reconnectDelay * 2, 30000);
}
close() {
if (this.transport) {
this.transport.close();
this.transport = null;
}
}
}
const client = new WebTransportClient('https://quic.dodatech.com:4433');
client.connect();
Server-Side WebTransport (Node.js)
WebTransport requires a QUIC-capable server. Using webtransport-node or a QUIC library.
// server.js — Requires Node.js with QUIC support
import { createServer } from 'webtransport-node';
const server = createServer({
key: fs.readFileSync('./server.key'),
cert: fs.readFileSync('./server.cert')
});
server.on('connection', (transport) => {
console.log('Client connected');
// Receive datagrams
transport.datagrams.readable.pipeTo(
new WritableStream({
write(chunk) {
const message = JSON.parse(new TextDecoder().decode(chunk));
console.log('Received:', message);
// Echo back
const writer = transport.datagrams.writable.getWriter();
writer.write(chunk);
writer.releaseLock();
}
})
);
// Handle streams
transport.on('stream', (stream) => {
const reader = stream.readable.getReader();
const writer = stream.writable.getWriter();
reader.read().then(({ value }) => {
const message = JSON.parse(new TextDecoder().decode(value));
console.log('Stream message:', message);
// Send acknowledgment
writer.write(new TextEncoder().encode(
JSON.stringify({ status: 'received', id: message.id })
));
});
});
});
server.listen({
port: 4433,
host: '0.0.0.0'
});
WebTransport vs WebSockets
| Feature | WebTransport | WebSockets |
|---|---|---|
| Protocol | QUIC / HTTP/3 | TCP |
| Transport | Reliable + Unreliable | Reliable only |
| Delivery modes | Streams + Datagrams | Message frames |
| Head-of-line blocking | None (per-stream) | Full TCP HOL |
| Connection overhead | 0-RTT handshake | TCP + TLS handshake |
| Multiplexing | Native (QUIC streams) | Single ordered channel |
| Browser support | Chrome 90+, Edge 90+ | Universal |
| Use case | Low-latency, real-time | General bidirectional |
Real-Time Threat Alert System
Combine all concepts into a practical application.
class ThreatAlertTransport {
constructor(serverUrl) {
this.serverUrl = serverUrl;
this.transport = null;
this.alertCallbacks = [];
}
async connect() {
this.transport = new WebTransport(this.serverUrl);
await this.transport.ready;
this.listenForAlerts();
this.monitorLatency();
}
async sendHeartbeat(nodeId) {
await this.sendDatagram({
type: 'heartbeat',
nodeId,
timestamp: Date.now(),
status: 'active'
});
}
async sendThreatReport(threat) {
// Use a stream for reliable delivery of critical data
const stream = await this.transport.createBidirectionalStream();
const writer = stream.writable.getWriter();
await writer.write(new TextEncoder().encode(
JSON.stringify({
type: 'threat_report',
severity: threat.severity,
signature: threat.signature,
filePath: threat.filePath,
timestamp: Date.now()
})
));
await writer.close();
// Read acknowledgment
const reader = stream.readable.getReader();
const { value } = await reader.read();
return JSON.parse(new TextDecoder().decode(value));
}
listenForAlerts() {
const reader = this.transport.datagrams.readable.getReader();
const decoder = new TextDecoder();
async function poll() {
while (true) {
const { value, done } = await reader.read();
if (done) break;
const alert = JSON.parse(decoder.decode(value));
this.alertCallbacks.forEach(cb => cb(alert));
}
}
poll();
}
onAlert(callback) {
this.alertCallbacks.push(callback);
}
monitorLatency() {
setInterval(() => {
const start = performance.now();
this.sendDatagram({ type: 'ping', time: start });
// Server echoes back, measure RTT
}, 10000);
}
async sendDatagram(data) {
const encoder = new TextEncoder();
await this.transport.datagrams.writable.getWriter()
.write(encoder.encode(JSON.stringify(data)));
}
}
const alerts = new ThreatAlertTransport('https://quic-threats.dodatech.com:4433');
await alerts.connect();
alerts.onAlert((alert) => {
console.log(`ALERT [${alert.severity}]: ${alert.message}`);
if (alert.severity === 'critical') {
triggerImmediateResponse(alert);
}
});
Common Errors
- WebTransport not supported — The API requires Chrome 90+ or Edge 90+. Always check
navigator.webtransportbefore use. Provide a WebSocket fallback. - TLS certificate errors — WebTransport requires valid TLS certificates. Self-signed certs fail in browsers. Use a trusted CA or
localhostfor development. - Forgetting to close writers — Open writers prevent stream closure. Always call
writer.close()orwriter.abort()after writing. - Datagram size limits — Datagrams are typically limited to ~1400 bytes (MTU). Larger payloads must use streams or be fragmented manually.
- Connection not ready — Sending data before
transport.readyrejects. Alwaysawait transport.readybefore reading or writing. - No built-in reconnection — WebTransport doesn't auto-reconnect. Implement reconnection with exponential backoff as shown above.
Practice Questions
Challenge
Build a real-time collaborative threat analysis dashboard. Multiple security analysts connect via WebTransport to a central server. Each client sends cursor positions (datagrams, 60fps), annotation strokes (streams, reliable), and threat markers (datagrams). The server broadcasts all positions and markers to connected clients. Implement reconnection with state reconciliation.
Real-World Task
Extend Durga Antivirus Pro's enterprise console with WebTransport. Replace the existing polling-based threat feed with a WebTransport connection to the central threat intelligence server. Use datagrams for real-time threat alerts (sub-100ms delivery), streams for signature database updates (reliable, ordered), and implement connection monitoring with automatic reconnection. Provide a WebSocket fallback for browsers that don't support WebTransport.
Previous: WebSockets Guide | Related: Web Performance Optimization | Related: HTTP Caching
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro