Skip to content

本页是 OpenClaw Voice Call 插件的完整参考,解决从零配置到生产环境故障的一切问题。插件支持 Twilio、Telnyx、Plivo 三大语音平台,以及本地 mock 测试。覆盖安装步骤、配置文件结构(含 streaming/realtime/TTS 子配置)、呼入白名单、每号码路由、Webhook 安全加固、CLI 命令(openclaw voicecall)和 Agent 工具(voice_call)的用法。故障排查部分集中处理 Webhook 暴露失败、签名验证出错、呼叫无语音等常见场景。

OpenClaw Voice Call 插件配置与排查完整指南

通过插件为 OpenClaw 添加电话通话能力,支持外拨通知、多轮对话、全双工实时语音、流式转录,以及基于白名单的呼入通话。

当前支持的语音提供商:

  • twilio(Programmable Voice + Media Streams)
  • telnyx(Call Control v2)
  • plivo(Voice API + XML transfer + GetInput speech)
  • mock(开发/无网络测试)

重要提示: Voice Call 插件运行在 Gateway 进程内部。如果你使用远程 Gateway,请在运行 Gateway 的机器上安装和配置插件,然后重启 Gateway 加载。

快速开始

第一步:安装插件

bash
# 从 npm 安装(推荐)
openclaw plugins install @openclaw/voice-call

# 或从本地目录安装(开发用)
PLUGIN_SRC=./path/to/local/voice-call-plugin
openclaw plugins install "$PLUGIN_SRC"
cd "$PLUGIN_SRC" && pnpm install

安装后 必须重启 Gateway,插件才能生效。

第二步:配置提供商和 Webhook

plugins.entries.voice-call.config 下设置。至少需要:provider、提供商凭证、fromNumber、一个公网可访问的 Webhook URL。详见下文 配置 章节。

第三步:验证配置

bash
openclaw voicecall setup

该命令会检查插件是否启用、提供商凭证是否有效、Webhook 是否公网可达,并且只会检查是否同时启用了 streamingrealtime(两者不能并存)。建议首次验证时直接用 openclaw voicecall setup,脚本中可使用 --json 参数。

bash
# 输出 JSON 格式结果(适合脚本解析)
openclaw voicecall setup --json

第四步:烟雾测试

bash
# 干运行(默认不会实际拨号)
openclaw voicecall smoke
openclaw voicecall smoke --to "+15555550123"

# 添加 --yes 参数后才会实际拨出一通简短的通知电话
openclaw voicecall smoke --to "+15555550123" --yes

注意: 对于 Twilio、Telnyx、Plivo,设置必须解析为 公网 Webhook URL。如果 publicUrl、隧道 URL、Tailscale URL 或 serve 兜底地址解析到环回地址或私有网络空间,openclaw voicecall setup 会直接失败,不会启动一个无法接收运营商 Webhook 的运行时。

配置

如果 enabled: true 但所选提供商缺少凭证,Gateway 启动时会记录一条“设置不完整”的警告(包含缺失的键名),并跳过该提供商的运行时。此时 CLI 命令、RPC 调用和 Agent 工具在被使用时仍会返回具体的缺失项信息。

凭证支持 SecretRef: plugins.entries.voice-call.config.twilio.authTokenplugins.entries.voice-call.config.realtime.providers.*.apiKeyplugins.entries.voice-call.config.streaming.providers.*.apiKeyplugins.entries.voice-call.config.tts.providers.*.apiKey 都能通过标准 SecretRef 表面解析;详见 SecretRef 凭证表面

