telephony.incoming / web.incoming

Kai įeinantis telefono skambutis pasiekia numerį be priskirto agento arba žiniatinklio valdiklio sesija pradedama naudojant viešinį raktą su mode="webhook", ThunderPhone siunčia blokuojančią telephony.incoming / web.incoming užklausą į jūsų senąjį žiniatinklio kablio URL ir laukia iki 10 sekundžių konfigūracijos atsakymo. Naudokite šį apsikeitimą, kad kiekvienam skambučiui dinamiškai parinktumėte raginimą, balsą ir įrankius — visą procesą rasite dinaminio skambučio konfigūravimo vadove.

Blokuojantis apsikeitimas neturi atsarginio varianto: jei jūsų apdorotojas grąžina ne 2xx būseną, baigiasi jo laikas arba jis grąžina konfigūraciją, kuri nepraeina tikrinimo, skambutis atmetamas (telefono skambutis neprisijungia; valdiklio sesijos užklausa nepavyksta su 502/422). Atsakykite greitai — kol sprendžiate, skambintojas girdi skambučio signalą.

Užklausos duomenys

Telefono skambučiams (telephony.incoming):

{
  "type": "telephony.incoming",
  "data": {
    "call_id":     987654321,
    "from_number": "+14155550199",
    "to_number":   "+15551234567"
  }
}
LaukasTipasAprašymas
call_idintegerSkambučio ID — nekinta visuose šio skambučio įvykiuose
from_numberstringE.164 skambintojo numeris
to_numberstringE.164 paskirties numeris (vienas iš jūsų ThunderPhone numerių)

Žiniatinklio valdiklio sesijoms (web.incoming) data identifikuoja įterpimo puslapį, o ne telefono numerius:

{
  "type": "web.incoming",
  "data": {
    "call_id": 987654322,
    "origin_domain": "https://example.com",
    "publishable_key_prefix": "pk_live_a1b2"
  }
}
LaukasTipasAprašymas
call_idintegerSkambučio ID
origin_domainstringPuslapio, kuriame talpinamas valdiklis, kilmė
publishable_key_prefixstringPirmieji viešinio rakto, kuriuo pradėta sesija, simboliai
language, primary_languagestringPateikiama, kai valdiklio sesijoje prašoma pakeisti kalbą
voicestringPateikiama, kai valdiklio sesijoje prašoma pakeisti balsą
website_contextstringPateikiama, kai valdiklis perduoda kiekvienos sesijos puslapio kontekstą

Atsako schema

Grąžinkite JSON objektą, aprašantį šio skambučio agento konfigūraciją. prompt ir voice yra privalomi; visa kita neprivaloma.

{
  "prompt":  "You are a helpful booking assistant for Acme Restaurant.",
  "voice":   "john",
  "product": "spark",
  "background_track": null,
  "tools":   []
}
LaukasTipasPrivalomasAprašymas
promptstringtaipSisteminė užklausa, valdanti agentą
voicestringtaipBalso ID iš GET /v1/voices, pvz., john. voice_name priimamas kaip sinonimas. Nežinomi balsai neatitinka validavimo ir skambutis atmetamas
productstringneNumatytoji reikšmė yra spark. Leidžiama: spark, bolt, storm-base, storm-base-with-ack, storm-extra, storm-extra-with-ack
thinking_levelstringneminimal, base (numatytoji reikšmė) arba extra. Storm produktams perrašoma: storm-extra* priverstinai naudoja extra, kiti storm-* priverstinai naudoja base
audio_context_modestringnefull (numatytoji reikšmė) arba reduced
watchdog_enabledbooleanneĮjungti šio skambučio priežiūrą. Numatytoji reikšmė false
storm_feedback_modestringnenone, acknowledgement (numatytoji reikšmė) arba tick
languagestringneTrumpinys, skirtas primary_language
primary_languagestringneKalbos kodas, normalizuotas (numatytoji reikšmė en). Neišsprendžiami kodai lemia skambučio atmetimą
has_additional_languagesbooleanneNumatytoji reikšmė false
additional_languagesarray of stringnePapildomos kalbos, į kurias agentas gali persijungti
background_trackstring | nullneAplinkos garso ID arba null
acknowledgement_prompt_modestringneauto (numatytoji reikšmė) arba manual (Storm su patvirtinimu produktams)
acknowledgement_promptstringneNaudojama, kai acknowledgement_prompt_mode="manual"
silence_interval_secondsinteger | nullne5–120. Skambinančiojo tylos sekundės prieš patikrinimą
silence_max_checkinsinteger | nullne1–10
silence_checkins_enabledbooleanneNumatytoji reikšmė true
connect_tone_enabledbooleanneNumatytoji reikšmė false
voicemail_actionstringneprompt (numatytoji reikšmė), hangup arba message
voicemail_messagestringneNaudojama, kai voicemail_action="message"
agent_namestringneRodomas pavadinimas, pateikiamas valdymo skydeliuose ir valdiklyje
org_namestringneOrganizacijos rodomas pavadinimas agento personai
toolsarrayneĮterptinės funkcijų įrankių schemos (žr. Funkcijų įrankiai)
call_idintegernePasirenkamas užklausos skambučio ID atkartojimas; ignoruojamas

