ThunderPhone 2.0 ir klāt.Sāciet patstāvīgi — no 2 ¢/min.Lasīt paziņojumu

Developer cookbook

Dinamiska konfigurācija katram zvanam

Katram ienākošajam zvanam atsevišķi izvēlieties atbildētāju balss aģentu vai pārrakstiet tā uzvedni un iestatījumus, izmantojot pielāgotu loģiku jūsu kontrolētā tīmekļa aizķerē.

Pēc noklusējuma katram tālruņa numuram un publicējamajai atslēgai ir piešķirts statisks aģents. Ja nepieciešama katram zvanītājam vai katram apmeklētājam pielāgota konfigurācija — VIP maršrutēšana, pieteikta lietotāja konteksts, A/B uzvedņu testi — pārslēdzieties uz tīmekļa aizķeres režīmu un ļaujiet serverim pieņemt lēmumu.

Kā tas darbojas

  1. Jūs abonējat telephony.incoming (tālrunis) vai web.incoming (logrīks) notikumu. Abi ir bloķējoši webhooki: ThunderPhone gaida jūsu atbildi, pirms turpināt zvanu — līdz 13 sekundēm tālruņa zvanam (ieskaitot vienu atkārtotu mēģinājumu) un 10 sekundēm logrīka sesijai.
  2. ThunderPhone nosūta jums {call_id, from_number, to_number} (logrīka sesijās numuru vietā tiek izmantoti logrīkam specifiski lauki — skatiet pieprasījuma shēmu).
  3. Jūsu serveris atbild ar balss aģenta konfigurāciju (uzvedne, balss, produkts, rīki). ThunderPhone izmanto šo konfigurāciju zvanam.
  4. Nav rezerves balss aģenta. Ja atgriežat {} vai nederīgu konfigurāciju vai pieprasījums joprojām neizdodas pēc atkārtota mēģinājuma, tālruņa zvans tiek nekavējoties pārtraukts un reģistrēts ar end_reason inbound_config_webhook_failed; logrīka sesijas pieprasījums neizdodas.

1. Konfigurējiet tīmekļa aizķeres galamērķi

Tālruņa numuriem abonējiet savu galapunktu notikumam 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"]
  }'

Atbildē ir vienreiz lietojams secret — saglabājiet to; tas būs nepieciešams paraksta verifikācijai.

Logrīka sesijām izveidojiet publicējamu atslēgu ar mode="webhook", kurā ir iekļauts jūsu galapunkta 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"]
  }'

Logrīks katras sesijas sākumā veiks POST pieprasījumu uz šo URL.

2. Ieviesiet apstrādātāju

Trīs pamatprincipi:

  • Pārbaudiet parakstu katram pieprasījumam (skatiet Tīmekļa aizķeru parakstu pārbaude). Neizlaidiet to izstrādes vidē — ieviesiet pareizi vienreiz un izmantojiet atkārtoti.
  • Atbildiet ātri. Tālruņa zvana pirmais mēģinājums tiek pārtraukts pēc 10 sekundēm, bet visa apmaiņa — pēc 13 sekundēm, un katra sekunde zvanītājam ir klusums. Ja nepieciešams, veiciet datubāzes uzmeklēšanu, bet nesauciet pakārtotos LLM sinhroni — ja vēlaties dinamisku uzvednes ģenerēšanu, iepriekš aprēķiniet un kešojiet.
  • Korekti izmantojiet rezerves variantu. Jebkuram neparedzētam stāvoklim atbildiet ar noklusējuma prompt un voice (vai saglabātu agent_id). Atgriežot {} vai kļūdas statusu, zvans tiek pārtraukts.
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. Atbildes shēma

Atbildes pamatteksts precīzi atbilst ienākošā zvana atbildes shēmai. Bieži izmantotie lauki:

