← 블로그 목록
가이드2026-05-21

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

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

ClawOps - AI 전화 에이전트 플랫폼

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가 자동 재시도)
  • 중복 처리 방지 (멱등성)

다음 단계


웹훅 하나로 전화가 비즈니스 시스템과 연결됩니다. 지금 시작하세요.

관련 글 더 보기

ClawOps AI 전화 API로 시작하기

070 번호 발급부터 AI 음성 통화까지, REST API 몇 줄이면 됩니다.