json5
{
  plugins: {
    entries: {
      "voice-call": {
        enabled: true,
        config: {
          provider: "twilio", // 或 "telnyx" | "plivo" | "mock"
          fromNumber: "+15550001234", // Twilio 也可用 TWILIO_FROM_NUMBER 环境变量
          toNumber: "+15550005678",
          sessionScope: "per-phone", // per-phone | per-call
          numbers: {
            "+15550009999": {
              inboundGreeting: "Silver Fox Cards, how can I help?",
              responseSystemPrompt: "You are a concise baseball card specialist.",
              tts: {
                providers: {
                  openai: { voice: "alloy" },
                },
              },
            },
          },

          twilio: {
            accountSid: "ACxxxxxxxx",
            authToken: "...",
          },
          telnyx: {
            apiKey: "...",
            connectionId: "...",
            // Telnyx webhook 公钥(Base64;也可通过 TELNYX_PUBLIC_KEY 环境变量设置)
            publicKey: "...",
          },
          plivo: {
            authId: "MAxxxxxxxxxxxxxxxxxxxx",
            authToken: "...",
          },

          // Webhook 服务器
          serve: {
            port: 3334,
            path: "/voice/webhook",
          },

          // Webhook 安全(隧道/代理场景强烈推荐)
          webhookSecurity: {
            allowedHosts: ["voice.example.com"],
            trustedProxyIPs: ["100.64.0.1"],
          },

          // 公网暴露方式(三选一)
          // publicUrl: "https://example.ngrok.app/voice/webhook",
          // tunnel: { provider: "ngrok" },
          // tailscale: { mode: "funnel", path: "/voice/webhook" },

          outbound: {
            defaultMode: "notify", // notify | conversation
          },

          streaming: { enabled: true /* 详见流式转录 */ },
          realtime: { enabled: false /* 详见实时语音对话 */ },
        },
      },
    },
  },
}

提供商暴露与安全说明

  • Twilio、Telnyx、Plivo 都需要一个 公网可访问 的 Webhook URL。
  • mock 是本地开发提供商,不会发起网络调用。
  • Telnyx 必须提供 telnyx.publicKey(或 TELNYX_PUBLIC_KEY 环境变量),除非 skipSignatureVerification 设为 true
  • skipSignatureVerification 仅限本地测试使用。
  • 使用 ngrok 免费版时,设置 publicUrl 为精确的 ngrok URL;签名验证始终强制执行。
  • tunnel.allowNgrokFreeTierLoopbackBypass: true 允许 Twilio 带无效签名的 Webhook,仅当 tunnel.provider="ngrok"serve.bind 为环回地址(ngrok 本地代理)时生效。仅限本地开发。
  • Ngrok 免费版 URL 会变化或出现中间页面;若 publicUrl 漂移,Twilio 签名验证会失败。生产环境建议使用固定域名或 Tailscale Funnel。

流媒体连接限制

  • streaming.preStartTimeoutMs:关闭从未发送有效 start 帧的套接字。
  • streaming.maxPendingConnections:限制未认证的 pre-start 套接字总数。
  • streaming.maxPendingConnectionsPerIp:限制每个源 IP 的未认证 pre-start 套接字。
  • streaming.maxConnections:限制打开的媒体流套接字总数(pending + active)。

旧配置键迁移

旧配置若使用 provider: "log"twilio.from 或旧版 streaming.* OpenAI 键,可以运行 openclaw doctor --fix 自动修复。运行时回退仍然接受旧的 voice-call 键,但迁移路径是 openclaw doctor --fix,兼容层是临时的。

自动迁移的流式键:

  • streaming.sttProviderstreaming.provider
  • streaming.openaiApiKeystreaming.providers.openai.apiKey
  • streaming.sttModelstreaming.providers.openai.model
  • streaming.silenceDurationMsstreaming.providers.openai.silenceDurationMs
  • streaming.vadThresholdstreaming.providers.openai.vadThreshold

会话作用域

默认 sessionScope: "per-phone",同一来电号码的重复通话会保留对话记忆。设置为 sessionScope: "per-call" 时,每次运营商通话都从头开始新上下文,适用于接待、预订、IVR、Google Meet 桥接等场景(同一号码可能代表不同会议)。

实时语音对话

realtime 选择一个全双工实时语音提供商处理实时通话音频。它与 streaming 互斥:streaming 仅将音频转发给实时转录提供商。