LauksTipsApraksts
promptvirkne (obligāts)Balss aģenta sistēmas uzvedne
voicevirkne (obligāts)Balss ID no GET /v1/voices
productvirknePēc noklusējuma ir spark. Eksperimentālie produkti ir pieejami tikai organizācijām atļauju sarakstā.
background_trackvirkne | nullFona audio ID
acknowledgement_prompt_modevirkneauto vai manual (tikai Storm ar apstiprinājumu)
acknowledgement_promptvirkneObligāts, ja režīms ir manual
language_switchingvirkneDaudzvalodu balss aģentiem: ask_first vai follow_caller
toolsmasīvsIekļautās funkciju rīku shēmas — skatiet Funkciju rīki

Saglabājiet balss aģentu un nododiet mainīgos

Atgrieziet {"agent_id": 12, "variables": {"name": "Ada"}}, lai izmantotu šīs organizācijas saglabāto balss aģentu ar katra izsaukuma datiem. Tā uzvednē var būt {{name}} vai {{name|Friend}}. Tīmekļa aizķeres mainīgie tiek apvienoti ar pieprasījuma līmeņa mainīgajiem; null izmanto viettura noklusējuma vērtību vai tukšu tekstu, ja tāda nav norādīta. Galīgās vērtības un neatrisinātie nosaukumi tiek parādīti izsaukuma detalizētajā informācijā un pabeigšanas tīmekļa aizķerēs. Saglabātā balss aģenta atbildes pieņem tikai agent_id, variables un timezone. Ja ir norādīts prompt, atbilde izmanto iekļauto konfigurāciju un ignorē agent_id (tostarp null vai neveselu skaitļu metadatus); iekļautajai uzvednei joprojām jābūt derīgai. Iekļautās konfigurācijas atbildēs var būt arī variables. Saglabātā balss aģenta atbildes izmanto aģenta izvietoto A/B sadalījumu gan tālruņa, gan logrīka izsaukumiem un pēc tam atveido mainīgos. Skatiet izsaukuma mainīgos, lai uzzinātu par ierobežojumiem un sesijas API atbalstu. Bloķējošā konfigurācija tiek iegūta no mantotā tālruņa numura/organizācijas URL vai tīmekļa aizķeres režīma logrīka atslēgas; galapunktu sistēmā ienākošie notikumi ir tikai paziņojumi.

Iestatiet laika joslu katram zvanam

Viens balss aģents var atbildēt kontaktpersonām dažādās laika joslās. Pievienojiet timezone — IANA nosaukumu — saglabātā aģenta vai iekļautajai atbildei, un aģents šim zvanam norādīs datumu un laiku, kā arī piedāvās laikus šajā joslā, nevis aģenta paša laika joslā:

{"agent_id": 12, "timezone": "America/Chicago", "variables": {"name": "Ada"}}

Sūtiet joslu, nevis laikspiedolu laukā variables: aģenta pulkstenis zvana laikā turpina darboties, un jūsu nodotais laiks noveco. Vērtība, kas nav IANA josla, tiek ignorēta, un zvans tiek savienots aģenta laika joslā.

Modeļi

Pieteikušās personas konteksts

Webhook režīma logrīkos apmeklētāja lapa jau zina, kas viņš ir. Izsauciet savu webhook ar vaicājuma virknes parametru, ko logrīka SDK pārsūta (?customer_id=123), un atrodiet klientu servera pusē.

A/B uzvedņu ieviešana

Pirms veidojat to manuāli, ņemiet vērā, ka ThunderPhone ir vietējā Eksperimentu funkcija (/dashboard/experiments un balss aģenta veidotāja cilne A/B), kas definē variantus, sadala datplūsmu un salīdzina rezultātus katram variantam — webhook nav nepieciešams.

Ja tomēr nepieciešama vadība webhook pusē: jauciet call_id → segments; apkalpojiet uzvedni A vērtībām 0..49 un uzvedni B vērtībām 50..99. Saglabājiet, kuru segmentu izvēlējāties, savā datubāzē un vēlāk sasaistiet to ar pabeigtā zvana vērtējumu.

Maršrutēšana pēc laika

Darba laikā → balss aģents “tiešais atbalsts”; ārpus darba laika → balss aģents “ziņas pieņemšana”. Vienkārša pārslēgšana uz new Date().getUTCHours() jūsu apstrādātājā.


Nākamie soļi