ThunderPhone 2.0 уже доступний.Самостійне підключення — від 2 центів за хвилину.Прочитати анонс

Webhooks

Огляд вебхуків

Як ThunderPhone надсилає події в реальному часі, як перевіряти підписи та чим відрізняються застаріла й кінцева моделі доставки.

ThunderPhone надсилає HTTP-запити POST на ваш сервер, коли під час дзвінка відбуваються певні події — починається вхідний дзвінок, завершується дзвінок, завершується запуск оцінювання, спрацьовує сповіщення тощо. Є дві моделі доставки:

Усі десять типів подій із каталогу подій доставляються через кінцеві точки вебхуків. Шість подій життєвого циклу дзвінка (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 вебхука на рівні вашої організації для застарілих доставок).

Кроки

  1. Прочитайте необроблене тіло запиту до будь-якого парсингу.
  2. Обчисліть hmac_sha256(secret, body).hexdigest().
  3. Порівняйте результат у сталий час із заголовком X-ThunderPhone-Signature.

Ми підписуємо точно ті байти, які передаємо, і ці байти є канонічною серіалізацією JSON (відсортовані ключі, компактні роздільники). Тому перевірка за необробленим тілом завжди працює — а якщо ваш фреймворк надає лише розібраний JSON, повторна серіалізація з відсортованими ключами та компактними роздільниками створює ідентичні байти. Обидва способи описано в посібнику з перевірки.

Python
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 "", 204
Node.js (Express)
import 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 + datatype + data + event_id
Ротація секретуЗамінює один секретСекрет для кожної кінцевої точки
Вимкнення без видаленняstatus=disabled
Видимість статусуactive / disabled / failing
Блокувальний обмін конфігурацієюТак (telephony.incoming / web.incoming)Ніколи — лише сповіщення
Найкраще дляДинамічної конфігурації викликівСпоживання подій у продакшні

Нові інтеграції мають отримувати події через вебхуки на основі кінцевих точок. Зберігайте (або додавайте) застарілу URL-адресу лише якщо ви динамічно налаштовуєте виклики під час прийняття або використовуєте виклик інструментів у режимі вебхуків — ці обміни запитами й відповідями працюють лише через застарілий шлях.


Пов’язані матеріали