Files
openclaw/docs/plugins/voice-call.md

338 lines
8.5 KiB
Markdown
Raw Normal View History

2026-01-12 01:16:46 +00:00
---
2026-01-13 17:16:02 +05:30
summary: "Voice Call plugin: outbound + inbound calls via Twilio/Telnyx/Plivo (plugin install + config + CLI)"
2026-01-12 01:16:46 +00:00
read_when:
2026-01-30 03:15:10 +01:00
- You want to place an outbound voice call from OpenClaw
2026-01-12 01:16:46 +00:00
- You are configuring or developing the voice-call plugin
title: "Voice Call Plugin"
2026-01-12 01:16:46 +00:00
---
# Voice Call (plugin)
2026-01-30 03:15:10 +01:00
Voice calls for OpenClaw via a plugin. Supports outbound notifications and
2026-01-13 17:16:02 +05:30
multi-turn conversations with inbound policies.
2026-01-12 01:27:05 +00:00
2026-01-12 01:16:46 +00:00
Current providers:
2026-01-31 21:13:13 +09:00
2026-01-12 21:40:22 +00:00
- `twilio` (Programmable Voice + Media Streams)
- `telnyx` (Call Control v2)
2026-01-13 17:16:02 +05:30
- `plivo` (Voice API + XML transfer + GetInput speech)
2026-01-12 21:40:22 +00:00
- `mock` (dev/no network)
2026-01-12 01:16:46 +00:00
2026-01-12 01:27:05 +00:00
Quick mental model:
2026-01-31 21:13:13 +09:00
2026-01-13 17:16:02 +05:30
- Install plugin
- Restart Gateway
- Configure under `plugins.entries.voice-call.config`
2026-01-30 03:15:10 +01:00
- Use `openclaw voicecall ...` or the `voice_call` tool
2026-01-12 01:16:46 +00:00
## Where it runs (local vs remote)
2026-01-13 17:16:02 +05:30
The Voice Call plugin runs **inside the Gateway process**.
2026-01-13 17:16:02 +05:30
If you use a remote Gateway, install/configure the plugin on the **machine running the Gateway**, then restart the Gateway to load it.
2026-01-12 01:16:46 +00:00
## Install
### Option A: install from npm (recommended)
```bash
2026-01-30 03:15:10 +01:00
openclaw plugins install @openclaw/voice-call
2026-01-12 01:16:46 +00:00
```
Restart the Gateway afterwards.
2026-01-12 01:27:05 +00:00
### Option B: install from a local folder (dev, no copying)
```bash
2026-01-30 03:15:10 +01:00
openclaw plugins install ./extensions/voice-call
cd ./extensions/voice-call && pnpm install
2026-01-12 01:27:05 +00:00
```
Restart the Gateway afterwards.
2026-01-13 17:16:02 +05:30
## Config
2026-01-12 01:16:46 +00:00
2026-01-13 17:16:02 +05:30
Set config under `plugins.entries.voice-call.config`:
2026-01-12 01:16:46 +00:00
```json5
{
plugins: {
entries: {
"voice-call": {
enabled: true,
config: {
2026-01-13 17:16:02 +05:30
provider: "twilio", // or "telnyx" | "plivo" | "mock"
2026-01-12 21:40:22 +00:00
fromNumber: "+15550001234",
toNumber: "+15550005678",
2026-01-13 17:16:02 +05:30
2026-01-12 01:16:46 +00:00
twilio: {
accountSid: "ACxxxxxxxx",
2026-01-31 21:13:13 +09:00
authToken: "...",
2026-01-12 21:40:22 +00:00
},
2026-01-13 17:16:02 +05:30
telnyx: {
apiKey: "...",
connectionId: "...",
// Telnyx webhook public key from the Telnyx Mission Control Portal
// (Base64 string; can also be set via TELNYX_PUBLIC_KEY).
publicKey: "...",
},
2026-01-13 17:16:02 +05:30
plivo: {
authId: "MAxxxxxxxxxxxxxxxxxxxx",
2026-01-31 21:13:13 +09:00
authToken: "...",
2026-01-13 17:16:02 +05:30
},
// Webhook server
serve: {
port: 3334,
2026-01-31 21:13:13 +09:00
path: "/voice/webhook",
2026-01-13 17:16:02 +05:30
},
// Webhook security (recommended for tunnels/proxies)
webhookSecurity: {
allowedHosts: ["voice.example.com"],
trustedProxyIPs: ["100.64.0.1"],
},
2026-01-13 17:16:02 +05:30
// Public exposure (pick one)
// publicUrl: "https://example.ngrok.app/voice/webhook",
// tunnel: { provider: "ngrok" },
// tailscale: { mode: "funnel", path: "/voice/webhook" }
outbound: {
2026-01-31 21:13:13 +09:00
defaultMode: "notify", // notify | conversation
2026-01-13 17:16:02 +05:30
},
streaming: {
enabled: true,
2026-01-31 21:13:13 +09:00
streamPath: "/voice/stream",
preStartTimeoutMs: 5000,
maxPendingConnections: 32,
maxPendingConnectionsPerIp: 4,
maxConnections: 128,
2026-01-31 21:13:13 +09:00
},
},
},
},
},
2026-01-12 01:16:46 +00:00
}
```
2026-01-12 21:40:22 +00:00
Notes:
2026-01-31 21:13:13 +09:00
2026-01-13 17:16:02 +05:30
- Twilio/Telnyx require a **publicly reachable** webhook URL.
- Plivo requires a **publicly reachable** webhook URL.
2026-01-12 21:40:22 +00:00
- `mock` is a local dev provider (no network calls).
- Telnyx requires `telnyx.publicKey` (or `TELNYX_PUBLIC_KEY`) unless `skipSignatureVerification` is true.
2026-01-12 21:40:22 +00:00
- `skipSignatureVerification` is for local testing only.
- If you use ngrok free tier, set `publicUrl` to the exact ngrok URL; signature verification is always enforced.
- `tunnel.allowNgrokFreeTierLoopbackBypass: true` allows Twilio webhooks with invalid signatures **only** when `tunnel.provider="ngrok"` and `serve.bind` is loopback (ngrok local agent). Use for local dev only.
- Ngrok free tier URLs can change or add interstitial behavior; if `publicUrl` drifts, Twilio signatures will fail. For production, prefer a stable domain or Tailscale funnel.
- Streaming security defaults:
- `streaming.preStartTimeoutMs` closes sockets that never send a valid `start` frame.
- `streaming.maxPendingConnections` caps total unauthenticated pre-start sockets.
- `streaming.maxPendingConnectionsPerIp` caps unauthenticated pre-start sockets per source IP.
- `streaming.maxConnections` caps total open media stream sockets (pending + active).
2026-01-12 21:40:22 +00:00
## Stale call reaper
Use `staleCallReaperSeconds` to end calls that never receive a terminal webhook
(for example, notify-mode calls that never complete). The default is `0`
(disabled).
Recommended ranges:
- **Production:** `120``300` seconds for notify-style flows.
- Keep this value **higher than `maxDurationSeconds`** so normal calls can
finish. A good starting point is `maxDurationSeconds + 3060` seconds.
Example:
```json5
{
plugins: {
entries: {
"voice-call": {
config: {
maxDurationSeconds: 300,
staleCallReaperSeconds: 360,
},
},
},
},
}
```
## Webhook Security
When a proxy or tunnel sits in front of the Gateway, the plugin reconstructs the
public URL for signature verification. These options control which forwarded
headers are trusted.
`webhookSecurity.allowedHosts` allowlists hosts from forwarding headers.
`webhookSecurity.trustForwardingHeaders` trusts forwarded headers without an allowlist.
`webhookSecurity.trustedProxyIPs` only trusts forwarded headers when the request
remote IP matches the list.
Webhook replay protection is enabled for Twilio and Plivo. Replayed valid webhook
requests are acknowledged but skipped for side effects.
Twilio conversation turns include a per-turn token in `<Gather>` callbacks, so
stale/replayed speech callbacks cannot satisfy a newer pending transcript turn.
Example with a stable public host:
```json5
{
plugins: {
entries: {
"voice-call": {
config: {
publicUrl: "https://voice.example.com/voice/webhook",
webhookSecurity: {
allowedHosts: ["voice.example.com"],
},
},
},
},
},
}
```
## TTS for calls
Voice Call uses the core `messages.tts` configuration (OpenAI or ElevenLabs) for
streaming speech on calls. You can override it under the plugin config with the
**same shape** — it deepmerges with `messages.tts`.
```json5
{
tts: {
provider: "elevenlabs",
elevenlabs: {
voiceId: "pMsXgVXv3BLzUgSXRplE",
2026-01-31 21:13:13 +09:00
modelId: "eleven_multilingual_v2",
},
},
}
```
Notes:
2026-01-31 21:13:13 +09:00
- **Edge TTS is ignored for voice calls** (telephony audio needs PCM; Edge output is unreliable).
- Core TTS is used when Twilio media streaming is enabled; otherwise calls fall back to provider native voices.
### More examples
Use core TTS only (no override):
```json5
{
messages: {
tts: {
provider: "openai",
2026-01-31 21:13:13 +09:00
openai: { voice: "alloy" },
},
},
}
```
Override to ElevenLabs just for calls (keep core default elsewhere):
```json5
{
plugins: {
entries: {
"voice-call": {
config: {
tts: {
provider: "elevenlabs",
elevenlabs: {
apiKey: "elevenlabs_key",
voiceId: "pMsXgVXv3BLzUgSXRplE",
2026-01-31 21:13:13 +09:00
modelId: "eleven_multilingual_v2",
},
},
},
},
},
},
}
```
Override only the OpenAI model for calls (deepmerge example):
```json5
{
plugins: {
entries: {
"voice-call": {
config: {
tts: {
openai: {
model: "gpt-4o-mini-tts",
2026-01-31 21:13:13 +09:00
voice: "marin",
},
},
},
},
},
},
}
```
2026-01-13 17:16:02 +05:30
## Inbound calls
2026-01-12 21:40:22 +00:00
2026-01-13 17:16:02 +05:30
Inbound policy defaults to `disabled`. To enable inbound calls, set:
2026-01-12 01:16:46 +00:00
```json5
2026-01-12 21:40:22 +00:00
{
2026-01-13 17:16:02 +05:30
inboundPolicy: "allowlist",
allowFrom: ["+15550001234"],
2026-01-31 21:13:13 +09:00
inboundGreeting: "Hello! How can I help?",
2026-01-12 21:40:22 +00:00
}
2026-01-12 01:16:46 +00:00
```
2026-01-13 17:16:02 +05:30
Auto-responses use the agent system. Tune with:
2026-01-31 21:13:13 +09:00
2026-01-13 17:16:02 +05:30
- `responseModel`
- `responseSystemPrompt`
- `responseTimeoutMs`
2026-01-12 01:16:46 +00:00
## CLI
```bash
2026-01-30 03:15:10 +01:00
openclaw voicecall call --to "+15555550123" --message "Hello from OpenClaw"
openclaw voicecall continue --call-id <id> --message "Any questions?"
openclaw voicecall speak --call-id <id> --message "One moment"
openclaw voicecall end --call-id <id>
openclaw voicecall status --call-id <id>
openclaw voicecall tail
openclaw voicecall expose --mode funnel
2026-01-12 01:16:46 +00:00
```
## Agent tool
Tool name: `voice_call`
2026-01-12 21:40:22 +00:00
Actions:
2026-01-31 21:13:13 +09:00
2026-01-12 21:40:22 +00:00
- `initiate_call` (message, to?, mode?)
- `continue_call` (callId, message)
- `speak_to_user` (callId, message)
- `end_call` (callId)
- `get_status` (callId)
2026-01-12 01:16:46 +00:00
2026-01-13 17:16:02 +05:30
This repo ships a matching skill doc at `skills/voice-call/SKILL.md`.
2026-01-12 01:16:46 +00:00
## Gateway RPC
2026-01-12 21:40:22 +00:00
- `voicecall.initiate` (`to?`, `message`, `mode?`)
- `voicecall.continue` (`callId`, `message`)
- `voicecall.speak` (`callId`, `message`)
- `voicecall.end` (`callId`)
- `voicecall.status` (`callId`)