Orbit Voice Protocol (OVP)
Design preview — the wire format below is defined and tested; the live gateway does not yet speak it. This page is the target specification for OVP. The typed events and binary-frame codec ship today in the
@devotel/voice package and are covered by conformance tests, so you can build and validate against the wire format now. The production voice bridge at wss://voice.orbit.devotel.io/voice/ws/{session_id} currently runs an earlier audio pipeline whose frame layout and audio sample rate differ from the spec on this page — it does not yet negotiate the orbit-voice/1.0 subprotocol or emit these events. Do not point a client at the live gateway expecting this protocol until this banner is removed. The endpoint, hostname, and JWT-authenticated upgrade described below are the intended shape of the service, not its current behaviour.wss://voice.orbit.devotel.io/voice/ws/{session_id}?token=<jwt>
Subprotocol: orbit-voice/1.0
Audio format: PCM 16-bit mono 16kHz little-endian, 20ms frames
Event wire format: UTF-8 JSON with type discriminator
Why a new protocol
Twilio’s ConversationRelay is functional. We talked to teams running it in production and found three sharp edges that we could close with a clean-sheet design:promptis two events welded together — sometimes “the user said this”, sometimes “send this to the LLM”. Devs have to inspect heuristics on each frame to know which one they’re looking at.- Tool calls are unobservable — they ride the same text channel as the LLM’s spoken output. You cannot subscribe to “show me every tool the agent invoked, with arguments and results, in order”.
- One
interruptfor three different semantics — caller barge-in, programmatic cancel, and human takeover all collapse onto one primitive. They should drive different LLM-context truncation policies.
Connection lifecycle
1. Open the WebSocket
The session id and its signed token are issued together when the call session is created — a browser softphone receives them from its session-start response, and the inbound SIP path receives them from the dialplan. The token travels in thetoken query parameter (not an Authorization header) so the gateway can authenticate the upgrade before the WebSocket handshake completes.
tenant_id, session_id, and the call context. It is minted server-side as part of session creation; there is no public per-agent token endpoint today.
If the subprotocol header is missing or wrong, the handshake fails with HTTP 400 and an OVP_SUBPROTOCOL_MISMATCH body. If the JWT is bad, the handshake completes but the server immediately sends an error event with OVP_AUTH_FAILED and closes.
2. Server sends session.start
First frame from the server. Includes the protocol version, agent config, audio invariants, and the set of tools the LLM may call. Clients should store the session_id — it appears on every subsequent event.
3. Bidirectional audio + events
- Binary frames carry PCM samples in both directions. Each frame starts with a 12-byte header (see below).
- Text frames carry JSON events. Every event has
type,seq,ts,session_id.
seq) are monotonic per direction. The server’s outbound counter and the client’s outbound counter increment independently. Servers must reject seq regressions with OVP_SEQ_REGRESSION to defend against replay.
4. Resume on drop
If the WebSocket drops within 30 seconds, the client may reconnect and sendsession.resume with the last seq it observed from the server. The server replays missed events and resumes the agent in place. Twilio drops the call.
5. End
Either the caller hangs up, the agent decides the call is over, the developer programmatically cancels, or a fatal error fires. The server emitssession.end with a reason and final stats, then closes the WS with code 1000.
Binary audio frame format
Every binary WebSocket frame carries one 20ms PCM chunk prefixed by a 12-byte header.Event reference
Every JSON event extends the envelope:Session lifecycle
session.start (server → client)
session.update (client → server)
voice_id, language, model, instructions, speech_rate, caller_context. Empty patch is rejected with OVP_SESSION_UPDATE_REJECTED. Twilio supports only language swap.
Audio path metadata
Binary frames carry the bytes; these JSON events carry the metadata around them.Transcripts
Agent reasoning + tool-use
OVP splits the LLM lifecycle into four distinct event types. Twilio collapses these into one text channel.Interrupts — three distinct types
This is where Twilio’sinterrupt falls short. OVP distinguishes:
Multi-modal sends
The agent can send media to the caller mid-call. Useful when the call is happening alongside SMS / WhatsApp / RCS (the customer’s phone has both channels available).Realtime observability
These events are emitted continuously during the call. Pipe them to your dashboard, your billing UI, your QA tool — Orbit does not charge for them and they fire even when no one is subscribed (so reconnects can replay).Human handoff lifecycle
DTMF
Error
recoverable: true means the WS stays open. recoverable: false is always followed by session.end.
Worked example: simple Q&A call
Caller dials a DID, asks one question, hangs up.Worked example: tool call mid-conversation
lookup_order with {order_id:12345}, returned in 412ms” without parsing the text channel.