ThunderPhone 2.0 är här.Kom igång själv, från 2 cent/minut.Läs lanseringsnyheten

Webhooks

telephony.incoming / web.incoming

Blockerande webhook som formar konfigurationen för ett inkommande samtal i realtid.

När ett inkommande telefonsamtal når ett nummer utan en tilldelad röstagent, eller när en webbwidgetsession startar med en publicerbar nyckel i mode="webhook", skickar ThunderPhone en blockerande telephony.incoming / web.incoming-begäran till din äldre webhook-URL och väntar upp till 10 sekunder på ett konfigurationssvar. Använd detta utbyte för att dynamiskt välja prompt, röst och verktyg per samtal — se guiden för dynamisk samtalskonfiguration för det kompletta mönstret.

Det blockerande utbytet har ingen reservlösning: om din hanterare returnerar en status som inte är 2xx, får en timeout eller returnerar en konfiguration som inte klarar valideringen, avvisas samtalet (telefonsamtalet kopplas inte fram; widgetsessionens begäran misslyckas med 502/422). Svara snabbt — den som ringer hör en uppringningssignal medan du fattar beslutet.

Begärans nyttolast

För telefonsamtal (telephony.incoming):

{
  "type": "telephony.incoming",
  "data": {
    "call_id":     987654321,
    "from_number": "+14155550199",
    "to_number":   "+15551234567"
  }
}
FältTypBeskrivning
call_idintegerSamtals-ID — stabilt i alla händelser för detta samtal
from_numberstringE.164-nummer för den som ringer
to_numberstringE.164-destination (ett av dina ThunderPhone-nummer)

För webbsessioner i webbwidgeten (web.incoming) identifierar data inbäddningssidan i stället för telefonnummer:

{
  "type": "web.incoming",
  "data": {
    "call_id": 987654322,
    "origin_domain": "https://example.com",
    "publishable_key_prefix": "pk_live_a1b2"
  }
}
FältTypBeskrivning
call_idintegerSamtals-ID
origin_domainstringSidans ursprung som är värd för widgeten
publishable_key_prefixstringDe första tecknen i den publicerbara nyckel som öppnade sessionen
language, primary_languagestringFinns när widgetsessionen begärde en språkåsidosättning
voicestringFinns när widgetsessionen begärde en röståsidosättning
website_contextstringFinns när widgeten skickade sidkontext per session

Svarsschema

Returnera ett JSON-objekt som beskriver agentkonfigurationen för det här samtalet. prompt och voice krävs; allt annat är valfritt.

{
  "prompt":  "You are a helpful booking assistant for Acme Restaurant.",
  "voice":   "john",
  "product": "spark",
  "background_track": null,
  "tools":   []
}
FältTypKrävsBeskrivning
promptstringjaSystemprompt som styr agenten
voicestringjaRöst-id från GET /v1/voices, t.ex. john. voice_name accepteras som ett alias. Okända röster underkänns vid validering och samtalet avvisas
productstringnejStandardvärdet är spark. Tillåtna: spark, bolt, storm-base, storm-base-with-ack, storm-extra, storm-extra-with-ack
thinking_levelstringnejminimal, base (standard) eller extra. Åsidosätts för Storm-produkter: storm-extra* tvingar extra, andra storm-* tvingar base
audio_context_modestringnejfull (standard) eller reduced
watchdog_enabledbooleannejAktivera övervakning för det här samtalet. Standardvärdet är false
additional_audio_contextboolean | nullnejInkludera de senaste turerna av uppringarens ljud i stället för endast den senaste turen, vilket förbättrar korrigeringar och insamling av data med mycket stavning eller siffror med en liten ökning av latens och kostnad. Aktiveras som standard för inkommande sessioner och är avstängt för utgående telefonsamtal; null behåller standardvärdet
storm_feedback_modestringnejnone, acknowledgement (standard) eller tick
languagestringnejFörkortning för primary_language
primary_languagestringnejSpråkkod, normaliserad (standard en). Koder som inte kan lösas avvisar samtalet
has_additional_languagesbooleannejStandardvärdet är false
additional_languagesarray of stringnejExtra språk som agenten kan växla till
native_voice_switchingbooleannejStandardvärdet är false. När samtalet växlar till ett annat språk byter du till en röst med språket som modersmål (matchad efter kön) i stället för att behålla den konfigurerade rösten
background_trackstring | nullnejId för bakgrundsljud eller null
acknowledgement_prompt_modestringnejauto (standard) eller manual (Storm-with-ack-produkter)
acknowledgement_promptstringnejAnvänds när acknowledgement_prompt_mode="manual"
silence_interval_secondsinteger | nullnej5–120. Sekunder av tystnad från uppringaren före en avstämning
silence_max_checkinsinteger | nullnej1–10
silence_checkins_enabledbooleannejStandardvärdet är true
connect_tone_enabledbooleannejStandardvärdet är false
voicemail_actionstringnejprompt (standard), hangup eller message
voicemail_messagestringnejAnvänds när voicemail_action="message"
agent_namestringnejVisningsnamn som rapporteras till instrumentpaneler och widgeten
org_namestringnejOrganisationens visningsnamn för agentens persona
toolsarraynejInfogade scheman för funktionsverktyg (se Funktionsverktyg)
call_idintegernejValfritt eko av begärans samtals-id; ignoreras

Eftersom prompt och voice krävs avvisar {} eller ett svar som inte klarar valideringen samtalet med 422 — det finns ingen reservagent på den här vägen (ett nummer eller en nyckel i webhook-läge har ingen tilldelad agent).


Begränsning av svarsstorlek


Exempel på hanterare

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({});
  },
);

Svar med funktionsverktyg

Koppla verktyg så att AI:n kan anropa dina API:er mitt i samtalet:

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

Snabbguide till produktnivåer

ProduktLatensResonemangBekräftelse
sparkLägstGrundläggande
boltLågFörbättrat
storm-baseMedelStarkt
storm-base-with-ackMedelStarktAutomatisk utfyllnad medan den tänker
storm-extraHögreDjupgående
storm-extra-with-ackHögreDjupgåendeAutomatisk utfyllnad medan den tänker

Relaterat