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

Developer cookbook

Динамічна конфігурація для кожного дзвінка

Вибирайте агента, який відповідатиме, або змінюйте його промпт і налаштування окремо для кожного вхідного дзвінка за допомогою власної логіки у вебхуку, яким ви керуєте.

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

Принцип роботи

  1. Підпишіться на подію telephony.incoming (телефон) або web.incoming (віджет). Обидві події є блокувальними вебхуками: ThunderPhone очікує до 10 секунд на вашу відповідь, перш ніж продовжити дзвінок.
  2. ThunderPhone надсилає вам {call_id, from_number, to_number} (сеанси віджета містять поля, специфічні для віджета, замість номерів — див. схему запиту).
  3. Ваш сервер відповідає конфігурацією агента (промпт, голос, продукт, інструменти). ThunderPhone використовує цю конфігурацію для дзвінка.
  4. Якщо ви повернете {}, відповідь не надійде вчасно або станеться помилка, як резервний варіант буде використано статично призначеного агента. Безпечне значення за замовчуванням.

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

Для телефонних номерів підпишіть вашу кінцеву точку на telephony.incoming:

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 — збережіть його; ви використаєте його для перевірки підпису.

Для сеансів віджета створіть публічний ключ у mode="webhook" із вбудованою URL-адресою вашої кінцевої точки:

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-адресу на початку кожного сеансу.

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

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

  • Перевіряйте підпис у кожному запиті (див. Перевірка підписів вебхуків). Не пропускайте це в розробці — налаштуйте правильно один раз і повторно використовуйте.
  • Відповідайте швидко. Десять секунд — жорсткий ліміт, і кожна секунда — тиша для абонента. За потреби виконуйте пошук у базі даних, але не викликайте нижчестоящі LLM синхронно — якщо вам потрібне динамічне генерування промптів, обчислюйте їх заздалегідь і кешуйте.
  • Коректно повертайтеся до резервного варіанта. Будь-який неочікуваний стан має повертати {}, щоб виклик обробив статично призначений агент.
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
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
  },
);

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

Тіло відповіді точно відповідає схемі відповіді на вхідний виклик. Найуживаніші поля:

ПолеТипОпис
promptрядок (обов’язково)Системний промпт для агента
voiceрядок (обов’язково)Ідентифікатор голосу з GET /v1/voices
productрядокТипово spark
background_trackрядок | nullІдентифікатор фонового аудіо
acknowledgement_prompt_modeрядокauto або manual (лише Storm-with-ack)
acknowledgement_promptрядокОбов’язкове, якщо режим — manual
toolsмасивВбудовані схеми інструментів-функцій — див. Інструменти-функції

Шаблони

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

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

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

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

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

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

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


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