ThunderPhone 2.0 is live.Direct zelf aan de slag, vanaf 2¢/min.Lees de aankondiging

Webhooks

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- of 429-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 een 2xx waarvan de body geen geldige configuratie is, is definitief en wordt niet opnieuw geprobeerd.
  • Beide pogingen verzenden dezelfde body, dezelfde header X-ThunderPhone-Signature en dezelfde header X-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"
  }
}
VeldTypeBeschrijving
call_idintegerOproep-ID — stabiel voor alle gebeurtenissen van deze oproep
from_numberstringE.164-nummer van de beller
to_numberstringE.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"
  }
}
VeldTypeBeschrijving
call_idintegerOproep-ID
origin_domainstringDe 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_prefixstringEerste tekens van de publiceerbare sleutel waarmee de sessie is geopend
language, primary_languagestringAanwezig wanneer de widgetsessie een taaloverschrijving heeft aangevraagd
voicestringAanwezig wanneer de widgetsessie een stemoverschrijving heeft aangevraagd
website_contextstringAanwezig 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":   []
}
VeldTypeVereistBeschrijving
promptstringjaSysteemprompt die de spraakagent aanstuurt
voicestringjaStem-ID van GET /v1/voices, bijvoorbeeld john. voice_name wordt geaccepteerd als alias. Onbekende stemmen mislukken bij validatie en weigeren de oproep
productstringneeStandaard ingesteld op spark. Toegestaan: spark, bolt, storm-base, storm-base-with-ack, storm-extra, storm-extra-with-ack
thinking_levelstringneeminimal, base (standaard) of extra. Overschreven voor Storm-producten: storm-extra* dwingt extra af, andere storm-* dwingen base af
audio_context_modestringneefull (standaard) of reduced
watchdog_enabledbooleanneeSchakel toezicht in voor deze oproep. Standaard false
additional_audio_contextboolean | nullneeNeem 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_modestringneenone, acknowledgement (standaard) of tick
languagestringneeVerkorte vorm van primary_language
primary_languagestringneeTaalcode, genormaliseerd (standaard en). Codes die niet kunnen worden opgelost, weigeren de oproep
has_additional_languagesbooleanneeStandaard false
additional_languagesarray van stringneeExtra talen waarnaar de spraakagent kan overschakelen
native_voice_switchingbooleanneeStandaard 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_switchingstring | nullneeask_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_trackstring | nullneeID van omgevingsaudio of null
acknowledgement_prompt_modestringneeauto (standaard) of manual (Storm-with-ack-producten)
acknowledgement_promptstringneeGebruikt wanneer acknowledgement_prompt_mode="manual"
silence_interval_secondsinteger | nullnee5–120. Seconden stilte van de beller vóór een controle
silence_max_checkinsinteger | nullnee1–10
silence_checkins_enabledbooleanneeStandaard true
connect_tone_enabledbooleanneeStandaard false
pronunciation_dictsboolean | nullneeStandaard 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_actionstringneeprompt (standaard), hangup of message
voicemail_messagestringneeGebruikt wanneer voicemail_action="message"
agent_namestringneeWeergavenaam die wordt doorgegeven aan dashboards en de widget
org_namestringneeWeergavenaam van de organisatie voor de persona van de spraakagent
toolsarrayneeInline schema's voor functiehulpmiddelen (zie Functiehulpmiddelen)
transfer_destinationsarray van object | nullneeMaximaal 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
timezonestringneeIANA-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_idintegerneeOptionele 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

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

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

ProductLatentieRedenerenBevestiging
sparkLaagstBasis—
boltLaagVerbeterd—
storm-baseGemiddeldSterk—
storm-base-with-ackGemiddeldSterkAutomatische opvulling tijdens het nadenken
storm-extraHogerDiepgaand—
storm-extra-with-ackHogerDiepgaandAutomatische opvulling tijdens het nadenken

Gerelateerd