WebTransport Datagrams and Unreliable Transmission

WebTransport brings low-latency, bidirectional communication to the web using the HTTP/3 and QUIC protocols. Unlike traditional WebSockets or WebRTC data channels, WebTransport natively provides access to both reliable streams and unreliable datagrams. This article explains what datagrams are within the WebTransport API, why they are essential for real-time applications, and how JavaScript developers handle unreliable, unordered data transmission in practice.

What Are Datagrams in WebTransport?

In WebTransport, a datagram is a discrete, self-contained message sent over the network. Datagrams are transmitted using QUIC’s underlying datagram extension, offering properties similar to the User Datagram Protocol (UDP):

This makes datagrams ideal for time-sensitive use cases where low latency is critical and outdated data is useless, such as online gaming state updates, live media streaming, and real-time telemetry.

The JavaScript WebTransport Datagram API

The JavaScript WebTransport interface exposes datagram functionality through the datagrams property, which utilizes the Streams API (ReadableStream and WritableStream).

Sending Datagrams

To send datagrams, you acquire a writer from the transport.datagrams.writable stream and write a Uint8Array buffer.

const transport = new WebTransport("https://example.com:4433/webtransport");
await transport.ready;

const writer = transport.datagrams.writable.getWriter();
const data = new TextEncoder().encode("player_position:120,340");

await writer.write(data);
writer.releaseLock();

Because datagrams are unreliable, the resolution of writer.write() only confirms that the datagram was handed off to the transport layer, not that the remote peer successfully received it. If network congestion occurs, the browser or operating system may drop datagrams automatically.

Receiving Datagrams

To receive datagrams, you read from the transport.datagrams.readable stream using an asynchronous loop:

const reader = transport.datagrams.readable.getReader();

try {
  while (true) {
    const { value, done } = await reader.read();
    if (done) break;
    // value is a Uint8Array
    const message = new TextDecoder().decode(value);
    console.log("Received datagram:", message);
  }
} catch (error) {
  console.error("Error reading datagrams:", error);
} finally {
  reader.releaseLock();
}

Handling Unreliable Transmission in JavaScript

Because datagrams lack built-in reliability and ordering, the application layer in JavaScript is responsible for managing packet anomalies.

1. Packet Size Limitations

Datagrams cannot exceed the Maximum Transmission Unit (MTU) of the network path without being fragmented or dropped. The WebTransport API exposes the maximum allowed payload size via transport.datagrams.maxDatagramSize:

const maxBytes = transport.datagrams.maxDatagramSize;
if (payload.byteLength <= maxBytes) {
  await writer.write(payload);
} else {
  // Split data or send via reliable WebTransport stream instead
}

2. Handling Out-of-Order Delivery

To detect and handle packets arriving out of sequence, applications typically attach a sequence number or timestamp to the beginning of each datagram payload:

let localSequence = 0;

function createDatagramPayload(dataBuffer) {
  const buffer = new ArrayBuffer(4 + dataBuffer.byteLength);
  const view = new DataView(buffer);
  
  // Set 32-bit sequence number header
  view.setUint32(0, localSequence++, false);
  
  new Uint8Array(buffer, 4).set(new Uint8Array(dataBuffer));
  return new Uint8Array(buffer);
}

On the receiving end, the receiver tracks the highest sequence number seen. Any incoming packet with an older sequence number can be discarded if the application only cares about the freshest state.

3. Handling Dropped Packets

Since lost datagrams are never re-sent by the protocol, JavaScript applications handle loss using one of two strategies: * State Replacement: The sender continuously transmits the latest complete snapshot (e.g., current player coordinates). Missing an intermediate update is harmless because the next packet provides the current state. * Application-Level Acknowledgment: If specific datagrams require confirmation, the receiver sends back a lightweight acknowledgment over an unreliable datagram or a reliable stream, prompting the sender to retransmit manually if necessary.