Recording Webhook
통화 녹음 파일 업로드 완료/실패 시 전송되는 Recording Webhook의 파라미터를 안내합니다.
Recording Webhook은 통화 녹음 파일이 GCS에 업로드되거나 업로드에 실패했을 때 설정된 URL로 알림을 전송합니다. 녹음은 통화 종료 직후 처리되며, 보통 수초 내에 webhook이 도착합니다. 계정 레벨에서 REST API로 등록합니다.
Recording Webhook을 받으려면 부가서비스 → 통화 녹음 이 활성화되어 있어야 합니다.
녹음이 시작되지 않은 통화 (예: 응답 없음, 회선 오류, 녹음 비활성 상태에서 발생한 통화) 는 recording.completed/recording.failed 둘 다 발생하지 않습니다.
등록
POST /v1/accounts/{accountId}/webhooks
Body:
{
"url": "https://my-app.com/recording-webhook",
"events": ["recording.completed", "recording.failed"]
}배송 로그 조회
SDK로 웹훅 배송 로그를 확인할 수 있습니다.
page = client.webhook_logs.list("cmob6...", page=0, page_size=20)
for log in page.data:
print(log)이벤트 종류
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
| recording.completed | event | 선택 | 녹음 mixed 파일 업로드 완료 — RecordingUrl 포함 |
| recording.failed | event | 선택 | 녹음 mixed 파일 업로드 실패 — Stage/ErrorMessage 포함 |
요청 파라미터
모든 요청은 Content-Type: application/x-www-form-urlencoded POST 입니다.
이벤트마다 전달되는 파라미터가 다르니 아래 두 표를 참고하세요.
recording.completed
mixed 녹음 파일이 GCS에 업로드되고 DB에 recording_url 이 반영된 직후 전송됩니다.
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
| CallId | string | 필수 | 통화 ID |
| AccountId | string | 필수 | 계정 ID |
| From | string | 필수 | 발신 번호 |
| To | string | 필수 | 수신 번호 |
| Direction | string | 필수 | 통화 방향 (inbound, outbound) |
| Event | string | 필수 | 고정값 recording.completed |
| Timestamp | string | 필수 | 이벤트 발생 시각 (ISO 8601) |
| RecordingUrl | string | 필수 | 녹음 파일 다운로드 API 엔드포인트 절대 URL. API key 인증으로 GET 하면 15분 TTL 의 GCS 서명 URL 로 302 redirect 됩니다 — HTTP 클라이언트가 자동 follow. |
| DurationSec | string | 선택 | 통화 길이(초). 일부 케이스에서 미포함될 수 있음 |
CallId=CAabc123&AccountId=ACxxx&From=%2B821011112222&To=%2B821033334444
&Direction=inbound&Event=recording.completed&Timestamp=2026-04-23T09:12:44.123Z
&RecordingUrl=https%3A%2F%2Fapi.claw-ops.com%2Fv1%2Faccounts%2FACxxx%2Frecordings%2FCAabc123
&DurationSec=87recording.failed
녹음은 시작되었으나 (mixed 파일이 생성됨) GCS 업로드 단계에서 최종 실패했을 때 전송됩니다.
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
| CallId | string | 필수 | 통화 ID |
| AccountId | string | 필수 | 계정 ID |
| From | string | 필수 | 발신 번호 |
| To | string | 필수 | 수신 번호 |
| Direction | string | 필수 | 통화 방향 (inbound, outbound) |
| Event | string | 필수 | 고정값 recording.failed |
| Timestamp | string | 필수 | 이벤트 발생 시각 (ISO 8601) |
| Stage | string | 필수 | 실패 단계 — 현재 upload (GCS 업로드 재시도 후 최종 실패) |
| ErrorMessage | string | 필수 | 사람 읽을 수 있는 오류 메시지 |
CallId=CAabc123&AccountId=ACxxx&From=%2B821011112222&To=%2B821033334444
&Direction=inbound&Event=recording.failed&Timestamp=2026-04-23T09:10:02.987Z
&Stage=upload&ErrorMessage=GCS+%EC%97%85%EB%A1%9C%EB%93%9C+%EC%B5%9C%EC%A2%85+%EC%8B%A4%ED%8C%A8+%28mixed%29RecordingUrl 다운로드 동작
RecordingUrl 은 ClawOps API 엔드포인트 절대 URL 입니다 (서명 URL 이 아님).
API key Bearer 인증으로 GET 하면 15분 TTL 의 GCS 서명 URL 로 302 redirect 됩니다.
GET https://api.claw-ops.com/v1/accounts/ACxxx/recordings/CAabc123
Authorization: Bearer sk_...
→ 302 Location: https://storage.googleapis.com/...&X-Goog-Signature=...
→ 200 audio/wav (RIFF WAVE, 16-bit PCM mono 8kHz)대부분의 HTTP 클라이언트 (requests, httpx, axios, curl -L, browser) 는 redirect 를 자동 follow 하므로 별도 처리가 필요 없습니다.
RecordingUrl 자체에는 자격증명이 들어가지 않습니다 — webhook payload 가 로그/메일에 새어도 API key 없이는 다운로드 불가합니다. 따라서 별도 만료 기간 없이 영구적으로 유효합니다 (단, 다운로드 시점마다 새 서명 URL 이 발급되며 그 서명만 15분 TTL).
예제
from flask import Flask, request
import requests
app = Flask(__name__)
CLAWOPS_API_KEY = "sk_..."
@app.route("/recording-webhook", methods=["POST"])
def recording_webhook():
event = request.form.get("Event")
call_id = request.form.get("CallId")
if event == "recording.completed":
url = request.form.get("RecordingUrl")
duration = request.form.get("DurationSec")
print(f"녹음 완료 [{call_id}]: {duration}s")
# API key 로 GET → 자동 302 follow → wav 바이트 수신
r = requests.get(url, headers={"Authorization": f"Bearer {CLAWOPS_API_KEY}"})
r.raise_for_status()
with open(f"{call_id}.wav", "wb") as f:
f.write(r.content)
elif event == "recording.failed":
stage = request.form.get("Stage")
err = request.form.get("ErrorMessage")
print(f"녹음 실패 [{call_id}] ({stage}): {err}")
return "", 204Recording Webhook 요청에도 X-Signature 헤더가 포함됩니다. 서명 검증 가이드를 참고하여 요청의 무결성을 확인하세요.
녹음이 시작되지 않은 통화 (응답 없음, 회선 오류 등) 에는 recording.failed 가 발생하지 않습니다.
"녹음 실패" 와 "녹음 안 됨" 을 구분해서 처리하세요 — 후자는 call.completed 의 status 와 hangup cause 로 판별합니다.
Recording Webhook 은 전달 보장(at-least-once) 방식이므로 드물게 같은 이벤트가 두 번 이상 도착할 수 있습니다.
수신 서버에서 CallId 를 idempotency key 로 사용해 중복 처리를 방지하세요.