Webhook 概覽
了解 ThunderPhone 如何傳送即時事件、如何驗證簽章,以及舊版與端點式傳送模式的比較。
ThunderPhone 會在通話期間發生事件時,向你的伺服器傳送 HTTP POST 請求——例如撥入通話開始、通話結束、評分執行完成、警示觸發等。有兩種傳送模式:
支援多個 URL、各端點專屬密鑰、各端點事件篩選,以及自動重試。
透過 GET/POST/PATCH/DELETE /v1/developer/webhook-endpoints 管理。
每個組織一個 URL。承載通話生命週期事件,包括阻塞式設定交換。透過 GET/PUT /v1/webhook 管理。
事件目錄中的全部十種事件類型,都會透過 Webhook 端點傳送。六種通話生命週期事件
(telephony.incoming、telephony.complete、telephony.tool、
web.incoming、web.complete、web.tool)也會傳送至舊版單一 URL Webhook——若你同時設有舊版 URL 與相符的端點,將在兩個路徑上收到該事件。阻塞式行為(telephony.incoming / web.incoming 設定交換與 Webhook 模式的工具分派)僅存在於舊版路徑;每次端點傳送都是即發即棄的通知。
Payload 格式
端點傳送的內容為 JSON 物件,包含 data、event_id 與
type:
{
"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。本文文件中的美化格式範例僅供閱讀使用。
請參閱事件目錄,取得完整的事件類型與 Payload 欄位清單。
簽章驗證
每個請求都會在 X-ThunderPhone-Signature 標頭中附帶一個針對原始請求
主體的 HMAC-SHA256 簽章。簽署金鑰為端點的 secret(若為舊版
傳遞,則為組織層級 webhook 的 secret)。
步驟
- 在進行任何剖析之前讀取原始請求主體。
- 計算
hmac_sha256(secret, body).hexdigest()。 - 以固定時間比較結果與
X-ThunderPhone-Signature標頭。
我們會對實際傳送的位元組進行簽署,而這些位元組採用標準 JSON 序列化格式 (排序後的鍵、精簡分隔符)。因此,使用原始主體驗證一定可行——如果你的框架 只提供已剖析的 JSON,則以排序後的鍵和精簡分隔符重新序列化,即可產生完全相同 的位元組。這兩種做法都涵蓋於驗證指南。
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 "", 204import 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 webhook 是單次同步嘗試,不會重試。
重試
每個事件都會立即嘗試傳遞一次。任何 2xx 回應
都會確認傳遞成功。若出現任何其他結果(非 2xx、
連線錯誤、逾時),我們會在首次嘗試後的 1 分鐘、5 分鐘、30 分鐘、2 小時、6 小時、
12 小時與 24 小時進行重試——共 8 次嘗試,
橫跨 24 小時。若每次嘗試皆失敗,傳遞將停止,且端點
會在webhook 端點中標示為
status="failing"。請在 payload 確實被永久接受後儘快回傳 2xx;
後續請以非同步方式處理。
排序
傳遞順序採盡力而為原則。實務上,我們會依事件發出的
順序傳遞,但失敗後的重試可能改變順序。
一律依 call_id/物件 ID 去除重複項目並進行核對。
重複
傳遞採用至少一次語意:在我們未收到回應後進行重試時,
可能會產生重複事件。每次重試都會帶有相同的
event_id,因此請儲存已處理的 ID 並略過重複項目。event_id
也會在端點之間共用——訂閱相同事件的兩個端點會收到相同的
event_id。
逾時
每次端點傳遞的逾時時間為 30 秒。在舊版路徑上,會影響即時
通話行為的阻塞式請求——telephony.incoming / web.incoming
設定交換——會在 10 秒後逾時,但緩慢的回應會延遲接聽通話,
因此請盡量在幾秒內回應。Webhook 模式的工具派送
預設允許 20 秒,且工具宣告可設定頂層 timeout。
來源 IP
傳出的 webhook 來自 ThunderPhone 的雲端 IP 範圍。 若你的防火牆需要允許清單,請聯絡支援團隊,我們會 提供目前的範圍。
在舊版與端點式 webhook 之間選擇
| 功能 | 舊版(/v1/webhook) | 端點(/v1/developer/webhook-endpoints) |
|---|---|---|
| URL 數量 | 每個組織 1 個 | 每個組織多個 |
| 事件涵蓋範圍 | 僅 telephony.*/web.* | 全部 10 種事件類型 |
| 事件篩選 | — | 依端點設定 |
| 重試 | 無 | 24 小時內 8 次嘗試 |
| 信封格式 | type + data | type + data + event_id |
| 密鑰輪替 | 替換單一密鑰 | 每個端點各自的密鑰 |
| 不刪除即可停用 | — | status=disabled |
| 狀態可見度 | — | active/disabled/failing |
| 阻塞式設定交換 | 是(telephony.incoming / web.incoming) | 永不——僅通知 |
| 最適合 | 動態通話設定 | 正式環境中的事件取用 |
新的整合應透過端點式 webhook 取用事件。只有在你於接聽時 動態設定通話,或使用 Webhook 模式的工具派送時,才保留(或新增) 舊版 URL——這些請求/回應交換僅會在舊版路徑上執行。