The telephony channel lets a betool pipeline answer calls and place them. Real-time audio, streaming transcription, streaming speech synthesis, and barge-in interruption are all supported.
Architecture
Under the hood:
- LiveKit handles real-time audio transport.
- LiveKit-SIP connects LiveKit to your carrier trunk (SIP).
- A dedicated worker orchestrates the call: transcription (STT), LLM, speech synthesis (TTS).
This stack runs as a separate process from the main backend. You do not configure it directly — the operator of your instance sets up the SIP bridge.
Voice providers
| Role | Providers | Default |
|---|---|---|
| Transcription (STT) | Deepgram, faster-whisper (run locally) | Deepgram |
| Synthesis (TTS) | ElevenLabs, Cartesia, OpenAI, Deepgram Aura | ElevenLabs |
The voice catalog of each TTS provider can be browsed in the admin, with a sample you can listen to before choosing. The LLM is the one of your choice (managed model, BYOK or private model — see Connect an LLM key).
Prerequisites
- A SIP trunk from a carrier.
- An inbound number and / or the ability to place outbound calls.
- Access to the chosen STT / TTS providers.
With the managed offering, betool can provision the SIP trunk and voice providers for you.
Designing a voice pipeline (inbound call)
A voice pipeline starts with a Start node carrying the phone_gateway receiver. On each transcribed turn of speech, the pipeline receives in particular:
user_messageandphone.transcript— the transcribed turn of speechphone.call— the call: identifier, caller / callee numbers, direction (inbound/outbound), linephone.from— the caller's numberphone.caller_timezone— the time zone inferred from the dialing code (empty when the code covers several time zones)
The text produced by the pipeline is read aloud by the TTS.
Receiver settings
The phone_gateway receiver sets the call's behavior, without code:
- Greeting — several greeting variants (one is picked at random), a separate greeting for outbound calls, a delay before the greeting.
- Silence — a prompt after a prolonged silence, then a goodbye message and hang-up if the silence persists; maximum call duration.
- Interruption (barge-in) — enabled by default: the caller can cut the agent off. Minimum duration and number of words for an interruption to count.
- End-of-turn detection — detection mode and end-of-speech delays.
- Fillers — holding phrases spoken when the response is slow, dedicated phrases while a tool is running, or a background holding sound (keyboard, office, hold music). Disabled by default.
- Voice — STT / TTS provider, model, language and voice, with fine voice settings (stability, speed…).
- Call dispositions — a closed list of outcome codes the agent can set on the call.
What the agent can do during the call
Telephony tools are available to the agents of a pipeline that has phone_gateway upstream:
phone_gateway.transfer_call— transfer the call to another number (SIP REFER). The number is validated in E.164 format; the transfer happens after the current response has been spoken.phone_gateway.hangup_call— hang up, after the goodbye message.phone_gateway.set_call_disposition— qualify the call with one of the configured codes.
An agent can also send an SMS during the call (tool allmysms.send_sms, see SMS): on this channel, the recipient is locked to the person on the call. If the number is withheld, sending is refused.
Offering time slots
The availability calculation (free_slots operation of the compute node) produces time slots ready to be spoken, in the caller's time zone. Combined with calendar reading (Google or Microsoft), a voice agent can state a few free slots and then book the appointment.
Outbound calls
A pipeline can place a call with the phone_call node: for example, calling back a lead as soon as it arrives, or following up on a list.
| Field | Role |
|---|---|
trunk_id | Line used to call |
voice_model | The model that leads the conversation (its Start must carry the phone_gateway receiver) |
to_number | Number to call (templatable), normalized to E.164 format |
call_vars | Variables passed to the conversation (phone.outbound.vars) — for example the first name, usable in the outbound greeting |
allowed_days / allowed_hours / timezone | Allowed time window: outside the window, the call is not placed |
max_in_flight / max_per_day | Caps on simultaneous calls (3 by default) and daily calls (100 by default) |
output_key | Output key (default phone.call_result) |
Safeguards:
- Exactly-once — a replay never redials a number already called.
- Refusal before dialing if the organization has no credits left.
- No calls in simulation — the node is skipped during a test run (dry-run).
The node returns as soon as the call rings: ok means "call started", not "answered". To know the outcome, add the phone_call_outcome receiver to the model's Start: it triggers the pipeline once the call has ended, with phone.outcome — answered or not (answered, no_answer, rejected, unknown), duration, number of turns, disposition set by the agent.
Best practices
- Keep missions concise. Response latency matters: an agent that hesitates for 4 seconds sounds frozen on a call. Prefer fast models except for decision-critical turns.
- Leave barge-in enabled. Callers must be able to interrupt the agent. This is the default setting.
- Configure fillers for turns that call tools: a long silence unsettles the caller.
Costs
See Pricing. A call has no per-minute price as such: it is billed by its components — transcription (by audio duration), speech synthesis (by number of characters) and LLM (by tokens, except with BYOK).
Known limitations
- The telephony channel is audio only. Video goes through other building blocks (video generation and dubbing with HeyGen), not through the call.
- Transfer to a human requires a SIP trunk that supports REFER.
- The agent cannot identify the caller without a CRM integration: it only knows the presented number.