Динамична конфигурация за всяко обаждане
По подразбиране на всеки телефонен номер и публикуем ключ е назначен статичен агент. Когато Ви е необходимо персонализиране за всеки обаждащ се или за всеки посетител — маршрутизиране на VIP клиенти, контекст за влезли потребители, A/B тестове на подкани — преминете към режим на уебхукове и оставете сървъра Ви да реши.
Как работи
- Абонирайте се за събитието
telephony.incoming(телефон) илиweb.incoming(уиджет). И двете са блокиращи уебхукове: ThunderPhone изчаква до 10 секунди за Вашия отговор, преди да продължи разговора. - ThunderPhone Ви изпраща
{call_id, from_number, to_number}(сесиите на уиджета съдържат специфични за уиджета полета вместо номера — вижте схемата на заявката). - Вашият сървър отговаря с конфигурация на агент (подкана, глас, продукт, инструменти). ThunderPhone използва тази конфигурация за разговора.
- Ако върнете
{}, изтече времето или възникне грешка, като резервен вариант се използва статично назначеният агент. Сигурна настройка по подразбиране.
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. Имплементирайте обработчика
Три практически правила:
- Проверявайте подписа при всяка заявка (вижте Проверка на webhook подписи). Не пропускайте това в среда за разработка — направете го правилно веднъж и го използвайте повторно.
- Отговаряйте бързо. Десет секунди е твърдият лимит, а всяка секунда е тишина за обаждащия се. Правете справки в базата данни, ако е необходимо, но не извиквайте синхронно последващи LLM модели — ако искате динамично генериране на подкани, предварително изчислявайте и кеширайте.
- Използвайте коректен резервен вариант. Всяко неочаквано състояние трябва да връща
{}, така че статично зададеният агент да обработи обаждането.
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
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 с потвърждение) |
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() в обработчика си.
Следващи стъпки
Точни схеми на заявките и отговорите, включително всеки конфигурационен ключ.
Настройте HMAC правилно веднъж; използвайте го повторно навсякъде.
Комбинирайте динамично маршрутизиране с инструменти за отделни агенти.
Повторни опити, подреждане, изчаквания.