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

Webhooks

Vue d’ensemble des webhooks

Comment ThunderPhone transmet les événements en temps réel, comment vérifier les signatures et comment les modèles de livraison hérités et basés sur les endpoints se comparent.

ThunderPhone envoie des requêtes HTTP POST à votre serveur lorsqu'un événement se produit pendant un appel : un appel entrant démarre, un appel se termine, une exécution d'évaluation se termine, une alerte se déclenche, etc. Il existe deux modèles de livraison :

Tous les types d'événements répertoriés dans le catalogue des événements sont transmis via des points de terminaison webhook. Les six événements du cycle de vie des appels (telephony.incoming, telephony.complete, telephony.tool, web.incoming, web.complete, web.tool) sont également envoyés au webhook hérité à URL unique. Si vous disposez à la fois d'une URL héritée et d'un point de terminaison correspondant, vous recevez l'événement sur les deux chemins. Le comportement bloquant (l'échange de configuration telephony.incoming / web.incoming et le répartiteur d'outils en mode webhook) se trouve exclusivement sur le chemin hérité ; chaque livraison vers un point de terminaison est une notification envoyée sans attente de réponse.

Format de la charge utile

Les livraisons vers les points de terminaison sont un objet JSON avec data, event_id et type :

{
  "data": {
    "call_id": 987654321,
    "from_number": "+14155550199",
    "to_number": "+15551234567"
  },
  "event_id": "3f6b2ad0-1c9e-4a57-9f2b-8f6f0f9d2f11",
  "type": "telephony.incoming"
}

event_id est unique pour chaque événement émis. Il est identique lors des tentatives et pour chaque point de terminaison qui reçoit l'événement : dédupliquez sur cette valeur.

Le webhook hérité à URL unique envoie les mêmes type et data, mais sans event_id :

{
  "type": "telephony.incoming",
  "data": { "call_id": 987654321, "from_number": "+14155550199", "to_number": "+15551234567" }
}

Sur le réseau, chaque corps est sérialisé de manière canonique : clés triées par ordre alphabétique, sans espaces, en UTF-8. Les exemples mis en forme dans cette documentation sont fournis uniquement pour faciliter la lecture.

Consultez le catalogue des événements pour obtenir la liste complète des types d'événements et des champs de charge utile.

Vérification de signature

Chaque requête contient deux signatures, toutes deux HMAC-SHA256 utilisant le secret du point de terminaison comme clé (ou le secret webhook au niveau de votre organisation pour les livraisons héritées) :

  • X-ThunderPhone-Signature-V2: t=<unix seconds>,v1=<hex> signe l’ horodatage, la méthode, le chemin et la requête, ainsi que le corps brut. Vérifiez celle-ci et rejetez les requêtes dont t diffère de plus de 5 minutes de votre horloge afin qu’une requête interceptée ne puisse pas être rejouée ultérieurement. Les tentatives sont signées à nouveau lors de leur envoi.
  • X-ThunderPhone-Signature signe uniquement le corps brut. Elle est toujours envoyée, sans modification, afin que les vérificateurs existants continuent de fonctionner.

Étapes

  1. Lisez l’en-tête, la méthode, le chemin et la requête bruts, ainsi que le corps brut avant toute analyse.
  2. Calculez le HMAC-SHA256 hexadécimal de <t>\n<METHOD>\n<path and query>\n<body> avec votre secret.
  3. Rejetez la requête si t diffère de plus de 300 secondes, ou si le résultat ne correspond pas à v1 lors d’une comparaison en temps constant.

Les corps utilisent la sérialisation JSON canonique (clés triées, séparateurs compacts), et nous signons exactement les octets que nous transmettons. Le guide de vérification contient la chaîne signée exacte, un exemple détaillé, du code sans le SDK et l’en-tête historique.

Python (Flask)
from flask import Flask, abort, request
from thunderphone import verify_signature
 
app = Flask(__name__)
 
@app.post("/thunderphone-webhook")
def handle():
    query = request.query_string.decode()
    if not verify_signature(
        request.headers.get("X-ThunderPhone-Signature-V2"),
        secret=WEBHOOK_SECRET,
        method=request.method,
        path=request.path + (f"?{query}" if query else ""),
        body=request.get_data(),  # raw bytes
    ):
        abort(401)
    event = request.get_json()
    # dispatch on event["type"] …
    return "", 204
Node.js (Express)
import express from "express";
import { verifySignature } from "@thunderphone/sdk";
 
