Open in
Dynamisk konfiguration per samtal
Välj den svarande röstagenten – eller skriv om dess prompt och inställningar – separat för varje inkommande samtal, styrt av anpassad logik i en webhook som du kontrollerar.
Som standard har varje telefonnummer och publicerbar nyckel en statisk agent kopplad. När du behöver anpassning per uppringare eller per besökare — VIP-dirigering, kontext för inloggade användare, A/B-tester av prompter — växlar du till webhook-läge och låter din server avgöra.
Så fungerar det
- Du prenumererar på händelsen
telephony.incoming(telefon) ellerweb.incoming(widget). Båda är blockerande webhookar: ThunderPhone väntar på ditt svar innan samtalet fortsätter, i upp till 13 sekunder för ett telefonsamtal (inklusive ett återförsök) och 10 sekunder för en widgetsession. - ThunderPhone skickar
{call_id, from_number, to_number}till dig (widget- sessioner innehåller widgetspecifika fält i stället för nummer — se begärandeschemat). - Din server svarar med en agentkonfiguration (prompt, röst, produkt, verktyg). ThunderPhone använder den konfigurationen för samtalet.
- Det finns ingen reservagent. Om du returnerar
{}eller en ogiltig konfiguration, eller om begäran fortfarande misslyckas efter sitt återförsök, avslutas telefonsamtalet direkt och registreras medend_reasoninbound_config_webhook_failed; en begäran om widgetsessions misslyckas.
1. Konfigurera webhookdestinationen
För telefonnummer prenumererar du din 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 innehåller en secret som bara visas en gång — spara den; du använder den
för signaturverifiering.
För widgetsessioner skapar du en publicerbar nyckel i mode="webhook"
med din slutpunkts-URL inbäddad:
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"]
}'Widgeten skickar en POST-begäran till denna URL vid varje sessionsstart.
2. Implementera hanteraren
Tre tumregler:
- Verifiera signaturen för varje begäran (se Verifiera webhook-signaturer). Hoppa inte över detta i utvecklingsmiljön — gör rätt en gång och återanvänd.
- Svara snabbt. Ett telefonsamtals första försök avbryts efter 10 sekunder och hela utbytet efter 13, och varje sekund är tystnad för den som ringer. Gör databasuppslag om du behöver, men anropa inte efterföljande LLM:er synkront — om du vill generera dynamiska promptar, förberäkna och cachelagra.
- Använd en ren reservlösning. Vid varje oväntat tillstånd ska du svara med en
standard-
promptochvoice(eller ett sparatagent_id). Att returnera{}eller en felstatus avslutar samtalet.
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. Svarsschema
Svarstexten matchar svarsschemat för inkommande samtal exakt. De vanligaste fälten:
| Fält | Typ | Beskrivning |
|---|---|---|
prompt | sträng (obligatoriskt) | Systemprompt för agenten |
voice | sträng (obligatoriskt) | Röst-ID från GET /v1/voices |
product | sträng | Standardvärdet är spark. Experimentella produkter är endast tillgängliga för tillåtelselistaorganisationer. |
background_track | sträng | null | ID för bakgrundsljud |
acknowledgement_prompt_mode | sträng | auto eller manual (endast Storm med bekräftelse) |
acknowledgement_prompt | sträng | Krävs när läget är manual |
language_switching | sträng | För flerspråkiga agenter: ask_first eller follow_caller |
tools | matris | Inline-scheman för funktionsverktyg — se Funktionsverktyg |
Behåll en sparad agent och ange variabler
Returnera {"agent_id": 12, "variables": {"name": "Ada"}} för att använda organisationens
sparade agent med data per samtal. Dess prompt kan innehålla {{name}} eller
{{name|Friend}}. Webhook-variabler slås samman ovanpå variabler på förfrågningsnivå; null
använder platshållarens standardvärde eller tom text om inget anges. De slutliga
värdena och olösta namnen visas i samtalsdetaljer och webhooks för slutförande.
Svar för sparade agenter accepterar endast agent_id, variables och timezone. Om prompt
finns används infogad konfiguration i svaret och agent_id ignoreras (inklusive
null eller metadata som inte är heltal); den infogade prompten måste fortfarande vara giltig. Svar med infogad
konfiguration kan också innehålla variables. Svar för sparade agenter använder
agentens driftsatta A/B-fördelning för både telefon- och widget-samtal och återger sedan variablerna. Se samtalsvariabler
för gränser och stöd för sessions-API. Blockerande konfiguration kommer från den äldre
URL:en för telefonnummer/organisation eller en widgetnyckel i webhook-läge; inkommande händelser från slutpunktssystemet är endast aviseringar.
Ställ in tidszon per samtal
En agent kan svara kontakter i olika tidszoner. Lägg till timezone, ett
IANA-namn, i ett sparat agentsvar eller ett infogat svar, så anger agenten datum
och tid samt erbjuder tider i den zonen för det här samtalet, före agentens egen
tidszon:
{"agent_id": 12, "timezone": "America/Chicago", "variables": {"name": "Ada"}}Skicka zonen i stället för en tidsstämpel i variables: agentens klocka fortsätter
att gå under samtalet, och en tid du skickar in blir inaktuell. Ett värde som inte
är en IANA-zon ignoreras och samtalet kopplas upp i agentens zon.
Mönster
Kontext för inloggad användare
I widgetar i webhookläge vet besökarens sida redan vem de
är. Anropa din webhook med en frågesträngsparameter som widget-SDK:n
vidarebefordrar (?customer_id=123) och slå upp kunden på serversidan.
A/B-lansering av promptar
Innan du bygger detta själv bör du notera att ThunderPhone har en inbyggd
funktion för Experiment
(/dashboard/experiments och fliken A/B i agentbyggaren) som
definierar varianter, delar upp trafiken och jämför resultat per variant —
ingen webhook krävs.
Om du ändå behöver kontroll på webhook-sidan: hasha call_id → bucket;
servera prompt A för 0..49 och prompt B för 50..99. Registrera vilken
bucket du valde i din egen databas och korrelera den senare med det
slutförda samtalets betyg.
Tidsbaserad routing
Öppettider → agent för "live-support"; utanför öppettider → agent för "ta ett meddelande".
Enbart växling på new Date().getUTCHours() i din hanterare.
Nästa steg
Exakta scheman för förfrågan och svar, inklusive alla konfigurationsnycklar.
Implementera HMAC korrekt en gång; återanvänd överallt.
Kombinera dynamisk routing med verktyg per agent.
Försök igen, ordning, tidsgränser.