Real-Time Text (RTT/TTY-TDD) Accessibility on Live Voice Calls
Real-time text (RTT) is the modern successor to the telephone-typewriter (TTY/TDD) accessibility channel. It lets a caller or agent exchange text — often character-by-character — on a live call, alongside the audio for a hearing caller or instead of it for a deaf or hard-of-hearing caller. On Orbit, RTT is implemented as RFC 4103 (T.140) text frames carried on the same voice leg, and each frame is recorded per-leg so the agent’s RTT panel and the caller’s endpoint can each see the text as the other party types it. This guide covers how to arm the RTT channel on a live call, how agents and callers exchange text on it, the 2000-character frame ceiling, and how to roll the capability out as a tenant-owned accessibility commitment — not a platform mandate. Every sample below is copy-pasteable; substitute your credentials and the live call id.Prerequisites
- A tenant with the voice/CCaaS pillar enabled.
- The browser softphone or an embedded WebRTC client — RTT opens on the live call, not in a separate chat surface. See Set up the browser (WebRTC) softphone.
- Credentials by surface: the internal routes (
/api/v1/internal/rtt/...) take a service-to-service credential — eitherAuthorization: Bearer <token>or the internal HMAC header pair — resolved against yourX-Tenant-Id: <organizationId>. The dashboard routes (/api/v1/voice/calls/:callId/rtt/...) take the operator’s dashboard session. Keep the two separate — the dashboard session token does not reach the internal routes.
1. Arm the RTT channel on a live call
RTT is off for every call until you arm it. Per-call opt-in is deliberate — a contact center treats RTT as a conversation-level flag, not a blanket tenant setting. Three arm paths exist:- From the softphone footer. While a call is answered, click the Real-time text (TTY) toggle to open the panel. The toggle calls
PATCHagainst the call’s RTT config withenabled: trueand pins the panel for that call. - From an IVR or queue step. A flow that takes an inbound call to a queue can open the channel before an agent picks up by posting
PATCH /api/v1/internal/rtt/<call-id>/configwith{ "enabled": true }.
200
Content-Type hand-holding beyond the standard JSON body:
- From the agent’s outgoing send. When an agent types the first outgoing line, the send endpoint (
POST /api/v1/voice/calls/<call-id>/rtt/send) accepts the frame and auto-flips the call’s RTT config toenabledon its path — a dashboard agent deliberately typing into the RTT panel is the per-call opt-in, and the softphone makes no separate arm call.
404 when the call does not exist, a 409 with VOICE_RTT_NOT_ENABLED when a text frame arrives before the channel is armed, and the structured enabled state in the response body on success — never a silent drop.
2. Exchange text on the live call
Once armed, every frame lands on both sides of the conversation:- Caller to agent. The caller’s TDD endpoint (or an app-side WebRTC client) streams text over the in-band T.140 leg. The softphone holds an open SSE connection to
GET /api/v1/voice/calls/:callId/rtt/stream, so the agent sees the caller’s text arriving as it is typed — a per-frame event ordered by the call-relative timestamp. - Agent to caller. The agent’s send endpoint persists the outgoing line and pushes the same frame back over the caller’s endpoint. The body is
{ text, final }—final: falsewhile the agent is still typing (debounced),trueon Enter. Each frame is recorded exactly once.
409 envelope from section 1:
200
POST /api/v1/internal/rtt/<call-id> with the internal credential and an explicit party:
party set to "caller".
To watch the caller side live the way the softphone does, hold an SSE connection open. -N disables output buffering and Accept: text/event-stream makes the intent explicit:
connected and status events, then delivers one rtt event per caller frame (the stream carries the caller side only — the panel appends the agent’s own outgoing lines locally, so there is no double-echo):
: ping keepalive line arrives every 15 seconds so proxies do not idle-close the socket, and the server-side session caps at 30 minutes — reconnect when it closes. When the call reaches a terminal status (completed, failed, busy, no-answer, or canceled) a final end event closes the stream cleanly.
The same per-frame events drive any dashboard the tenant embeds — the RTT panel subscribes via SSE, not poll, which is what makes the sub-second latency target hold.
3. The 2000-character frame ceiling
A single RTT frame is capped at 2000 characters. The ceiling exists to defend against a malformed or hostile endpoint pushing a multi-kilobyte blob into one frame — it mirrors the same kind of cap on the live-interpretation module. Both the inbound caller frame and the outbound agent line are truncated at the frame endpoint, so the persisted row never violates the ceiling and the SSE event never ships a partial frame to the panel. Each persisted row is taggedsource = "rtt_relay" and carries an is_final bit. Replay consumers (QA panels, post-call transcript exports) filter by that tag and sort by segmentStartMs — the same call-relative timeline the speech-to-text and post-call transcript sources use, so RTT rows interleave cleanly in the transcript view.
4. Replay the transcript
Two surfaces read the RTT receipt after the call:GET /api/v1/internal/rtt/<call-id>returns the call’s RTT frames in playback order, cursor-paginated (limit1–500, default page;cursorcontinues).
200
next_cursor is non-null, pass it back as ?cursor=<value> on the next request.
- The dashboard’s call-details page shows the same receipt on the RTT panel — it reads the persisted rows with
source = "rtt_relay", which keeps RTT frames from mixing into the post-call speech-to-text rows.
5. Roll out as a tenant-owned accessibility gate
The FCC/ADA accessibility commitment for deaf and hard-of-hearing callers sits on each contact center — Orbit is the text-relay surface, not the accessibility mandate. A tenant usually adopts RTT in one of two postures:- Arm-it-on-demand. The softphone, the IVR, or the queue arms the channel per call once an agent flags the caller as a text-relay user. This is the default posture — RTT stays off until something opts in.
- Arm-it-up-front. A flagged accessibility lane opens the config before the call lands on an agent, so every leg already has the channel on.
Worked example — one flagged caller end to end
- The tenant’s CCaaS voice pillar is enabled and the agent’s softphone registers per the browser-softphone guide.
- Phone the tenant’s DID from an outside line. The supervisor flips the Real-time text (TTY) toggle as the call arrives, opening the panel.
- The agent types assistance into the panel; the caller’s text arrives over the SSE stream in the same panel.
- Complete wrap-up. The persisted rows with
source = "rtt_relay"land on the call-details transcript panel, interleaved by timestamp with the speech-to-text rows, and clear on the retention sweep the org already runs.
Related reading
- Set up the browser (WebRTC) softphone — the panel RTT runs in
- Contact-center (voice/CCaaS) go-live runbook — the fuller rollout checklist
- Voice call lifecycle — the receipt side of an RTT frame
- RTT (Real-Time Text) / TTY-TDD in the glossary — the term in one paragraph, including the round-trip-time disambiguation