> ## Documentation Index
> Fetch the complete documentation index at: https://daily-ms-ws-body-url-encode.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# MoQ Transport

> MoqTransport for the JavaScript SDK: Media over QUIC audio and RTVI messaging to a Pipecat bot, over WebTransport.

`MoqTransport` connects a `PipecatClient` to a Pipecat bot over [Media over QUIC](https://quic.video) — either through a relay both sides dial, or directly to a bot serving its own socket.

It publishes the microphone as an Opus broadcast and consumes the bot's, discovering codec and sample rate from the bot's catalog at connect time rather than pinning them in code. RTVI messages travel in both directions over a dedicated JSON stream track, so this is a full RTVI transport rather than an audio-only path. The transport automatically deduplicates messages across reconnects, preventing redelivery of messages such as `client-ready` when either side replays its log.

Connection uses WebTransport with a WebSocket fallback (raced by `@moq/net`), with auto-reconnect via `Connection.Reload`. Once the session reaches `ready`, relay reconnects happen in the background without changing the state — messages sent during a reconnect are queued and replayed to the bot when it resubscribes.

## Installation

```bash theme={null}
npm install @pipecat-ai/client-js @pipecat-ai/moq-transport
```

## Usage

### Basic Setup

```javascript theme={null}
import { PipecatClient } from "@pipecat-ai/client-js";
import { MoqTransport } from "@pipecat-ai/moq-transport";

const transport = new MoqTransport({
  relayUrl: "https://relay.example.com:4080/moq",
  clientId: "request",
  botId: "response",
});

const pcClient = new PipecatClient({ transport });

await pcClient.connect();
```

### With the development runner

The Pipecat development runner returns everything the transport needs from `POST /start`, under a `moq` key — the relay URL, the certificate hash in serve mode, the namespace, and the participant ids. Pass it straight through:

```javascript theme={null}
const { moq } = await fetch("/start", { method: "POST" }).then((r) => r.json());

const transport = new MoqTransport({
  relayUrl: moq.relayUrl,
  namespace: moq.namespace,
  clientId: moq.clientId,
  botId: moq.botId,
  serverCertificateHashes: moq.certHash
    ? [{ algorithm: "sha-256", value: moq.certHash }]
    : undefined,
});
```

<Warning>
  Constructing the transport by hand, take care with `clientId` and `botId`. The
  defaults here are `client0` and `bot0`, while a Pipecat bot publishes under
  `response` and listens on `request`. With defaults on both sides the two never
  find each other — so set `clientId: "request"` and `botId: "response"`, or
  take the values from `/start` as above.
</Warning>

### Self-signed development relay

A bot in serve mode mints its own certificate and reports the fingerprint. Pin it rather than disabling verification:

```javascript theme={null}
const transport = new MoqTransport({
  relayUrl: "https://localhost:4080/moq",
  serverCertificateHashes: [{ algorithm: "sha-256", value: certHashBytes }],
});
```

## API Reference

### Constructor Options

<ParamField path="relayUrl" type="string" required>
  Full URL of the MoQ peer, e.g. `https://relay.example.com:4080/moq`.
</ParamField>

<ParamField path="clientId" type="string" default="client0">
  This client's participant id. The client publishes under
  `<namespace>/<clientId>`.
</ParamField>

<ParamField path="botId" type="string" default="bot0">
  The bot's participant id. The client subscribes to `<namespace>/<botId>`.
</ParamField>

<ParamField path="namespace" type="string" default="pipecat">
  Top-level namespace, analogous to a room name.
</ParamField>

<ParamField path="serverCertificateHashes" type="WebTransportHash[]">
  Certificate hashes to pin, for a self-signed development relay. Same shape as
  WebTransport's own `serverCertificateHashes`.
</ParamField>

<ParamField path="transcriptTrack" type="string" default="transcript.json.z">
  Track name for the bidirectional RTVI message channel. Discovered by
  convention rather than through the catalog, so it has to match the bot's.
</ParamField>

<ParamField path="audioLatencyMs" type="number" default="80">
  Jitter buffer floor, in milliseconds. Lower is more interactive and drops more
  on a poor network; higher is smoother at the cost of delay.
</ParamField>

<ParamField path="audioBufferMaxMs" type="number | 'real-time'" default="30000">
  Ceiling for buffered playback. A bot writes TTS audio faster than real time
  with future-dated timestamps, and the player buffers it rather than skipping
  ahead — this caps how much may build up. An interruption flushes it early.
  Pass `"real-time"` to collapse to the floor instead, which only makes sense
  against a genuinely live publisher.
</ParamField>

<ParamField path="audioSampleRate" type="number" default="48000">
  Rate the client publishes its microphone at. One of Opus's supported rates:
  8000, 12000, 16000, 24000, or 48000. The bot reads this from the catalog and
  resamples, so exact agreement isn't required.
</ParamField>

### Broadcast paths

Each side publishes on one path and subscribes to the other's:

```
client publishes   <namespace>/<clientId>
client subscribes  <namespace>/<botId>
```

Audio track names within a broadcast come from the bot's catalog, so they aren't configured here. The transcript track is the exception — it's a JSON stream rather than a media rendition, so the catalog doesn't describe it and both sides agree on the name by convention.

### Transcript records

Each transcript track is a single group that a subscriber reads from its first record, so a reconnect on either side, including the automatic one from `@moq/net`, replays the whole log. Every record the transport sends is the RTVI message plus `seq`, its position in the connection's log, and `epoch`, an id minted per connection. Incoming records at or below the last `seq` accepted for the bot's current `epoch` are dropped, a different `epoch` is a new bot whose count starts over, and both fields are removed before the message reaches `PipecatClient`. A record without `seq` is delivered unchanged, so a bot that predates the fields keeps working. The bot side of the same format is described with the [server transport](/api-reference/server/services/transport/moq#transcript-records); `acceptTranscriptRecord` and the `TranscriptRecord` type are exported for other implementations of the stream.

Each side also sends a `session-ending` marker on its transcript before it leaves. The bot's tracks ending after the marker is a hangup and the transport disconnects. Ending without it may be a relay between the peers failing, which looks the same on the wire, so the transport redials: the bot either reappears on the new session or is never announced on it. `disconnect()` sends the marker from this side, so the bot ends the call at once.

## Events

`MoqTransport` reports connection state through the standard [`PipecatClient` callbacks](/api-reference/client/js/callbacks) — there are no MoQ-specific events.

## More Information

<CardGroup cols={2}>
  <Card title="Server-side MoQ transport" icon="server" href="/api-reference/server/services/transport/moq">
    The bot half of the pair, including serve and relay modes
  </Card>

  <Card title="Choosing a transport" icon="shuffle" href="/client/concepts/choosing-a-transport">
    How MoQ compares to the other client transports
  </Card>
</CardGroup>
