Open in
telephony.incoming / web.incoming
Blokkerende webhook die de configuratie van een inkomende oproep in realtime vormgeeft.
Wanneer een inkomend telefoongesprek een nummer bereikt zonder
toegewezen spraakagent, of wanneer een websidgetsessie start op een publiceerbare sleutel in
mode="webhook", stuurt ThunderPhone een blokkerend
telephony.incoming / web.incoming-verzoek naar je
verouderde webhook-URL
en wacht op een configuratieantwoord: maximaal 13 seconden voor een
telefoongesprek, inclusief één nieuwe poging, en maximaal 10 seconden voor een widgetsessie
(zie Time-outs en nieuwe pogingen). Gebruik deze
uitwisseling om per gesprek dynamisch een prompt, stem en tools te kiezen —
zie de gids voor dynamische oproepconfiguratie
voor het volledige patroon.
Time-outs en nieuwe pogingen
Reageer snel: de beller wacht aan de lijn terwijl je beslist.
Voor een telefoongesprek (telephony.incoming):
- De eerste poging kan maximaal 10 seconden duren.
- Als deze een time-out krijgt, geen verbinding kan maken of een
5xx-,408- of429-antwoord ontvangt, probeert ThunderPhone het nog één keer. De nieuwe poging krijgt wat er over is van een budget van 13 seconden voor de volledige uitwisseling: ongeveer 3 seconden na een eerste poging met een time-out, bijna alle 13 seconden na een snelle fout. Er wordt niet opnieuw geprobeerd als er minder dan 2 seconden over zouden zijn. - Elk ander
4xx-antwoord, een antwoord boven de groottelimiet, of een2xxwaarvan de body geen geldige configuratie is, is definitief en wordt niet opnieuw geprobeerd. - Beide pogingen verzenden dezelfde body, dezelfde header
X-ThunderPhone-Signatureen dezelfde headerX-ThunderPhone-Idempotency-Key(telephony-incoming-<call_id>), zodat een handler met neveneffecten de nieuwe poging kan herkennen.
Er is geen fallback-spraakagent. Als er geen bruikbare configuratie binnenkomt, wordt de oproep direct beëindigd in plaats van de beller op een stille lijn te laten wachten. De oproep verschijnt in de Oproepgeschiedenis als een mislukte, niet-gefactureerde oproep met end_reason inbound_config_webhook_failed, en de gebruikelijke gebeurtenis telephony.complete wordt verzonden.
Een widgetsessie (web.incoming) krijgt één poging van maximaal 10 seconden. Als deze mislukt, mislukt het sessieverzoek met 502 of 422.
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 herkomstpagina waarop de widget wordt gehost, zoals de browser deze heeft verzonden. Een niet-browserclient kan elke waarde verzenden, dus beschouw dit niet als bewijs van waar het verzoek vandaan kwam |
publishable_key_prefix | string | Eerste tekens van de publiceerbare sleutel waarmee de sessie is geopend |
language, primary_language | string | Aanwezig wanneer de widgetsessie een taaloverschrijving heeft aangevraagd |
voice | string | Aanwezig wanneer de widgetsessie een stemoverschrijving heeft aangevraagd |
website_context | string | Aanwezig wanneer de widget paginacontext per sessie heeft doorgegeven |
De overschrijvingsvelden (language, voice, website_context en eventuele
browser-variables) worden alleen verzonden wanneer de publiceerbare sleutel deze toestaat in
de bijbehorende browseroverschrijvingen. Een
sessieverzoek met een overschrijving die de sleutel niet toestaat, wordt afgewezen voordat
deze webhook wordt aangeroepen.
Antwoordschema
Geef een JSON-object terug dat de configuratie van de spraakagent voor dit gesprek beschrijft.
prompt en voice zijn vereist; 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 | Vereist | Beschrijving |
|---|---|---|---|
prompt | string | ja | Systeemprompt die de spraakagent aanstuurt |
voice | string | ja | Stem-ID van 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* dwingt extra af, andere storm-* dwingen base af |
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. Dit verbetert correcties en het verzamelen van gegevens met veel spelling of nummers, met een kleine extra vertraging en kosten. 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). Codes die niet kunnen worden opgelost, weigeren de oproep |
has_additional_languages | boolean | nee | Standaard false |
additional_languages | array van string | nee | Extra talen waarnaar de spraakagent kan overschakelen |
native_voice_switching | boolean | nee | Standaard false. Wanneer de oproep naar een andere taal overschakelt, wissel je naar een stem die moedertaalspreker is van die taal (afgestemd op gender) in plaats van de geconfigureerde stem te behouden |
language_switching | string | null | nee | ask_first (standaard) of follow_caller; null is hetzelfde als ask_first. Bepaalt hoe een meertalige spraakagent omgaat met de andere persoon die een van zijn andere talen spreekt. ask_first schakelt direct over wanneer deze persoon om de taal vraagt of er een volledige zin in zegt, en vraagt na iets korts, zoals „sí”, of er in die taal moet worden doorgegaan. follow_caller schakelt over naar elke geconfigureerde taal die deze persoon spreekt, zelfs bij één woord. Geldt alleen wanneer additional_languages is ingesteld |
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 |
pronunciation_dicts | boolean | null | nee | Standaard false; null is hetzelfde als false. Wanneer true, gebruikt de Cartesia-spraak van de oproep het uitspraakwoordenboek van ThunderPhone voor de taal van de oproep, als er een bestaat. Werkt voor telefoon- en widgetoproepen. Geldt alleen voor de eigen Cartesia-stemmen van ThunderPhone, niet voor stemmen met je eigen Cartesia-API-sleutel. Niet gebruikt met agent_id, waarbij de instellingen van de opgeslagen spraakagent gelden |
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 spraakagent |
tools | array | nee | Inline schema's voor functiehulpmiddelen (zie Functiehulpmiddelen) |
transfer_destinations | array van object | null | nee | Maximaal 50 items {name, number} waarnaar deze oproep kan worden doorverbonden, gevalideerd zoals de doorschakelnummers van een spraakagent. De spraakagent verbindt door op naam en alleen naar deze items; [] betekent geen doorschakelingen. Laat dit weg of stuur null om de spraakagent naar een nummer naar keuze te laten doorverbinden |
timezone | string | nee | IANA-tijdzone voor deze oproep, zoals America/Chicago. De spraakagent vermeldt de datum en tijd daarin, in plaats van die in de eigen tijdzone van de spraakagent. Ook toegestaan naast agent_id. Waarden die geen IANA-tijdzones zijn, worden genegeerd. Zie De tijdzone per oproep instellen |
call_id | integer | nee | Optionele echo van de oproep-ID van het verzoek; genegeerd |
Omdat prompt en voice vereist zijn, weigert het retourneren van {} of een andere respons die de validatie niet doorstaat de oproep: een telefoongesprek wordt beëindigd zoals beschreven in Time-outs en nieuwe pogingen, en een widgetsessieverzoek mislukt met 422. Er is geen terugval op een statische agent via 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.