重要: realtime.enabledstreaming.enabled 不能同时为 true,每个通话只能选一种音频模式。

当前运行时行为:

  • realtime.enabled 支持 Twilio Media Streams。
  • realtime.provider 可选。若未设置,Voice Call 使用第一个注册的实时语音提供商。
  • 内置实时语音提供商:Google Gemini Live (google) 和 OpenAI (openai),由各自的提供商插件注册。
  • 提供商的原始配置位于 realtime.providers.<providerId>
  • Voice Call 默认暴露共享工具 openclaw_agent_consult。实时模型可以在来电者需要深度推理、最新信息或普通 OpenClaw 工具时调用它。
  • realtime.consultPolicy 可选,为实时模型何时调用 openclaw_agent_consult 添加指导。
  • realtime.agentContext.enabled 默认关闭。启用后 Voice Call 在会话建立时,将绑定的 Agent 标识、系统提示覆盖和选定的工作区文件胶囊注入到实时提供商指令中。
  • realtime.fastContext.enabled 默认关闭。启用后 Voice Call 先搜索索引记忆/会话上下文中与咨询问题相关的内容,在 realtime.fastContext.timeoutMs 内返回这些片段;仅当 realtime.fastContext.fallbackToConsulttrue 时才回退到完整查询 Agent。
  • 如果 realtime.provider 指向未注册的提供商,或没有任何实时语音提供商被注册,Voice Call 会记录警告并跳过实时媒体,不会导致整个插件失败。
  • 咨询会话键复用已存储的通话会话(如果可用),否则回退到配置的 sessionScope(默认为 per-phone,或 per-call 用于隔离通话)。

工具策略

realtime.toolPolicy 控制咨询运行的权限:

策略行为
safe-read-only暴露咨询工具,并将常规 Agent 限制为 readweb_searchweb_fetchx_searchmemory_searchmemory_get
owner暴露咨询工具,让常规 Agent 使用正常的 Agent 工具策略
none不暴露咨询工具。自定义 realtime.tools 仍然传递给实时提供商

realtime.consultPolicy 仅控制实时模型的指令:

策略指导
auto使用默认提示,让提供商自行决定何时调用咨询工具
substantive简单的对话衔接直接回答,但在涉及事实、记忆、工具或上下文时先咨询
always每个实质性回答前都先咨询

Agent 语音上下文

当语音桥接应听起来像配置的 OpenClaw Agent 但又不想在每个普通对话轮次都支付一次完整的 Agent 咨询往返时,启用 realtime.agentContext。上下文胶囊在实时会话创建时仅添加一次,因此不会增加每轮延迟。对 openclaw_agent_consult 的调用仍然运行完整的 OpenClaw Agent,用于工具工作、最新信息、记忆查找或工作区状态。

json5
{
  plugins: {
    entries: {
      "voice-call": {
        config: {
          agentId: "main",
          realtime: {
            enabled: true,
            provider: "google",
            toolPolicy: "safe-read-only",
            consultPolicy: "substantive",
            agentContext: {
              enabled: true,
              maxChars: 6000,
              includeIdentity: true,
              includeSystemPrompt: true,
              includeWorkspaceFiles: true,
              files: ["SOUL.md", "IDENTITY.md", "USER.md"],
            },
          },
        },
      },
    },
  },
}

实时提供商示例

Google Gemini Live:

默认值:API 密钥来自 realtime.providers.google.apiKeyGEMINI_API_KEYGOOGLE_GENERATIVE_AI_API_KEY;模型 gemini-2.5-flash-native-audio-preview-12-2025;语音 KoresessionResumptioncontextWindowCompression 默认开启以支持更长的可重连接通话。使用 silenceDurationMsstartSensitivityendSensitivity 调整电话音频上的更快轮转。

