Skip to content

WebTransport API — Next-Gen Communication

DodaTech Updated 2026-06-24 8 min read

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

  1. WebTransport not supported — The API requires Chrome 90+ or Edge 90+. Always check navigator.webtransport before use. Provide a WebSocket fallback.
  2. TLS certificate errors — WebTransport requires valid TLS certificates. Self-signed certs fail in browsers. Use a trusted CA or localhost for development.
  3. Forgetting to close writers — Open writers prevent stream closure. Always call writer.close() or writer.abort() after writing.
  4. Datagram size limits — Datagrams are typically limited to ~1400 bytes (MTU). Larger payloads must use streams or be fragmented manually.
  5. Connection not ready — Sending data before transport.ready rejects. Always await transport.ready before reading or writing.
  6. No built-in reconnection — WebTransport doesn't auto-reconnect. Implement reconnection with exponential backoff as shown above.

Practice Questions

What is the difference between datagrams and streams in WebTransport?

Datagrams are unreliable, unordered messages limited to ~1400 bytes — like UDP packets. Streams are reliable, ordered, arbitrarily sized — like TCP connections. Use datagrams for real-time data where speed matters more than delivery, streams for reliable data transfer.

What protocol does WebTransport use?

WebTransport uses QUIC (Quick UDP Internet Connections) over HTTP/3. QUIC provides multiplexed streams, 0-RTT connection establishment, and native encryption — all over UDP instead of TCP.

How does WebTransport achieve lower latency than WebSockets?

WebTransport uses QUIC, which has 0-RTT connection establishment (no TCP handshake). It also eliminates head-of-line blocking by using independent streams instead of a single ordered channel. Datagrams can be sent with minimal overhead.

Can WebTransport replace WebSockets?

For new applications targeting modern browsers, yes — WebTransport offers better performance. However, WebSockets have universal browser support and simpler server infrastructure. Use WebTransport for latency-critical apps and WebSockets for broad compatibility.

What server infrastructure is needed for WebTransport?

WebTransport requires a server with QUIC and HTTP/3 support. Options include: Node.js with webtransport-node, nginx with QUIC module, Caddy, Cloudflare's QUIC support, or custom servers using QUIC libraries like quinn (Rust) or aioquic (Python)

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