콘텐츠로 이동

OpenClaw 음성 통화 플러그인 설정: 5분 만에 연결하기

전화 기능을 애플리케이션에 통합하는 작업은 생각보다 까다로울 때가 많아요. API 연동부터 실시간 스트리밍, 보안 설정까지 신경 써야 할 부분이 한두 가지가 아니니까요. OpenClaw의 Voice Call 플러그인을 사용하면 이런 복잡한 과정을 훨씬 단순하게 만들고, 에이전트가 직접 전화를 걸고 받을 수 있는 환경을 빠르게 구축할 수 있어요.

Voice Call 플러그인을 통해 OpenClaw에서 전화 통화 기능을 사용할 수 있어요. 아웃바운드 알림은 물론, 인바운드 정책을 활용한 멀티턴 대화까지 지원합니다.

현재 지원하는 프로바이더는 다음과 같아요:

  • twilio (Programmable Voice + Media Streams)
  • telnyx (Call Control v2)
  • plivo (Voice API + XML transfer + GetInput speech)
  • mock (개발용/네트워크 연결 없음)

핵심 개념을 간단히 정리해 드릴게요:

  • 플러그인 설치
  • Gateway 재시작
  • plugins.entries.voice-call.config에서 설정
  • openclaw voicecall ... 명령어 또는 voice_call 도구 사용

Voice Call 플러그인은 Gateway 프로세스 내부에서 실행돼요.

원격 Gateway를 사용하는 경우, Gateway가 실행 중인 머신에 플러그인을 설치하고 설정해야 합니다. 그 다음 Gateway를 재시작하여 플러그인을 로드해 주세요.

Terminal window
openclaw plugins install @openclaw/voice-call

설치 후 Gateway를 재시작하세요.

방법 B: 로컬 폴더에서 설치 (개발용)

섹션 제목: “방법 B: 로컬 폴더에서 설치 (개발용)”
Terminal window
PLUGIN_SRC=./path/to/local/voice-call-plugin
openclaw plugins install "$PLUGIN_SRC"
cd "$PLUGIN_SRC" && pnpm install

설치 후 Gateway를 재시작하세요.

plugins.entries.voice-call.config 항목 아래에 설정을 추가하세요:

{
plugins: {
entries: {
"voice-call": {
enabled: true,
config: {
provider: "twilio", // or "telnyx" | "plivo" | "mock"
fromNumber: "+15550001234",
toNumber: "+15550005678",
twilio: {
accountSid: "ACxxxxxxxx",
authToken: "...",
},
telnyx: {
apiKey: "...",
connectionId: "...",
// Telnyx webhook public key from the Telnyx Mission Control Portal
// (Base64 string; can also be set via TELNYX_PUBLIC_KEY).
publicKey: "...",
},
plivo: {
authId: "MAxxxxxxxxxxxxxxxxxxxx",
authToken: "...",
},
// Webhook server
serve: {
port: 3334,
path: "/voice/webhook",
},
// Webhook security (recommended for tunnels/proxies)
webhookSecurity: {
allowedHosts: ["voice.example.com"],
trustedProxyIPs: ["100.64.0.1"],
},
// Public exposure (pick one)
// publicUrl: "https://example.ngrok.app/voice/webhook",
// tunnel: { provider: "ngrok" },
// tailscale: { mode: "funnel", path: "/voice/webhook" }
outbound: {
defaultMode: "notify", // notify | conversation
},
streaming: {
enabled: true,
streamPath: "/voice/stream",
preStartTimeoutMs: 5000,
maxPendingConnections: 32,
maxPendingConnectionsPerIp: 4,
maxConnections: 128,
},
},
},
},
},
}

참고 사항:

  • Twilio/Telnyx는 외부에서 접근 가능한 webhook URL이 필요해요.
  • Plivo 역시 외부에서 접근 가능한 webhook URL이 필요합니다.
  • mock은 로컬 개발용 프로바이더로, 실제 네트워크 호출을 하지 않아요.
  • Telnyx는 skipSignatureVerification이 true가 아니라면 telnyx.publicKey (또는 TELNYX_PUBLIC_KEY) 설정이 필수예요.
  • skipSignatureVerification은 로컬 테스트 용도로만 사용하세요.
  • ngrok 무료 티어를 사용한다면 publicUrl을 정확한 ngrok URL로 설정하세요. 서명 검증(signature verification)이 항상 강제됩니다.
  • tunnel.allowNgrokFreeTierLoopbackBypass: true를 설정하면 tunnel.provider="ngrok"이고 serve.bind가 루프백(ngrok 로컬 에이전트)일 때만 서명이 유효하지 않은 Twilio webhook을 허용해요. 로컬 개발 시에만 사용하세요.
  • ngrok 무료 티어 URL은 변경되거나 중간 페이지가 나타날 수 있어요. publicUrl이 달라지면 Twilio 서명 검증에 실패하게 됩니다. 운영 환경에서는 고정 도메인이나 Tailscale funnel을 권장해요.
  • 스트리밍 보안 기본값:
    • streaming.preStartTimeoutMs: 유효한 start 프레임을 보내지 않는 소켓을 닫습니다.
    • streaming.maxPendingConnections: 인증 전의 pre-start 소켓 총합을 제한합니다.
    • streaming.maxPendingConnectionsPerIp: 소스 IP당 인증 전 pre-start 소켓 수를 제한합니다.
    • streaming.maxConnections: 열려 있는 전체 미디어 스트림 소켓(대기 중 + 활성) 총합을 제한합니다.

