Open in
Динамична конфигурация за всяко обаждане
Избирайте отговарящия агент — или пренаписвайте неговата подкана и настройки — поотделно за всяко входящо обаждане, чрез персонализирана логика в webhook, който контролирате.
По подразбиране на всеки телефонен номер и публикуем ключ е зададен статичен агент. Когато Ви е необходима персонализация за всеки обаждащ се или за всеки посетител — маршрутизиране на 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. Имплементирайте обработчика
Три основни правила:
- Проверявайте подписа при всяка заявка (вижте Проверка на подписи за уебкуки). Не пропускайте това в среда за разработка — направете го правилно веднъж и го използвайте повторно.
- Отговаряйте бързо. Десет секунди е твърдият лимит, а всяка секунда е тишина за обаждащия се. Правете справки в базата данни, ако е необходимо, но не извиквайте последващи 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 ...
passimport 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 | масив | Вградени схеми за инструменти-функции — вижте Инструменти за функции |
Запазете агент и подайте променливи
Върнете {"agent_id": 12, "variables": {"name": "Ada"}}, за да използвате
запазения агент на тази организация с данни за всяко обаждане. Подканата му може
да съдържа {{name}} или {{name|Friend}}. Променливите от уебхука се обединяват
с променливите на ниво заявка; null използва стойността по подразбиране на
заместителя или празен текст, ако не е зададена такава. Крайните стойности и
неразрешените имена се показват в подробностите за обаждането и в уебхуковете при
завършване. Отговорите за запазен агент приемат само agent_id и variables.
Ако присъства prompt, отговорът използва вградена конфигурация и игнорира
agent_id (включително null или метаданни, които не са цели числа); вградената
подкана все пак трябва да е валидна. Отговорите с вградена конфигурация може също
да включват variables. Отговорите за запазен агент използват внедреното A/B
разпределение на агента както за телефонни обаждания, така и за обаждания от
уиджета, след което визуализират променливите. Вижте променливи за обаждания
за ограниченията и поддръжката на интерфейса за сесии. Конфигурацията за блокиране
идва от наследения URL адрес за телефонен номер/организация или от ключ на уиджет в
режим на уебхук; входящите събития на системата за крайни точки са само известия.
Шаблони
Контекст на влязъл потребител
В уиджети в режим на уебхук страницата на посетителя вече знае кой е той.
Извикайте своя уебхук с параметър на низа за заявка, който SDK на уиджета
препраща (?customer_id=123), и потърсете клиента от страна на сървъра.
A/B внедряване на подкани
Преди да го реализирате ръчно, имайте предвид, че ThunderPhone има вградена
функция Експерименти
(/dashboard/experiments и раздела A/B в конструктора на агенти), която
дефинира варианти, разделя трафика и сравнява резултатите за всеки вариант —
без необходимост от уебхук.
Ако все пак се нуждаете от контрол от страна на уебхука: хеширайте call_id → кофа;
подайте подкана A за 0..49 и подкана B за 50..99. Запишете избраната кофа в
собствената си DB и по-късно я съпоставете с оценката на завършеното обаждане.
Маршрутизиране според времето
Работно време → агент за „поддръжка на живо“; извън работно време → агент за
„приемане на съобщения“. Само превключване по new Date().getUTCHours() във вашия обработчик.
Следващи стъпки
Точни схеми за заявка и отговор, включително всеки ключ за конфигурация.
Настройте HMAC правилно веднъж; използвайте го повторно навсякъде.
Комбинирайте динамично маршрутизиране с инструменти за всеки агент.
Повторни опити, подреждане, изчаквания.