Open in
Dinaminė konfigūracija kiekvienam skambučiui
Kiekvienam įeinančiam skambučiui atskirai pasirinkite atsiliepiantį agentą arba pakeiskite jo raginimą ir nustatymus, remdamiesi pasirinkta logika jūsų valdomame webhook.
Pagal numatytuosius nustatymus kiekvienam telefono numeriui ir viešajam raktui priskiriamas statinis agentas. Kai reikia tinkinti kiekvienam skambintojui arba kiekvienam lankytojui — nukreipti VIP klientus, įtraukti prisijungusio naudotojo kontekstą, atlikti A/B raginimų bandymus — perjunkite į žiniatinklio kabliuko režimą ir leiskite spręsti savo serveriui.
Kaip tai veikia
- Užsiprenumeruojate
telephony.incoming(telefono) arbaweb.incoming(valdiklio) įvykį. Abu yra blokuojantys žiniatinklio kabliukai: ThunderPhone laukia jūsų atsakymo prieš tęsdamas skambutį — iki 13 sekundžių telefono skambučiui (įskaitant vieną pakartotinį bandymą) ir 10 sekundžių valdiklio sesijai. - ThunderPhone jums siunčia
{call_id, from_number, to_number}(valdiklio sesijose vietoj numerių pateikiami valdikliui skirti laukai — žr. užklausos schemą). - Jūsų serveris pateikia balso agento konfigūraciją (nurodymą, balsą, produktą, įrankius). ThunderPhone naudoja tą konfigūraciją skambučiui.
- Atsarginio agento nėra. Jei grąžinate
{}arba neteisingą konfigūraciją arba užklausa vis tiek nepavyksta po pakartotinio bandymo, telefono skambutis nedelsiant nutraukiamas ir užregistruojamas suend_reasoninbound_config_webhook_failed; valdiklio sesijos užklausa nepavyksta.
1. Sukonfigūruokite žiniatinklio kabliuko paskirties vietą
Telefono numeriams užsiprenumeruokite savo galinį tašką įvykiui 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"]
}'Atsakyme pateikiamas vienkartinis secret — išsaugokite jį; naudosite jį
parašo tikrinimui.
Valdiklio sesijoms sukurkite viešąjį raktą su mode="webhook",
kuriame įtraukta jūsų galinio taško URL:
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"]
}'Valdiklis siųs POST užklausą į šį URL kiekvienos sesijos pradžioje.
2. Įdiekite tvarkytuvę
Trys pagrindinės taisyklės:
- Patikrinkite parašą kiekvienoje užklausoje (žr. Tikrinkite žiniatinklio kablio parašus). Nepraleiskite šio veiksmo kūrimo aplinkoje — tinkamai įgyvendinkite vieną kartą ir naudokite pakartotinai.
- Atsakykite greitai. Pirmasis telefono skambučio bandymas nutraukiamas po 10 sekundžių, o visas apsikeitimas — po 13, todėl kiekviena sekundė skambinančiajam yra tyla. Jei reikia, atlikite duomenų bazės užklausas, tačiau nekvieskite paskesnių LLM sinchroniškai — jei norite dinamiškai generuoti raginimus, iš anksto juos apskaičiuokite ir išsaugokite talpykloje.
- Tvarkingai naudokite atsarginį variantą. Esant bet kokiai netikėtai būsenai, atsakykite
numatytaisiais
promptirvoice(arba išsaugotuagent_id). Grąžinus{}arba klaidos būseną, skambutis nutraukiamas.
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. Atsakymo schema
Atsakymo turinys tiksliai atitinka įeinančio skambučio atsakymo schemą. Dažniausiai naudojami laukai:
| Laukas | Tipas | Aprašymas |
|---|---|---|
prompt | eilutė (privaloma) | Agento sistemos raginimas |
voice | eilutė (privaloma) | Balso ID iš GET /v1/voices |
product | eilutė | Numatytoji reikšmė yra spark. Eksperimentiniai Bolt produktai prieinami tik organizacijoms, įtrauktoms į leidžiamųjų sąrašą. |
background_track | eilutė | null | Aplinkos garso ID |
acknowledgement_prompt_mode | eilutė | auto arba manual (tik Storm su patvirtinimu) |
acknowledgement_prompt | eilutė | Privaloma, kai režimas yra manual |
tools | masyvas | Įterptinės funkcijų įrankių schemos — žr. Funkcijų įrankiai |
Išsaugokite agentą ir pateikite kintamuosius
Grąžinkite {"agent_id": 12, "variables": {"name": "Ada"}}, kad naudotumėte tos organizacijos
išsaugotą agentą su kiekvieno skambučio duomenimis. Jo nurodyme gali būti {{name}} arba
{{name|Friend}}. Žiniatinklio kabliuko kintamieji sujungiami su užklausos lygio kintamaisiais ir turi pirmenybę; null
naudoja vietos ženklo numatytąją reikšmę arba tuščią tekstą, jei reikšmė nepateikta. Galutinės
reikšmės ir neišspręsti pavadinimai rodomi skambučio išsamioje informacijoje ir užbaigimo žiniatinklio kabliukuose.
Išsaugoto agento atsakymai priima tik agent_id, variables ir timezone. Jei yra prompt,
atsakyme naudojama įterptinė konfigūracija ir nepaisoma agent_id (įskaitant
null arba ne sveikojo skaičiaus metaduomenis); įterptinis nurodymas vis tiek turi būti tinkamas. Įterptinės
konfigūracijos atsakymai taip pat gali apimti variables. Išsaugoto agento atsakymai naudoja
diegiamą agento A/B paskirstymą tiek telefono, tiek valdiklio skambučiams, tada atvaizduoja kintamuosius. Žr. skambučių kintamieji
dėl apribojimų ir sesijos API palaikymo. Blokuojanti konfigūracija gaunama iš pasenusio
telefono numerio / organizacijos URL arba valdiklio, veikiančio žiniatinklio kabliuko režimu, rakto; galinių taškų sistemos
gaunami įvykiai yra tik pranešimai.
Nustatykite laiko juostą kiekvienam skambučiui
Vienas agentas gali atsiliepti kontaktams skirtingose laiko juostose. Pridėkite timezone – IANA pavadinimą – prie išsaugoto agento arba įterptinio atsakymo, ir šio skambučio metu agentas nurodys datą bei laiką ir pasiūlys laikus toje juostoje, o ne savo laiko juostoje:
{"agent_id": 12, "timezone": "America/Chicago", "variables": {"name": "Ada"}}Siųskite juostą, o ne laiko žymą per variables: skambučio metu agento laikrodis veikia toliau, todėl jūsų perduotas laikas pasensta. Reikšmė, kuri nėra IANA laiko juosta, ignoruojama, o skambutis sujungiamas naudojant agento laiko juostą.
Modeliai
Prisijungusio naudotojo kontekstas
Žiniatinklio kablio režimo valdikliuose lankytojo puslapis jau žino, kas jis
yra. Iškvieskite savo žiniatinklio kablį su užklausos eilutės parametru, kurį
valdiklio SDK persiunčia (?customer_id=123), ir suraskite klientą serverio pusėje.
A/B raginimų diegimas
Prieš kurdami tai patys, atkreipkite dėmesį, kad ThunderPhone turi vietinę
eksperimentų funkciją
(/dashboard/experiments ir agento kūrimo priemonės A/B skirtuką), kuri
apibrėžia variantus, paskirsto srautą ir palygina kiekvieno varianto rezultatus —
žiniatinklio kablio nereikia.
Jei vis tiek reikia valdymo žiniatinklio kablio pusėje: maišykite call_id → segmentas;
pateikite raginimą A reikšmėms 0..49 ir raginimą B reikšmėms 50..99. Įrašykite pasirinktą
segmentą savo DB ir vėliau susiekite jį su užbaigto skambučio įvertinimu.
Maršrutizavimas pagal laiką
Darbo valandos → „tiesioginio palaikymo“ agentas; ne darbo valandomis → „priimti žinutę“
agentas. Tvarkyklėje tiesiog perjunkite pagal new Date().getUTCHours().
Tolesni veiksmai
Tikslios užklausų ir atsakymų schemos, įskaitant kiekvieną konfigūracijos raktą.
Vieną kartą teisingai nustatykite HMAC; naudokite visur.
Derinkite dinaminį maršrutizavimą su agentui skirtais įrankiais.
Pakartotiniai bandymai, tvarka, laiko limitai.