Open in
Webhook の概要
ThunderPhone がリアルタイムイベントを配信する方法、署名を検証する方法、従来の配信モデルとエンドポイントベースの配信モデルの比較を説明します。
ThunderPhoneは、通話中にイベントが発生するとサーバーへ HTTP POST リクエストを送信します。たとえば、着信通話の開始、通話の終了、評価実行の完了、アラートの発火などです。配信モデルは 2種類あります。
複数の URL、エンドポイントごとのシークレット、エンドポイントごとのイベントフィルター、
および自動再試行を提供します。
GET/POST/PATCH/DELETE /v1/developer/webhook-endpoints で管理します。
組織ごとに1つの URL。ブロッキング構成の交換を含む、
通話ライフサイクルイベントを配信します。GET/PUT /v1/webhook で管理します。
イベントカタログに記載されているすべてのイベントタイプは、
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 を使用します。このドキュメントの整形済みの例は、読みやすさのみを目的としています。
イベントタイプとペイロードフィールドの完全な一覧は、イベントカタログを参照してください。
署名の検証
各リクエストには 2 つの署名が含まれます。いずれもエンドポイントの secret(レガシー配信では組織レベルの webhook secret)をキーとする HMAC-SHA256 です。
X-ThunderPhone-Signature-V2: t=<unix seconds>,v1=<hex>は、タイムスタンプ、メソッド、パスとクエリ、生の本文に署名します。こちらを検証し、取得されたリクエストが後で再送されないよう、tがサーバー時刻から 5 分を超えてずれているリクエストは拒否してください。再試行は送信時に再度署名されます。X-ThunderPhone-Signatureは生の本文のみに署名します。既存の検証処理が引き続き機能するよう、変更せずに送信されます。
手順
- ヘッダー、メソッド、生のパスとクエリ、および生の本文を、解析前に読み取ります。
- シークレットを使用して、
<t>\n<METHOD>\n<path and query>\n<body>の16進数HMAC-SHA256を計算します。 tが300秒を超えてずれている場合、または定数時間比較で結果がv1と一致しない場合は、リクエストを拒否します。
本文は正規JSONシリアル化(キーをソートし、区切り文字をコンパクト化)であり、送信するバイト列をそのまま署名します。検証ガイドには、正確な署名対象文字列、実例、SDKを使用しないコード、レガシーヘッダーが記載されています。
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 "", 204import 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
Webhook は再試行なしの単一同期試行ですが、電話通話の
telephony.incoming
設定交換のみ、1 回再試行されます。
再試行
各イベントは直ちに 1 回試行されます。2xx 応答はすべて、
配信を確認します。その他の結果(非 2xx、
接続エラー、タイムアウト)の場合は、最初の試行から 1 m、5 m、30 m、2 h、6 h、
12 h、24 h 後に再試行します。つまり 24 時間にわたり
8 回試行します。すべての試行が失敗すると配信は停止し、エンドポイントは
Webhook エンドポイントで
status="failing" としてマークされます。組織の
オーナーと管理者にメールで通知し、ダッシュボードにも表示します。ペイロードが
永続的に受け入れられたら、できるだけ早く 2xx を返してください。
処理は非同期で行ってください。
エンドポイントテスト API からのテスト送信は、最初の試行から約 1 分後に
1 回再試行されますが、エンドポイントのステータスは変更されません。
順序
配信順序はベストエフォートです。実際にはイベントが発行された順序で配信しますが、
失敗時の再試行によって順序が変わることがあります。
常に call_id / オブジェクト ID で重複を排除し、整合性を確認してください。
重複
配信は少なくとも 1 回です。確認できなかった応答後の再試行により、
イベントが重複する可能性があります。すべての再試行には同じ
event_id が含まれるため、処理済み ID を保存して重複をスキップしてください。event_id は
エンドポイント間でも共有されます。同じイベントを購読している 2 つのエンドポイントは、
同じ event_id を受け取ります。
タイムアウト
エンドポイント配信では、各試行のタイムアウトは 30 s です。従来の
パスでは、リアルタイムの通話動作を制御するブロッキングリクエスト、つまり
telephony.incoming / web.incoming
設定交換は、電話通話の最初の試行では 10 s、1 回の
再試行を含む交換全体では 13 s、ウィジェットセッションでは
10 s に制限されます。応答が遅いと通話の応答が遅延するため、
数秒以内に応答することを目標にしてください。Webhook モードのツールディスパッチでは、
デフォルトで 20 s が許可され、ツール宣言ではトップレベルの timeout を設定できます。
送信元 IP
送信 Webhook は ThunderPhone のクラウド IP 範囲から送信されます。 ファイアウォールで許可リストが必要な場合は、サポートにお問い合わせください。最新の 範囲を共有します。
従来型 Webhook とエンドポイントベース Webhook の選択
| 機能 | レガシー (/v1/webhook) | エンドポイント (/v1/developer/webhook-endpoints) |
|---|---|---|
| URL数 | 組織ごとに1つ | 組織ごとに複数 |
| イベント対応範囲 | telephony.* / web.* のみ | 全10種類のイベント |
| イベントフィルター | — | エンドポイントごと |
| 再試行 | 電話通話設定の交換を除きなし | 24時間で8回試行 |
| エンベロープ | type + data | type + data + event_id |
| シークレットのローテーション | 単一のシークレットを置換 | エンドポイントごとのシークレット |
| 削除せずに無効化 | {"url": ""} を指定した PUT /v1/webhook | status=disabled |
| ステータスの可視性 | — | active / disabled / failing |
| ブロッキング設定交換 | あり(telephony.incoming / web.incoming) | なし — 通知のみ |
| 最適な用途 | 動的な通話設定 | 本番環境でのイベント処理 |
新しい統合では、エンドポイントベースの Webhook を通じてイベントを処理してください。通話応答時に動的に設定する場合、 または Webhook モードのツールディスパッチを使用する場合にのみ、従来型 URL を維持または追加してください。これらの リクエスト/レスポンス交換は従来のパスでのみ実行されます。