ThunderPhone 2.0 正式上線。自助開通,每分鐘 2¢ 起。查看公告

Webhooks

Webhook 概覽

瞭解 ThunderPhone 如何傳送即時事件、如何驗證簽章,以及舊版與端點式傳送模型的比較。

ThunderPhone 會在通話期間發生事件時,向你的伺服器傳送 HTTP POST 請求——例如來電開始、通話結束、評分執行完成、警示觸發等。提供 兩種傳遞 模式:

事件目錄中列出的所有事件類型,都會透過 webhook 端點 傳遞。六種通話生命週期事件 (telephony.incoming、telephony.complete、telephony.tool、 web.incoming、web.complete、web.tool)也會傳送至 舊版單一 URL webhook——如果你同時設定舊版 URL 與相符的端點, 就會在 兩個路徑上收到事件。阻塞式 行為(telephony.incoming / web.incoming 設定 交換和 webhook 模式的 工具派送)僅存在於舊版路徑; 每次端點傳遞皆為即發即忘的通知。

酬載格式

端點傳遞的 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。本文檔中的美化範例僅為提升可讀性。

如需完整的事件類型與酬載欄位清單,請參閱事件目錄。

簽章驗證

每個請求都帶有兩個簽章,兩者皆使用端點的 secret(舊版傳送則使用組織層級 Webhook 的 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> 的十六進位 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 webhook 是單次同步嘗試,不會重試,唯一例外是電話通話的 telephony.incoming 設定交換,會重試一次。

重試

每個事件都會立即嘗試傳送一次。任何 2xx 回應 都會確認此次傳送。若出現其他結果(非 2xx、 連線錯誤、逾時),我們會在首次嘗試後的 1 分鐘、5 分鐘、30 分鐘、2 小時、6 小時、 12 小時及 24 小時重試——共 8 次嘗試, 橫跨 24 小時。若所有嘗試皆失敗,傳送將停止,且端點 會在 webhook 端點中標記為 status="failing";我們會透過電子郵件通知你組織的 擁有者與管理員,並在控制台中顯示。請在 payload 被可靠接收後儘快回傳 2xx;以非同步方式處理。 從端點測試 API 發出的測試傳送,會在首次嘗試約一分鐘後重試一次, 且絕不會變更端點的狀態。

排序

傳遞排序採盡力而為。在實務上,我們會依事件發出的 順序傳遞,但重試可能會在失敗時重新排序。 請一律依 call_id/物件 ID 去除重複並核對。

重複

傳遞採用至少一次語意:在我們未收到回應後進行的重試, 可能會重複傳遞事件。每次重試都帶有相同的 event_id,因此請儲存已處理的 ID 並略過重複項目。event_id 也會在端點之間共用——訂閱相同事件的兩個端點會收到相同的 event_id。

逾時

端點傳送每次嘗試的逾時時間為 30 秒。在 舊版路徑中,會驅動即時通話行為的阻塞式請求—— telephony.incoming / web.incoming 設定交換——電話通話的首次嘗試限制為 10 秒, 包含其一次重試在內的整個交換限制為 13 秒, 而小工具工作階段則限制為 10 秒。回應緩慢會延遲 接聽通話,因此請盡量在幾秒內回應。Webhook 模式的工具調度 預設允許 20 秒,且工具宣告可設定頂層 timeout。

來源 IP

傳出 webhook 來自 ThunderPhone 的雲端 IP 範圍。 若你的防火牆需要允許清單,請聯絡支援團隊,我們將 提供目前的範圍。

在舊版與端點式 webhook 之間選擇

功能舊版 (/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)永不——僅傳送通知
最適合用於動態通話設定在正式環境中處理事件

新的整合應透過端點式 webhook 消費事件。只有在你需要於 接聽時動態設定通話,或使用 webhook 模式工具派送時,才應保留(或新增) 舊版 URL——這些請求/回應交換僅會在舊版路徑上執行。


相關資源