Dinamička konfiguracija po pozivu

Prema zadanim postavkama svakom telefonskom broju i javnom ključu dodijeljen je statički agent. Kada trebate prilagodbu po pozivatelju ili po posjetitelju — VIP usmjeravanje, kontekst prijavljenog korisnika, A/B testove upita — prijeđite na način rada webhooka i prepustite odluku svom poslužitelju.

Kako funkcionira

  1. Pretplatite se na događaj telephony.incoming (telefon) ili web.incoming (widget). Oba su blokirajući webhookovi: ThunderPhone čeka do 10 sekundi na vaš odgovor prije nastavka poziva.
  2. ThunderPhone vam šalje {call_id, from_number, to_number} (sesije widgeta sadrže polja specifična za widget umjesto brojeva — pogledajte shemu zahtjeva).
  3. Vaš poslužitelj odgovara konfiguracijom agenta (upit, glas, proizvod, alati). ThunderPhone tu konfiguraciju upotrebljava za poziv.
  4. Ako vratite {}, istekne vrijeme ili dođe do pogreške, statički dodijeljeni agent upotrebljava se kao zamjenska opcija. Sigurna zadana postavka.

1. Konfigurirajte odredište webhooka

Telefonski pozivi

Za telefonske brojeve pretplatite svoju krajnju točku 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 uključuje jednokratni secret — spremite ga; upotrebljavat ćete ga za provjeru potpisa.

Web widget

Za sesije widgeta izradite javni ključ u mode="webhook" s ugrađenim URL-om vaše krajnje 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"]
  }'

Widget će poslati POST zahtjev na ovaj URL pri svakom početku sesije.

2. Implementirajte obrađivač

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

Tijelo odgovora u potpunosti odgovara shemi odgovora za dolazni poziv. Najčešće korištena polja:

PoljeVrstaOpis
promptniz znakova (obavezno)Sistemska uputa za agenta
voiceniz znakova (obavezno)ID glasa iz GET /v1/voices
productniz znakovaZadano je spark
background_trackniz znakova | nullID ambijentalnog zvuka
acknowledgement_prompt_modeniz znakovaauto ili manual (samo Storm s potvrdom)
acknowledgement_promptniz znakovaObavezno kada je način rada manual
toolspoljeUgrađene sheme funkcionalnih alata — pogledajte Funkcionalni alati

Obrasci

Kontekst prijavljenog korisnika

U widgetima u načinu rada web-dojavnika stranica posjetitelja već zna tko je on. Pozovite svoj web-dojavnik s parametrom niza upita koji SDK widgeta prosljeđuje (?customer_id=123) i potražite korisnika na poslužitelju.

Uvođenje A/B upita

Prije nego što ovo izradite ručno, imajte na umu da ThunderPhone ima ugrađenu značajku Eksperimenti (/dashboard/experiments i karticu A/B u alatu za izradu agenta) koja definira varijante, dijeli promet i uspoređuje rezultate po varijanti — web-dojavnik nije potreban.

Ako vam ipak treba upravljanje na strani web-dojavnika: raspršite call_id → spremnik; poslužite upit A za 0..49 i upit B za 50..99. Zabilježite koji ste spremnik odabrali u vlastitoj bazi podataka i kasnije ga povežite s ocjenom dovršenog poziva.

Usmjeravanje prema vremenu

Radno vrijeme → agent za „podršku uživo”; izvan radnog vremena → agent za „primanje poruke”. Čisto grananje prema new Date().getUTCHours() u vašem rukovatelju.


Sljedeći koraci