오래된 통화 정리 (Stale call reaper)

섹션 제목: “오래된 통화 정리 (Stale call reaper)”

staleCallReaperSeconds를 사용하면 종료 webhook을 받지 못한 통화(예: 완료되지 않은 notify 모드 통화)를 강제로 종료할 수 있어요. 기본값은 0(비활성)입니다.

권장 설정 범위:

  • 운영 환경: notify 스타일 플로우의 경우 120–300초.
  • 일반적인 통화가 정상적으로 끝날 수 있도록 이 값을 maxDurationSeconds보다 크게 설정하세요. maxDurationSeconds + 30–60초 정도가 적당합니다.

예시:

{
plugins: {
entries: {
"voice-call": {
config: {
maxDurationSeconds: 300,
staleCallReaperSeconds: 360,
},
},
},
},
}

프록시나 터널이 Gateway 앞에 있는 경우, 플러그인은 서명 검증을 위해 퍼블릭 URL을 다시 구성해요. 아래 옵션들로 어떤 전달된 헤더(forwarded headers)를 신뢰할지 제어할 수 있습니다.

webhookSecurity.allowedHosts는 전달된 헤더의 호스트를 허용 목록으로 관리해요.

webhookSecurity.trustForwardingHeaders는 허용 목록 없이 전달된 헤더를 신뢰합니다.

webhookSecurity.trustedProxyIPs는 요청의 원격 IP가 목록과 일치할 때만 전달된 헤더를 신뢰해요.

Twilio와 Plivo에 대해서는 Webhook 재전송 공격 방지(replay protection)가 활성화되어 있어요. 재전송된 유효한 webhook 요청은 확인되지만 부수적인 효과(side effects)는 무시됩니다.

Twilio 대화 턴에는 <Gather> 콜백에 턴당 토큰이 포함되어 있어, 오래되거나 재전송된 음성 콜백이 새로운 대기 중인 트랜스크립트 턴을 처리할 수 없도록 설계되었습니다.

프로바이더가 요구하는 서명 헤더가 없는 인증되지 않은 webhook 요청은 바디를 읽기 전에 거부돼요.

voice-call webhook은 공유된 pre-auth 바디 프로필(64 KB / 5초)과 서명 검증 전 IP당 동시 요청 제한을 함께 사용합니다.

고정된 퍼블릭 호스트를 사용하는 예시:

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

Voice Call은 통화 중 스트리밍 음성을 위해 코어의 messages.tts 설정을 사용해요. 플러그인 설정 내에서 동일한 구조로 이 설정을 덮어쓸 수 있으며, 이는 messages.tts와 딥 머지(deep-merge)됩니다.

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

참고 사항:

  • 플러그인 설정 내부의 레거시 tts.<provider> 키(openai, elevenlabs, microsoft, edge)는 로드 시 tts.providers.<provider>로 자동 마이그레이션돼요. 가급적 providers 구조를 사용해 주세요.
  • Microsoft 음성은 음성 통화에서 무시됩니다 (전화 오디오는 PCM이 필요한데, 현재 Microsoft 트랜스포트는 전화용 PCM 출력을 지원하지 않아요).
  • Twilio 미디어 스트리밍이 활성화되면 코어 TTS가 사용되고, 그렇지 않으면 프로바이더의 네이티브 음성으로 대체됩니다.
  • Twilio 미디어 스트림이 이미 활성 상태라면, Voice Call은 TwiML <Say>로 대체되지 않아요. 그 상태에서 전화 TTS를 사용할 수 없다면 재생 요청은 실패하게 됩니다.
  • 전화 TTS가 보조 프로바이더로 대체될 때, Voice Call은 디버깅을 위해 프로바이더 체인(from, to, attempts) 정보와 함께 경고 로그를 남깁니다.

