Open in
Dinamikus, hívásonkénti konfiguráció
Minden bejövő híváshoz külön válassza ki a válaszadó AI-ügynököt – vagy írja át annak promptját és beállításait – az Ön által vezérelt webhookban megvalósított egyedi logika alapján.
Alapértelmezés szerint minden telefonszámhoz és publikálható kulcshoz statikus ügynök van hozzárendelve. Ha hívónkénti vagy látogatónkénti testreszabásra van szüksége — VIP-irányítás, bejelentkezett felhasználói környezet, A/B prompttesztek — váltson webhook módra, és hagyja, hogy a szervere döntsön.
Működése
- Feliratkozik a
telephony.incoming(telefonos) vagy aweb.incoming(widget) eseményre. Mindkettő blokkoló webhook: a ThunderPhone a hívás folytatása előtt megvárja a válaszát, telefonhívás esetén legfeljebb 13 másodpercig (egy újrapróbálkozással együtt), widgetmunkamenet esetén pedig 10 másodpercig. - A ThunderPhone elküldi Önnek a következőt:
{call_id, from_number, to_number}(a widget- munkamenetek számok helyett widgetspecifikus mezőket tartalmaznak — lásd a kéréssémát). - A szervere egy ügynökkonfigurációval válaszol (prompt, hang, termék, eszközök). A ThunderPhone ezt a konfigurációt használja a híváshoz.
- Nincs tartalék ügynök. Ha
{}értéket vagy érvénytelen konfigurációt ad vissza, illetve ha a kérés a újrapróbálkozás után is sikertelen, a telefonhívás azonnal befejeződik, és azend_reasoninbound_config_webhook_failedértékkel kerül rögzítésre; a widgetmunkamenet kérése sikertelen lesz.
1. A webhook célhelyének konfigurálása
Telefonszámokhoz iratkoztassa fel a végpontját a telephony.incoming eseményre:
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"]
}'A válasz egy egyszer használatos secret értéket tartalmaz — mentse el; erre lesz szüksége
az aláírás ellenőrzéséhez.
Widgetes munkamenetekhez hozzon létre egy mode="webhook" értékű publikálható kulcsot,
amelybe be van építve a végpont URL-je:
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"]
}'A widget minden munkamenet indításakor POST kérést küld erre az URL-re.
2. Implementálja a kezelőt
Három ökölszabály:
- Ellenőrizze az aláírást minden kérésnél (lásd: Webhook-aláírások ellenőrzése). Fejlesztés közben se hagyja ki — valósítsa meg egyszer helyesen, majd használja újra.
- Válaszoljon gyorsan. Egy telefonhívás első próbálkozása 10 másodperc után megszakad, a teljes adatcsere pedig 13 másodperc után, és minden másodperc csend a hívó számára. Végezzen adatbázis-lekérdezéseket, ha szükséges, de ne hívjon szinkron módon downstream LLM-eket — ha dinamikus promptgenerálást szeretne, számítsa ki előre, és tárolja gyorsítótárban.
- Alkalmazzon rendezett tartalék megoldást. Bármilyen váratlan állapot esetén válaszoljon egy
alapértelmezett
promptésvoiceértékkel (vagy egy mentettagent_idértékkel). A{}vagy hibás állapotkód visszaadása megszakítja a hívást.
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. Válaszséma
A válasz törzse pontosan megegyezik a bejövő hívás válaszsémájával. A gyakran használt mezők:
| Mező | Típus | Leírás |
|---|---|---|
prompt | string (kötelező) | Az ügynök rendszerszintű promptja |
voice | string (kötelező) | Hangazonosító a GET /v1/voices végpontból |
product | string | Alapértelmezett értéke: spark. A kísérleti termékek csak engedélyezett szervezetek számára érhetők el. |
background_track | string | null | Környezeti hang azonosítója |
acknowledgement_prompt_mode | string | auto vagy manual (csak Storm-with-ack esetén) |
acknowledgement_prompt | string | Kötelező, ha a mód manual |
tools | array | Beágyazott funkcióeszköz-sémák — lásd: Funkcióeszközök |
Mentett ügynök megtartása és változók megadása
Adjon vissza {"agent_id": 12, "variables": {"name": "Ada"}} értéket az adott szervezet
mentett AI-ügynökének hívásonkénti adatokkal való használatához. Az ügynök promptja tartalmazhat {{name}} vagy
{{name|Friend}} értéket. A webhookváltozók összeolvadnak a kérésszintű változókkal; a null
a helyőrző alapértelmezett értékét használja, vagy üres szöveget, ha nincs megadva ilyen. A végső
értékek és a feloldatlan nevek a hívás részleteiben és a befejezési webhookokban jelennek meg.
A mentett AI-ügynök válaszai csak az agent_id, variables és timezone mezőket fogadják el. Ha a prompt
jelen van, a válasz beágyazott konfigurációt használ, és figyelmen kívül hagyja az agent_id értékét (beleértve
a null vagy nem egész szám típusú metaadatokat is); a beágyazott promptnak továbbra is érvényesnek kell lennie. A beágyazott
konfigurációs válaszok variables mezőt is tartalmazhatnak. A mentett AI-ügynök válaszai az ügynök üzembe helyezett A/B felosztását használják telefonos és widgetes hívásoknál is, majd megjelenítik a változókat. A korlátokról és a munkamenet-API támogatásáról lásd: hívásváltozók.
A blokkoló konfiguráció a régi telefonos szám/szervezet URL-jéből vagy egy webhookmódú widgetkulcsból származik; a végpontrendszer bejövő eseményei csak értesítések.
Időzóna beállítása hívásonként
Egy AI-ügynök különböző időzónákban lévő kapcsolattartók hívásait is fogadhatja. Adja hozzá az IANA-nevet tartalmazó timezone mezőt egy mentett ügynök- vagy beágyazott válaszhoz, és az ügynök ezen hívás során ebben az időzónában közli a dátumot és az időt, valamint időpontokat ajánl fel, az ügynök saját időzónája helyett:
{"agent_id": 12, "timezone": "America/Chicago", "variables": {"name": "Ada"}}Időbélyeg helyett az időzónát küldje a variables mezőben: az ügynök órája a hívás során tovább jár, ezért az átadott idő elavul. Az IANA-időzónának nem megfelelő érték figyelmen kívül marad, és a hívás az ügynök időzónájában kapcsolódik.
Minták
Bejelentkezett felhasználói környezet
Webhook módú widgetekben a látogató oldala már tudja, ki ő.
Hívja meg webhookját egy olyan lekérdezési karakterlánc-paraméterrel, amelyet a widget SDK továbbít
(?customer_id=123), majd keresse meg az ügyfelet szerveroldalon.
A/B prompt bevezetése
Mielőtt ezt saját maga valósítaná meg, vegye figyelembe, hogy a ThunderPhone natív
Kísérletek funkcióval rendelkezik
(a /dashboard/experiments felületen és az ügynökszerkesztő A/B lapján), amely
változatokat definiál, felosztja a forgalmat, és változatonként összehasonlítja az eredményeket —
webhook nélkül.
Ha mégis webhookoldali vezérlésre van szüksége: hashelje a call_id értéket → tároló;
szolgálja ki az A promptot 0..49, a B promptot pedig 50..99 esetén. Rögzítse
a saját adatbázisában, melyik tárolót választotta, majd később korrelálja azt
a befejezett hívás értékelésével.
Időalapú útválasztás
Üzleti nyitvatartási idő → „élő támogatási” AI-ügynök; nyitvatartási időn kívül → „üzenetfogadó”
AI-ügynök. Egyszerű elágaztatás a kezelőben a new Date().getUTCHours() alapján.
Következő lépések
Pontos kérési és válaszsémák, minden konfigurációs kulccsal együtt.
Állítsa be helyesen egyszer a HMAC-ot; használja újra mindenhol.
Kombinálja a dinamikus útválasztást ügynökönkénti eszközökkel.
Újrapróbálkozások, sorrendiség, időtúllépések.