---
title: "Динамічна конфігурація для кожного дзвінка"
description: "Вибирайте агента, який відповідатиме, або змінюйте його промпт і налаштування окремо для кожного вхідного дзвінка, керуючись власною логікою у вебхуку, який ви контролюєте."
---

За замовчуванням за кожним номером телефону та публічним ключем закріплено статичного агента. Якщо вам потрібне налаштування **для кожного абонента** або **для кожного відвідувача** — VIP-маршрутизація, контекст авторизованого користувача, A/B-тести промптів — перейдіть у режим вебхуків і дозвольте серверу ухвалювати рішення.

## Як це працює

1. Ви підписуєтеся на подію [`telephony.incoming`](/uk/webhooks/events)
   (телефон) або [`web.incoming`](/uk/webhooks/events) (віджет).
   Обидва вебхуки є **блокувальними**: ThunderPhone очікує на вашу
   відповідь, перш ніж продовжити виклик, до 13 секунд для телефонного
   виклику (включно з однією повторною спробою) і 10 секунд для сеансу віджета.
2. ThunderPhone надсилає вам `{call_id, from_number, to_number}` (сеанси віджета
   містять специфічні для віджета поля замість номерів — див. [схему
   запиту](/uk/webhooks/call-incoming)).
3. Ваш сервер відповідає конфігурацією агента (промпт, голос,
   продукт, інструменти). ThunderPhone використовує цю конфігурацію для виклику.
4. Резервного агента немає. Якщо ви повертаєте `{}` або недійсну
   конфігурацію, або запит усе ще не виконується після
   [повторної спроби](/uk/webhooks/call-incoming), телефонний
   виклик негайно завершується та записується з `end_reason`
   `inbound_config_webhook_failed`; запит на сеанс віджета завершується помилкою.

<Note>
  Однаково працює для телефонних дзвінків (`telephony.incoming`) і сеансів
  віджета (`web.incoming`), незалежно від того, чи доставляються вони до кінцевої
  точки вебхука або до застарілого вебхука з однією URL-адресою.
</Note>

<Warning>
  **Для дзвінків, налаштованих через вбудований вебхук, не відтворюється
  повідомлення ThunderPhone про згоду.** Цей шлях оминає оголошення на
  початку дзвінка на рівні агента та прямо виключений із системи
  повідомлень ThunderPhone про згоду (Умови надання послуг, розділ
  «Записування та згода»). Ваша організація відповідає за всі повідомлення
  та згоди щодо записування, моніторингу, участі ШІ й ідентифікації
  абонента в цих дзвінках. Надайте їх у власному сценарії — наприклад, у
  вступному сценарії промпту — перед увімкненням режиму вбудованого вебхука.
  Відповідь вебхука, що посилається на збережений `agent_id`, використовує
  звичайну політику цього агента щодо записування та розкриття інформації.
</Warning>

## 1. Налаштуйте призначення вебхука

<Tabs>
<Tab title="Телефонні дзвінки">
Для телефонних номерів підпишіть свою кінцеву точку на `telephony.incoming`:

```bash
curl -X POST https://api.thunderphone.com/v1/developer/webhook-endpoints \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "label":  "Prod call-incoming",
    "url":    "https://example.com/thunderphone/incoming",
    "events": ["telephony.incoming"]
  }'
```

Відповідь містить одноразовий `secret` — збережіть його; ви використаєте його
для перевірки підпису.
</Tab>
<Tab title="Вебвіджет">
Для сеансів віджета створіть публічний ключ у `mode="webhook"`
із вбудованою URL-адресою вашої кінцевої точки:

```bash
curl -X POST https://api.thunderphone.com/v1/publishable-key \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name":            "Dynamic widget",
    "mode":            "webhook",
    "webhook_url":     "https://example.com/thunderphone/widget-incoming",
    "allowed_domains": ["example.com"]
  }'
```

Віджет надсилатиме POST-запит на цю URL-адресу на початку кожного сеансу.
</Tab>
</Tabs>

## 2. Реалізуйте обробник

Три практичні правила:

- **Перевіряйте підпис** у кожному запиті (див.
  [Перевірка підписів вебхуків](/uk/guides/verify-webhook-signatures)).
  Не пропускайте це в розробці — налаштуйте один раз і використовуйте повторно.
- **Відповідайте швидко**. Перша спроба телефонного дзвінка переривається через 10
  секунд, а весь обмін — через 13, і кожна секунда для абонента — це тиша.
  За потреби виконуйте пошук у базі даних, але не викликайте залежні LLM синхронно —
  якщо вам потрібне динамічне генерування промптів, попередньо обчислюйте та кешуйте їх.
- **Коректно використовуйте резервний варіант**. Для будь-якого неочікуваного стану відповідайте
  стандартними `prompt` і `voice` (або збереженим `agent_id`). Повернення `{}`
  або статусу помилки завершує дзвінок.

<CodeGroup>
```python FastAPI
import hashlib
import hmac
import json
import os

from fastapi import FastAPI, HTTPException, Request

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

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

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

    event = json.loads(body)
    if event["type"] not in ("telephony.incoming", "web.incoming"):
        return {}  # fall back to default

    caller = event["data"]["from_number"]
    # Cheap DB lookup: is this a known VIP?
    customer = lookup_customer(caller)
    if customer and customer.tier == "vip":
        return {
            "prompt":  f"You are a VIP concierge for {customer.name}. Be proactive…",
            "voice":   "john",
            "product": "storm-base",
        }
    return {}  # default agent handles non-VIPs

def lookup_customer(phone: str):
    # ... your CRM integration ...
    pass
```

