새로운 문서를 작성 중입니다.새 문서로 이동
ClawOps Docs

Call Flow Webhook

콜 플로우(ARS)가 통화에서 수집한 값을 통화 종료 시 전달하는 callflow.ended Webhook을 안내합니다.

Call Flow Webhook은 콜 플로우(결정형 ARS)가 통화에서 수집한 값을 통화가 끝난 직후 전달합니다. 발신자가 누른 번호, 녹음한 음성, HTTP 블럭이 조회한 값이 한 번에 담겨 옵니다. 계정 레벨에서 REST API로 등록합니다.

통화 실시간으로 값을 받고 싶다면 이 webhook 대신 플로우 안에 HTTP 요청 블럭을 두세요. 원하는 시점에 원하는 형태로 보낼 수 있습니다. callflow.ended 는 "통화가 끝났고 이만큼 모였다" 를 한 번 알리는 용도입니다.

배치 발신(캠페인) 의 수신자별 결과도 이 이벤트로 받습니다 — 통화 1건이 끝날 때마다 즉시 도착하므로 대량 배치에서는 폴링보다 이쪽이 낫습니다.

등록

POST /v1/accounts/{accountId}/webhooks

Body:
{
  "url": "https://my-app.com/callflow-webhook",
  "events": ["callflow.ended"]
}

이벤트 종류

파라미터타입필수설명
callflow.endedevent선택콜 플로우 통화 종료 — 수집 변수 전체 + Status(완주/이탈) 포함

끝까지 진행한 통화와 중간에 끊긴 통화 모두 이 이벤트를 보냅니다. 둘은 Status 로 구분하세요. 중도 이탈도 그때까지 모인 값이 그대로 담겨 옵니다 — 버리지 말고 부분 응답으로 활용할 수 있습니다.

요청 파라미터

모든 요청은 Content-Type: application/x-www-form-urlencoded POST 입니다.

파라미터타입필수설명
CallIdstring필수통화 ID
AccountIdstring필수계정 ID
Fromstring필수발신 번호
Tostring필수수신 번호
Directionstring필수통화 방향 (inbound, outbound)
Eventstring필수고정값 callflow.ended
Timestampstring필수이벤트 발생 시각 (ISO 8601)
FlowIdstring필수이 통화를 처리한 콜 플로우 ID
Statusstring필수completed = 종료 블럭까지 정상 도달 / abandoned = 그 전에 통화가 끊김(부분 데이터)
Variablesstring필수수집 변수 전체를 담은 JSON 객체 문자열. 값이 없으면 {} 입니다 — JSON.parse 해서 사용하세요.
VariablesTruncatedstring선택고정값 true. 변수 전체가 8KB를 넘어 일부 키가 잘렸을 때만 포함됩니다.
CallId=CAabc123&AccountId=ACxxx&From=%2B821011112222&To=%2B821033334444
&Direction=outbound&Event=callflow.ended&Timestamp=2026-07-27T09:12:44.123Z
&FlowId=cmryw3ycm000001s6on0kp9a8&Status=completed
&Variables=%7B%22choice%22%3A%222%22%2C%22preferred_time%22%3A%22https%3A...%22%7D

Status 는 문자열입니다. 불리언처럼 쓰지 마세요 — form 인코딩에서는 모든 값이 문자열이라 if (body.Status) 같은 검사는 abandoned 에도 참이 됩니다. 반드시 값을 비교하세요.

if (body.Status === "completed") { /* 완주 */ }

Variables — 무엇이 담기나

플로우가 통화 중 만든 값이 전부 담깁니다.

파라미터타입필수설명
메뉴 입력블럭 설정선택메뉴 블럭의 '입력 캡처' 에 변수명을 넣으면 누른 키가 담깁니다. 비워두면 분기만 하고 값은 남지 않습니다.
녹음블럭 설정선택녹음 블럭의 '녹음 저장' 에 변수명을 넣으면 그 이름으로 녹음 URL 이, <이름>_duration 에 길이(초)가 담깁니다.
외부 조회 결과블럭 설정선택HTTP 요청 블럭의 saveAs 로 응답 JSON 에서 뽑은 값
직접 만든 값블럭 설정선택변수 설정(setvar) 블럭으로 만든 값
시작 변수발신 시 주입선택createCall 의 Variables 로 넣은 값(고객 이름 등)이 그대로 되돌아옵니다.
caller / callee자동필수발신·수신 번호. 항상 포함됩니다.
recording_url / recording_duration자동선택녹음 블럭이 있으면 **마지막** 녹음의 URL·길이. 녹음이 여러 개면 블럭마다 '녹음 저장' 이름을 지정해 따로 받으세요.

