---
title: "Обзор вебхуков"
description: "Как ThunderPhone доставляет события в реальном времени, как проверять подписи и чем отличаются устаревшая и основанная на конечных точках модели доставки."
---

ThunderPhone отправляет HTTP-запросы `POST` на ваш сервер, когда во время звонка
происходят события: начинается входящий звонок, завершается звонок, завершается
запуск оценки, срабатывает оповещение и так далее. Доступны **две модели
доставки**:

<CardGroup cols={2}>
  <Card title="Конечные точки вебхуков (рекомендуется)" icon="bolt" href="/ru/webhooks/endpoints">
    Несколько URL, отдельные секреты для каждой конечной точки, фильтры событий для каждой конечной точки
    и автоматические повторные попытки.
    Управление через `GET/POST/PATCH/DELETE /v1/developer/webhook-endpoints`.
  </Card>
  <Card title="Устаревший вебхук с одним URL" icon="link" href="/api-reference/organizations#legacy-single-url-webhook">
    Один URL на организацию. Передаёт события жизненного цикла звонка, включая
    **блокирующие** обмены конфигурацией. Управляется через `GET/PUT /v1/webhook`.
  </Card>
</CardGroup>

Все перечисленные типы событий в [каталоге событий](/ru/webhooks/events)
доставляются через конечные точки вебхуков. Шесть событий жизненного цикла звонка
(`telephony.incoming`, `telephony.complete`, `telephony.tool`,
`web.incoming`, `web.complete`, `web.tool`) **также** отправляются в
устаревший вебхук с одним URL — если у вас есть и устаревший URL, и
подходящая конечная точка, вы получите событие по **обоим** путям. Блокирующее
поведение ([обмен конфигурацией `telephony.incoming` / `web.incoming`](/ru/webhooks/call-incoming)
и [вызов инструментов](/ru/tools/overview) в режиме вебхуков)
существует исключительно на устаревшем пути; каждая доставка в конечную точку
является уведомлением без ожидания ответа.

## Формат полезной нагрузки

Доставка в конечную точку представляет собой объект JSON с полями `data`, `event_id` и
`type`:

```json
{
  "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`:

```json
{
  "type": "telephony.incoming",
  "data": { "call_id": 987654321, "from_number": "+14155550199", "to_number": "+15551234567" }
}
```

При передаче по сети каждое тело сериализуется канонически — ключи сортируются
по алфавиту, без пробельных символов, в UTF-8. Примеры с форматированием в
этой документации приведены только для удобства чтения.

Полный список типов событий и полей полезной нагрузки см. в
[каталоге событий](/ru/webhooks/events).

## Проверка подписи

Каждый запрос содержит две подписи, обе HMAC-SHA256 с ключом `secret`
конечной точки (или `secret` вебхука на уровне организации для устаревших
доставок):

- `X-ThunderPhone-Signature-V2: t=<unix seconds>,v1=<hex>` подписывает
  метку времени, метод, путь и параметры запроса, а также необработанное тело. Проверяйте
  эту подпись и отклоняйте запросы, у которых `t` отличается от времени на ваших
  часах более чем на **5 минут**, чтобы перехваченный запрос нельзя было повторно
  отправить позже. Повторные попытки подписываются заново при отправке.
- `X-ThunderPhone-Signature` подписывает только необработанное тело. Она по-прежнему отправляется
  без изменений, чтобы существующие средства проверки продолжали работать.

### Шаги

1. Прочитайте заголовок, метод, необработанные путь и строку запроса, а также необработанное
   тело **до** любого разбора.
2. Вычислите HMAC-SHA256 в шестнадцатеричном формате для
   `<t>\n<METHOD>\n<path and query>\n<body>` с помощью вашего секрета.
3. Отклоните запрос, если `t` отличается более чем на 300 секунд или если
   результат не совпадает с `v1` при сравнении за постоянное время.

Тела представляют собой каноническую сериализацию JSON (отсортированные ключи, компактные
разделители), и мы подписываем в точности те байты, которые передаём. В
[руководстве по проверке](/ru/guides/verify-webhook-signatures) приведены точная
подписываемая строка, рабочий пример, код без SDK и устаревший
заголовок.

<CodeGroup>
```python 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
```

```javascript 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);
  },
);
```
</CodeGroup>

## Семантика доставки

Эта семантика применяется к доставкам **конечных точек**. Устаревший вебхук с одним URL выполняет одну синхронную попытку без повторов, за исключением обмена конфигурацией телефонного вызова [`telephony.incoming`](/ru/webhooks/call-incoming), который повторяется один раз.