json5
{
  plugins: {
    entries: {
      "voice-call": {
        config: {
          provider: "twilio",
          inboundPolicy: "allowlist",
          allowFrom: ["+15550005678"],
          realtime: {
            enabled: true,
            provider: "google",
            instructions: "Speak briefly. Call openclaw_agent_consult before using deeper tools.",
            toolPolicy: "safe-read-only",
            consultPolicy: "substantive",
            consultThinkingLevel: "low",
            consultFastMode: true,
            agentContext: { enabled: true },
            providers: {
              google: {
                apiKey: "${GEMINI_API_KEY}",
                model: "gemini-2.5-flash-native-audio-preview-12-2025",
                voice: "Kore",
                silenceDurationMs: 500,
                startSensitivity: "high",
              },
            },
          },
        },
      },
    },
  },
}

OpenAI:

json5
{
  plugins: {
    entries: {
      "voice-call": {
        config: {
          realtime: {
            enabled: true,
            provider: "openai",
            providers: {
              openai: { apiKey: "${OPENAI_API_KEY}" },
            },
          },
        },
      },
    },
  },
}

相关提供商文档:Google providerOpenAI provider

流式转录

streaming 选择一个实时转录提供商处理实时通话音频。

当前运行时行为:

  • streaming.provider 可选。若未设置,Voice Call 使用第一个注册的实时转录提供商。
  • 内置实时转录提供商:Deepgram (deepgram)、ElevenLabs (elevenlabs)、Mistral (mistral)、OpenAI (openai)、xAI (xai),由各自的提供商插件注册。
  • 提供商的原始配置位于 streaming.providers.<providerId>
  • 收到 Twilio 的 start 消息后,Voice Call 立即注册该流,在提供商连接期间将入站媒体排队通过转录提供商,只有在实时转录就绪后才开始播放初始问候。
  • 如果 streaming.provider 指向未注册的提供商,或没有任何提供商被注册,Voice Call 会记录警告并跳过媒体流,不会导致整个插件失败。

流式提供商示例

OpenAI:

默认值:API 密钥 streaming.providers.openai.apiKeyOPENAI_API_KEY;模型 gpt-4o-transcribesilenceDurationMs: 800vadThreshold: 0.5

json5
{
  plugins: {
    entries: {
      "voice-call": {
        config: {
          streaming: {
            enabled: true,
            provider: "openai",
            streamPath: "/voice/stream",
            providers: {
              openai: {
                apiKey: "sk-...", // 如果已设置 OPENAI_API_KEY 则可选
                model: "gpt-4o-transcribe",
                silenceDurationMs: 800,
                vadThreshold: 0.5,
              },
            },
          },
        },
      },
    },
  },
}

xAI:

默认值:API 密钥 streaming.providers.xai.apiKeyXAI_API_KEY;端点 wss://api.x.ai/v1/stt;编码 mulaw;采样率 8000endpointingMs: 800interimResults: true

json5
{
  plugins: {
    entries: {
      "voice-call": {
        config: {
          streaming: {
            enabled: true,
            provider: "xai",
            streamPath: "/voice/stream",
            providers: {
              xai: {
                apiKey: "${XAI_API_KEY}", // 如果已设置 XAI_API_KEY 则可选
                endpointingMs: 800,
                language: "en",
              },
            },
          },
        },
      },
    },
  },
}

通话 TTS 配置

Voice Call 使用核心 messages.tts 配置进行通话中的流式语音合成。你也可以在插件配置中覆盖它(使用与 messages.tts 相同的结构,深度合并)。

json5
{
  tts: {
    provider: "elevenlabs",
    providers: {
      elevenlabs: {
        voiceId: "pMsXgVXv3BLzUgSXRplE",
        modelId: "eleven_multilingual_v2",
      },
    },
  },
}

重要:Microsoft 语音在通话中被忽略。 电话音频需要 PCM,当前 Microsoft 传输不暴露电话 PCM 输出。

