# WebRTC Vendor Adapters

> Browser, LiveKit, Daily, Chime, Twilio, and Vapi bridges.

The FirstByte gateway accepts WebRTC media on a dedicated signaling
port (`7443` by default). It bridges the media into the same call
lifecycle as QUIC and SIP. Six adapter shapes are built in. They share
the same post-bridge code path, but they speak different signaling
dialects on the ingress side.

## Surface summary

| Adapter   | Family                | Signaling                                                | Auth                          | Codec on the wire        |
| --------- | --------------------- | -------------------------------------------------------- | ----------------------------- | ------------------------ |
| Browser   | WebRTC                | SDP offer/answer over WebSocket on `/signal/browser/<tenant_id>` | `SessionAuth` JWT             | Opus 48 kHz / PCM        |
| LiveKit   | WebRTC                | Protobuf SDP exchange on `/signal/livekit/<tenant_id>`   | LiveKit JWT (`participant`)   | Opus / PCMU passthrough  |
| Daily     | WebRTC                | JSON text-frames on `/signal/daily/<tenant_id>`          | Daily-issued meeting token    | Opus                     |
| Chime     | WebRTC                | wss:// signal URL handed off after meeting create        | AWS SigV4 + Chime attendee    | Opus                     |
| Twilio    | WebSocket-PCM         | Twilio Media Streams on `/signal/twilio/<tenant_id>`     | `X-Twilio-Signature` HMAC     | PCMU 8 kHz / linear16    |
| Vapi      | WebSocket-PCM         | Plain WS frames on `/signal/vapi/<tenant_id>`            | Vapi-issued bearer token      | linear16 16 kHz          |

The first four are full WebRTC (DTLS-SRTP, ICE-lite, congestion control).
The last two use a plain WebSocket. For those two, the gateway
transcodes to and from the trunk's negotiated codec.

## Tenant-scoped paths

Every signaling endpoint includes the tenant id in the URL path so the
gateway can route to the right config without an extra round-trip:

```
wss://engine.firstbyte.fast:7443/signal/<vendor>/<tenant_id>
```

For vendor-side connections, the path is the *only* place where tenant
scoping appears. There is no separate JWT step on the FirstByte
side, because the gateway pins the vendor's own auth.

## Per-vendor notes

### Browser

The TypeScript SDK in browser mode uses this adapter when you opt into
WebRTC media. The default is WebTransport. Pass `media: "webrtc"` to
`connect()`. The SDK then negotiates SRTP locally. It shows inbound
audio via the standard `MediaStreamTrack` API instead of `onAudioFrame`
byte callbacks.

### LiveKit

Pass a participant JWT that your LiveKit server minted. The
FirstByte gateway does not mint LiveKit tokens. The adapter joins
the LiveKit room as a participant. It subscribes to one published
track. It bridges that track to the matching `call_sid` on the gateway.

Use this adapter when the same call must fan out to multiple human
listeners in the LiveKit room (agents, supervisors, transcribers) in
addition to the AI bridge.

### Daily

Daily uses plain JSON signaling. Pass the meeting URL and the
participant token. The adapter joins as a hidden participant and
bridges audio.

### Chime

First create the Chime meeting out-of-band (via the AWS SDK or REST).
Then pass the `MeetingId`, `AttendeeId`, and `JoinToken` that Chime
returned. SigV4 signing happens transparently.

### Twilio

Twilio Media Streams are PCMU at 8 kHz over a plain WebSocket. The
adapter checks the `X-Twilio-Signature` HMAC against a per-trunk
shared secret (set via [`AddTrunk.api_bearer_token`](/admin/trunks)).
Then it bridges raw PCMU bytes to the gateway without transcoding.

### Vapi

Vapi sends linear16 at 16 kHz on a plain WS. The adapter resamples to
the trunk's negotiated codec, typically PCMU 8 kHz for SIP. It uses the
same SIMD path that the C++ core uses for QUIC media.

## Why scope is locked

The set above is intentionally not extensible at runtime. Every adapter
has an opinionated mapping from vendor-specific lifecycle events
(`participant_joined`, `track_subscribed`, `media_stream.start`) into
the gateway's internal `CallEvent` machine. New vendors require a code
change, not a config knob. This is how the gateway keeps the same
`CallEvent` semantics across every transport.
