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.ended | event | 선택 | 콜 플로우 통화 종료 — 수집 변수 전체 + Status(완주/이탈) 포함 |
끝까지 진행한 통화와 중간에 끊긴 통화 모두 이 이벤트를 보냅니다. 둘은 Status 로 구분하세요.
중도 이탈도 그때까지 모인 값이 그대로 담겨 옵니다 — 버리지 말고 부분 응답으로 활용할 수 있습니다.
요청 파라미터
모든 요청은 Content-Type: application/x-www-form-urlencoded POST 입니다.
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
| CallId | string | 필수 | 통화 ID |
| AccountId | string | 필수 | 계정 ID |
| From | string | 필수 | 발신 번호 |
| To | string | 필수 | 수신 번호 |
| Direction | string | 필수 | 통화 방향 (inbound, outbound) |
| Event | string | 필수 | 고정값 callflow.ended |
| Timestamp | string | 필수 | 이벤트 발생 시각 (ISO 8601) |
| FlowId | string | 필수 | 이 통화를 처리한 콜 플로우 ID |
| Status | string | 필수 | completed = 종료 블럭까지 정상 도달 / abandoned = 그 전에 통화가 끊김(부분 데이터) |
| Variables | string | 필수 | 수집 변수 전체를 담은 JSON 객체 문자열. 값이 없으면 {} 입니다 — JSON.parse 해서 사용하세요. |
| VariablesTruncated | string | 선택 | 고정값 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%7DStatus 는 문자열입니다. 불리언처럼 쓰지 마세요 — 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.wav2026년 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 "", 200app.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 는 "플로우가 쓰지만 통화 중에는 만들어지지 않는 값" 입니다. 채우지 않으면 그 자리가 빈 문자열로 나갑니다.