녹음 블럭이 여러 개인데 '녹음 저장' 이름을 지정하지 않으면 마지막 녹음이 앞의 것을 덮어씁니다 (모두 recording_url 하나를 씁니다). 질문마다 답을 따로 받으려면 블럭별로 다른 이름을 주세요.

녹음 URL 다운로드

Variables 안의 녹음 URL은 만료되지 않습니다. 그대로 보관해 두었다가 필요할 때 조회하세요. 다만 다른 API 와 같이 API 키 인증이 필요합니다 — Authorization: Bearer <API 키> 헤더를 붙여 GET 하면 녹음 파일(WAV)이 내려옵니다. 자세한 규격은 녹음 조회 API 를 보세요.

curl -L -H "Authorization: Bearer $CLAWOPS_API_KEY" \
  "https://api.claw-ops.com/v1/accounts/{accountId}/recordings/{callId}/verb/{recordingName}" \
  -o answer.wav

2026년 7월 28일 이전에 받은 녹음 URL은 24시간짜리 서명 URL이라 이미 만료됐을 수 있습니다. 링크는 만료돼도 파일은 그대로 남아 있으니, 콘솔의 배치 결과 화면에서 그대로 재생·확인할 수 있습니다.

통화 전체 녹음(믹스 파일)은 이 webhook 이 아니라 Recording Webhook 으로 옵니다. 둘은 다른 파일입니다 — 여기 담기는 건 녹음 블럭이 받은 그 질문의 답변이고, Recording Webhook 은 통화 전체입니다.

예제

from flask import Flask, request
import json

app = Flask(__name__)

@app.route("/callflow-webhook", methods=["POST"])
def callflow_webhook():
    # 서명 검증은 /docs/webhooks/signature-verification 참고 — 프로덕션에서는 필수
    call_id = request.form["CallId"]
    status = request.form["Status"]
    variables = json.loads(request.form.get("Variables", "{}"))

    if status == "completed":
        save_survey(call_id, request.form["From"], variables)
    else:
        # 중도 이탈 — 그때까지 모인 값만 들어 있습니다
        save_partial(call_id, request.form["From"], variables)

    if request.form.get("VariablesTruncated") == "true":
        log.warning("수집 변수가 8KB를 넘어 일부가 잘렸습니다: %s", call_id)

    return "", 200
app.post("/callflow-webhook", express.urlencoded({ extended: false }), (req, res) => {
  const { CallId, From, Status, FlowId } = req.body
  const variables = JSON.parse(req.body.Variables || "{}")

  if (Status === "completed") {
    saveSurvey({ callId: CallId, phone: From, flowId: FlowId, ...variables })
  } else {
    savePartial({ callId: CallId, phone: From, ...variables })
  }

  res.sendStatus(200)   // 2xx 가 아니면 재시도됩니다
})

Call Flow Webhook 요청에도 X-Signature 헤더가 포함됩니다. 서명 검증 가이드를 참고하여 요청의 무결성을 확인하세요.

전달 보장(at-least-once) 방식이므로 드물게 같은 이벤트가 두 번 이상 도착할 수 있습니다. 수신 서버에서 CallId 를 idempotency key 로 사용해 중복 처리를 방지하세요.

배치 발신에서 재시도 차수(Rounds 2개 이상)를 쓰면 한 수신자에게 통화가 여러 번 일어나므로 이 이벤트도 여러 번 발생합니다. 각 통화의 CallId 는 서로 다르니 위의 중복 방지에는 걸리지 않습니다 — 수신자당 한 번을 가정한 처리가 있다면 전화번호 기준으로 따로 다뤄야 합니다.

발신 시 변수 넣기

플로우 멘트에 {{이름}} 이 있으면 발신할 때 값을 채워 보낼 수 있고, 그 값도 Variables 로 되돌아옵니다.

POST /v1/accounts/{accountId}/calls

{
  "To": "01012345678",
  "From": "07052753864",
  "CallFlowId": "cmryw3ycm000001s6on0kp9a8",
  "Variables": { "name": "홍길동" }
}

어떤 변수를 넣어야 하는지는 콜 플로우 조회로 확인합니다.

GET /v1/accounts/{accountId}/call-flows

→ { "data": [{ "callFlowId": "cmryw...", "name": "고객센터 ARS", "variables": ["name"] }] }

variables 는 "플로우가 쓰지만 통화 중에는 만들어지지 않는 값" 입니다. 채우지 않으면 그 자리가 빈 문자열로 나갑니다.