코어 TTS만 사용 (덮어쓰기 없음):

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

통화에만 ElevenLabs 사용 (다른 곳은 코어 기본값 유지):

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

통화용 OpenAI 모델만 변경 (딥 머지 예시):

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

수신 정책(inbound policy)의 기본값은 disabled입니다. 수신 전화를 허용하려면 다음과 같이 설정하세요:

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

inboundPolicy: "allowlist"는 발신자 ID를 확인하는 기본적인 보안 방식이에요. 플러그인은 프로바이더가 제공한 From 값을 정규화하여 allowFrom 목록과 비교합니다. Webhook 검증은 프로바이더의 전송과 데이터 무결성을 인증하지만, 실제 PSTN/VoIP 발신 번호의 소유권을 증명하지는 않아요. 따라서 allowFrom은 강력한 신원 증명이 아닌 발신자 ID 필터링 정도로 이해해 주세요.

자동 응답은 에이전트 시스템을 사용하며, 아래 옵션으로 튜닝할 수 있어요:

  • responseModel
  • responseSystemPrompt
  • responseTimeoutMs

음성 출력 규약 (Spoken output contract)

섹션 제목: “음성 출력 규약 (Spoken output contract)”

자동 응답을 위해 Voice Call은 시스템 프롬프트에 엄격한 음성 출력 규약을 추가해요:

  • {"spoken":"..."}

그 후 Voice Call은 다음과 같이 안전하게 음성 텍스트를 추출합니다:

  • 추론(reasoning)이나 에러 내용으로 표시된 페이로드는 무시합니다.
  • 직접적인 JSON, 코드 블록 형태의 JSON, 또는 인라인 "spoken" 키를 파싱합니다.
  • 실패 시 일반 텍스트로 대체하며, 계획이나 메타 정보가 담긴 도입부 단락은 제거합니다.

이를 통해 음성 재생이 발신자에게 전달될 텍스트에만 집중되도록 하고, 내부 계획 단계의 텍스트가 오디오로 유출되는 것을 방지해요.

아웃바운드 conversation 통화의 경우, 첫 번째 메시지 처리는 실시간 재생 상태와 연결됩니다:

  • 초기 인사말이 실제로 재생되는 동안에만 끼어들기(barge-in) 큐 비우기와 자동 응답이 억제됩니다.
  • 초기 재생에 실패하면 통화는 listening 상태로 돌아가고, 초기 메시지는 재시도를 위해 큐에 남습니다.
  • Twilio 스트리밍의 초기 재생은 스트림이 연결되는 즉시 추가 지연 없이 시작됩니다.

Twilio 스트림 연결 해제 유예 시간

섹션 제목: “Twilio 스트림 연결 해제 유예 시간”

Twilio 미디어 스트림의 연결이 끊어지면, Voice Call은 통화를 자동으로 종료하기 전에 2000ms 동안 기다려요:

  • 유예 시간 동안 스트림이 다시 연결되면 자동 종료는 취소됩니다.
  • 유예 시간이 지나도 스트림이 다시 등록되지 않으면, 통화가 활성 상태로 멈춰 있는 것을 방지하기 위해 통화를 종료합니다.
Terminal window
openclaw voicecall call --to "+15555550123" --message "Hello from OpenClaw"
openclaw voicecall start --to "+15555550123" # alias for call
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 latency # summarize turn latency from logs
openclaw voicecall expose --mode funnel

latency 명령어는 기본 voice-call 저장 경로에서 calls.jsonl 파일을 읽어요. --file <path>를 사용해 다른 로그 파일을 지정하거나, --last <n>으로 분석할 레코드 수를 제한할 수 있습니다(기본값 200). 출력 결과에는 턴 레이턴시와 대기 시간의 p50/p90/p99 값이 포함됩니다.

도구 이름: voice_call

지원 액션:

  • initiate_call (message, to?, mode?)
  • continue_call (callId, message)
  • speak_to_user (callId, message)
  • end_call (callId)
  • get_status (callId)

이 저장소의 skills/voice-call/SKILL.md에서 관련 스킬 문서를 확인할 수 있어요.

  • voicecall.initiate (to?, message, mode?)
  • voicecall.continue (callId, message)
  • voicecall.speak (callId, message)
  • voicecall.end (callId)
  • voicecall.status (callId)

궁금한 점이 있거나 설정에 도움이 필요하면 언제든 물어봐 주세요!

AI Setup Assistant

OpenClaw

OpenClaw Expert

아직 막혀 있나요?

이 문서에서 답을 못 찾았다면 OpenClaw Expert에게 바로 물어보세요.