ThunderPhone 2.0 este acum disponibil.Îl configurați singur, de la 2 ¢/min.Citiți anunțul

Webhooks

telephony.incoming / web.incoming

Webhook blocant care configurează în timp real o apelare de intrare.

Când un apel telefonic de intrare ajunge la un număr fără un agent atribuit sau când începe o sesiune de widget web cu o cheie publicabilă în mode="webhook", ThunderPhone trimite o solicitare blocantă telephony.incoming / web.incoming către URL-ul webhook tradițional și așteaptă până la 10 secunde pentru un răspuns de configurare. Utilizați acest schimb pentru a alege dinamic un prompt, o voce și instrumente pentru fiecare apel — consultați ghidul de configurare dinamică a apelurilor pentru fluxul complet.

Schimbul blocant nu are soluție de rezervă: dacă handlerul dumneavoastră returnează un status non-2xx, expiră sau returnează o configurație care nu trece validarea, apelul este respins (apelul telefonic nu se conectează; solicitarea sesiunii widgetului eșuează cu 502/422). Răspundeți rapid — apelantul aude tonul de apel în timp ce luați decizia.

Încărcătura solicitării

Pentru apelurile telefonice (telephony.incoming):

{
  "type": "telephony.incoming",
  "data": {
    "call_id":     987654321,
    "from_number": "+14155550199",
    "to_number":   "+15551234567"
  }
}
CâmpTipDescriere
call_idintegerID-ul apelului — stabil în toate evenimentele pentru acest apel
from_numberstringNumărul E.164 al apelantului
to_numberstringDestinația E.164 (unul dintre numerele dumneavoastră ThunderPhone)

Pentru sesiunile widgetului web (web.incoming), data identifică pagina de încorporare în locul numerelor de telefon:

{
  "type": "web.incoming",
  "data": {
    "call_id": 987654322,
    "origin_domain": "https://example.com",
    "publishable_key_prefix": "pk_live_a1b2"
  }
}
CâmpTipDescriere
call_idintegerID-ul apelului
origin_domainstringOriginea paginii care găzduiește widgetul
publishable_key_prefixstringPrimele caractere ale cheii publicabile care a deschis sesiunea
language, primary_languagestringPrezente când sesiunea widgetului a solicitat o suprascriere a limbii
voicestringPrezentă când sesiunea widgetului a solicitat o suprascriere a vocii
website_contextstringPrezent când widgetul a transmis contextul paginii pentru fiecare sesiune

Schema de răspuns

Returnați un obiect JSON care descrie configurația agentului pentru acest apel. prompt și voice sunt obligatorii; toate celelalte câmpuri sunt opționale.

{
  "prompt":  "You are a helpful booking assistant for Acme Restaurant.",
  "voice":   "john",
  "product": "spark",
  "background_track": null,
  "tools":   []
}
CâmpTipObligatoriuDescriere
promptstringdaPrompt de sistem care controlează agentul
voicestringdaID vocal din GET /v1/voices, de exemplu john. voice_name este acceptat ca alias. Vocile necunoscute nu trec validarea și resping apelul
productstringnuValoarea implicită este spark. Valori permise: spark, bolt, storm-base, storm-base-with-ack, storm-extra, storm-extra-with-ack
thinking_levelstringnuminimal, base (implicit) sau extra. Suprascris pentru produsele Storm: storm-extra* impune extra, iar celelalte storm-* impun base
audio_context_modestringnufull (implicit) sau reduced
watchdog_enabledbooleannuActivați supravegherea pentru acest apel. Valoarea implicită este false
additional_audio_contextboolean | nullnuIncludeți ultimele câteva replici din audio-ul apelantului, nu doar cea mai recentă replică, pentru a îmbunătăți corectările și colectarea datelor cu multe litere sau numere, cu un mic cost suplimentar de latență și cost. Este activat implicit pentru sesiunile de intrare și dezactivat pentru apelurile telefonice de ieșire; null păstrează valoarea implicită
storm_feedback_modestringnunone, acknowledgement (implicit) sau tick
languagestringnuPrescurtare pentru primary_language
primary_languagestringnuCod de limbă, normalizat (implicit en). Codurile care nu pot fi rezolvate resping apelul
has_additional_languagesbooleannuValoarea implicită este false
additional_languagesarray of stringnuLimbi suplimentare la care agentul poate comuta
native_voice_switchingbooleannuValoarea implicită este false. Când apelul comută la altă limbă, folosiți o voce nativă pentru acea limbă (potrivită după gen) în loc să păstrați vocea configurată
background_trackstring | nullnuID audio ambiental sau null
acknowledgement_prompt_modestringnuauto (implicit) sau manual (produse Storm-with-ack)
acknowledgement_promptstringnuFolosit când acknowledgement_prompt_mode="manual"
silence_interval_secondsinteger | nullnu5–120. Secunde de tăcere ale apelantului înainte de o verificare
silence_max_checkinsinteger | nullnu1–10
silence_checkins_enabledbooleannuValoarea implicită este true
connect_tone_enabledbooleannuValoarea implicită este false
voicemail_actionstringnuprompt (implicit), hangup sau message
voicemail_messagestringnuFolosit când voicemail_action="message"
agent_namestringnuNume afișat raportat în tablourile de bord și widget
org_namestringnuNumele de afișare al organizației pentru persona agentului
toolsarraynuScheme inline pentru instrumente de funcții (consultați Instrumente de funcții)
call_idintegernuEcou opțional al ID-ului apelului din cerere; este ignorat

Deoarece prompt și voice sunt obligatorii, returnarea {} sau a oricărui răspuns care nu trece validarea respinge apelul cu 422 — nu există o rezervă pentru agent static pe această rută (un număr sau o cheie în modul webhook nu are un agent atribuit).


Limită de dimensiune a răspunsului


Exemplu de handler

Python (FastAPI)
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 {}
Node.js (Express)
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({});
  },
);

Răspuns cu instrumente de funcții

Atașați instrumente astfel încât AI-ul să vă poată apela API-urile în timpul conversației:

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

Fișă rapidă a nivelurilor de produs

ProdusLatențăRaționamentConfirmare
sparkCea mai micăDe bază
boltMicăÎmbunătățit
storm-baseMediePuternic
storm-base-with-ackMediePuternicText de umplere automat în timpul procesării
storm-extraMai mareProfund
storm-extra-with-ackMai mareProfundText de umplere automat în timpul procesării

Asociate