ThunderPhone 2.0 est disponible.En libre-service, à partir de 2 ¢/min.Découvrir l’annonce

Webhooks

telephony.incoming / web.incoming

Webhook bloquant qui façonne la configuration d’un appel entrant en temps réel.

Lorsqu'un appel téléphonique entrant atteint un numéro sans agent attribué, ou qu'une session de widget web démarre sur une clé publiable en mode="webhook", ThunderPhone envoie une requête telephony.incoming / web.incoming bloquante à votre URL de webhook héritée et attend jusqu'à 10 secondes une réponse de configuration. Utilisez cet échange pour choisir dynamiquement un prompt, une voix et des outils pour chaque appel — consultez le guide de configuration dynamique des appels pour le modèle de bout en bout.

L'échange bloquant n'a aucun repli : si votre gestionnaire renvoie un statut non-2xx, expire ou renvoie une configuration qui échoue à la validation, l'appel est rejeté (l'appel téléphonique ne se connecte pas ; la requête de session du widget échoue avec 502/422). Répondez rapidement — l'appelant entend la tonalité de retour pendant votre décision.

Corps de la requête

Pour les appels téléphoniques (telephony.incoming) :

{
  "type": "telephony.incoming",
  "data": {
    "call_id":     987654321,
    "from_number": "+14155550199",
    "to_number":   "+15551234567"
  }
}
ChampTypeDescription
call_idintegerID de l'appel — stable sur tous les événements de cet appel
from_numberstringNuméro de l'appelant au format E.164
to_numberstringDestination E.164 (l'un de vos numéros ThunderPhone)

Pour les sessions de widget web (web.incoming), data identifie la page d'intégration au lieu des numéros de téléphone :

{
  "type": "web.incoming",
  "data": {
    "call_id": 987654322,
    "origin_domain": "https://example.com",
    "publishable_key_prefix": "pk_live_a1b2"
  }
}
ChampTypeDescription
call_idintegerID de l'appel
origin_domainstringOrigine de la page qui héberge le widget
publishable_key_prefixstringPremiers caractères de la clé publiable ayant ouvert la session
language, primary_languagestringPrésent lorsque la session du widget a demandé un remplacement de langue
voicestringPrésent lorsque la session du widget a demandé un remplacement de voix
website_contextstringPrésent lorsque le widget a transmis un contexte de page par session

Schéma de réponse

Renvoyez un objet JSON décrivant la configuration de l’agent pour cet appel. prompt et voice sont obligatoires ; tous les autres champs sont facultatifs.

{
  "prompt":  "You are a helpful booking assistant for Acme Restaurant.",
  "voice":   "john",
  "product": "spark",
  "background_track": null,
  "tools":   []
}
ChampTypeObligatoireDescription
promptstringouiPrompt système qui pilote l’agent
voicestringouiIdentifiant vocal provenant de GET /v1/voices, par exemple john. voice_name est accepté comme alias. Les voix inconnues échouent à la validation et refusent l’appel
productstringnonLa valeur par défaut est spark. Valeurs autorisées : spark, bolt, storm-base, storm-base-with-ack, storm-extra, storm-extra-with-ack
thinking_levelstringnonminimal, base (par défaut) ou extra. Remplacé pour les produits Storm : storm-extra* force extra, les autres storm-* forcent base
audio_context_modestringnonfull (par défaut) ou reduced
watchdog_enabledbooleannonActivez la supervision pour cet appel. Valeur par défaut : false
additional_audio_contextboolean | nullnonInclut les derniers tours audio de l’appelant plutôt que seulement le tour le plus récent, ce qui améliore les corrections et la collecte de données riches en orthographe ou en nombres, avec un léger surcoût de latence et de coût. Activé par défaut pour les sessions entrantes et désactivé pour les appels téléphoniques sortants ; null conserve la valeur par défaut
storm_feedback_modestringnonnone, acknowledgement (par défaut) ou tick
languagestringnonRaccourci pour primary_language
primary_languagestringnonCode de langue, normalisé (valeur par défaut : en). Les codes non résolubles refusent l’appel
has_additional_languagesbooleannonValeur par défaut : false
additional_languagesarray of stringnonLangues supplémentaires vers lesquelles l’agent peut basculer
native_voice_switchingbooleannonValeur par défaut : false. Lorsque l’appel bascule vers une autre langue, remplace la voix par une voix native de cette langue (correspondant au genre) au lieu de conserver la voix configurée
background_trackstring | nullnonIdentifiant audio d’ambiance ou null
acknowledgement_prompt_modestringnonauto (par défaut) ou manual (produits Storm-with-ack)
acknowledgement_promptstringnonUtilisé lorsque acknowledgement_prompt_mode="manual"
silence_interval_secondsinteger | nullnon5 à 120. Secondes de silence de l’appelant avant une vérification
silence_max_checkinsinteger | nullnon1 à 10
silence_checkins_enabledbooleannonValeur par défaut : true
connect_tone_enabledbooleannonValeur par défaut : false
voicemail_actionstringnonprompt (par défaut), hangup ou message
voicemail_messagestringnonUtilisé lorsque voicemail_action="message"
agent_namestringnonNom d’affichage transmis aux dashboards et au widget
org_namestringnonNom d’affichage de l’organisation pour le persona de l’agent
toolsarraynonSchémas de function tools intégrés (voir Function Tools)
call_idintegernonÉcho facultatif de l’identifiant d’appel de la requête ; ignoré

Comme prompt et voice sont obligatoires, renvoyer {} ou toute réponse qui échoue à la validation refuse l’appel avec 422 : il n’y a aucun repli sur un agent statique sur cette route (un numéro ou une clé en mode webhook n’a aucun agent attribué).


Limite de taille de réponse


Exemple de gestionnaire

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

Réponse avec des outils de fonction

Ajoutez des outils afin que l'IA puisse appeler vos API pendant la conversation :

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

Aide-mémoire des niveaux de produit

ProduitLatenceRaisonnementAcquiescement
sparkLa plus faibleBasique
boltFaibleAmélioré
storm-baseMoyenneSolide
storm-base-with-ackMoyenneSolideRemplissage automatique pendant la réflexion
storm-extraPlus élevéeApprofondi
storm-extra-with-ackPlus élevéeApprofondiRemplissage automatique pendant la réflexion

Associé