行为说明:

  • 插件配置中位于 tts.&lt;provider&gt; 下的旧版键(openaielevenlabsmicrosoftedge)会被 openclaw doctor --fix 修复;应使用 tts.providers.&lt;provider&gt; 格式的配置。
  • 启用 Twilio 媒体流时使用核心 TTS;否则通话回退到提供商原生语音。
  • 如果 Twilio 媒体流已激活,Voice Call 不会回退到 TwiML <Say>。若此时电话 TTS 不可用,播放请求会失败,而不是混合两种播放路径。
  • 当电话 TTS 回退到二级提供商时,Voice Call 会记录一条警告(包含 fromtoattempts),用于调试。
  • 当 Twilio barge-in 或流拆除清空待处理 TTS 队列时,已排队的播放请求会静默解决,不会让等待播放完成的调用方挂起。

TTS 示例

仅核心 TTS:

json5
{
  messages: {
    tts: {
      provider: "openai",
      providers: {
        openai: { voice: "alloy" },
      },
    },
  },
}

覆盖为 ElevenLabs(仅用于通话):

json5
{
  plugins: {
    entries: {
      "voice-call": {
        config: {
          tts: {
            provider: "elevenlabs",
            providers: {
              elevenlabs: {
                apiKey: "elevenlabs_key",
                voiceId: "pMsXgVXv3BLzUgSXRplE",
                modelId: "eleven_multilingual_v2",
              },
            },
          },
        },
      },
    },
  },
}

OpenAI 模型覆盖(深度合并):

json5
{
  plugins: {
    entries: {
      "voice-call": {
        config: {
          tts: {
            providers: {
              openai: {
                model: "gpt-4o-mini-tts",
                voice: "marin",
              },
            },
          },
        },
      },
    },
  },
}

呼入通话

呼入策略默认为 disabled。启用呼入通话:

json5
{
  inboundPolicy: "allowlist",
  allowFrom: ["+15550001234"],
  inboundGreeting: "Hello! How can I help?",
}

重要: inboundPolicy: "allowlist" 是低保证的来电号码过滤。插件将提供商提供的 From 值规范化后与 allowFrom 比对。Webhook 验证可以认证提供商交付和负载完整性,但 不能证明 PSTN/VoIP 主叫号码的所有权。将 allowFrom 视为来电号码过滤,而非强身份认证。

自动回复使用 Agent 系统,可用以下键调整:responseModelresponseSystemPromptresponseTimeoutMs

每号码路由

使用 numbers 配置段:当一个 Voice Call 插件为多个电话号码接收来电,且每个号码应表现不同线路时。例如,一个号码可以使用随和的个人助手,另一个使用商务角色、不同的响应 Agent 和不同的 TTS 语音。

路由根据提供商提供的被叫 To 号码选择。键必须是 E.164 格式(如 +15550001111)。来电到达时,Voice Call 解析匹配的路由一次,存储该匹配路由到通话记录中,然后在整个问候、经典自动回复路径、实时咨询路径和 TTS 播放中复用该有效配置。如果没有匹配的路由,则使用全局 Voice Call 配置。出站通话不使用 numbers;出站通话需显式传递目标、消息和会话。

当前支持的路由覆盖键:

  • inboundGreeting
  • tts
  • agentId
  • responseModel
  • responseSystemPrompt
  • responseTimeoutMs

tts 路由值会与全局 Voice Call 的 tts 配置深度合并,因此通常只需覆盖提供商语音:

json5
{
  inboundGreeting: "Hello from the main line.",
  responseSystemPrompt: "You are the default voice assistant.",
  tts: {
    provider: "openai",
    providers: {
      openai: { voice: "coral" },
    },
  },
  numbers: {
    "+15550001111": {
      inboundGreeting: "Silver Fox Cards, how can I help?",
      responseSystemPrompt: "You are a concise baseball card specialist.",
      tts: {
        providers: {
          openai: { voice: "alloy" },
        },
      },
    },
  },
}

语音输出契约

对于自动回复,Voice Call 会在系统提示中附加一个严格的语音输出契约:

text
{"spoken":"..."}

Voice Call 防御性地提取语音文本:

  • 忽略标记为推理/错误内容的数据。
  • 解析直接 JSON、fenced JSON 或内联 "spoken" 键。
  • 回退到纯文本,并移除可能的计划/元引导段落。

