---
title: "telephony.incoming / web.incoming"
description: "Блокирующий вебхук, который в реальном времени задаёт конфигурацию входящего звонка."
---

Когда входящий телефонный звонок поступает на номер **без назначенного
агента**, или сеанс веб-виджета запускается с публикуемым ключом в
`mode="webhook"`, ThunderPhone отправляет **блокирующий**
запрос `telephony.incoming` / `web.incoming` на ваш
[устаревший URL вебхука](/api-reference/organizations#legacy-single-url-webhook)
и ожидает ответ с конфигурацией: до **13 секунд** для телефонного
звонка, включая одну повторную попытку, и до **10 секунд** для сеанса
виджета (см. [Тайм-ауты и повторные попытки](#timeouts-and-retries)). Используйте этот
обмен, чтобы динамически выбирать промпт, голос и инструменты для каждого звонка —
полный подход описан в [руководстве по динамической конфигурации звонков](/ru/guides/dynamic-call-config).

<Note>
  Подписанные [конечные точки вебхуков](/ru/webhooks/endpoints) также получают
  `telephony.incoming` / `web.incoming` — для **каждого** входящего звонка
  и веб-сеанса, независимо от того, настроен ли агент, — однако эти доставки
  являются уведомлениями без ожидания ответа с `event_id` и никогда не блокируют
  выполнение. Только устаревший вебхук с единым URL передаёт обмен конфигурацией,
  описанный на этой странице. Форматы уведомлений конечных точек приведены в
  [каталоге событий](/ru/webhooks/events).
</Note>

## Тайм-ауты и повторные попытки

Отвечайте быстро: звонящий ждёт на линии, пока вы принимаете решение.

Для телефонного звонка (`telephony.incoming`):

- Первая попытка может занять до **10 секунд**.
- Если истекает время ожидания, не удаётся подключиться или получен
  ответ `5xx`, `408` или `429`, ThunderPhone делает ещё одну попытку. На повторную попытку отводится всё, что
  остаётся от **13-секундного** бюджета на весь обмен: около 3 секунд после
  первой попытки, завершившейся по тайм-ауту, и почти все 13 секунд после быстрого сбоя.
  Повторная попытка не выполняется, если остаётся менее 2 секунд.
- Любой другой ответ `4xx`, ответ сверх
  [ограничения размера](#response-size-limit) или `2xx`, тело которого не является
  действительной конфигурацией, считается окончательным и не повторяется.
- Обе попытки отправляют одно и то же тело, один и тот же `X-ThunderPhone-Signature`,
  и один и тот же заголовок `X-ThunderPhone-Idempotency-Key`
  (`telephony-incoming-<call_id>`), поэтому обработчик с побочными эффектами
  может распознать повторную попытку.

Резервного агента нет. Если пригодная конфигурация не поступает,
звонок немедленно завершается, вместо того чтобы оставлять звонящего на тихой
линии. В истории звонков он отображается как неуспешный звонок без тарификации с
`end_reason` `inbound_config_webhook_failed`, и отправляется обычное событие
[`telephony.complete`](/ru/webhooks/call-complete).

Сессия виджета (`web.incoming`) получает одну попытку длительностью до 10
секунд. Если она завершается ошибкой, запрос сессии завершается с `502` или `422`.

<Warning>
  **Звонки, настроенные через вебхук, не содержат уведомления ThunderPhone
  о согласии.** Звонки, настроенные через этот обмен, обходят уведомление
  агента в начале звонка и явно исключены из механизма уведомлений ThunderPhone
  о согласии (Условия обслуживания, раздел «Запись и согласие»). Ваша организация
  несёт исключительную ответственность за все уведомления и согласия на запись,
  мониторинг, участие ИИ и идентификацию звонящего, требуемые для этих звонков —
  звонки по-прежнему могут записываться, расшифровываться, анализироваться и
  обслуживаться ИИ. Включите необходимые уведомления в собственный сценарий
  звонка, прежде чем включать этот путь.
</Warning>

## Полезная нагрузка запроса

Для телефонных звонков (`telephony.incoming`):

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

| Поле | Тип | Описание |
|-------|------|-------------|
| `call_id` | integer | Идентификатор звонка — неизменен во всех событиях этого звонка |
| `from_number` | string | Номер звонящего в формате E.164 |
| `to_number` | string | Адресат в формате E.164 (один из ваших номеров ThunderPhone) |

Для сеансов веб-виджета (`web.incoming`) объект `data` определяет
встраивающую страницу вместо телефонных номеров:

```json
{
  "type": "web.incoming",
  "data": {
    "call_id": 987654322,
    "origin_domain": "https://example.com",
    "publishable_key_prefix": "pk_live_a1b2"
  }
}
```

| Поле | Тип | Описание |
|-------|------|-------------|
| `call_id` | integer | Идентификатор звонка |
| `origin_domain` | string | Источник страницы, на которой размещён виджет, в том виде, в котором его передал браузер. Небраузерный клиент может отправить любое значение, поэтому не считайте его подтверждением того, откуда поступил запрос |
| `publishable_key_prefix` | string | Первые символы публикуемого ключа, открывшего сессию |
| `language`, `primary_language` | string | Присутствует, когда сессия виджета запросила переопределение языка |
| `voice` | string | Присутствует, когда сессия виджета запросила переопределение голоса |
| `website_context` | string | Присутствует, когда виджет передал контекст страницы для конкретной сессии |

Поля переопределения (`language`, `voice`, `website_context` и любые
браузерные `variables`) передаются только тогда, когда публикуемый ключ разрешает их в
своих [переопределениях браузера](/ru/guides/embed-web-widget). Запрос
сессии, содержащий параметр, который ключ не разрешает, отклоняется до вызова
этого вебхука.

<Note>
  Виджеты в режиме вебхука отправляют этот запрос на собственный
  `webhook_url` публикуемого ключа, если он задан, иначе используют URL
  вебхука на уровне организации. В обоих случаях запрос подписывается
  `secret` вебхука организации.
</Note>

---

## Схема ответа

Верните объект JSON, описывающий конфигурацию агента для этого звонка.
`prompt` и `voice` обязательны; всё остальное необязательно.

```json
{
  "prompt":  "You are a helpful booking assistant for Acme Restaurant.",
  "voice":   "john",
  "product": "spark",
  "background_track": null,
  "tools":   []
}
```

| Поле | Тип | Обязательное | Описание |
|-------|------|----------|-------------|
| `prompt` | строка | да | Системный промпт, управляющий агентом |
| `voice` | строка | да | Идентификатор голоса из [`GET /v1/voices`](/api-reference/agents#voices), например `john`. `voice_name` принимается как псевдоним. Неизвестные голоса не проходят проверку и отклоняют вызов |
| `product` | строка | нет | По умолчанию — `spark`. Допустимые значения: `spark`, `bolt`, `storm-base`, `storm-base-with-ack`, `storm-extra`, `storm-extra-with-ack` |
| `thinking_level` | строка | нет | `minimal`, `base` (по умолчанию) или `extra`. Для продуктов Storm значение переопределяется: `storm-extra*` принудительно устанавливает `extra`, остальные `storm-*` — `base` |
| `audio_context_mode` | строка | нет | `full` (по умолчанию) или `reduced` |
| `watchdog_enabled` | логическое значение | нет | Включает мониторинг этого вызова. По умолчанию — `false` |
| `additional_audio_context` | логическое значение \| null | нет | Включает несколько последних реплик аудио звонящего вместо только самой последней, улучшая исправления и сбор данных с большим количеством букв и цифр при небольшом увеличении задержки и стоимости. По умолчанию включено для входящих сессий и выключено для исходящих телефонных вызовов; `null` сохраняет значение по умолчанию |
| `storm_feedback_mode` | строка | нет | `none`, `acknowledgement` (по умолчанию) или `tick` |
| `language` | строка | нет | Сокращение для `primary_language` |
| `primary_language` | строка | нет | Код языка в нормализованном виде (по умолчанию `en`). Нераспознаваемые коды отклоняют вызов |
| `has_additional_languages` | логическое значение | нет | По умолчанию — `false` |
| `additional_languages` | массив строк | нет | Дополнительные языки, на которые агент может переключаться |
| `native_voice_switching` | логическое значение | нет | По умолчанию — `false`. Когда вызов переключается на другой язык, используется голос, для которого этот язык является родным (с учётом пола), вместо сохранения настроенного голоса |
| `language_switching` | строка \| null | нет | `ask_first` (по умолчанию) или `follow_caller`; `null` равнозначен `ask_first`. Определяет, как многоязычный агент реагирует, когда собеседник говорит на другом из поддерживаемых им языков. `ask_first` сразу переключается, когда собеседник просит использовать язык или произносит на нём полное предложение, и спрашивает, следует ли продолжить на этом языке, после чего-то короткого, например «sí». `follow_caller` переключается на любой настроенный язык, на котором говорит собеседник, даже после одного слова. Применяется только при заданном `additional_languages` |
| `background_track` | строка \| null | нет | Идентификатор фонового аудио или `null` |
| `acknowledgement_prompt_mode` | строка | нет | `auto` (по умолчанию) или `manual` (для продуктов Storm-with-ack) |
| `acknowledgement_prompt` | строка | нет | Используется, когда `acknowledgement_prompt_mode="manual"` |
| `silence_interval_seconds` | целое число \| null | нет | 5–120. Количество секунд тишины со стороны звонящего перед проверочным сообщением |
| `silence_max_checkins` | целое число \| null | нет | 1–10 |
| `silence_checkins_enabled` | логическое значение | нет | По умолчанию — `true` |
| `connect_tone_enabled` | логическое значение | нет | По умолчанию — `false` |
| `pronunciation_dicts` | логическое значение \| null | нет | По умолчанию — `false`; `null` равнозначен `false`. Если установлено `true`, речь Cartesia в вызове использует словарь произношения ThunderPhone для языка вызова, если такой словарь существует. Работает для телефонных вызовов и вызовов через виджет. Применяется только к собственным голосам Cartesia ThunderPhone, а не к голосам на вашем собственном ключе API Cartesia. Не используется с `agent_id`, для которого применяются настройки сохранённого агента |
| `voicemail_action` | строка | нет | `prompt` (по умолчанию), `hangup` или `message` |
| `voicemail_message` | строка | нет | Используется, когда `voicemail_action="message"` |
| `agent_name` | строка | нет | Отображаемое имя, передаваемое в дашборды и виджет |
| `org_name` | строка | нет | Отображаемое название организации для персоны агента |
| `tools` | массив | нет | Встроенные схемы инструментов-функций (см. [Инструменты-функции](/ru/tools/overview)) |
| `transfer_destinations` | массив объектов \| null | нет | До 50 записей `{name, number}`, на которые этот вызов может быть переведён; они проверяются так же, как [номера для перевода](/api-reference/agents#transfer-numbers) агента. Агент переводит по имени и только на эти номера; `[]` означает отсутствие переводов. Не указывайте поле или отправьте `null`, чтобы агент мог перевести на выбранный им номер |
| `timezone` | строка | нет | Часовой пояс IANA для этого вызова, например `America/Chicago`. Агент сообщает дату и время в этом часовом поясе, а не в собственном часовом поясе агента. Также допускается рядом с `agent_id`. Значения, не являющиеся часовыми поясами IANA, игнорируются. См. [Настройка часового пояса для каждого вызова](/ru/guides/dynamic-call-config) |
| `call_id` | целое число | нет | Необязательное эхо идентификатора вызова из запроса; игнорируется |

<Note>
  Неизвестные ключи верхнего уровня молча **игнорируются** — поле с
  опечаткой в имени не отклоняет конфигурацию, оно просто не применяется.
  Порядок произнесения и `max_hold_seconds` здесь не принимаются; их можно
  настроить только у самого [агента](/api-reference/agents).
</Note>

Поскольку `prompt` и `voice` обязательны, возврат `{}` или любого
ответа, не прошедшего проверку, отклоняет вызов: телефонный звонок завершается
согласно описанию в [Тайм-аутах и повторных попытках](#timeouts-and-retries), а
запрос сеанса виджета завершается ошибкой `422`. Для этого пути нет резервного
статического агента (у номера или ключа в режиме вебхука нет назначенного
агента).

---

## Ограничение размера ответа

<Warning>
  Размер ответов конфигурации ограничен **5 МиБ**. Если обработчик
  возвращает ответ большего размера, в том числе со статусом `2xx`,
  ThunderPhone сообщает, что ответ превысил лимит, и
  отклоняет вызов или сеанс виджета. Оставляйте в ответе только поля,
  необходимые для настройки вызова; размещайте большие объёмы данных в инструментах функций или
  другом сервисе вместо встраивания их в конфигурацию.
</Warning>

---

## Пример обработчика

<CodeGroup>
```python Python (FastAPI)
import hashlib
import hmac
import json
import os

from fastapi import FastAPI, HTTPException, Request

app = FastAPI()
WEBHOOK_SECRET = os.environ["THUNDERPHONE_WEBHOOK_SECRET"]

def verify(body: bytes, signature: str) -> bool:
    expected = hmac.new(WEBHOOK_SECRET.encode(), body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature or "")

@app.post("/thunderphone-webhook")
async def webhook(request: Request):
    body = await request.body()
    if not verify(body, request.headers.get("X-ThunderPhone-Signature", "")):
        raise HTTPException(status_code=401)

    event = json.loads(body)
    if event["type"] == "telephony.incoming":
        caller = event["data"]["from_number"]
        prompt = (
            "Greet the caller as a San Francisco local…"
            if caller.startswith("+1415")
            else "You are a friendly customer support agent…"
        )
        return {
            "prompt": prompt,
            "voice": "john",
            "product": "spark",
        }
    if event["type"] == "web.incoming":
        return {
            "prompt": "You are the website's helpful voice assistant…",
            "voice": "john",
            "product": "spark",
        }
    return {}
```

```javascript Node.js (Express)
import crypto from "node:crypto";
import express from "express";

const app = express();
const SECRET = process.env.THUNDERPHONE_WEBHOOK_SECRET;

function verify(body, signature) {
  const expected = crypto
    .createHmac("sha256", SECRET)
    .update(body)
    .digest("hex");
  return signature &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}

app.post(
  "/thunderphone-webhook",
  express.raw({ type: "application/json" }),
  (req, res) => {
    if (!verify(req.body, req.header("X-ThunderPhone-Signature"))) {
      return res.sendStatus(401);
    }
    const event = JSON.parse(req.body.toString("utf8"));

    if (event.type === "telephony.incoming" || event.type === "web.incoming") {
      const caller = event.data.from_number || "web";
      const prompt = caller.startsWith("+1415")
        ? "Greet the caller as a San Francisco local…"
        : "You are a friendly customer support agent…";
      return res.json({
        prompt,
        voice: "john",
        product: "spark",
      });
    }
    res.json({});
  },
);
```
</CodeGroup>

---

## Ответ с инструментами функций

Добавьте инструменты, чтобы ИИ мог вызывать ваши API во время разговора:

```json
{
  "prompt":  "You are a booking assistant. Use the available tools to help customers schedule appointments.",
  "voice":   "john",
  "product": "spark",
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "search_appointments",
        "description": "Find available appointment slots",
        "parameters": {
          "type": "object",
          "properties": {
            "date": { "type": "string", "description": "YYYY-MM-DD" },
            "service": { "type": "string" }
          },
          "required": ["date"]
        }
      },
      "endpoint": {
        "url": "https://api.example.com/appointments/search",
        "method": "POST",
        "headers": {
          "X-Api-Key": "your-key"
        }
      }
    }
  ]
}
```

<Tip>
  Запросы к конечным точкам инструментов подписываются тем же **секретом
  вебхука организации**, которым подписан этот обмен. Точное
  описание структуры и формата подписанного запроса см. в разделе
  [Инструменты функций](/ru/tools/overview).
</Tip>

---

## Краткая памятка по тарифам продуктов

| Продукт | Задержка | Рассуждение | Подтверждение |
|---------|----------|-------------|---------------|
| `spark` | Минимальная | Базовое | — |
| `bolt` | Низкая | Улучшенное | — |
| `storm-base` | Средняя | Сильное | — |
| `storm-base-with-ack` | Средняя | Сильное | Автоматическая фраза-заполнитель во время размышления |
| `storm-extra` | Более высокая | Глубокое | — |
| `storm-extra-with-ack` | Более высокая | Глубокое | Автоматическая фраза-заполнитель во время размышления |

---

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

<CardGroup cols={2}>
  <Card title="telephony.complete / web.complete" icon="phone-slash" href="/ru/webhooks/call-complete">
    Неблокирующее событие завершения звонка.
  </Card>
  <Card title="Инструменты функций" icon="screwdriver-wrench" href="/ru/tools/overview">
    Полная JSON-схема для `tools[]` и контракт подписанного конечного адреса.
  </Card>
  <Card title="Конечные адреса вебхуков" icon="bolt" href="/ru/webhooks/endpoints">
    Подпишите несколько URL на `telephony.incoming` / `web.incoming`.
  </Card>
  <Card title="Динамическая конфигурация звонка" icon="wand-magic-sparkles" href="/ru/guides/dynamic-call-config">
    Шаблоны для промптов, инструментов и A/B-тестов для каждого звонящего.
  </Card>
</CardGroup>
