ThunderPhone 2.0을 출시했습니다.셀프서비스로, 분당 2¢부터.출시 소식 보기

Webhooks

Webhook 개요

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

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

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

페이로드 형식

엔드포인트 전달은 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 webhook은 동일한 type 및 data를 전송하지만 event_id는 포함하지 않습니다.

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

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

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

서명 검증

모든 요청에는 두 개의 서명이 포함되며, 둘 다 엔드포인트의 secret(레거시 전송의 경우 조직 수준 웹훅 secret)을 키로 사용하는 HMAC-SHA256입니다.

  • X-ThunderPhone-Signature-V2: t=<unix seconds>,v1=<hex>는 타임스탬프, 메서드, 경로 및 쿼리, 원시 본문에 서명합니다. 이 서명을 검증하고, 캡처된 요청이 나중에 재전송되지 않도록 t가 현재 시간보다 5분 이상 차이 나는 요청은 거부합니다. 재시도 요청은 전송될 때 다시 서명됩니다.
  • X-ThunderPhone-Signature는 원시 본문에만 서명합니다. 기존 검증기가 계속 작동하도록 변경 없이 계속 전송됩니다.

단계

  1. 파싱하기 전에 헤더, 메서드, 원시 경로 및 쿼리, 원시 본문을 읽습니다.
  2. 시크릿을 사용하여 <t>\n<METHOD>\n<path and query>\n<body>의 16진수 HMAC-SHA256을 계산합니다.
  3. t가 300초를 초과하여 차이 나거나, 상수 시간 비교에서 결과가 v1과 일치하지 않으면 요청을 거부합니다.

본문은 정규 JSON 직렬화(키 정렬, 압축 구분자)이며, 전송하는 바이트를 정확히 서명합니다. 검증 가이드에는 정확한 서명 문자열, 예제, SDK 없이 작성하는 코드 및 레거시 헤더가 포함되어 있습니다.

Python (Flask)
from flask import Flask, abort, request
from thunderphone import verify_signature
 
app = Flask(__name__)
 
@app.post("/thunderphone-webhook")
def handle():
    query = request.query_string.decode()
    if not verify_signature(
        request.headers.get("X-ThunderPhone-Signature-V2"),
        secret=WEBHOOK_SECRET,
        method=request.method,
        path=request.path + (f"?{query}" if query else ""),
        body=request.get_data(),  # raw bytes
    ):
        abort(401)
    event = request.get_json()
    # dispatch on event["type"] …
    return "", 204
Node.js (Express)
import express from "express";
import { verifySignature } from "@thunderphone/sdk";
 
const app = express();
app.post(
  "/thunderphone-webhook",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const ok = verifySignature({
      header: req.header("X-ThunderPhone-Signature-V2"),
      secret: process.env.WEBHOOK_SECRET,
      method: req.method,
      path: req.originalUrl,
      body: req.body,
    });
    if (!ok) return res.sendStatus(401);
    const event = JSON.parse(req.body.toString("utf8"));
    // dispatch on event.type …
    res.sendStatus(204);
  },
);

전달 의미론

이러한 의미론은 엔드포인트 전송에 적용됩니다. 레거시 단일 URL 웹훅은 재시도 없이 한 번 동기적으로 시도합니다. 단, 전화 통화 telephony.incoming 구성 교환은 한 번 재시도합니다.

재시도

각 이벤트는 즉시 한 번 시도됩니다. 모든 2xx 응답은 전송을 확인합니다. 그 외 결과(2xx가 아님, 연결 오류, 시간 초과)가 발생하면 첫 번째 시도 후 1분, 5분, 30분, 2시간, 6시간, 12시간 및 24시간에 재시도합니다. 즉, 24시간에 걸쳐 총 8회 시도합니다. 모든 시도가 실패하면 전송이 중지되고 엔드포인트는 웹훅 엔드포인트에서 status="failing"으로 표시됩니다. 조직의 소유자와 관리자에게 이메일을 보내고 대시보드에도 표시합니다. 페이로드가 안정적으로 수락되는 즉시 2xx를 반환하고 비동기적으로 처리합니다. 엔드포인트 테스트 API의 테스트 전송은 첫 번째 시도 후 약 1분 뒤에 한 번 재시도되며 엔드포인트 상태는 변경하지 않습니다.

순서

전달 순서는 최선 노력 방식입니다. 실제로는 이벤트가 발생한 순서대로 전달하지만, 실패 시 재시도로 인해 순서가 바뀔 수 있습니다. 항상 call_id / 객체 ID를 기준으로 중복을 제거하고 조정하십시오.

중복

전달은 최소 한 번 방식입니다. 수신하지 못한 응답 이후의 재시도로 인해 이벤트가 중복될 수 있습니다. 모든 재시도에는 동일한 event_id가 포함되므로 처리한 ID를 저장하고 반복 항목을 건너뛰십시오. event_id는 엔드포인트 간에도 공유됩니다. 동일한 이벤트를 구독하는 두 엔드포인트는 같은 event_id를 수신합니다.

시간 초과

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

소스 IP

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

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

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

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


관련 항목