# WebRTC Vendor Adapters

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

The ClutchCall gateway accepts WebRTC media on a dedicated signaling
port (`7443` by default) and bridges it into the same call lifecycle as
QUIC and SIP. Six adapter shapes are built in — they share the same
post-bridge code path but 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 ride a plain WebSocket and the gateway handles transcoding
to/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.clutchcall.dev:7443/signal/<vendor>/<tenant_id>
```

The path is the *only* place tenant scoping appears for vendor-side
connections — there's no separate JWT step on the ClutchCall side
because the vendor's own auth is what the gateway pins.

## Per-vendor notes

### Browser

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

### LiveKit

Pass a participant JWT minted by your LiveKit server (the ClutchCall
gateway doesn't mint LiveKit tokens). The adapter joins the LiveKit
room as a participant, subscribes to one published track, and bridges
that track to the matching `call_sid` on the gateway.

Use this when the same call needs to 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

The adapter expects you to have already created the Chime meeting
out-of-band (via the AWS SDK or REST). Pass the `MeetingId`, `AttendeeId`,
and `JoinToken` that Chime returned. SigV4 happens transparently.

### Twilio

Twilio Media Streams are PCMU at 8 kHz over a plain WebSocket. The
adapter validates the `X-Twilio-Signature` HMAC against a per-trunk
shared secret (set via [`AddTrunk.api_bearer_token`](/admin/trunks)),
then 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) using the
same SIMD path 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 — that's how the gateway keeps the same
`CallEvent` semantics across every transport.
