웹훅 수신 및 서명 확인
Ody는 새 메시지, 통화 결과, 연락처 변경과 같은 작업 공간 이벤트를 API를 통해 등록하는 HTTPS 엔드포인트로 푸시하며, 모든 전달은 표준 웹훅 사양에 따라 서명되므로 Ody에서 전송되었음을 증명할 수 있습니다.
시작하기 전에
webhooks:manage범위가 있는 API 키가 필요합니다. — Ody API 키 가져오기를 참조하세요.- 엔드포인트는
2xx상태로 빠르게 응답하는 HTTPS URL이어야 합니다 (전달은 10초 후에 시간 초과됩니다). - 각 작업 공간은 최대 50개의 엔드포인트를 등록할 수 있습니다.
단계별 안내
- 구독할 수 있는 이벤트 유형을 나열합니다:
curl https://api.ody.co/v1/api/webhook-events \
-H "Authorization: Bearer ody_live_…"
현재 카탈로그: message.received, message.delivered, message.failed, call.ringing, call.answered, call.completed, call.forwarded, call.missed, call.recording.completed, call.summary.completed, call.transcript.completed, call.voicemail.completed, call.flow.started, call.flow.completed, call.flow.aborted, contact.updated, contact.deleted. "*"를 구독하면 현재 및 향후의 모든 이벤트 유형을 포함합니다.
call.flow.* 이벤트는 통화 흐름에 의해 통화가 응답될 때 발생합니다: 발신자가 흐름에 진입할 때 started, 목적지(착신, AI 비서, 음성 사서함, 전달 또는 끊기)에 도달할 때 completed, 흐름을 완료할 수 없을 때 aborted가 발생합니다.
- 엔드포인트를 등록합니다:
curl https://api.ody.co/v1/api/webhooks \
-H "Authorization: Bearer ody_live_…" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/ody-webhook","events":["message.received"],"label":"prod"}'
- 응답에서
secret을 즉시 저장하세요.whsec_…서명 비밀 키는 생성 및 회전 시에만 반환되며, 다시는 표시되지 않습니다. - 서명된 테스트 이벤트를 자신에게 보내고 엔드포인트가 이를 확인하고 수락하는지 확인합니다:
curl -X POST https://api.ody.co/v1/api/webhooks/WEBHOOK_ID/test \
-H "Authorization: Bearer ody_live_…"
테스트는 실제 전달과 동일하게 서명된 {"type":"ping"} 이벤트를 게시합니다.
전달의 모습
모든 전달은 JSON 본문과 세 개의 서명 헤더를 포함하는 POST입니다:
{
"id": "evt_…",
"apiVersion": "2026-06-01",
"createdAt": "2026-08-16T12:00:00.000Z",
"type": "message.received",
"data": { "conversationId": "…", "activityId": "…", "numberE164": "+1…" }
}
| 헤더 | 의미 |
|---|---|
webhook-id |
이벤트 ID — 재시도 시 재사용되므로 이를 기준으로 중복 제거 |
webhook-timestamp |
전송 시점의 Unix 초 |
webhook-signature |
<id>.<timestamp>.<raw body>에 대한 v1,<base64 HMAC-SHA256> |
서명 확인
HMAC 키는 whsec_ 접두사 뒤의 비밀 키의 base64-디코딩된 부분이며, 서명된 메시지는 <webhook-id>.<webhook-timestamp>.<raw body>입니다. 항상 원시 요청 바이트에 대해 확인해야 합니다. — 파싱된 JSON을 다시 직렬화하면 서명이 손상됩니다. 몇 분 이상 오래된 타임스탬프는 재생 공격을 차단하기 위해 거부하세요.
import { createHmac, timingSafeEqual } from "node:crypto";
import express from "express";
const app = express();
app.post("/ody-webhook", express.raw({ type: "application/json" }), (req, res) => {
const secret = process.env.ODY_WEBHOOK_SECRET; // whsec_…
const id = req.headers["webhook-id"];
const timestamp = req.headers["webhook-timestamp"];
const signature = req.headers["webhook-signature"];
// Replay guard: reject deliveries older than 5 minutes.
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return res.status(401).end();
const key = Buffer.from(secret.slice("whsec_".length), "base64");
const expected = "v1," + createHmac("sha256", key)
.update(`${id}.${timestamp}.${req.body}`)
.digest("base64");
const a = Buffer.from(expected);
const b = Buffer.from(String(signature));
if (a.length !== b.length || !timingSafeEqual(a, b)) return res.status(401).end();
const event = JSON.parse(req.body.toString("utf8"));
console.log("verified event:", event.type);
res.status(200).end();
});
공식 SDK는 이를 한 줄로 제공합니다. — verifyWebhookSignature (TypeScript) 및 verify_webhook_signature (Python)는 모두 상수 시간 비교와 5분 재생 방지 기능이 내장되어 있습니다. SDK 빠른 시작: TypeScript 및 Python을 참조하세요.
재시도, 기록 및 관리
- 재시도: 실패한 전달(2xx가 아닌 응답, 시간 초과 또는 연결 오류)은 첫 번째 시도 후 약 5초 그리고 30초 후에 재시도됩니다. — 총 세 번의 시도, 각 시도당 10초 시간 초과.
- 기록:
GET /v1/api/webhooks/:id/deliveries는 최신 순으로 최근 전달 목록을 표시하며, 시도별 상태 코드, 오류 및 지속 시간을 포함합니다. — 이벤트가 누락된 것처럼 보일 때 가장 먼저 확인할 곳입니다. - 엔드포인트 관리:
GET /v1/api/webhooks는 엔드포인트를 나열하고(비밀 키는 표시되지 않음),PATCH /v1/api/webhooks/:id는url,events,label또는status(enabled/disabled)를 업데이트하며,DELETE /v1/api/webhooks/:id는 전달 기록과 함께 엔드포인트를 제거합니다. POST /v1/api/webhooks/:id/rotate를 사용하여 언제든지 비밀 키를 회전할 수 있습니다. — 새whsec_…는 한 번만 반환되며, 이전 서명은 즉시 유효하지 않게 됩니다.
문제 해결
- 서명 확인이 항상 실패합니다 — 아마도 다시 직렬화된 본문을 확인하고 있을 것입니다. 원시 요청 바이트를 사용하고,
whsec_를 제거한 후 비밀 키를 base64-디코딩했는지 확인하세요. - 전달이 도착하지 않습니다 — 엔드포인트의
status가enabled인지,events목록에 예상하는 유형(또는"*"), 그리고 시도별 오류에 대한 전달 기록을 확인하세요. - 중복 이벤트 — 재시도는 동일한
webhook-id를 재사용합니다. 이를 기준으로 중복을 제거하세요. webhook limit reached— 작업 공간에 대해 50개의 엔드포인트에 도달했습니다. 먼저 사용하지 않는 엔드포인트를 삭제하세요.
관련 문서
자주 묻는 질문
웹훅 전달은 어떻게 서명되나요?
표준 웹훅 사양에 따라: whsec_ 비밀 키를 사용하여 '<webhook-id>.<webhook-timestamp>.<raw body>'에 대해 계산된 v1,<base64 HMAC-SHA256> 형식의 webhook-signature 헤더입니다.
엔드포인트가 다운되면 어떻게 되나요?
Ody는 첫 번째 실패 후 약 5초, 그리고 30초 후에 두 번 재시도합니다. 시도별 상태 코드 및 오류는 GET /v1/api/webhooks/:id/deliveries에서 확인하세요.
서명 비밀 키를 잃어버렸습니다. 어떻게 해야 하나요?
POST /v1/api/webhooks/:id/rotate를 사용하여 회전시키세요. 새 whsec_ 비밀 키는 한 번만 반환되며, 이전 서명은 즉시 유효하지 않게 됩니다.