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

Webhooks

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"
  }
}
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 pagina-origin waarop de widget wordt gehost
publishable_key_prefixstringEerste tekens van de publiceerbare sleutel die de sessie heeft geopend
language, primary_languagestringAanwezig wanneer de widgetsessie een afwijkende taalinstelling heeft aangevraagd
voicestringAanwezig wanneer de widgetsessie een afwijkende steminstelling heeft aangevraagd
website_contextstringAanwezig 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":   []
}
VeldTypeVerplichtBeschrijving
promptstringjaSysteemprompt die de agent aanstuurt
voicestringjaSpraak-ID uit 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* forceert extra, andere storm-* forceren base
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, 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_modestringneenone, acknowledgement (standaard) of tick
languagestringneeVerkorte vorm van primary_language
primary_languagestringneeTaalcode, genormaliseerd (standaard en). Niet-oplosbare codes weigeren de oproep
has_additional_languagesbooleanneeStandaard false
additional_languagesarray of stringneeExtra talen waarnaar de agent kan overschakelen
native_voice_switchingbooleanneeStandaard 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_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
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 agent
toolsarrayneeInline schema's voor functietools (zie Functietools)
call_idintegerneeOptionele 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

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