VoiceML
XML 기반 통화 제어 마크업 언어. 요청/응답 형식과 Say, Play, Gather, Record, Dial, Connect, Hangup, Redirect 태그 레퍼런스.
개요
ClawOps 번호로 수신 전화가 들어오면, 플랫폼은 설정된 웹훅 URL로 HTTP 요청을 전송합니다. 서버는 VoiceML(XML)로 응답하여 통화 흐름을 제어합니다.
VoiceML은 TwiML 호환 형식으로, 기존 TwiML 기반 코드를 최소한의 수정으로 사용할 수 있습니다.
요청 형식
POST 요청 (기본) — Content-Type: application/x-www-form-urlencoded 파라미터가 요청 본문(body)으로 전송됩니다.
GET 요청 — 파라미터가 쿼리 문자열(query string)으로 전송됩니다.
POST와 GET 모두 X-Signature 헤더가 포함됩니다. 서명 검증을 통해 요청의 무결성을 확인할 수 있습니다.
요청 파라미터
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
| CallId | string | 필수 | 통화 고유 ID (예: CA1a2b3c...) |
| AccountId | string | 필수 | 계정 ID (예: AC...) |
| From | string | 필수 | 발신 번호 (예: 01012345678) |
| To | string | 필수 | 수신 번호 — ClawOps 번호 (예: 07012340001) |
| CallStatus | string | 필수 | 통화 상태 (예: in-progress) |
| Direction | string | 필수 | 통화 방향 (예: inbound) |
POST vs GET
POST https://your-server.com/voice HTTP/1.1
Content-Type: application/x-www-form-urlencoded
X-Signature: abc123...
CallId=CA...&AccountId=AC...&From=010...&To=070...&CallStatus=in-progress&Direction=inboundGET https://your-server.com/voice?CallId=CA...&AccountId=AC...&From=010...&To=070...&CallStatus=in-progress&Direction=inbound HTTP/1.1
X-Signature: abc123...응답 형식
웹훅 서버는 Content-Type: application/xml 헤더와 함께 VoiceML XML을 반환해야 합니다. 모든 응답은 <Response> 루트 엘리먼트로 시작합니다.
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Say language="ko">안녕하세요.</Say>
<Gather timeout="5" action="https://your-server.com/gather">
<Say language="ko">서비스를 선택하세요. 1번 상담, 2번 안내</Say>
</Gather>
<Say language="ko">응답이 없습니다.</Say>
<Hangup/>
</Response>동사 레퍼런스
<Say>
텍스트를 음성으로 읽어줍니다 (TTS).
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
| language | string | 선택 | 음성 언어 (ko, en, ja). 기본값: ko |
| voice | string | 선택 | 음성 종류 |
language 속성은 ko(한국어), en(영어), ja(일본어)를 지원합니다. 생략 시 한국어가 기본 적용됩니다.
<Say language="ko">안녕하세요. 고객센터입니다.</Say><Play>
음성 파일을 재생합니다.
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
| url | string | 필수 | 재생할 오디오 파일 URL |
<Play url="https://example.com/welcome.wav"/><Gather>
DTMF 입력(키패드)을 수집합니다. <Say>, <Play>를 중첩할 수 있습니다.
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
| timeout | integer | 선택 | 입력 대기 시간 (초). 기본값: 5 |
| numDigits | integer | 선택 | 수집할 자릿수 |
| action | string | 선택 | 입력 완료 후 요청할 콜백 URL |
| method | string | 선택 | action URL의 HTTP 메서드. 기본값: POST |
<Gather timeout="5" numDigits="1" action="/handle-input" method="POST">
<Say language="ko">1번 상담, 2번 안내, 3번 기타</Say>
</Gather>Gather 완료 시 action URL로 요청이 전송되며, Digits 파라미터에 사용자 입력값이 포함됩니다.
<Record>
통화 내용을 녹음합니다.
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
| maxLength | integer | 선택 | 최대 녹음 시간 (초). 기본값: 3600 |
| action | string | 선택 | 녹음 완료 후 요청할 URL |
| method | string | 선택 | action의 HTTP 메서드. 기본값: POST |
<Record maxLength="60" action="https://your-server.com/recording"/><Dial>
외부 번호로 전화를 연결합니다. 하위에 <Number> 또는 <Sip>을 사용합니다.
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
| callerId | string | 선택 | 발신자 번호 |
| timeout | integer | 선택 | 연결 대기 시간 (초). 기본값: 30 |
<Dial callerId="07012340001" timeout="30">
<Number>01012345678</Number>
</Dial>SIP 연결:
<Dial>
<Sip>sip:user@domain.com</Sip>
</Dial><Connect>
WebSocket Stream을 연결하여 실시간 양방향 오디오를 처리합니다. AI Agent 연동에 주로 사용됩니다.
하위에 <Stream> 엘리먼트를 포함하며, <Stream>은 <Parameter> 자식을 가질 수 있습니다.
Stream 속성
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
| url | string | 필수 | WebSocket 서버 URL (wss://) |
| track | string | 선택 | 스트리밍할 오디오 트랙 (inbound, outbound, both) |
Parameter 속성
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
| name | string | 필수 | 파라미터 이름 |
| value | string | 필수 | 파라미터 값 |
<Connect>
<Stream url="wss://your-server.com/stream" track="inbound">
<Parameter name="userId" value="123"/>
<Parameter name="language" value="ko"/>
</Stream>
</Connect>Stream 프로토콜에 대한 자세한 내용은 Stream WebSocket 문서를 참고하세요.
<Hangup>
통화를 종료합니다. 속성이 없습니다.
<Hangup/><Redirect>
다른 URL로 요청을 리다이렉트하여 새로운 VoiceML을 가져옵니다. 엘리먼트 텍스트에 URL을 포함합니다.
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
| method | string | 선택 | HTTP 메서드. 기본값: POST |
<Redirect method="POST">https://your-server.com/next-step</Redirect>중첩 규칙
| 부모 엘리먼트 | 허용되는 자식 엘리먼트 |
|---|---|
<Gather> | <Say>, <Play> |
<Dial> | <Number>, <Sip> |
<Connect> | <Stream> |
<Stream> | <Parameter> |
<Say>, <Play>, <Record>, <Hangup>, <Redirect> | 중첩 불가 |
중첩 규칙을 따르지 않는 XML은 파싱 오류가 발생합니다. 각 동사에 허용된 자식 엘리먼트만 사용하세요.
서버 구현 예제
Webhook 엔드포인트를 구현하여 전화를 수신하고 VoiceML로 응답하는 예제입니다.
Python (Flask)
from flask import Flask, request
app = Flask(__name__)
@app.route("/voice", methods=["POST"])
def handle_call():
call_id = request.form.get("CallId")
from_number = request.form.get("From")
return """<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Say language="ko">안녕하세요. ClawOps에 연결되었습니다.</Say>
<Hangup/>
</Response>""", 200, {"Content-Type": "application/xml"}로컬 개발 시 ngrok 등의 터널링 도구를 사용하면 외부에서 로컬 서버로 Webhook을 전달받을 수 있습니다. Webhook 없이 AI 에이전트로 전화를 처리하려면 Voice Agent를 참고하세요.
전체 예제: IVR 시나리오
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Say language="ko">안녕하세요. 고객센터에 연결되었습니다.</Say>
<Gather timeout="5" numDigits="1" action="/handle-input" method="POST">
<Say>상품 문의는 1번, 배송 조회는 2번, 상담원 연결은 3번을 눌러주세요.</Say>
</Gather>
<Say>응답이 없습니다. 상담원에게 연결합니다.</Say>
<Dial callerId="07012340001" timeout="30">
<Number>01012345678</Number>
</Dial>
</Response>