ThunderPhone 2.0을 출시했습니다.별도 문의 없이 분당 2¢부터.출시 소식 보기

Webhooks

웹훅 개요

ThunderPhone이 실시간 이벤트를 전달하는 방식, 서명을 검증하는 방법, 그리고 레거시 및 엔드포인트 기반 전달 모델을 비교하는 방법을 설명합니다.

ThunderPhone는 통화 중 이벤트가 발생하면 서버로 HTTP POST 요청을 보냅니다. 예를 들어 수신 통화가 시작되거나, 통화가 종료되거나, 평가 실행이 완료되거나, 알림이 발생하는 경우 등이 있습니다. 두 가지 전송 모델이 있습니다.

이벤트 카탈로그의 10가지 이벤트 유형은 모두 웹훅 엔드포인트를 통해 전송됩니다. 6가지 통화 수명 주기 이벤트 (telephony.incoming, telephony.complete, telephony.tool, web.incoming, web.complete, web.tool)는 레거시 단일 URL 웹훅에도 함께 전송됩니다. 레거시 URL과 일치하는 엔드포인트가 모두 있으면 경로에서 이벤트를 수신합니다. 차단 동작(telephony.incoming / web.incoming 구성 교환 및 웹훅 모드 도구 디스패치)은 레거시 경로에서만 제공됩니다. 모든 엔드포인트 전송은 응답을 기다리지 않는 알림입니다.

페이로드 형식

엔드포인트 전송은 data, event_id, type을 포함하는 JSON 객체입니다.

{
  "data": {
    "call_id": 987654321,
    "from_number": "+14155550199",
    "to_number": "+15551234567"
  },
  "event_id": "3f6b2ad0-1c9e-4a57-9f2b-8f6f0f9d2f11",
  "type": "telephony.incoming"
}

event_id는 발생한 이벤트별로 고유합니다. 재시도 시에도, 이벤트를 수신하는 모든 엔드포인트에서도 동일합니다. 이를 기준으로 중복을 제거합니다.

레거시 단일 URL 웹훅은 동일한 typedata를 보내지만 event_id포함하지 않습니다.

{
  "type": "telephony.incoming",
  "data": { "call_id": 987654321, "from_number": "+14155550199", "to_number": "+15551234567" }
}

전송 시 모든 본문은 표준 형식으로 직렬화됩니다. 키는 알파벳순으로 정렬되고, 공백은 없으며, UTF-8을 사용합니다. 이 문서의 보기 좋게 정렬된 예시는 가독성을 위한 것입니다.

전체 이벤트 유형 및 페이로드 필드 목록은 이벤트 카탈로그를 참조하세요.

서명 검증

모든 요청에는 X-ThunderPhone-Signature 헤더에 원시 요청 본문에 대한 HMAC-SHA256 서명이 포함됩니다. 서명 키는 엔드포인트의 secret(또는 레거시 전송의 경우 조직 수준 웹훅 secret)입니다.

단계

  1. 파싱하기 에 원시 요청 본문을 읽습니다.
  2. hmac_sha256(secret, body).hexdigest()를 계산합니다.
  3. X-ThunderPhone-Signature 헤더와 상수 시간으로 비교합니다.

ThunderPhone은 전송하는 바이트 그대로 서명하며, 해당 바이트는 표준 JSON 직렬화(정렬된 키, 압축 구분 기호)입니다. 따라서 원시 본문을 기준으로 검증하면 항상 작동합니다. 프레임워크가 파싱된 JSON만 제공하는 경우에도 정렬된 키와 압축 구분 기호를 사용해 다시 직렬화하면 동일한 바이트가 생성됩니다. 두 방법 모두 검증 가이드에서 다룹니다.

Python
import hmac
import hashlib
 
def verify_signature(body: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode("utf-8"),
        body,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, signature or "")
 
# Example Flask handler
from flask import Flask, request, abort
app = Flask(__name__)
 
@app.post("/thunderphone-webhook")
def handle():
    body = request.get_data()
    sig = request.headers.get("X-ThunderPhone-Signature", "")
    if not verify_signature(body, sig, WEBHOOK_SECRET):
        abort(401)
    event = request.get_json()
    # dispatch on event["type"] …
    return "", 204
