Skip to main content
Version: 0.9.14

Text chat

Delphi supports pure text chat in the browser through the same TelAPI session surface as voice — without WebRTC, microphone access, or a SIP leg. The client opens a session with mode: 'text', connects to /ws/session, and exchanges chat messages on the WebSocket channel.

Not the same as “text overlay” on a voice call

During an active voice session you can also send text with enableTextChat / sendTextChat on the same channel. That keeps one voice_conversation session and toggles whether the AI processes typed input. This page covers the dedicated text session mode (web_chat flow entry) and session migration between text and voice. See SDK → Text chat quick start for both patterns.

Prerequisites

RequirementWhy
webrtc feature flag onGates all client-facing TelAPI session routes, including text.
Endpoint with a web_chat entry pointText mode runs the flow's web-chat path, not the phone or web-voice path.
API key with CREATE_CALL_TOKENRequired for POST /api/v1/sessions/token, upgrade, and downgrade — same scope as call tokens.
For text ↔ voice handoffThe flow should expose both web_chat and web_voice entry points. TelAPI advertises text_to_voice and voice_to_text in runtime capabilities when both are present.

Open a text session

1. Request a session token

POST /api/v1/sessions/token
X-API-Key: <team-api-key>
Content-Type: application/json

{
"endpointId": "0f6c3d4e-8b8f-4c59-9d0e-7e5e3b1f7f0f",
"mode": "text"
}

Response (no WebRTC fields):

{
"sessionId": "call-loc5q7g0-k7p3f9x2",
"wsToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…",
"wsTokenExpiresIn": 300,
"expiresIn": 3600
}

Exact shapes are in the API reference under Sessions → Request a session token.

2. Connect the WebSocket

wss://<api-domain>/ws/session?sessionId=<sessionId>&token=<wsToken>

The server sends an initial status message with state: connected. All further traffic uses the ChannelMessage envelope — see SDK → Channel protocol.

3. Send and receive chat

DirectionMessagePurpose
Client → servertype: chat, direction: to_ariUser message; set chat.intent: 'conversation' and responseExpected: true for a normal turn.
Server → clienttype: chat, direction: to_browserAssistant reply (chat.role: 'assistant').
Client → servertype: chat, intent: 'browser_context', responseExpected: falseDurable page context without triggering a reply.

The client SDK wraps this as sendTextChat() / onChat on a SessionClient opened with mode: 'text'.

Session idle timeout

Non-voice sessions (including text) auto-close after the SDK's sessionIdleTimeoutMs (default 5 minutes). Every inbound or outbound message resets the clock. Voice sessions ignore this timeout. See SDK → Sessions and modes.

Upgrade text → voice

When the user moves from typing to speaking on the same conversation, TelAPI migrates the active text session and re-issues a voice_conversation token for the same sessionId. FlowEngine state is serialised to Redis so TelPhi can resume context when the WebRTC leg connects.

POST /api/v1/sessions/upgrade
X-API-Key: <team-api-key>
Content-Type: application/json

{
"sessionId": "call-loc5q7g0-k7p3f9x2",
"endpointId": "0f6c3d4e-8b8f-4c59-9d0e-7e5e3b1f7f0f"
}

Success response includes telproDomain and webrtcGatewayUrl in addition to a fresh wsToken. The client should:

  1. Close the text WebSocket (or let the SDK replace the session entry).
  2. Reconnect /ws/session with the new token.
  3. Initialise WebRTC against the returned gateway and dial the SIP leg.

SDK: delphi.upgradeToVoice({ endpointId, autoDial?: true, browserContext? }) — requires an open text session for that endpoint. See SDK quick start → Upgrade to voice.

ErrorTypical cause
404 NOT_FOUNDSession expired or unknown sessionId.
409 INVALID_STATESession is not an active text session for that endpoint, or migration failed.
503 WEBRTC_DISABLED / TELPRO_UNCONFIGUREDPlatform cannot place browser voice calls.

Downgrade voice → text

After the WebRTC leg ends (or is about to), TelAPI re-issues a text token for the same sessionId so chat can continue with persisted history.

POST /api/v1/sessions/downgrade
X-API-Key: <team-api-key>
Content-Type: application/json

{
"sessionId": "call-loc5q7g0-k7p3f9x2",
"endpointId": "0f6c3d4e-8b8f-4c59-9d0e-7e5e3b1f7f0f"
}

SDK: delphi.downgradeToText({ endpointId, endCallFirst?: true }) — requires an active voice_conversation session. The SDK sends a prepare_voice_to_text_handoff control message, calls the downgrade endpoint, hangs up WebRTC when endCallFirst is true (default), then opens a new text session with the returned token and re-imports the message list for UI continuity.

Capability discovery

Before showing “Start chat”, “Call”, or handoff buttons, query what the endpoint supports:

GET /api/v1/runtime/capabilities?endpointId=<uuid>
X-API-Key: <team-api-key>
FieldUse
interactionModes.textWhether mode: 'text' is allowed.
interactionModes.voice_conversationWhether WebRTC voice is allowed.
flows.entryPointsExpect web_chat for text-only; both web_chat and web_voice for handoff.
runtime.migrationIncludes text_to_voice and voice_to_text when the flow supports both web entry points.

The SDK exposes this as delphi.getCapabilities(endpointId) — see SDK → Capability discovery.

Text overlay during an active voice call

If you only need typed side input during a call — without changing session mode — keep the voice_conversation session and use channel control messages:

ControlEffect
enable_text_chatAI processes typed chat messages (optional responseMode: text, voice, or both).
disable_text_chatAI returns to voice-only input.
set_response_modeSwitch AI output modality without closing the call.

SDK: session.enableTextChat(), session.disableTextChat(), session.setResponseMode(). Server acks arrive as status states text_chat_enabled / text_chat_disabled / response_mode_changed.

This does not replace upgrade/downgrade — it is the in-call typing UX on a single voice session.

Use the client SDK:

const session = await delphi.openSession({endpointId, mode: 'text'});
session.sendTextChat('Hello');
// optional: await delphi.upgradeToVoice({ endpointId, autoDial: true })

Building without the SDK? Follow the REST + WebSocket steps above and use the SDK source as the reference implementation for message shapes.

See also