Open in
Обзор вебхуков
Как 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 с ключом secret
конечной точки (или secret вебхука на уровне организации для устаревших
доставок):
X-ThunderPhone-Signature-V2: t=<unix seconds>,v1=<hex>подписывает метку времени, метод, путь и параметры запроса, а также необработанное тело. Проверяйте эту подпись и отклоняйте запросы, у которыхtотличается от времени на ваших часах более чем на 5 минут, чтобы перехваченный запрос нельзя было повторно отправить позже. Повторные попытки подписываются заново при отправке.X-ThunderPhone-Signatureподписывает только необработанное тело. Она по-прежнему отправляется без изменений, чтобы существующие средства проверки продолжали работать.
Шаги
- Прочитайте заголовок, метод, необработанные путь и строку запроса, а также необработанное тело до любого разбора.
- Вычислите HMAC-SHA256 в шестнадцатеричном формате для
<t>\n<METHOD>\n<path and query>\n<body>с помощью вашего секрета. - Отклоните запрос, если
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 выполняет одну синхронную попытку без повторов, за исключением обмена конфигурацией телефонного вызова telephony.incoming, который повторяется один раз.
Повторные попытки
Каждое событие немедленно отправляется один раз. Любой ответ 2xx
подтверждает доставку. При любом другом результате (не-2xx,
ошибка подключения, тайм-аут) повторные попытки выполняются через 1 мин, 5 мин, 30 мин, 2 ч, 6 ч,
12 ч и 24 ч после первой попытки — всего 8 попыток за
24 часа. Если все попытки завершатся неудачно, доставка прекращается, а конечная точка
помечается как status="failing" в разделе
конечные точки вебхуков; мы отправляем email владельцам
и администраторам вашей организации и показываем это в панели управления. Верните 2xx, как только
полезная нагрузка будет надёжно принята; обрабатывайте её асинхронно.
Тестовые отправки из тестового API конечной точки получают одну повторную попытку примерно через минуту после
первой попытки и никогда не изменяют статус конечной точки.
Порядок
Порядок доставки обеспечивается по мере возможности. На практике мы доставляем события в
порядке их создания, но повторные попытки при сбоях могут изменить порядок.
Всегда устраняйте дубликаты и сверяйте данные по call_id / идентификатору объекта.
Дубликаты
Доставка выполняется как минимум один раз: повторная попытка после ответа, который мы не
получили, может создать дубликат события. Каждая повторная попытка содержит тот же
event_id, поэтому сохраняйте обработанные идентификаторы и пропускайте повторы. event_id
также общий для конечных точек — две конечные точки, подписанные на
одно и то же событие, получают одинаковый event_id.
Тайм-ауты
Для доставок конечных точек действует тайм-аут 30 с на попытку. В
устаревшем пути блокирующие запросы, определяющие поведение активного вызова, — обмен
конфигурацией telephony.incoming / web.incoming
— ограничены 10 с для первой попытки телефонного вызова
и 13 с для всего обмена, включая одну
повторную попытку, а также 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 |
| Ротация секрета | Заменяет единственный секрет | Секрет для каждой конечной точки |
| Отключение без удаления | PUT /v1/webhook с {"url": ""} | status=disabled |
| Видимость статуса | — | active / disabled / failing |
| Блокирующий обмен конфигурацией | Да (telephony.incoming / web.incoming) | Никогда — только уведомления |
| Лучше всего подходит для | Динамической настройки звонков | Получения событий в продакшене |
Новые интеграции должны получать события через вебхуки на основе конечных точек. Сохраняйте (или добавляйте) устаревший URL, только если вы динамически настраиваете звонки в момент принятия или используете вызов инструментов в режиме вебхука — эти обмены «запрос/ответ» выполняются только по устаревшему пути.
Связанные материалы
Все типы событий и их полезные нагрузки.
Управляйте несколькими конечными точками, фильтрами событий и секретами.
Блокирующий запрос, на который ваш сервер должен ответить для настройки звонков.
Полезная нагрузка после звонка с расшифровкой, записью и метриками.