Node.js (Express)
import crypto from "node:crypto";
import express from "express";
 
function verifySignature(body, signature, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(body)
    .digest("hex");
  if (!signature || expected.length !== signature.length) return false;
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature),
  );
}
 
const app = express();
app.post(
  "/thunderphone-webhook",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const sig = req.header("X-ThunderPhone-Signature") || "";
    if (!verifySignature(req.body, sig, process.env.WEBHOOK_SECRET)) {
      return res.sendStatus(401);
    }
    const event = JSON.parse(req.body.toString("utf8"));
    // dispatch on event.type …
    res.sendStatus(204);
  },
);

전송 의미론

이 의미론은 엔드포인트 전송에 적용됩니다. 레거시 단일 URL 웹훅은 재시도 없이 단일 동기식으로 한 번 시도됩니다.

재시도

각 이벤트는 즉시 한 번 시도됩니다. 모든 2xx 응답은 전송을 확인합니다. 그 외의 모든 결과(2xx가 아님, 연결 오류, 시간 초과)에서는 첫 번째 시도 후 1분, 5분, 30분, 2시간, 6시간, 12시간 및 24시간에 재시도합니다. 즉, 24시간 동안 총 8회 시도합니다. 모든 시도가 실패하면 전송이 중단되고 엔드포인트는 웹훅 엔드포인트에서 status="failing"으로 표시됩니다. 페이로드가 영구적으로 수락되는 즉시 2xx를 반환하고, 비동기적으로 처리합니다.

순서

전송 순서는 최선의 노력으로 보장됩니다. 실제로는 이벤트가 발생한 순서대로 전송하지만, 실패 시 재시도로 인해 순서가 바뀔 수 있습니다. 항상 call_id / 객체 ID로 중복을 제거하고 조정합니다.

중복

전송은 최소 한 번 수행됩니다. 응답을 받지 못한 후 재시도하면 이벤트가 중복될 수 있습니다. 모든 재시도에는 동일한 event_id가 포함되므로, 처리된 ID를 저장하고 반복 항목은 건너뜁니다. event_id는 엔드포인트 간에도 공유됩니다. 동일한 이벤트를 구독하는 두 엔드포인트는 같은 event_id를 받습니다.

시간 초과

엔드포인트 전송은 시도당 30초의 시간 초과가 적용됩니다. 레거시 경로에서는 실시간 통화 동작을 제어하는 차단 요청, 즉 telephony.incoming / web.incoming 구성 교환이 10초 후 시간 초과됩니다. 하지만 느린 응답은 통화 수신을 지연시키므로 몇 초 이내에 응답하는 것을 목표로 합니다. 웹훅 모드의 도구 디스패치는 기본적으로 20초를 허용하며, 도구 선언에서 최상위 timeout을 설정할 수 있습니다.

소스 IP

아웃바운드 웹훅은 ThunderPhone의 클라우드 IP 범위에서 발생합니다. 방화벽에 허용 목록이 필요하면 지원팀에 문의하세요. 현재 범위를 공유해 드립니다.

레거시 웹훅과 엔드포인트 기반 웹훅 선택

기능레거시 (/v1/webhook)엔드포인트 (/v1/developer/webhook-endpoints)
URL 수조직당 1개조직당 여러 개
이벤트 범위telephony.* / web.*10가지 이벤트 유형 모두
이벤트 필터엔드포인트별
재시도없음24시간 동안 8회 시도
엔벌로프type + datatype + data + event_id
시크릿 교체단일 시크릿 교체엔드포인트별 시크릿
삭제 없이 비활성화status=disabled
상태 표시active / disabled / failing
차단 구성 교환예 (telephony.incoming / web.incoming)아니요 — 알림 전용
적합한 용도동적 통화 구성프로덕션에서의 이벤트 소비

새 통합에서는 엔드포인트 기반 웹훅을 통해 이벤트를 소비해야 합니다. 통화 수신 시 동적으로 통화를 구성하거나 웹훅 모드 도구 디스패치를 사용하는 경우에만 레거시 URL을 유지하거나 추가하세요. 이러한 요청/응답 교환은 레거시 경로에서만 실행됩니다.


관련 항목