这样可以确保语音播报聚焦于面向呼叫者的文本,避免将计划文本泄露到音频中。

会话启动行为

对于出站 conversation 通话,首条消息处理与实时播放状态绑定:

  • 只有在初始问候语 正在播放 时,才会抑制 barge-in 队列清空和自动回复。
  • 如果初始播放失败,通话会返回 listening 状态,初始消息保留在队列中等待重试。
  • Twilio Streaming 的初始播放在 stream 连接时立即开始,无需额外延迟。
  • Barge-in 会中止当前播放并清空已排队但尚未播放的 Twilio TTS 条目。清空的条目会以“已跳过”状态解决,因此后续响应逻辑可以继续,而无需等待永远不会播放的音频。
  • 实时语音对话使用实时流自身的开场轮次。Voice Call 不会 为该初始消息发布传统的 <Say> TwiML 更新,因此出站 <Connect><Stream> 会话保持附加。

Twilio 流断开宽限期

当 Twilio 媒体流断开时,Voice Call 会等待 2000 ms,然后自动结束通话:

  • 如果流在此窗口内重新连接,自动结束被取消。
  • 如果宽限期过后仍无流重新注册,通话将被结束,以防止卡住的活跃通话。

失效通话清理器

使用 staleCallReaperSeconds 结束那些从未收到终止 Webhook 的通话(例如通知模式通话一直未完成)。默认值 0(禁用)。

推荐设置:

  • 生产环境: notify 风格的通话设为 120300 秒。
  • 保持此值 大于 maxDurationSeconds,以便正常通话能正常完成。好的起点是 maxDurationSeconds + 30–60 秒。
json5
{
  plugins: {
    entries: {
      "voice-call": {
        config: {
          maxDurationSeconds: 300,
          staleCallReaperSeconds: 360,
        },
      },
    },
  },
}

Webhook 安全

当代理或隧道位于 Gateway 前面时,插件会重建公网 URL 以进行签名验证。以下选项控制哪些转发头被信任:

  • webhookSecurity.allowedHosts: string[]:从转发头中白名单允许的主机。
  • webhookSecurity.trustForwardingHeaders: boolean:不使用白名单直接信任转发头。
  • webhookSecurity.trustedProxyIPs: string[]:仅当请求远程 IP 匹配列表时信任转发头。

其他保护措施:

  • Webhook 重放保护:已为 Twilio 和 Plivo 启用。重放的有效 Webhook 请求会被确认但跳过副作用。
  • Twilio 对话轮次在 <Gather> 回调中包含每轮令牌,因此旧的/重放的语音回调无法满足新的待处理转录轮次。
  • 未认证的 Webhook 请求在读取请求体之前就会被拒绝(提供商必需的签名头缺失时)。
  • Voice Call 的 Webhook 使用共享的预认证体配置文件(64 KB / 5 秒),加上每个 IP 的 in-flight 上限(在签名验证之前)。

示例(使用固定公网域名):

json5
{
  plugins: {
    entries: {
      "voice-call": {
        config: {
          publicUrl: "https://voice.example.com/voice/webhook",
          webhookSecurity: {
            allowedHosts: ["voice.example.com"],
          },
        },
      },
    },
  },
}

CLI 命令

bash
openclaw voicecall call --to "+15555550123" --message "Hello from OpenClaw"
openclaw voicecall start --to "+15555550123"   # call 的别名
openclaw voicecall continue --call-id <id> --message "Any questions?"
openclaw voicecall speak --call-id <id> --message "One moment"
openclaw voicecall dtmf --call-id <id> --digits "ww123456#"
openclaw voicecall end --call-id <id>
openclaw voicecall status --call-id <id>
openclaw voicecall tail
openclaw voicecall latency                      # 从日志汇总通话轮次延迟
openclaw voicecall expose --mode funnel

Gateway 运行中的情况下,操作性的 voicecall 命令会委托给 Gateway 拥有的 voice-call 运行时,因此 CLI 不会绑定第二个 Webhook 服务器。如果没有 Gateway 可达,命令会回退到独立的 CLI 运行时。