```javascript Express
import crypto from "node:crypto";
import express from "express";

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

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

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

    const IMPORTANT_TYPES = new Set([
      "telephony.incoming",
      "web.incoming",
    ]);
    if (!IMPORTANT_TYPES.has(event.type)) return res.json({});

    const customer = await lookupCustomer(event.data.from_number);
    if (customer?.tier === "vip") {
      return res.json({
        prompt:  `You are a VIP concierge for ${customer.name}. Be proactive…`,
        voice:   "john",
        product: "storm-base",
      });
    }
    res.json({}); // fall back to default agent
  },
);
```
</CodeGroup>

## 3. Схема відповіді

Тіло відповіді точно відповідає
[схемі відповіді вхідного виклику](/uk/webhooks/call-incoming).
Поширені поля:

| Поле | Тип | Опис |
|-------|------|-------------|
| `prompt` | рядок (обов’язково) | Системний промпт для агента |
| `voice` | рядок (обов’язково) | Ідентифікатор голосу з [`GET /v1/voices`](/api-reference/agents#voices) |
| `product` | рядок | За замовчуванням — `spark`. Експериментальні продукти доступні лише для організацій із дозволом. |
| `background_track` | рядок \| null | Ідентифікатор фонового аудіо |
| `acknowledgement_prompt_mode` | рядок | `auto` або `manual` (лише Storm з підтвердженням) |
| `acknowledgement_prompt` | рядок | Обов’язково, якщо вибрано режим `manual` |
| `tools` | масив | Вбудовані схеми інструментів-функцій — див. [Інструменти-функції](/uk/tools/overview) |

<Note>
  Порядок озвучення для окремого виклику та `max_hold_seconds` недоступні у
  відповіді вебхука. Налаштуйте їх для
  [агента](/api-reference/agents), на якого посилаєтеся.
</Note>

### Використовуйте збереженого агента та передавайте змінні

Поверніть `{"agent_id": 12, "variables": {"name": "Ada"}}`, щоб використати
збереженого агента цієї організації з даними для кожного виклику. Його промпт може містити `{{name}}` або
`{{name|Friend}}`. Змінні вебхука об’єднуються зі змінними рівня запиту; значення null
використовує значення за замовчуванням для заповнювача або порожній текст, якщо його не вказано. Остаточні
значення та нерозв’язані імена відображаються в деталях виклику й вебхуках завершення.
Відповіді для збереженого агента приймають лише `agent_id`, `variables` і `timezone`. Якщо `prompt`
наявний, відповідь використовує вбудовану конфігурацію та ігнорує `agent_id` (зокрема
метадані зі значенням null або нецілим числом); вбудований промпт усе одно має бути дійсним. Відповіді з вбудованою
конфігурацією також можуть містити `variables`. Відповіді для збереженого агента використовують розгорнутий для агента A/B-розподіл як для телефонних викликів, так і для викликів віджета, а потім підставляють змінні. Перегляньте [змінні виклику](/uk/guides/call-variables),
щоб дізнатися про обмеження та підтримку API сесій. Блокувальна конфігурація надходить із застарілої
URL-адреси номера телефону/організації або ключа віджета в режимі вебхука; вхідні події системи кінцевих точок є лише сповіщеннями.

### Установлення часового поясу для кожного дзвінка

Один агент може відповідати контактам у різних часових поясах. Додайте `timezone`, назву IANA, до збереженого агента або вбудованої відповіді, і агент називатиме дату й час та пропонуватиме час у цьому поясі для цього дзвінка, незалежно від власного часового поясу агента:

```json
{"agent_id": 12, "timezone": "America/Chicago", "variables": {"name": "Ada"}}
```

Передавайте часовий пояс, а не часову позначку, у `variables`: годинник агента продовжує працювати під час дзвінка, а переданий час застаріває. Значення, яке не є часовим поясом IANA, ігнорується, і дзвінок з’єднується в часовому поясі агента.

## Шаблони

### Контекст авторизованого користувача

У віджетах у режимі вебхука сторінка відвідувача вже знає, ким він
є. Викличте свій вебхук із параметром рядка запиту, який передає SDK віджета
(`?customer_id=123`), і знайдіть клієнта на сервері.

### A/B-розгортання промптів

Перш ніж реалізовувати це вручну, зверніть увагу, що ThunderPhone має вбудовану
функцію [Експерименти](/uk/guides/concepts)
(`/dashboard/experiments` і вкладка **A/B** конструктора агентів), яка
визначає варіанти, розподіляє трафік і порівнює результати для кожного варіанта —
вебхук не потрібен.

Якщо вам усе одно потрібен контроль на боці вебхука: хешуйте `call_id` → сегмент;
віддавайте промпт A для `0..49` і промпт B для `50..99`. Запишіть вибраний
сегмент у власну БД, а потім зіставте його з оцінкою завершеного
виклику.

### Маршрутизація за часом

Робочі години → агент «підтримка наживо»; поза робочими годинами → агент «прийом повідомлень».
Просте перемикання за `new Date().getUTCHours()` у вашому обробнику.

---

## Наступні кроки

<CardGroup cols={2}>
  <Card title="Довідник вебхука вхідних викликів" icon="phone" href="/uk/webhooks/call-incoming">
    Точні схеми запиту й відповіді, включно з кожним ключем конфігурації.
  </Card>
  <Card title="Перевірка підписів вебхуків" icon="shield-check" href="/uk/guides/verify-webhook-signatures">
    Налаштуйте HMAC один раз і використовуйте всюди.
  </Card>
  <Card title="Створення інтеграції з інструментом" icon="screwdriver-wrench" href="/uk/guides/build-tool-integration">
    Поєднуйте динамічну маршрутизацію з інструментами для окремих агентів.
  </Card>
  <Card title="Семантика доставки" icon="bolt" href="/uk/webhooks/overview">
    Повторні спроби, порядок, тайм-аути.
  </Card>
</CardGroup>
