Dynamisk konfiguration pr. opkald
Vælg den besvarende agent — eller omskriv dens prompt og indstillinger — separat for hvert indgående opkald, styret af brugerdefineret logik i en webhook, du kontrollerer.
Som standard har hvert telefonnummer og hver publicerbar nøgle en statisk agent tilknyttet. Når du har brug for tilpasning pr. opkalder eller pr. besøgende — VIP-routing, kontekst for indloggede brugere, A/B-tests af prompts — skal du skifte til webhooktilstand og lade din server beslutte.
Sådan fungerer det
- Du abonnerer på hændelsen
telephony.incoming(telefon) ellerweb.incoming(widget). Begge er blokerende webhooks: ThunderPhone venter op til 10 sekunder på dit svar, før opkaldet fortsætter. - ThunderPhone sender dig
{call_id, from_number, to_number}(widget- sessioner indeholder widgetspecifikke felter i stedet for numre — se anmodningsskemaet). - Din server svarer med en agentkonfiguration (prompt, stemme, produkt, værktøjer). ThunderPhone bruger den konfiguration til opkaldet.
- Hvis du returnerer
{}, får timeout eller en fejl, bruges den statisk tildelte agent som reserve. Et sikkert standardvalg.
1. Konfigurer webhookdestinationen
For telefonnumre skal du abonnere dit slutpunkt på 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"]
}'Svaret indeholder en secret til engangsbrug — gem den; du skal bruge den
til signaturverifikation.
For widgetsessioner skal du oprette en publicerbar nøgle i mode="webhook"
med URL'en til dit slutpunkt indbygget:
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"]
}'Widgetten sender POST til denne URL ved starten af hver session.
2. Implementer handleren
Tre tommelfingerregler:
- Bekræft signaturen på hver anmodning (se Bekræft webhook-signaturer). Spring ikke dette over under udvikling — gør det rigtigt én gang, og genbrug det.
- Svar hurtigt. Ti sekunder er den faste grænse, og hvert sekund er stilhed for den, der ringer. Foretag databaseopslag, hvis du har brug for det, men kald ikke efterfølgende LLM'er synkront — hvis du vil have dynamisk promptgenerering, skal du forudberegne og cache.
- Fald rent tilbage. Enhver uventet tilstand skal returnere
{}, så den statisk tildelte agent håndterer opkaldet.
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. Svarskema
Svarbrødteksten matcher svarskemaet for indgående opkald præcist. De mest anvendte felter:
| Felt | Type | Beskrivelse |
|---|---|---|
prompt | streng (påkrævet) | Systemprompt for agenten |
voice | streng (påkrævet) | Stemme-id fra GET /v1/voices |
product | streng | Standard er spark |
background_track | streng | null | Id for baggrundslyd |
acknowledgement_prompt_mode | streng | auto eller manual (kun Storm med bekræftelse) |
acknowledgement_prompt | streng | Påkrævet, når tilstanden er manual |
tools | array | Indlejrede skemaer for funktionsværktøjer — se Funktionsværktøjer |
Mønstre
Kontekst for indlogget bruger
I widgets i webhooktilstand ved den besøgendes side allerede, hvem de
er. Kald din webhook med en query string-parameter, som widget-SDK'et
videresender (?customer_id=123), og slå kunden op på serversiden.
A/B-udrulning af prompt
Før du selv implementerer dette, skal du være opmærksom på, at ThunderPhone har en indbygget
Eksperimenter-funktion
(/dashboard/experiments og stemmeagentbyggerens A/B-fane), der
definerer varianter, fordeler trafik og sammenligner resultater pr. variant —
ingen webhook påkrævet.
Hvis du alligevel har brug for kontrol på webhook-siden: hash call_id → bucket;
servér prompt A for 0..49 og prompt B for 50..99. Registrer, hvilken
bucket du valgte, i din egen database, og korrelér den senere med det
afsluttede opkalds vurdering.
Tidsbaseret routing
Åbningstid → stemmeagenten "live support"; uden for åbningstid → stemmeagenten "tag en besked".
Ren switch på new Date().getUTCHours() i din handler.
Næste trin
Præcise request- og response-skemaer, inklusive alle konfigurationsnøgler.
Få HMAC rigtigt én gang; genbrug det overalt.
Kombinér dynamisk routing med værktøjer pr. stemmeagent.
Genforsøg, rækkefølge, timeouts.