latency 从默认的 voice-call 存储路径读取 calls.jsonl。使用 --file &lt;path&gt; 指定其他日志文件,--last &lt;n&gt; 限制分析最近 N 条记录(默认 200)。输出包含通话轮次延迟和等待时间(listen-wait)的 p50/p90/p99。

Agent 工具

工具名称:voice_call

动作参数
initiate_callmessageto?mode?dtmfSequence?
continue_callcallIdmessage
speak_to_usercallIdmessage
send_dtmfcallIddigits
end_callcallId
get_statuscallId

本仓库附带一份匹配的技能文档 skills/voice-call/SKILL.md

Gateway RPC

方法参数
voicecall.initiateto?messagemode?dtmfSequence?
voicecall.continuecallIdmessage
voicecall.speakcallIdmessage
voicecall.dtmfcallIddigits
voicecall.endcallId
voicecall.statuscallId

dtmfSequence 仅在 mode: "conversation" 时有效。通知模式通话应在通话建立后使用 voicecall.dtmf 发送后续 DTMF 信号。

故障排查

设置时 Webhook 暴露检查失败

在与 Gateway 相同的环境中运行 setup:

bash
openclaw voicecall setup
openclaw voicecall setup --json

对于 twiliotelnyxplivowebhook-exposure 检查必须为绿色。配置了 publicUrl 但指向本地或私有网络地址时仍然会失败,因为运营商无法回调到这些地址。不要在 publicUrl 中使用 localhost127.0.0.10.0.0.010.x172.16.x172.31.x192.168.x169.254.xfc00::/7fd00::/8

Twilio 通知模式出站通话会在创建通话的请求中直接发送初始 <Say> TwiML,因此首条播报消息不依赖于 Twilio 获取 Webhook TwiML。但公网 Webhook 对于状态回调、对话模式通话、预连接 DTMF、实时流和连接后通话控制仍然是必需的。

使用一种公网暴露方式:

json5
{
  plugins: {
    entries: {
      "voice-call": {
        config: {
          publicUrl: "https://voice.example.com/voice/webhook",
          // 或
          tunnel: { provider: "ngrok" },
          // 或
          tailscale: { mode: "funnel", path: "/voice/webhook" },
        },
      },
    },
  },
}

更改配置后,重启或重新加载 Gateway,然后运行:

bash
openclaw voicecall setup
openclaw voicecall smoke

voicecall smoke 默认是干运行,除非传入 --yes 参数。

提供商凭据验证失败

检查所选提供商的必填字段:

  • Twilio: twilio.accountSidtwilio.authTokenfromNumber,或者环境变量 TWILIO_ACCOUNT_SIDTWILIO_AUTH_TOKENTWILIO_FROM_NUMBER
  • Telnyx: telnyx.apiKeytelnyx.connectionIdtelnyx.publicKeyfromNumber
  • Plivo: plivo.authIdplivo.authTokenfromNumber

凭据必须存在于 Gateway 主机上。仅编辑本地 shell 配置文件不会影响已运行的 Gateway,直到重启或重新加载环境。

通话已发起但提供商 Webhook 没有送达

确认提供商控制台中配置的 Webhook URL 与 publicUrl 完全一致,包括路径:

text
https://voice.example.com/voice/webhook

然后检查运行时状态:

bash
openclaw voicecall status --call-id <id>
openclaw voicecall tail
openclaw logs --follow

常见原因:

  • publicUrl 指向的路径与 serve.path 不同。
  • 隧道 URL 在 Gateway 启动后发生了变化。
  • 代理转发请求但删除了或重写了 host/proto 头。
  • 防火墙或 DNS 将公网主机名路由到了 Gateway 以外的地址。
  • Gateway 重启后未启用 Voice Call 插件。

