telephony.incoming / web.incoming
Blokkerende webhook die de configuratie van een inkomende oproep in realtime vormgeeft.
Wanneer een inkomende telefoongesprek een nummer bereikt zonder een
toegewezen agent, of een webwidgetsessie start met een publiceerbare sleutel in
mode="webhook", stuurt ThunderPhone een blokkerend
telephony.incoming / web.incoming-verzoek naar je
verouderde webhook-URL
en wacht maximaal 10 seconden op een configuratieantwoord. Gebruik deze
uitwisseling om per gesprek dynamisch een prompt, stem en hulpmiddelen te kiezen —
zie de handleiding voor dynamische oproepconfiguratie
voor het volledige patroon.
De blokkerende uitwisseling heeft geen terugvaloptie: als je handler een
niet-2xx-status retourneert, een time-out krijgt of een configuratie retourneert
die de validatie niet doorstaat, wordt de oproep geweigerd (het telefoongesprek
wordt niet verbonden; het verzoek voor de widgetsessie mislukt met 502/422).
Reageer snel — de beller hoort een kiestoon terwijl je beslist.
Verzoekpayload
Voor telefoongesprekken (telephony.incoming):
{
"type": "telephony.incoming",
"data": {
"call_id": 987654321,
"from_number": "+14155550199",
"to_number": "+15551234567"
}
}| Veld | Type | Beschrijving |
|---|---|---|
call_id | integer | Oproep-ID — stabiel voor alle gebeurtenissen van deze oproep |
from_number | string | E.164-nummer van de beller |
to_number | string | E.164-bestemming (een van je ThunderPhone-nummers) |
Voor webwidgetsessies (web.incoming) identificeert data de
insluitende pagina in plaats van telefoonnummers:
{
"type": "web.incoming",
"data": {
"call_id": 987654322,
"origin_domain": "https://example.com",
"publishable_key_prefix": "pk_live_a1b2"
}
}| Veld | Type | Beschrijving |
|---|---|---|
call_id | integer | Oproep-ID |
origin_domain | string | De pagina-origin waarop de widget wordt gehost |
publishable_key_prefix | string | Eerste tekens van de publiceerbare sleutel die de sessie heeft geopend |
language, primary_language | string | Aanwezig wanneer de widgetsessie een afwijkende taalinstelling heeft aangevraagd |
voice | string | Aanwezig wanneer de widgetsessie een afwijkende steminstelling heeft aangevraagd |
website_context | string | Aanwezig wanneer de widget paginacontext per sessie heeft doorgegeven |
Antwoordschema
Retourneer een JSON-object dat de agentconfiguratie voor deze oproep beschrijft.
prompt en voice zijn verplicht; al het andere is optioneel.
{
"prompt": "You are a helpful booking assistant for Acme Restaurant.",
"voice": "john",
"product": "spark",
"background_track": null,
"tools": []
}| Veld | Type | Verplicht | Beschrijving |
|---|---|---|---|
prompt | string | ja | Systeemprompt die de agent aanstuurt |
voice | string | ja | Spraak-ID uit GET /v1/voices, bijvoorbeeld john. voice_name wordt geaccepteerd als alias. Onbekende stemmen mislukken bij validatie en weigeren de oproep |
product | string | nee | Standaard ingesteld op spark. Toegestaan: spark, bolt, storm-base, storm-base-with-ack, storm-extra, storm-extra-with-ack |
thinking_level | string | nee | minimal, base (standaard) of extra. Overschreven voor Storm-producten: storm-extra* forceert extra, andere storm-* forceren base |
audio_context_mode | string | nee | full (standaard) of reduced |
watchdog_enabled | boolean | nee | Schakel toezicht in voor deze oproep. Standaard false |
additional_audio_context | boolean | null | nee | Neem de laatste paar beurten van de audio van de beller op in plaats van alleen de meest recente beurt, wat correcties en gegevensverzameling met veel spelling of cijfers verbetert tegen een kleine extra vertraging en kostprijs. Standaard ingeschakeld voor inkomende sessies en uitgeschakeld voor uitgaande telefoongesprekken; null behoudt de standaardinstelling |
storm_feedback_mode | string | nee | none, acknowledgement (standaard) of tick |
language | string | nee | Verkorte vorm van primary_language |
primary_language | string | nee | Taalcode, genormaliseerd (standaard en). Niet-oplosbare codes weigeren de oproep |
has_additional_languages | boolean | nee | Standaard false |
additional_languages | array of string | nee | Extra talen waarnaar de agent kan overschakelen |
native_voice_switching | boolean | nee | Standaard false. Wanneer de oproep naar een andere taal overschakelt, wissel dan naar een stem die moedertaalspreker is van die taal (afgestemd op gender) in plaats van de geconfigureerde stem te behouden |
background_track | string | null | nee | ID van omgevingsaudio of null |
acknowledgement_prompt_mode | string | nee | auto (standaard) of manual (Storm-with-ack-producten) |
acknowledgement_prompt | string | nee | Gebruikt wanneer acknowledgement_prompt_mode="manual" |
silence_interval_seconds | integer | null | nee | 5–120. Seconden stilte van de beller vóór een controle |
silence_max_checkins | integer | null | nee | 1–10 |
silence_checkins_enabled | boolean | nee | Standaard true |
connect_tone_enabled | boolean | nee | Standaard false |
voicemail_action | string | nee | prompt (standaard), hangup of message |
voicemail_message | string | nee | Gebruikt wanneer voicemail_action="message" |
agent_name | string | nee | Weergavenaam die wordt doorgegeven aan dashboards en de widget |
org_name | string | nee | Weergavenaam van de organisatie voor de persona van de agent |
tools | array | nee | Inline schema's voor functietools (zie Functietools) |
call_id | integer | nee | Optionele echo van de oproep-ID van het verzoek; genegeerd |
Omdat prompt en voice verplicht zijn, weigert het retourneren van {} of een
antwoord dat de validatie niet doorstaat de oproep met 422 — er is
geen fallback voor een statische agent op dit pad (een nummer of sleutel in webhookmodus
heeft geen toegewezen agent).
Limiet voor responsgrootte
Voorbeeldhandler
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({});
},
);Respons met functietools
Koppel tools zodat de AI tijdens het gesprek je API's kan aanroepen:
{
"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"
}
}
}
]
}Spiekbriefje productpakketten
| Product | Latentie | Redeneren | Bevestiging |
|---|---|---|---|
spark | Laagst | Basis | — |
bolt | Laag | Verbeterd | — |
storm-base | Gemiddeld | Sterk | — |
storm-base-with-ack | Gemiddeld | Sterk | Automatische opvulling tijdens het nadenken |
storm-extra | Hoger | Diepgaand | — |
storm-extra-with-ack | Hoger | Diepgaand | Automatische opvulling tijdens het nadenken |
Gerelateerd
De niet-blokkerende gebeurtenis aan het einde van een oproep.
Volledig JSON-schema voor tools[] en het ondertekende endpointcontract.
Abonneer meerdere URL's op telephony.incoming / web.incoming.
Patronen voor prompts, tools en A/B-tests per beller.