Огляд вебхуків
Як ThunderPhone надсилає події в реальному часі, як перевіряти підписи та чим відрізняються застаріла й кінцева моделі доставки.
ThunderPhone надсилає HTTP-запити POST на ваш сервер, коли під час дзвінка
відбуваються певні події — починається вхідний дзвінок, завершується дзвінок,
завершується запуск оцінювання, спрацьовує сповіщення тощо. Є дві моделі
доставки:
Кілька URL-адрес, секрети для кожної кінцевої точки, фільтри подій для кожної кінцевої точки
та автоматичні повторні спроби.
Керуйте через GET/POST/PATCH/DELETE /v1/developer/webhook-endpoints.
Одна URL-адреса для кожної організації. Передає події життєвого циклу дзвінка, зокрема
блокувальні обміни конфігурацією. Керується через GET/PUT /v1/webhook.
Усі десять типів подій із каталогу подій
доставляються через кінцеві точки вебхуків. Шість подій життєвого циклу дзвінка
(telephony.incoming, telephony.complete, telephony.tool,
web.incoming, web.complete, web.tool) також надсилаються до
застарілого вебхука з одним URL — якщо у вас є і застарілий URL, і відповідна
кінцева точка, ви отримаєте подію обома шляхами. Блокувальна
поведінка (обмін конфігурацією
telephony.incoming / web.incoming та
виклик інструментів у режимі вебхуків)
доступна лише за застарілим шляхом; кожна доставка до кінцевої точки є
сповіщенням без очікування відповіді.
Формат корисного навантаження
Доставки до кінцевих точок є 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 надсилає ті самі type і data, але
без event_id:
{
"type": "telephony.incoming",
"data": { "call_id": 987654321, "from_number": "+14155550199", "to_number": "+15551234567" }
}Під час передавання кожне тіло серіалізується канонічно — ключі відсортовано за алфавітом, без пробілів, UTF-8. Відформатовані приклади в цій документації наведено лише для зручності читання.
Перегляньте каталог подій, щоб отримати повний список типів подій і полів корисного навантаження.
Перевірка підпису
Кожен запит містить підпис HMAC-SHA256 для необробленого тіла
запиту в заголовку X-ThunderPhone-Signature. Ключем підпису є
secret кінцевої точки (або 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-адресою виконує одну синхронну спробу без повторних спроб.
Повторні спроби
Кожна подія негайно надсилається один раз. Будь-яка відповідь 2xx
підтверджує доставку. За будь-якого іншого результату (не-2xx,
помилка з’єднання, перевищення часу очікування) ми повторюємо спробу через 1 хв, 5 хв, 30 хв, 2 год, 6 год,
12 год і 24 год після першої спроби — 8 спроб протягом
24 годин. Якщо всі спроби завершаться невдачею, доставка припиняється, а кінцева точка
позначається як status="failing" у
кінцевих точках вебхуків. Поверніть 2xx, щойно
корисне навантаження буде надійно прийнято; обробляйте його асинхронно.
Порядок
Порядок доставки забезпечується за можливості. На практиці ми доставляємо події в
порядку їх створення, але повторні спроби можуть змінити порядок у разі помилки.
Завжди усувайте дублікати та звіряйте дані за call_id / ідентифікатором об’єкта.
Дублікати
Доставка здійснюється щонайменше один раз: повторна спроба після відповіді, яку ми не
отримали, може дублювати подію. Кожна повторна спроба має той самий
event_id, тому зберігайте ідентифікатори оброблених подій і пропускайте повтори. event_id
також спільний для кінцевих точок — дві кінцеві точки, підписані на
ту саму подію, отримають однаковий event_id.
Час очікування
Доставка до кінцевих точок має час очікування 30 с на кожну спробу. У
застарілому шляху блокувальні запити, що визначають поведінку активного виклику —
обмін конфігурацією telephony.incoming / web.incoming —
завершуються за 10 с, але повільна відповідь затримує прийняття виклику, тому
намагайтеся відповідати протягом кількох секунд. Виклик інструментів у режимі вебхуків дозволяє 20 с
за замовчуванням, а оголошення інструментів можуть установлювати timeout на верхньому рівні.
Вихідні IP-адреси
Вихідні вебхуки надсилаються з діапазону хмарних IP-адрес ThunderPhone. Якщо ваш брандмауер потребує списку дозволених адрес, зверніться до служби підтримки, і ми надамо актуальні діапазони.
Вибір між застарілими вебхуками та вебхуками на основі кінцевих точок
| Можливість | Застарілий (/v1/webhook) | Кінцеві точки (/v1/developer/webhook-endpoints) |
|---|---|---|
| Кількість URL-адрес | 1 на організацію | Багато на організацію |
| Охоплення подій | Лише telephony.* / web.* | Усі 10 типів подій |
| Фільтр подій | — | Для кожної кінцевої точки |
| Повторні спроби | Немає | 8 спроб протягом 24 год |
| Конверт | type + data | type + data + event_id |
| Ротація секрету | Замінює один секрет | Секрет для кожної кінцевої точки |
| Вимкнення без видалення | — | status=disabled |
| Видимість статусу | — | active / disabled / failing |
| Блокувальний обмін конфігурацією | Так (telephony.incoming / web.incoming) | Ніколи — лише сповіщення |
| Найкраще для | Динамічної конфігурації викликів | Споживання подій у продакшні |
Нові інтеграції мають отримувати події через вебхуки на основі кінцевих точок. Зберігайте (або додавайте) застарілу URL-адресу лише якщо ви динамічно налаштовуєте виклики під час прийняття або використовуєте виклик інструментів у режимі вебхуків — ці обміни запитами й відповідями працюють лише через застарілий шлях.
Пов’язані матеріали
Усі типи подій і їхні корисні навантаження.
Керуйте кількома кінцевими точками, фільтрами подій і секретами.
Блокувальний запит, на який ваш сервер має відповісти для налаштування викликів.
Корисне навантаження після виклику з транскриптом, записом і метриками.