Plugins

음성 통화 Plugin

OpenClaw용 음성 통화를 Plugin으로 제공합니다. 아웃바운드 알림, 다중 턴 대화, 전이중 실시간 음성, 스트리밍 전사, 허용 목록 정책이 적용된 인바운드 통화를 지원합니다.

현재 제공자: twilio(Programmable Voice + Media Streams), telnyx(Call Control v2), plivo(Voice API + XML transfer + GetInput speech), mock(개발/네트워크 없음).

빠른 시작

  • Plugin 설치

    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
    

    현재 공식 릴리스 태그를 따르려면 기본 패키지를 사용하세요. 재현 가능한 설치가 필요할 때만 정확한 버전을 고정하세요.

    이후 Plugin이 로드되도록 Gateway를 재시작하세요.

  • 제공자 및 Webhook 구성

    plugins.entries.voice-call.config 아래에 구성을 설정하세요(전체 형태는 아래 구성을 참고하세요). 최소한 provider, 제공자 자격 증명, fromNumber, 공개적으로 접근 가능한 Webhook URL이 필요합니다.

  • 설정 확인

    openclaw voicecall setup
    

    기본 출력은 채팅 로그와 터미널에서 읽기 쉽습니다. 이 명령은 Plugin 활성화, 제공자 자격 증명, Webhook 노출, 그리고 하나의 오디오 모드(streaming 또는 realtime)만 활성화되어 있는지 확인합니다. 스크립트에는 --json을 사용하세요.

  • 스모크 테스트

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

    둘 다 기본적으로 드라이런입니다. 실제로 짧은 아웃바운드 알림 통화를 걸려면 --yes를 추가하세요.

    openclaw voicecall smoke --to "+15555550123" --yes
    
  • 구성

    enabled: true이지만 선택한 제공자의 자격 증명이 누락된 경우, Gateway 시작 시 누락된 키와 함께 setup-incomplete 경고를 로그에 남기고 런타임 시작을 건너뜁니다. 명령, RPC 호출, 에이전트 도구는 사용될 때 여전히 정확히 누락된 제공자 구성을 반환합니다.

    {
      plugins: {
        entries: {
          "voice-call": {
            enabled: true,
            config: {
              provider: "twilio", // or "telnyx" | "plivo" | "mock"
              fromNumber: "+15550001234", // or TWILIO_FROM_NUMBER for Twilio
              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 public key from the Mission Control Portal
                // (Base64; 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 /* see Streaming transcription */ },
              realtime: { enabled: false /* see Realtime voice */ },
            },
          },
        },
      },
    }
    
    제공자 노출 및 보안 참고 사항
    • Twilio, Telnyx, Plivo는 모두 공개적으로 접근 가능한 Webhook URL이 필요합니다.
    • mock은 로컬 개발 제공자입니다(네트워크 호출 없음).
    • Telnyx는 skipSignatureVerification이 true가 아닌 한 telnyx.publicKey(또는 TELNYX_PUBLIC_KEY)가 필요합니다.
    • skipSignatureVerification은 로컬 테스트 전용입니다.
    • ngrok 무료 티어에서는 publicUrl을 정확한 ngrok URL로 설정하세요. 서명 검증은 항상 적용됩니다.
    • tunnel.allowNgrokFreeTierLoopbackBypass: truetunnel.provider="ngrok"이고 serve.bind가 loopback(ngrok 로컬 에이전트)일 때만 잘못된 서명을 가진 Twilio Webhook을 허용합니다. 로컬 개발 전용입니다.
    • 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이며 호환성 shim은 임시입니다.

    자동 마이그레이션되는 스트리밍 키:

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

    세션 범위

    기본적으로 Voice Call은 sessionScope: "per-phone"을 사용하므로 같은 발신자의 반복 통화가 대화 메모리를 유지합니다. 각 통신사 통화가 새로운 컨텍스트로 시작해야 하는 경우 sessionScope: "per-call"로 설정하세요. 예를 들어 접수, 예약, IVR 또는 같은 전화번호가 서로 다른 회의를 나타낼 수 있는 Google Meet 브리지 플로우에 적합합니다.

    실시간 음성 대화

    realtime은 실시간 통화 오디오를 위한 전이중 실시간 음성 제공자를 선택합니다. 이는 오디오를 실시간 전사 제공자에게만 전달하는 streaming과는 별개입니다.

    현재 런타임 동작:

    • realtime.enabled는 Twilio Media Streams에서 지원됩니다.
    • realtime.provider는 선택 사항입니다. 설정하지 않으면 Voice Call은 처음 등록된 실시간 음성 제공자를 사용합니다.
    • 번들 실시간 음성 제공자: Google Gemini Live(google) 및 OpenAI(openai). 각 제공자 Plugin이 등록합니다.
    • 제공자가 소유한 원시 구성은 realtime.providers.<providerId> 아래에 있습니다.
    • Voice Call은 기본적으로 공유 openclaw_agent_consult 실시간 도구를 노출합니다. 호출자가 더 깊은 추론, 최신 정보 또는 일반 OpenClaw 도구를 요청할 때 실시간 모델이 이를 호출할 수 있습니다.
    • realtime.consultPolicy는 실시간 모델이 openclaw_agent_consult를 호출해야 하는 시점에 대한 지침을 선택적으로 추가합니다.
    • realtime.agentContext.enabled는 기본적으로 꺼져 있습니다. 활성화하면 Voice Call은 세션 설정 시 제한된 에이전트 ID, 시스템 프롬프트 재정의, 선택된 워크스페이스 파일 캡슐을 실시간 제공자 지침에 주입합니다.
    • realtime.fastContext.enabled는 기본적으로 꺼져 있습니다. 활성화하면 Voice Call은 먼저 consult 질문에 대해 인덱싱된 메모리/세션 컨텍스트를 검색하고, realtime.fastContext.fallbackToConsult가 true일 때만 전체 consult 에이전트로 폴백하기 전에 해당 스니펫을 realtime.fastContext.timeoutMs 내에 실시간 모델에 반환합니다.
    • realtime.provider가 등록되지 않은 제공자를 가리키거나 실시간 음성 제공자가 전혀 등록되어 있지 않은 경우, Voice Call은 전체 Plugin을 실패시키는 대신 경고를 로그에 남기고 실시간 미디어를 건너뜁니다.
    • consult 세션 키는 사용 가능한 경우 저장된 통화 세션을 재사용한 다음, 구성된 sessionScope(per-phone이 기본값, 격리된 통화에는 per-call)로 폴백합니다.

    도구 정책

    realtime.toolPolicy는 consult 실행을 제어합니다.

    정책 동작
    safe-read-only consult 도구를 노출하고 일반 에이전트를 read, web_search, web_fetch, x_search, memory_search, memory_get으로 제한합니다.
    owner consult 도구를 노출하고 일반 에이전트가 일반 에이전트 도구 정책을 사용하도록 합니다.
    none consult 도구를 노출하지 않습니다. 사용자 지정 realtime.tools는 여전히 실시간 제공자에게 전달됩니다.

    realtime.consultPolicy는 실시간 모델 지침만 제어합니다.

    정책 지침
    auto 기본 프롬프트를 유지하고 제공자가 consult 도구를 호출할 시점을 결정하게 합니다.
    substantive 간단한 대화 연결 표현은 직접 답하고, 사실, 메모리, 도구 또는 컨텍스트 전에는 consult합니다.
    always 모든 실질적인 답변 전에 consult합니다.

    에이전트 음성 컨텍스트

    일반 턴에서 전체 에이전트 consult 왕복을 지불하지 않고 음성 브리지가 구성된 OpenClaw 에이전트처럼 들려야 할 때 realtime.agentContext를 활성화하세요. 컨텍스트 캡슐은 실시간 세션이 생성될 때 한 번 추가되므로 턴별 지연 시간이 추가되지 않습니다. openclaw_agent_consult 호출은 여전히 전체 OpenClaw 에이전트를 실행하며, 도구 작업, 최신 정보, 메모리 조회 또는 워크스페이스 상태에 사용해야 합니다.

    {
      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

    기본값: realtime.providers.google.apiKey, GEMINI_API_KEY 또는 GOOGLE_GENERATIVE_AI_API_KEY의 API 키, 모델 gemini-2.5-flash-native-audio-preview-12-2025, 음성 Kore. 더 길고 재연결 가능한 통화에는 sessionResumptioncontextWindowCompression이 기본적으로 켜져 있습니다. 전화 음성에서 더 빠른 턴 테이킹을 조정하려면 silenceDurationMs, startSensitivity, endSensitivity를 사용하세요.

    {
      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",
                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

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

    제공자별 실시간 음성 옵션은 Google 제공자OpenAI 제공자를 참조하세요.

    스트리밍 전사

    streaming은 실시간 통화 오디오를 위한 실시간 전사 제공자를 선택합니다.

    현재 런타임 동작:

    • streaming.provider는 선택 사항입니다. 설정하지 않으면 Voice Call은 등록된 첫 번째 실시간 전사 제공자를 사용합니다.
    • 번들 실시간 전사 제공자: Deepgram(deepgram), ElevenLabs(elevenlabs), Mistral(mistral), OpenAI(openai), xAI(xai)이며, 각 제공자 Plugin에 의해 등록됩니다.
    • 제공자가 소유한 원시 구성은 streaming.providers.<providerId> 아래에 있습니다.
    • Twilio가 수락된 스트림 start 메시지를 보낸 후 Voice Call은 스트림을 즉시 등록하고, 제공자가 연결되는 동안 전사 제공자를 통해 인바운드 미디어를 대기열에 넣으며, 실시간 전사가 준비된 후에만 초기 인사를 시작합니다.
    • streaming.provider가 등록되지 않은 제공자를 가리키거나 등록된 제공자가 없으면, Voice Call은 전체 Plugin을 실패시키는 대신 경고를 기록하고 미디어 스트리밍을 건너뜁니다.

    스트리밍 제공자 예시

    OpenAI

    기본값: API 키 streaming.providers.openai.apiKey 또는 OPENAI_API_KEY, 모델 gpt-4o-transcribe, silenceDurationMs: 800, vadThreshold: 0.5.

    {
      plugins: {
        entries: {
          "voice-call": {
            config: {
              streaming: {
                enabled: true,
                provider: "openai",
                streamPath: "/voice/stream",
                providers: {
                  openai: {
                    apiKey: "sk-...", // optional if OPENAI_API_KEY is set
                    model: "gpt-4o-transcribe",
                    silenceDurationMs: 800,
                    vadThreshold: 0.5,
                  },
                },
              },
            },
          },
        },
      },
    }
    

    xAI

    기본값: API 키 streaming.providers.xai.apiKey 또는 XAI_API_KEY, 엔드포인트 wss://api.x.ai/v1/stt, 인코딩 mulaw, 샘플 레이트 8000, endpointingMs: 800, interimResults: true.

    {
      plugins: {
        entries: {
          "voice-call": {
            config: {
              streaming: {
                enabled: true,
                provider: "xai",
                streamPath: "/voice/stream",
                providers: {
                  xai: {
                    apiKey: "${XAI_API_KEY}", // optional if XAI_API_KEY is set
                    endpointingMs: 800,
                    language: "en",
                  },
                },
              },
            },
          },
        },
      },
    }
    

    통화용 TTS

    Voice Call은 통화에서 스트리밍 음성을 위해 코어 messages.tts 구성을 사용합니다. Plugin 구성 아래에서 동일한 형태로 재정의할 수 있으며, 이는 messages.tts와 깊은 병합됩니다.

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

    동작 참고 사항:

    • Plugin 구성 안의 기존 tts.<provider> 키(openai, elevenlabs, microsoft, edge)는 openclaw doctor --fix로 복구됩니다. 커밋된 구성은 tts.providers.<provider>를 사용해야 합니다.
    • Twilio 미디어 스트리밍이 활성화되면 코어 TTS가 사용됩니다. 그렇지 않으면 통화는 제공자 네이티브 음성으로 폴백합니다.
    • Twilio 미디어 스트림이 이미 활성 상태이면 Voice Call은 TwiML OPENCLAW_DOCS_MARKER:calloutOpen:U2F5로 폴백하지 않습니다. 그 상태에서 전화 TTS를 사용할 수 없으면, 두 재생 경로를 섞는 대신 재생 요청이 실패합니다.
    • 전화 TTS가 보조 제공자로 폴백하면 Voice Call은 디버깅을 위해 제공자 체인(from, to, attempts)과 함께 경고를 기록합니다.
    • Twilio 끼어들기 또는 스트림 종료가 보류 중인 TTS 대기열을 지우면, 대기 중인 재생 요청은 통화자가 재생 완료를 기다리며 멈춰 있지 않고 완료 처리됩니다.

    TTS 예시

    코어 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",
            },
          },
        },
      },
    },
    },
    },
    }
    

    인바운드 통화

    인바운드 정책의 기본값은 disabled입니다. 인바운드 통화를 활성화하려면 다음을 설정하세요.

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

    자동 응답은 에이전트 시스템을 사용합니다. responseModel, responseSystemPrompt, responseTimeoutMs로 조정하세요.

    번호별 라우팅

    하나의 Voice Call Plugin이 여러 전화번호의 통화를 받고 각 번호가 서로 다른 회선처럼 동작해야 할 때 numbers를 사용하세요. 예를 들어 한 번호는 캐주얼한 개인 비서를 사용할 수 있고, 다른 번호는 비즈니스 페르소나, 다른 응답 에이전트, 다른 TTS 음성을 사용할 수 있습니다.

    경로는 제공자가 제공한 다이얼된 To 번호에서 선택됩니다. 키는 E.164 번호여야 합니다. 통화가 도착하면 Voice Call은 일치하는 경로를 한 번 해석하고, 일치한 경로를 통화 레코드에 저장한 뒤, 인사말, 클래식 자동 응답 경로, 실시간 상담 경로, TTS 재생에 해당 유효 구성을 재사용합니다. 일치하는 경로가 없으면 전역 Voice Call 구성이 사용됩니다. 아웃바운드 통화는 numbers를 사용하지 않습니다. 통화를 시작할 때 아웃바운드 대상, 메시지, 세션을 명시적으로 전달하세요.

    경로 재정의는 현재 다음을 지원합니다.

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

    tts 경로 값은 전역 Voice Call tts 구성 위에 깊은 병합되므로, 보통 제공자 음성만 재정의하면 됩니다.

    {
    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은 엄격한 음성 출력 계약을 시스템 프롬프트에 추가합니다.

    {"spoken":"..."}
    

    Voice Call은 방어적으로 음성 텍스트를 추출합니다.

    • 추론/오류 콘텐츠로 표시된 페이로드를 무시합니다.
    • 직접 JSON, 펜스 처리된 JSON 또는 인라인 "spoken" 키를 파싱합니다.
    • 일반 텍스트로 폴백하고, 계획/메타 성격으로 보이는 앞부분 문단을 제거합니다.

    이렇게 하면 음성 재생이 발신자 대상 텍스트에 집중되고 계획 텍스트가 오디오로 유출되는 것을 방지할 수 있습니다.

    대화 시작 동작

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

    • 끼어들기 대기열 비우기와 자동 응답은 초기 인사말이 실제로 말해지는 동안에만 억제됩니다.
    • 초기 재생이 실패하면 통화는 listening으로 돌아가며 초기 메시지는 재시도를 위해 대기열에 남습니다.
    • Twilio 스트리밍의 초기 재생은 스트림 연결 시 추가 지연 없이 시작됩니다.
    • 끼어들기는 활성 재생을 중단하고 대기 중이지만 아직 재생되지 않은 Twilio TTS 항목을 지웁니다. 지워진 항목은 건너뛴 것으로 해석되므로, 후속 응답 로직은 절대 재생되지 않을 오디오를 기다리지 않고 계속될 수 있습니다.
    • 실시간 음성 대화는 실시간 스트림 자체의 시작 턴을 사용합니다. Voice Call은 해당 초기 메시지에 대해 기존 OPENCLAW_DOCS_MARKER:calloutOpen:U2F5 TwiML 업데이트를 게시하지 않으므로, 아웃바운드 &lt;Connect&gt;&lt;Stream&gt; 세션은 연결된 상태로 유지됩니다.

    Twilio 스트림 연결 해제 유예

    Twilio 미디어 스트림 연결이 해제되면 Voice Call은 통화를 자동 종료하기 전에 2000 ms를 기다립니다.

    • 해당 시간 창 안에 스트림이 다시 연결되면 자동 종료가 취소됩니다.
    • 유예 기간 후에도 스트림이 다시 등록되지 않으면 멈춘 활성 통화를 방지하기 위해 통화가 종료됩니다.

    오래된 통화 정리기

    종료 Webhook을 받지 못하는 통화(예: 완료되지 않는 알림 모드 통화)를 종료하려면 staleCallReaperSeconds를 사용하세요. 기본값은 0(비활성화)입니다.

    권장 범위:

    • 프로덕션: 알림 스타일 플로우에는 120~300초.
    • 일반 호출이 완료될 수 있도록 이 값을 maxDurationSeconds보다 높게 유지하세요. 좋은 시작점은 maxDurationSeconds + 30–60초입니다.
    {
    plugins: {
    entries: {
      "voice-call": {
        config: {
          maxDurationSeconds: 300,
          staleCallReaperSeconds: 360,
        },
      },
    },
    },
    }
    

    Webhook 보안

    프록시나 터널이 Gateway 앞에 있을 때, Plugin은 서명 검증을 위해 공개 URL을 재구성합니다. 이 옵션들은 신뢰할 전달 헤더를 제어합니다.

    webhookSecurity.allowedHostsstring[]

    전달 헤더의 호스트 허용 목록입니다.

    webhookSecurity.trustForwardingHeadersboolean

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

    webhookSecurity.trustedProxyIPsstring[]

    요청 원격 IP가 목록과 일치할 때만 전달 헤더를 신뢰합니다.

    추가 보호 기능:

    • Twilio와 Plivo에는 Webhook 재생 공격 보호가 활성화되어 있습니다. 재생된 유효한 Webhook 요청은 확인 응답을 받지만 부수 효과는 건너뜁니다.
    • Twilio 대화 턴은 &lt;Gather&gt; 콜백에 턴별 토큰을 포함하므로, 오래되었거나 재생된 음성 콜백이 더 새로운 대기 중인 transcript 턴을 충족할 수 없습니다.
    • 인증되지 않은 Webhook 요청은 provider의 필수 서명 헤더가 없으면 본문을 읽기 전에 거부됩니다.
    • voice-call Webhook은 공유 사전 인증 본문 프로필(64 KB / 5초)과 서명 검증 전 IP별 진행 중 요청 상한을 사용합니다.

    안정적인 공개 호스트가 있는 예시:

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

    CLI

    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 dtmf --call-id <id> --digits "ww123456#"
    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
    

    Gateway가 이미 실행 중이면 운영용 voicecall 명령은 CLI가 두 번째 Webhook 서버를 바인딩하지 않도록 Gateway 소유 voice-call 런타임에 위임합니다. 도달 가능한 Gateway가 없으면 명령은 독립 실행형 CLI 런타임으로 대체됩니다.

    latency는 기본 voice-call 스토리지 경로에서 calls.jsonl을 읽습니다. 다른 로그를 가리키려면 --file <path>를 사용하고, 분석을 마지막 N개 레코드(기본값 200)로 제한하려면 --last <n>을 사용하세요. 출력에는 턴 지연 시간과 수신 대기 시간의 p50/p90/p99가 포함됩니다.

    Agent 도구

    도구 이름: voice_call.

    작업 인수
    initiate_call message, to?, mode?, dtmfSequence?
    continue_call callId, message
    speak_to_user callId, message
    send_dtmf callId, digits
    end_call callId
    get_status callId

    이 저장소에는 skills/voice-call/SKILL.md에 일치하는 Skills 문서가 포함되어 있습니다.

    Gateway RPC

    메서드 인수
    voicecall.initiate to?, message, mode?, dtmfSequence?
    voicecall.continue callId, message
    voicecall.speak callId, message
    voicecall.dtmf callId, digits
    voicecall.end callId
    voicecall.status callId

    dtmfSequencemode: "conversation"에서만 유효합니다. 알림 모드 호출에서 연결 후 숫자 입력이 필요하다면 호출이 존재한 뒤 voicecall.dtmf를 사용해야 합니다.

    문제 해결

    설정이 Webhook 노출에 실패함

    Gateway를 실행하는 것과 같은 환경에서 설정을 실행하세요.

    openclaw voicecall setup
    openclaw voicecall setup --json
    

    twilio, telnyx, plivo의 경우 webhook-exposure가 통과 상태여야 합니다. 구성된 publicUrl도 로컬 또는 사설 네트워크 공간을 가리키면 실패합니다. 통신사가 해당 주소로 콜백할 수 없기 때문입니다. publicUrllocalhost, 127.0.0.1, 0.0.0.0, 10.x, 172.16.x-172.31.x, 192.168.x, 169.254.x, fc00::/7, fd00::/8을 사용하지 마세요.

    Twilio 알림 모드 아웃바운드 호출은 초기 OPENCLAW_DOCS_MARKER:calloutOpen:U2F5 TwiML을 생성 호출 요청에 직접 보내므로, 첫 번째 음성 메시지는 Twilio가 Webhook TwiML을 가져오는 것에 의존하지 않습니다. 상태 콜백, 대화 호출, 연결 전 DTMF, 실시간 스트림, 연결 후 호출 제어에는 여전히 공개 Webhook이 필요합니다.

    공개 노출 경로 하나를 사용하세요.

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

    구성을 변경한 뒤 Gateway를 다시 시작하거나 다시 로드한 다음 실행하세요.

    openclaw voicecall setup
    openclaw voicecall smoke
    

    voicecall smoke--yes를 전달하지 않으면 드라이런입니다.

    Provider 자격 증명 실패

    선택한 provider와 필수 자격 증명 필드를 확인하세요.

    • Twilio: twilio.accountSid, twilio.authToken, fromNumber, 또는 TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_FROM_NUMBER.
    • Telnyx: telnyx.apiKey, telnyx.connectionId, telnyx.publicKey, fromNumber.
    • Plivo: plivo.authId, plivo.authToken, fromNumber.

    자격 증명은 Gateway 호스트에 있어야 합니다. 로컬 셸 프로필을 편집해도 Gateway가 다시 시작되거나 환경을 다시 로드하기 전까지는 이미 실행 중인 Gateway에 영향을 주지 않습니다.

    호출은 시작되지만 provider Webhook이 도착하지 않음

    provider 콘솔이 정확한 공개 Webhook URL을 가리키는지 확인하세요.

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

    그런 다음 런타임 상태를 검사하세요.

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

    일반적인 원인:

    • publicUrlserve.path와 다른 경로를 가리킵니다.
    • Gateway가 시작된 뒤 터널 URL이 변경되었습니다.
    • 프록시가 요청을 전달하지만 host/proto 헤더를 제거하거나 다시 씁니다.
    • 방화벽 또는 DNS가 공개 호스트 이름을 Gateway가 아닌 다른 곳으로 라우팅합니다.
    • Voice Call Plugin이 활성화되지 않은 상태로 Gateway가 다시 시작되었습니다.

    Gateway 앞에 리버스 프록시나 터널이 있으면 webhookSecurity.allowedHosts를 공개 호스트 이름으로 설정하거나, 알려진 프록시 주소에 webhookSecurity.trustedProxyIPs를 사용하세요. webhookSecurity.trustForwardingHeaders는 프록시 경계가 사용자의 제어 하에 있을 때만 사용하세요.

    서명 검증 실패

    Provider 서명은 OpenClaw가 들어오는 요청에서 재구성한 공개 URL을 기준으로 확인됩니다. 서명이 실패하면 다음을 확인하세요.

    • provider Webhook URL이 스킴, 호스트, 경로를 포함해 publicUrl과 정확히 일치하는지 확인하세요.
    • ngrok 무료 티어 URL의 경우 터널 호스트 이름이 변경될 때 publicUrl을 업데이트하세요.
    • 프록시가 원래 host 및 proto 헤더를 보존하는지 확인하거나 webhookSecurity.allowedHosts를 구성하세요.
    • 로컬 테스트 외부에서는 skipSignatureVerification을 활성화하지 마세요.

    Google Meet Twilio 참가 실패

    Google Meet은 Twilio 전화 접속 참가에 이 Plugin을 사용합니다. 먼저 Voice Call을 확인하세요.

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

    그런 다음 Google Meet 전송을 명시적으로 확인하세요.

    openclaw googlemeet setup --transport twilio
    

    Voice Call은 정상인데 Meet 참가자가 들어오지 않는다면 Meet 전화 접속 번호, PIN, --dtmf-sequence를 확인하세요. 전화 통화는 정상일 수 있지만 회의가 잘못된 DTMF 시퀀스를 거부하거나 무시할 수 있습니다.

    Google Meet은 연결 전 DTMF 시퀀스와 함께 voicecall.start를 통해 Twilio 전화 구간을 시작합니다. PIN에서 파생된 시퀀스는 선행 Twilio 대기 숫자로 Google Meet Plugin의 voiceCall.dtmfDelayMs를 포함합니다. 기본값은 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이 DTMF 이후 지연 뒤 voicecall.speak로 도입 음성을 요청합니다.

    openclaw voicecall tail은 여전히 지속 저장된 호출 레코드를 표시합니다. 호출 상태와 transcript에는 유용하지만 모든 Webhook/실시간 전환이 거기에 나타나는 것은 아닙니다.

    실시간 호출에 음성이 없음

    오디오 모드가 하나만 활성화되어 있는지 확인하세요. realtime.enabledstreaming.enabled는 둘 다 true일 수 없습니다.

    실시간 Twilio 호출의 경우 다음도 확인하세요.

    • 실시간 provider Plugin이 로드되고 등록되어 있습니다.
    • realtime.provider가 설정되지 않았거나 등록된 provider를 이름으로 지정합니다.
    • provider API 키를 Gateway 프로세스에서 사용할 수 있습니다.
    • openclaw logs --follow에 실시간 TwiML 제공, 실시간 브리지 시작, 초기 인사 큐 등록이 표시됩니다.

    관련 항목