Megérkezett a ThunderPhone 2.0.Önkiszolgálóan, már 2¢/perctől.Olvassa el a bejelentést

Developer cookbook

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

  1. Feliratkozik a telephony.incoming (telefonos) vagy a web.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.
  2. 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).
  3. 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.
  4. 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 az end_reason inbound_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 és voice értékkel (vagy egy mentett agent_id értékkel). A {} vagy hibás állapotkód visszaadása megszakítja a hívást.
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. 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ípusLeírás
promptstring (kötelező)Az ügynök rendszerszintű promptja
voicestring (kötelező)Hangazonosító a GET /v1/voices végpontból
productstringAlapértelmezett értéke: spark. A kísérleti termékek csak engedélyezett szervezetek számára érhetők el.
background_trackstring | nullKörnyezeti hang azonosítója
acknowledgement_prompt_modestringauto vagy manual (csak Storm-with-ack esetén)
acknowledgement_promptstringKötelező, ha a mód manual
toolsarrayBeá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