<AccordionGroup>
  <Accordion title="Повторные попытки">
    Каждое событие немедленно отправляется один раз. Любой ответ `2xx`
    подтверждает доставку. При любом другом результате (не-2xx,
    ошибка подключения, тайм-аут) повторные попытки выполняются через **1 мин, 5 мин, 30 мин, 2 ч, 6 ч,
    12 ч и 24 ч после первой попытки** — всего 8 попыток за
    24 часа. Если все попытки завершатся неудачно, доставка прекращается, а конечная точка
    помечается как `status="failing"` в разделе
    [конечные точки вебхуков](/ru/webhooks/endpoints); мы отправляем email владельцам
    и администраторам вашей организации и показываем это в панели управления. Верните `2xx`, как только
    полезная нагрузка будет надёжно принята; обрабатывайте её асинхронно.
    Тестовые отправки из тестового API конечной точки получают одну повторную попытку примерно через минуту после
    первой попытки и никогда не изменяют статус конечной точки.
  </Accordion>

  <Accordion title="Порядок">
    Порядок доставки обеспечивается по мере возможности. На практике мы доставляем события в
    порядке их создания, но повторные попытки при сбоях могут изменить порядок.
    Всегда устраняйте дубликаты и сверяйте данные по `call_id` / идентификатору объекта.
  </Accordion>

  <Accordion title="Дубликаты">
    Доставка выполняется **как минимум один раз**: повторная попытка после ответа, который мы не
    получили, может создать дубликат события. Каждая повторная попытка содержит тот же
    `event_id`, поэтому сохраняйте обработанные идентификаторы и пропускайте повторы. `event_id`
    также общий для конечных точек — две конечные точки, подписанные на
    одно и то же событие, получают одинаковый `event_id`.
  </Accordion>

  <Accordion title="Тайм-ауты">
    Для доставок конечных точек действует тайм-аут **30 с** на попытку. В
    устаревшем пути блокирующие запросы, определяющие поведение активного вызова, — обмен
    конфигурацией [`telephony.incoming` / `web.incoming`](/ru/webhooks/call-incoming)
    — ограничены **10 с** для первой попытки телефонного вызова
    и **13 с** для всего обмена, включая одну
    повторную попытку, а также **10 с** для сеанса виджета. Медленный ответ задерживает
    принятие вызова, поэтому старайтесь отвечать в течение нескольких секунд. [Вызов инструмента](/ru/tools/overview) в режиме вебхуков
    по умолчанию допускает 20 с, а объявления инструментов могут задавать `timeout` верхнего уровня.
  </Accordion>

  <Accordion title="Исходные IP-адреса">
    Исходящие вебхуки отправляются из диапазона облачных IP-адресов ThunderPhone.
    Если ваш межсетевой экран требует списка разрешённых адресов, обратитесь в поддержку, и мы
    предоставим актуальные диапазоны.
  </Accordion>
</AccordionGroup>

## Выбор между устаревшими вебхуками и вебхуками на основе конечных точек

| Возможность | Устаревшие (`/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`](/ru/webhooks/call-incoming)) | Никогда — только уведомления |
| Лучше всего подходит для | Динамической настройки звонков | Получения событий в продакшене |

Новые интеграции должны получать события через вебхуки на основе конечных точек.
Сохраняйте (или добавляйте) устаревший URL, только если вы динамически настраиваете звонки
в момент принятия или используете вызов инструментов в режиме вебхука — эти
обмены «запрос/ответ» выполняются только по устаревшему пути.

---

## Связанные материалы

<CardGroup cols={2}>
  <Card title="Каталог событий" icon="list" href="/ru/webhooks/events">
    Все типы событий и их полезные нагрузки.
  </Card>
  <Card title="Конечные точки вебхуков" icon="bolt" href="/ru/webhooks/endpoints">
    Управляйте несколькими конечными точками, фильтрами событий и секретами.
  </Card>
  <Card title="telephony.incoming / web.incoming" icon="phone" href="/ru/webhooks/call-incoming">
    Блокирующий запрос, на который ваш сервер должен ответить для настройки звонков.
  </Card>
  <Card title="telephony.complete / web.complete" icon="phone" href="/ru/webhooks/call-complete">
    Полезная нагрузка после звонка с расшифровкой, записью и метриками.
  </Card>
</CardGroup>
