Dinamična konfiguracija za posamezen klic

Vsaki telefonski številki in objavljivemu ključu je privzeto dodeljen statični agent. Ko potrebujete prilagajanje za posameznega klicatelja ali za posameznega obiskovalca — usmerjanje VIP, kontekst prijavljenega uporabnika, A/B-preizkuse pozivov — preklopite v način webhook in naj odloči vaš strežnik.

Kako deluje

  1. Naročite se na dogodek telephony.incoming (telefon) ali web.incoming (gradnik). Oba sta blokirajoča webhooka: ThunderPhone pred nadaljevanjem klica na vaš odgovor čaka do 10 sekund.
  2. ThunderPhone vam pošlje {call_id, from_number, to_number} (seje gradnika namesto številk vsebujejo polja, značilna za gradnik — glejte shemo zahteve).
  3. Vaš strežnik odgovori s konfiguracijo agenta (poziv, glas, izdelek, orodja). ThunderPhone to konfiguracijo uporabi za klic.
  4. Če vrnete {}, pride do časovne omejitve ali napake, se kot nadomestna možnost uporabi statično dodeljeni agent. Varen privzeti način.

1. Konfigurirajte cilj webhooka

Telefonski klici

Za telefonske številke naročite svojo končno točko na 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"]
  }'

Odgovor vključuje enkratno vrednost secret — shranite jo; uporabili jo boste za preverjanje podpisa.

Spletni gradnik

Za seje gradnika ustvarite objavljivi ključ v mode="webhook" z vgrajenim URL-jem svoje končne točke:

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"]
  }'

Gradnik bo ob vsakem začetku seje poslal zahtevo POST na ta URL.

2. Implementirajte obravnavalnik

Tri osnovna pravila:

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
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. Shema odgovora

Telo odgovora se natančno ujema s odzivno shemo za dohodni klic. Pogosto uporabljena polja:

PoljeVrstaOpis
promptniz (obvezno)Sistemski poziv za agenta
voiceniz (obvezno)ID glasu iz GET /v1/voices
productnizPrivzeto je spark
background_trackniz | nullID ambientalnega zvoka
acknowledgement_prompt_modenizauto ali manual (samo Storm s potrditvijo)
acknowledgement_promptnizObvezno, kadar je način manual
toolspoljeVdelane sheme funkcijskih orodij — glejte Funkcijska orodja

Vzorci

Kontekst prijavljenega uporabnika

V gradnikih v načinu webhook stran obiskovalca že ve, kdo je. Pokličite svoj webhook s parametrom poizvedbenega niza, ki ga SDK gradnika posreduje naprej (?customer_id=123), in poiščite stranko na strani strežnika.

Uvajanje pozivov A/B

Preden to izdelate sami, upoštevajte, da ima ThunderPhone vgrajeno funkcijo Eksperimenti (/dashboard/experiments in zavihek A/B gradnika agenta), ki določa različice, razdeli promet in primerja rezultate za posamezno različico — webhook ni potreben.

Če vseeno potrebujete nadzor na strani webhooka: zgostite call_id → vedro; za 0..49 uporabite poziv A, za 50..99 pa poziv B. Zabeležite, katero vedro ste izbrali, v svoji podatkovni zbirki in ga pozneje povežite z oceno zaključenega klica.

Usmerjanje glede na čas

Delovni čas → agent za »podporo v živo«; izven delovnega časa → agent za »sprejem sporočil«. Čisto preklapljanje na podlagi new Date().getUTCHours() v vašem obdelovalniku.


Naslednji koraki