웹훅으로 전화 자동화 구현하기: Python + FastAPI 실전 코드
웹훅으로 전화 자동화 구현하기: Python + FastAPI 실전 코드

AI 전화 에이전트가 전화를 받고 걸 수 있게 됐습니다. 그런데 진짜 자동화는 여기서 시작입니다. 전화가 끝나면 CRM에 기록을 남기고, 주문이 들어오면 확인 전화를 걸고, 부재중이면 슬랙으로 알림을 보내는 — 이런 연결을 만들어야 합니다.
n8n, Make, Zapier 같은 노코드 도구로 연결하는 방법도 있지만, 이 글에서는 개발자가 직접 Python으로 구현하는 방법을 다룹니다.
웹훅이란
ClawOps에서 이벤트가 발생하면 지정된 URL로 HTTP POST 요청을 보내는 것입니다:
전화 이벤트 발생 → ClawOps 서버 → HTTP POST → 내 서버 → 비즈니스 로직
ClawOps에는 두 가지 웹훅 메커니즘이 있습니다.
1) 통화별 상태 콜백 (StatusCallback) — 발신 시 status_callback을 지정하면 통화 상태가 바뀔 때마다 호출됩니다. 요청은 application/x-www-form-urlencoded 이며, 파라미터는 PascalCase입니다.
CallStatus 값 | 설명 | 활용 |
|---|---|---|
initiated | 발신 시작 | 통화 시작 시간 기록 |
ringing | 벨 울림 | — |
answered (→ in-progress) | 응답됨 | — |
completed | 통화 종료 | CRM 기록, 후처리 |
페이로드 키: CallId, AccountId, CallStatus, Direction(inbound/outbound), Timestamp, Duration(종료 시).
2) 계정 이벤트 구독 웹훅 — 계정 단위로 구독하는 이벤트입니다. 실재 이벤트는 transcript.completed, transcript.failed, recording.completed, recording.failed, summary.completed, summary.failed, message.sent, message.failed, message.received, agent.connected, agent.disconnected 입니다.
기본 웹훅 서버 (FastAPI)
pip install fastapi uvicorn clawops
from fastapi import FastAPI, Request
from datetime import datetime
app = FastAPI()
@app.post("/webhook/call-status")
async def call_status(request: Request):
"""통화 상태 변경 시 호출됨 (StatusCallback, form-urlencoded)"""
data = await request.form()
call_id = data.get("CallId")
status = data.get("CallStatus")
direction = data.get("Direction")
duration = data.get("Duration")
print(f"[{datetime.now()}] {call_id}: {status} ({direction})")
if status == "completed":
print(f" 통화 종료 — {direction}, {duration}초")
# 여기서 비즈니스 로직 실행
return {"status": "ok"}
uvicorn main:app --host 0.0.0.0 --port 8000
시나리오 1: 부재중 전화 슬랙 알림
놓친 전화를 슬랙으로 알려줍니다:
import httpx
from fastapi import FastAPI, Request
from clawops import ClawOps
app = FastAPI()
client = ClawOps()
SLACK_WEBHOOK = "https://hooks.slack.com/services/T.../B.../..."
@app.post("/webhook/missed-call")
async def missed_call(request: Request):
data = await request.form()
status = data.get("CallStatus")
direction = data.get("Direction")
# 인바운드 + 미응답인 경우
if direction == "inbound" and status == "completed":
duration = int(data.get("Duration") or 0)
if duration == 0: # 통화 시간 0초 = 부재중
# 발신자 정보는 CallId 로 통화 상세를 조회해 얻습니다
call = client.calls.get(data.get("CallId"))
caller = call.from_
called_at = data.get("Timestamp")
# 슬랙 알림 발송
async with httpx.AsyncClient() as http:
await http.post(SLACK_WEBHOOK, json={
"text": f"📞 부재중 전화\n"
f"발신자: {caller}\n"
f"시각: {called_at}\n"
f"콜백이 필요합니다."
})
return {"status": "ok"}
시나리오 2: 주문 접수 → 자동 확인 전화
쇼핑몰에서 주문이 들어오면 자동으로 확인 전화를 걸어줍니다:
from fastapi import FastAPI, Request
from clawops.agent import ClawOpsAgent, OpenAIRealtime
import asyncio
app = FastAPI()
@app.post("/webhook/new-order")
async def new_order(request: Request):
"""쇼핑몰 주문 웹훅 수신"""
order = await request.json()
customer_phone = order["customer_phone"]
customer_name = order["customer_name"]
items = order["items"]
total = order["total_price"]
# AI가 확인 전화 발신
agent = ClawOpsAgent(
from_="07052351234",
session=OpenAIRealtime(
system_prompt=f"""{customer_name}님에게 주문 확인 전화를 합니다.
주문 내용:
- 상품: {', '.join(items)}
- 결제 금액: {total:,}원
할 일:
1. "{customer_name}님, 주문해주셔서 감사합니다"로 시작
2. 주문 내용을 읽어줍니다
3. "배송지 확인을 위해 주소를 말씀해주세요"
4. 주소 확인 후 "감사합니다, 내일 오전 중 발송됩니다"
5. 통화 종료""",
voice="marin",
language="ko",
),
)
async def make_call():
session = await agent.call(customer_phone, timeout=30)
await session.wait()
await agent.disconnect()
asyncio.create_task(make_call())
return {"status": "call_initiated"}
시나리오 3: 통화 종료 → CRM 자동 기록
통화가 끝나면 트랜스크립트를 분석해서 CRM에 자동 기록합니다:
from fastapi import FastAPI, Request
from clawops import ClawOps
import httpx
app = FastAPI()
client = ClawOps()
CRM_API = "https://api.my-crm.com"
CRM_TOKEN = "crm-api-token"
@app.post("/webhook/call-completed")
async def call_completed(request: Request):
data = await request.form()
if data.get("CallStatus") != "completed":
return {"status": "skipped"}
call_id = data.get("CallId")
duration = data.get("Duration")
# 통화 상세에서 발신자 조회
call = client.calls.get(call_id)
caller = call.from_
# 트랜스크립트 조회
transcript = client.calls.get_transcript(call_id)
if transcript.status == "completed":
# 전체 대화 텍스트 추출
conversation = "\n".join(
f"[{seg.speaker}] {seg.text}"
for seg in (transcript.segments or [])
)
# 통화 요약 조회
summary = client.calls.get_summary(call_id)
summary_text = ""
if summary.status == "completed" and summary.result_json:
summary_text = summary.result_json.get("coreSummary", "")
# CRM에 기록
async with httpx.AsyncClient() as http:
await http.post(
f"{CRM_API}/activities",
headers={"Authorization": f"Bearer {CRM_TOKEN}"},
json={
"type": "call",
"phone": caller,
"duration": duration,
"summary": summary_text,
"transcript": conversation,
"call_id": call_id,
},
)
return {"status": "recorded"}
시나리오 4: 예약 리마인더 (문자 + 전화)
예약 시간 전에 문자를 보내고, 확인이 없으면 전화를 걸어줍니다:
from clawops import ClawOps
import time
client = ClawOps()
def send_reminder(appointment):
"""예약 1시간 전 실행"""
name = appointment["name"]
phone = appointment["phone"]
time_str = appointment["time"]
# 1단계: 문자 발송
msg = client.messages.create(
to=phone,
from_="07052351234",
body=f"[강남내과] {name}님, 오늘 {time_str} 예약 안내드립니다. "
f"방문이 어려우시면 070-5235-1234로 연락 주세요.",
)
print(f"리마인더 문자 발송: {msg.message_id}")
# 2단계: 10분 후 확인 없으면 전화 발신
time.sleep(600)
# 전화로 확인
call = client.calls.create(
to=phone,
from_="07052351234",
url=f"https://my-server.com/reminder-call?name={name}&time={time_str}",
)
print(f"리마인더 전화 발신: {call.call_id}")
웹훅 보안: 서명 검증
프로덕션에서는 웹훅 요청이 정말 ClawOps에서 온 것인지 확인해야 합니다. ClawOps는 요청 시 X-Signature 헤더에 HMAC-SHA256 서명을 담아 보냅니다. SDK의 client.webhooks.verify로 위조 요청을 방지할 수 있습니다:
from fastapi import FastAPI, Request, HTTPException
from clawops import ClawOps
from clawops import WebhookVerificationError
app = FastAPI()
client = ClawOps()
WEBHOOK_URL = "https://my-server.com/webhook/secure"
SIGNING_KEY = "your_signing_key"
@app.post("/webhook/secure")
async def secure_webhook(request: Request):
form = await request.form()
params = dict(form)
signature = request.headers.get("X-Signature", "")
try:
client.webhooks.verify(
url=WEBHOOK_URL,
params=params,
signature=signature,
signing_key=SIGNING_KEY,
)
except WebhookVerificationError:
raise HTTPException(status_code=401, detail="Invalid signature")
# 안전하게 처리
return {"status": "ok"}
웹훅 URL 등록
번호별 웹훅
from clawops import ClawOps
client = ClawOps()
# 번호 생성 시 웹훅 등록
number = client.numbers.create(
webhook_url="https://my-server.com/webhook/voice"
)
# 기존 번호 웹훅 변경
client.numbers.update(
"07052351234",
webhook_url="https://my-server.com/webhook/voice-v2",
)
통화별 상태 콜백
call = client.calls.create(
to="01012345678",
from_="07052351234",
url="https://my-server.com/voice",
status_callback="https://my-server.com/webhook/call-status",
status_callback_event="initiated ringing answered completed",
)
배포 체크리스트
프로덕션에 배포할 때:
- HTTPS 적용 (웹훅은 HTTPS만 지원)
- 서명 검증 활성화
- 타임아웃 설정 (웹훅 응답은 5초 이내 권장)
- 에러 핸들링 및 로깅
- 웹훅 실패 시 재시도 로직 (ClawOps가 자동 재시도)
- 중복 처리 방지 (멱등성)
다음 단계
- 웹훅 공식 문서 — 이벤트 전체 목록, 서명 검증 상세
- VoiceML 가이드 — 통화 흐름 XML 제어
- n8n/Make/Zapier 연동 — 노코드로 연결하기
- Python SDK 입문 — SDK 전체 개요
- AI 전화 에이전트 만들기 — AI 에이전트 구축 가이드
웹훅 하나로 전화가 비즈니스 시스템과 연결됩니다. 지금 시작하세요.
관련 글 더 보기
한국 SMS/LMS/MMS API 완벽 가이드: Python으로 문자 보내기
한국에서 SMS, LMS, MMS를 API로 발송하는 방법. ClawOps Python SDK로 단문·장문·이미지 문자를 보내고, 수신 웹훅으로 자동 응답하는 실전 가이드입니다.
가이드AI 전화 에이전트에 Tool 연동하기: 예약 조회, DB 검색, 외부 API 호출
ClawOps Voice Agent SDK의 Tool 등록 기능으로 AI 전화 에이전트가 DB 조회, 예약 확인, 외부 API 호출을 수행하게 만드는 방법. Python/Node.js 실전 코드 포함.
가이드통화 녹음 전사 & AI 요약 API: 상담 내용을 자동으로 정리하는 법
ClawOps의 통화 전사(Transcript)와 AI 요약(Summary) API로 상담 통화를 텍스트로 변환하고 핵심 내용을 자동 추출하는 방법. Python/Node.js 코드와 웹훅 연동 가이드.
가이드AI 전화 에이전트 만들기: AI가 실제로 전화를 걸고 받도록 구축하는 방법
ClawOps MCP 서버를 활용해 한국어 AI 전화 에이전트을 처음부터 끝까지 구축하는 방법. 070 번호 발급, 발신/수신 전화 처리, 웹훅 연동까지 단계별로 안내합니다.
가이드Twilio 한국 070 안 된다 — ClawOps로 5분 마이그레이션
Twilio 한국 070 안 된다 — ClawOps로 5분 마이그레이션