Kadangi prompt ir voice yra privalomi, grąžinus {} arba bet kokį validavimo neatitinkantį atsaką, skambutis atmetamas su 422 — šiame kelyje nėra statinio agento atsarginio varianto (numeriui arba raktui žiniatinklio kablio režimu nėra priskirtas agentas).


Atsako dydžio riba


Pavyzdinė apdorojimo funkcija

import hashlib
import hmac
import json
import os

from fastapi import FastAPI, HTTPException, Request

app = FastAPI()
WEBHOOK_SECRET = os.environ["THUNDERPHONE_WEBHOOK_SECRET"]

def verify(body: bytes, signature: str) -> bool:
    expected = hmac.new(WEBHOOK_SECRET.encode(), body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature or "")

@app.post("/thunderphone-webhook")
async def webhook(request: Request):
    body = await request.body()
    if not verify(body, request.headers.get("X-ThunderPhone-Signature", "")):
        raise HTTPException(status_code=401)

    event = json.loads(body)
    if event["type"] == "telephony.incoming":
        caller = event["data"]["from_number"]
        prompt = (
            "Greet the caller as a San Francisco local…"
            if caller.startswith("+1415")
            else "You are a friendly customer support agent…"
        )
        return {
            "prompt": prompt,
            "voice": "john",
            "product": "spark",
        }
    if event["type"] == "web.incoming":
        return {
            "prompt": "You are the website's helpful voice assistant…",
            "voice": "john",
            "product": "spark",
        }
    return {}
import crypto from "node:crypto";
import express from "express";

const app = express();
const SECRET = process.env.THUNDERPHONE_WEBHOOK_SECRET;

function verify(body, signature) {
  const expected = crypto
    .createHmac("sha256", SECRET)
    .update(body)
    .digest("hex");
  return signature &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}

app.post(
  "/thunderphone-webhook",
  express.raw({ type: "application/json" }),
  (req, res) => {
    if (!verify(req.body, req.header("X-ThunderPhone-Signature"))) {
      return res.sendStatus(401);
    }
    const event = JSON.parse(req.body.toString("utf8"));

    if (event.type === "telephony.incoming" || event.type === "web.incoming") {
      const caller = event.data.from_number || "web";
      const prompt = caller.startsWith("+1415")
        ? "Greet the caller as a San Francisco local…"
        : "You are a friendly customer support agent…";
      return res.json({
        prompt,
        voice: "john",
        product: "spark",
      });
    }
    res.json({});
  },
);

Atsakymas su funkcijų įrankiais

Pridėkite įrankius, kad DI galėtų iškviesti jūsų API pokalbio metu:

{
  "prompt":  "You are a booking assistant. Use the available tools to help customers schedule appointments.",
  "voice":   "john",
  "product": "spark",
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "search_appointments",
        "description": "Find available appointment slots",
        "parameters": {
          "type": "object",
          "properties": {
            "date": { "type": "string", "description": "YYYY-MM-DD" },
            "service": { "type": "string" }
          },
          "required": ["date"]
        }
      },
      "endpoint": {
        "url": "https://api.example.com/appointments/search",
        "method": "POST",
        "headers": {
          "X-Api-Key": "your-key"
        }
      }
    }
  ]
}

Produktų lygių atmintinė

ProduktasVėlinimasSamprotavimasPatvirtinimas
sparkMažiausiasBazinis
boltMažasPatobulintas
storm-baseVidutinisStiprus
storm-base-with-ackVidutinisStiprusAutomatinis užpildymas mąstant
storm-extraDidesnisGilus
storm-extra-with-ackDidesnisGilusAutomatinis užpildymas mąstant

Susiję