Megérkezett a ThunderPhone 2.0.Önkiszolgáló használat már 2 cent/perctől.Olvassa el a bejelentést

Developer cookbook

Dinamikus hívásonkénti konfiguráció

Válassza ki a válaszoló AI-ügynököt — vagy írja át annak promptját és beállításait — minden egyes bejövő híváshoz külön, az Ön által vezérelt webhookban megvalósított egyéni logika alapján.

Alapértelmezés szerint minden telefonszámhoz és publikálható kulcshoz statikusan van hozzárendelve egy ügynök. Ha hívónkénti vagy látogatónkénti testreszabásra van szüksége — VIP-irányításra, bejelentkezett felhasználói kontextusra, A/B prompttesztekre — váltson webhook módra, és hagyja, hogy a szervere döntsön.

Működése

  1. Iratkozzon fel a telephony.incoming (telefon) vagy a web.incoming (widget) eseményre. Mindkettő blokkoló webhook: a ThunderPhone akár 10 másodpercig vár a válaszára, mielőtt folytatná a hívást.
  2. A ThunderPhone elküldi Önnek a következőt: {call_id, from_number, to_number} (a widgetes munkamenetek a számok helyett widgetspecifikus mezőket tartalmaznak — lásd a kérés sémájá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. Ha {} értéket ad vissza, időtúllépés történik vagy hiba lép fel, a statikusan hozzárendelt ügynök lesz a tartalék. Biztonságos alapértelmezés.

1. A webhook célhelyének konfigurálása

Telefonszámok esetén 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; ezt fogja használni az aláírás ellenőrzéséhez.

Widgetes munkamenetekhez hozzon létre egy mode="webhook" beállítású 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. A kezelő implementálása

Három gyakorlati szabály:

  • Ellenőrizze az aláírást minden kérésnél (lásd: Webhook-aláírások ellenőrzése). Ezt fejlesztés során se hagyja ki — végezze el egyszer helyesen, majd használja újra.
  • Válaszoljon gyorsan. A tíz másodperc a szigorú felső határ, és minden másodperc néma várakozás 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 — dinamikus promptgeneráláshoz számítsa elő és gyorsítótárazza az adatokat.
  • Biztosítson tiszta tartalék megoldást. Minden váratlan állapotnak {} értéket kell visszaadnia, hogy a statikusan hozzárendelt ügynök kezelje 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 megfelel a bejövő hívás válaszsémájának. A gyakran használt mezők:

MezőTípusLeírás
promptstring (kötelező)Az ügynök rendszerpromptja
voicestring (kötelező)Hangazonosító a GET /v1/voices végpontból
productstringAlapértelmezett értéke: spark
background_trackstring | nullHáttérhang-azonosító
acknowledgement_prompt_modestringauto vagy manual (csak visszaigazolással rendelkező Storm esetén)
acknowledgement_promptstringKötelező, ha a mód manual
toolsarrayBeágyazott függvényeszköz-sémák — lásd: Függvényeszközök

Minták

Bejelentkezett felhasználói környezet

Webhook módú widgetekben a látogató oldala már tudja, ki ő. Hívja meg a webhookot egy 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 és az ügynökkészítő A/B lapja), amely változatokat határoz meg, megosztja a forgalmat, és változatonként hasonlítja össze az eredményeket — nincs szükség webhookra.

Ha mégis webhookoldali vezérlésre van szüksége: hashelje a call_id értékét → kosár; a 0..49 értékekhez az A promptot, az 50..99 értékekhez a B promptot szolgálja ki. Rögzítse, melyik kosarat választotta a saját adatbázisában, majd később korrelálja azt a befejezett hívás értékelésével.

Időalapú útválasztás

Üzleti időben → „élő támogatási” ügynök; munkaidőn kívül → „üzenetrögzítő” ügynök. Egyszerű váltás a kezelőben a new Date().getUTCHours() alapján.


Következő lépések