← 블로그 목록
가이드2026-06-01
070 인바운드 webhook 라우팅 — 시간·번호·DTMF 기준 동적 분기
070 인바운드 webhook 라우팅 — 시간·번호·DTMF 기준 동적 분기
같은 070 번호로 들어오는 통화도 시간대(낮/밤)·발신자(VIP/일반)·DTMF 응답(1=예약/2=문의/3=결제) 기준으로 다른 시나리오로 분기해야 한다. ClawOps webhook 으로 통화 시작 직전에 라우팅 결정.
0. 사전 준비
- ClawOps 070 발급 완료
- HTTPS 가능한 webhook endpoint (Vercel/Fly/Render OK)
1. webhook 등록
client.numbers.update(
number_id="num_abc",
webhook_url="https://your-server.com/voice-route",
webhook_events=["call.initiated", "call.dtmf", "call.ended"],
)
또는 Console UI 에서:
Number → Settings → Webhook URL → https://your-server.com/voice-route
2. webhook 페이로드 (call.initiated)
ClawOps 가 통화 시작시 다음 형태로 POST:
{
"event": "call.initiated",
"call": {
"id": "call_xyz",
"from": "01012345678",
"to": "07052358010",
"started_at": "2026-05-27T10:30:00Z",
"metadata": {}
}
}
당신의 endpoint 는 라우팅 결정을 JSON 으로 응답:
{
"action": "ai_handle",
"agent_config": {
"system_prompt": "안녕하세요, ABC 회사입니다...",
"language": "ko-KR",
"voice": "alloy"
}
}
3. 라우팅 패턴들
A. 영업시간 기준 분기
from flask import Flask, request, jsonify
import datetime
from zoneinfo import ZoneInfo
app = Flask(__name__)
@app.route("/voice-route", methods=["POST"])
def voice_route():
payload = request.json
now = datetime.datetime.now(ZoneInfo("Asia/Seoul"))
is_business_hours = 9 <= now.hour < 18 and now.weekday() < 5
if is_business_hours:
return jsonify({
"action": "ai_handle",
"agent_config": {
"system_prompt": "안녕하세요, ABC 회사입니다. 어떤 도움이 필요하신가요?",
"language": "ko-KR",
},
})
else:
return jsonify({
"action": "ai_handle",
"agent_config": {
"system_prompt": (
"안녕하세요, ABC 회사 야간 응대입니다. "
"지금은 영업시간이 아니라 메모만 받겠습니다. "
"성함과 용건 말씀해주시면 영업일 첫 시간에 연락드립니다."
),
"language": "ko-KR",
"max_duration_seconds": 120,
},
})
B. 발신자 번호 화이트리스트 (VIP)
VIP_NUMBERS = {"01011112222", "01033334444"}
@app.route("/voice-route", methods=["POST"])
def voice_route():
caller = request.json["call"]["from"]
if caller in VIP_NUMBERS:
return jsonify({
"action": "transfer",
"to": "010-VIP담당자-번호",
"mode": "blind",
})
return jsonify({"action": "ai_handle", "agent_config": {...}})
C. DTMF 응답 기반 메뉴
@app.route("/voice-route", methods=["POST"])
def voice_route():
# 첫 응답 — 메뉴 안내
return jsonify({
"action": "ai_handle",
"agent_config": {
"system_prompt": (
"다음 중 선택해주세요. "
"예약은 1번, 결제 문의는 2번, 상담사 연결은 0번을 눌러주세요."
),
"language": "ko-KR",
"expect_dtmf": True, # DTMF 이벤트 emit
},
})
@app.route("/voice-dtmf", methods=["POST"]) # call.dtmf 이벤트 endpoint
def voice_dtmf():
digit = request.json["dtmf"]["digit"]
call_id = request.json["call"]["id"]
if digit == "1":
return jsonify({
"action": "switch_agent",
"agent_config": {
"system_prompt": "예약 안내 시작 — 날짜와 인원을 알려주세요...",
},
})
elif digit == "2":
return jsonify({
"action": "switch_agent",
"agent_config": {
"system_prompt": "결제 문의 안내 — 주문번호 알려주세요...",
},
})
elif digit == "0":
return jsonify({
"action": "transfer",
"to": "010-상담사-번호",
"mode": "warm",
"context": "고객이 메뉴에서 상담사 연결 선택",
})
D. 발신자 history 기반 (DB 조회)
@app.route("/voice-route", methods=["POST"])
def voice_route():
caller = request.json["call"]["from"]
customer = db.query("SELECT * FROM customers WHERE phone = %s", caller).fetchone()
if customer is None:
prompt = "안녕하세요, 처음 전화 주신 분이시군요. ABC 회사입니다."
elif customer.tier == "premium":
prompt = f"{customer.name}님 안녕하세요. 프리미엄 고객 전용 응대입니다."
elif customer.unpaid_invoices > 0:
prompt = f"{customer.name}님, 미결제 청구건이 있어서 안내드립니다..."
else:
prompt = f"{customer.name}님 안녕하세요. 어떤 도움이 필요하신가요?"
return jsonify({
"action": "ai_handle",
"agent_config": {"system_prompt": prompt, "language": "ko-KR"},
})
E. 부재중 fallback (영업시간 외 / AI 처리 실패)
@app.route("/voice-route", methods=["POST"])
def voice_route():
# 영업시간 외 → 메시지 받기 모드
return jsonify({
"action": "ai_handle",
"agent_config": {
"system_prompt": (
"지금은 영업시간이 아닙니다. "
"성함, 연락처, 용건을 차례로 말씀해주시면 다음 영업일에 callback 드립니다."
),
"tools": ["hang_up"], # AI 가 끝나면 자동 종료
"post_call_actions": [ # 끝난 뒤 자동 실행
{"type": "summarize"},
{"type": "webhook", "url": "https://your-server.com/voicemail"},
],
},
})
4. 작동 원리 — 분기 흐름
[고객] → 070 다이얼
↓
[ClawOps SIP 수신]
↓ POST /voice-route
[당신의 endpoint] 라우팅 결정
↓ JSON 응답
[ClawOps Agent 시작] OR [Transfer] OR [Voicemail]
↓
[고객]
5. 성능 — endpoint 응답 시간
ClawOps 는 webhook 응답을 통화 시작 전 200ms 안에 기대. endpoint 가 늦으면 default agent_config 로 fallback.
- DB 조회 1개 (<50ms) OK
- 외부 API 호출은 비동기 큐로
6. 보안
- ClawOps webhook 은
X-ClawOps-Signature헤더에 HMAC-SHA256 서명 보냄 - 검증:
import hmac, hashlib
def verify(body: bytes, sig: str) -> bool:
expected = hmac.new(WEBHOOK_SECRET.encode(), body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, sig)
다음 단계
관련 글 더 보기
가이드
관리번호로 070 대량 발급·관리하기 — 파트너 가입중개(External Assignment)
프랜차이즈·플랫폼·법인이 최종이용자에게 070을 일괄 발급·관리하는 가이드. ClawOps 관리번호 Add-on + 발급 링크·본인인증 + 인입 라우팅 코드.
가이드070 번호 5분 발급 가이드 — API 한 줄, 무약정, 카드 등록 없이
한국 070 번호를 API 한 줄로 5분에 발급받는 가이드. 통신사 영업·KISA 등록·벌크 약정 없이 ClawOps Trial 무료로 시작.
가이드1588·1566 대표번호 ClawOps 무료 연동 — 기존 8자리 번호 그대로 AI 응답
기존 1588·1566 대표번호를 ClawOps 070 인프라에 무료로 연동해서 AI 상담원이 받게 만드는 가이드. 기존 통신사 계약 유지하면서 AI 응대 추가.
가이드MCP 전화 tool 설계 — make_call / hang_up / transfer / DTMF 패턴 정리
MCP 서버에서 전화 기능을 노출할 때 어떤 tool 을 어떻게 쪼개야 LLM 이 제대로 쓰는지. ClawOps SDK 기반 실전 patterns.
가이드Retell AI 사용자가 ClawOps 070 추가하기 — 한국 시장 진출 5분 가이드
Retell AI voice agent 에 한국 070 번호를 추가하는 마이그레이션 가이드. SIP 직접연결 hybrid 또는 ClawOps Pipeline 으로 풀 전환.