const app = express();
app.post(
  "/thunderphone-webhook",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const ok = verifySignature({
      header: req.header("X-ThunderPhone-Signature-V2"),
      secret: process.env.WEBHOOK_SECRET,
      method: req.method,
      path: req.originalUrl,
      body: req.body,
    });
    if (!ok) return res.sendStatus(401);
    const event = JSON.parse(req.body.toString("utf8"));
    // dispatch on event.type …
    res.sendStatus(204);
  },
);

Sémantique de livraison

Ces sémantiques s’appliquent aux livraisons de points de terminaison. Le webhook hérité à URL unique effectue une seule tentative synchrone sans nouvelle tentative, à l’exception de l’échange de configuration d’appel téléphonique telephony.incoming, qui fait l’objet d’une nouvelle tentative.

Nouvelles tentatives

Chaque événement fait immédiatement l’objet d’une tentative. Toute réponse 2xx confirme la livraison. Pour tout autre résultat (non-2xx, erreur de connexion, délai d’expiration), nous effectuons une nouvelle tentative 1 min, 5 min, 30 min, 2 h, 6 h, 12 h et 24 h après la première tentative — soit 8 tentatives sur 24 heures. Si toutes les tentatives échouent, la livraison s’arrête et le point de terminaison est marqué status="failing" dans les points de terminaison de webhook ; nous envoyons un e-mail aux propriétaires et administrateurs de votre organisation et l’affichons dans le tableau de bord. Renvoyez 2xx dès que la charge utile est acceptée de manière durable ; traitez-la de manière asynchrone. Les envois de test depuis l’API de test du point de terminaison bénéficient d’une nouvelle tentative environ une minute après la première tentative et ne modifient jamais l’état du point de terminaison.

Ordre

L’ordre de livraison est fourni au mieux. En pratique, nous livrons les événements dans l’ordre de leur émission, mais les nouvelles tentatives peuvent les réordonner en cas d’échec. Dédupliquez et rapprochez toujours par call_id / ID d’objet.

Doublons

La livraison est au moins une fois : une nouvelle tentative après une réponse que nous n’avons jamais reçue peut dupliquer un événement. Chaque nouvelle tentative contient le même event_id ; stockez donc les ID traités et ignorez les répétitions. event_id est également partagé entre les points de terminaison — deux points de terminaison abonnés au même événement reçoivent le même event_id.

Délais d’expiration

Les livraisons de points de terminaison ont un délai d’expiration de 30 s par tentative. Sur le chemin hérité, les requêtes bloquantes qui pilotent le comportement des appels en direct — l’échange de configuration telephony.incoming / web.incoming — sont limitées à 10 s pour la première tentative d’un appel téléphonique et à 13 s pour l’ensemble de l’échange, y compris son unique nouvelle tentative, et à 10 s pour une session de widget. Une réponse lente retarde la prise d’appel ; visez donc une réponse en quelques secondes. La répartition des outils en mode webhook autorise 20 s par défaut, et les déclarations d’outils peuvent définir un timeout de niveau supérieur.

IP source

Les webhooks sortants proviennent de la plage d’IP cloud de ThunderPhone. Si votre pare-feu exige une liste d’autorisation, contactez le support et nous vous communiquerons les plages actuelles.

Choisir entre les webhooks historiques et ceux basés sur des points de terminaison

FonctionnalitéHérité (/v1/webhook)Points de terminaison (/v1/developer/webhook-endpoints)
Nombre d’URL1 par organisationPlusieurs par organisation
Couverture des événementstelephony.* / web.* uniquementLes 10 types d’événements
Filtre d’événements—Par point de terminaison
Nouvelles tentativesAucune, sauf une pour l’échange de configuration d’appel téléphonique8 tentatives sur 24 h
Enveloppetype + datatype + data + event_id
Rotation du secretRemplace le secret uniqueSecret par point de terminaison
Désactiver sans supprimerPUT /v1/webhook avec {"url": ""}status=disabled
Visibilité du statut—active / disabled / failing
Échange de configuration bloquantOui (telephony.incoming / web.incoming)Jamais — notifications uniquement
Idéal pourConfiguration dynamique des appelsConsommation d’événements en production

Les nouvelles intégrations doivent consommer les événements via des webhooks basés sur des points de terminaison. Conservez (ou ajoutez) une URL historique uniquement si vous configurez les appels dynamiquement au moment de la prise d’appel ou utilisez la répartition d’outils en mode webhook — ces échanges requête/réponse ne fonctionnent que sur le chemin historique.


Associé