当反向代理或隧道位于 Gateway 前面时,设置 webhookSecurity.allowedHosts 为公网主机名,或使用 webhookSecurity.trustedProxyIPs 指定已知代理地址。仅在你完全控制代理边界时才使用 webhookSecurity.trustForwardingHeaders

签名验证失败

提供商签名会对照 OpenClaw 从入站请求重建的公网 URL 进行校验。如果签名失败:

  • 确认提供商的 Webhook URL 与 publicUrl 完全一致,包括协议、主机和路径。
  • 使用 ngrok 免费版时,隧道主机名变化后更新 publicUrl
  • 确保代理保留了原始 host 和 proto 头,或配置 webhookSecurity.allowedHosts
  • 不要在本地测试之外启用 skipSignatureVerification

Google Meet 与 Twilio 的接入失败

Google Meet 通过此插件实现 Twilio 拨入接入。首先验证 Voice Call 本身:

bash
openclaw voicecall setup
openclaw voicecall smoke --to "+15555550123"

然后显式验证 Google Meet 传输:

bash
openclaw googlemeet setup --transport twilio

如果 Voice Call 正常但 Meet 参与者从未加入,请检查 Meet 拨入号码、PIN 和 --dtmf-sequence。电话通话正常,但会议可能拒绝或忽略错误的 DTMF 序列。

Google Meet 通过 voicecall.start 启动 Twilio 电话腿,并携带预连接 DTMF 序列。PIN 衍生的序列包含 Google Meet 插件的 voiceCall.dtmfDelayMs 作为前导 Twilio 等待数字。默认值是 12 秒,因为 Meet 拨入提示可能较晚到达。之后 Voice Call 在问候语请求前重定向回实时处理。

使用 openclaw logs --follow 查看实时阶段跟踪。健康的 Twilio Meet 接入会记录以下顺序:

  • Google Meet 将 Twilio 接入委托给 Voice Call。
  • Voice Call 存储预连接 DTMF TwiML。
  • Twilio 初始 TwiML 在实时处理前被消耗并提供。
  • Voice Call 为 Twilio 通话提供实时 TwiML。
  • Google Meet 在 post-DTMF 延迟后通过 voicecall.speak 请求介绍语音。

openclaw voicecall tail 仍然显示持久化的通话记录,对于通话状态和转录很有用,但并非每个 webhook/实时变迁都会出现在那里。

实时通话没有声音

确认只有一个音频模式启用:realtime.enabledstreaming.enabled 不能同时为 true

对于实时 Twilio 通话,还需验证:

  • 实时提供商插件已加载并注册。
  • realtime.provider 未设置或指向已注册的提供商。
  • 提供商 API 密钥在 Gateway 进程中可用。
  • openclaw logs --follow 显示已提供实时 TwiML、实时桥接已启动、初始问候已排队。

相关资源

常见问题

Webhook 暴露检查一直失败,但我的服务器能在公网访问

可能是 publicUrl 配置了私有网络地址。即使你本机可以访问,运营商(Twilio/Telnyx/Plivo)无法回调到 localhost127.0.0.1192.168.x 等地址。使用 ngrok、Tailscale Funnel 或固定公网域名。运行 openclaw voicecall setup 后会告诉你具体是哪个地址有问题。

通话已经拨出,但对方听不到声音,TTS 不生效

首先检查你是否同时启用了 streamingrealtime,两者不能共存。如果使用 Twilio Media Streams,Voice Call 会使用核心 TTS 而非 TwiML <Say>。确保 TTS 提供商配置正确,且不是 Microsoft(电话模式不支持)。运行 openclaw voicecall setupopenclaw logs --follow 查看实时阶段日志。

呼入电话从白名单号码打来却总是被拒绝,怎么办?

检查 inboundPolicy 是否已设为 "allowlist"(默认是 "disabled")。然后确认 allowFrom 中的号码格式与提供商传入的 From 完全一致,建议使用 E.164 格式(如 +15550001234)。可以在 openclaw voicecall tail 中查看来电的 raw 号码。如果格式不匹配(例如缺少国家代码前导加号),插件会拒绝。