> ## Documentation Index
> Fetch the complete documentation index at: https://docs.thunderphone.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Présentation des webhooks

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

ThunderPhone envoie des requêtes HTTP `POST` à votre serveur lorsque des événements
se produisent pendant un appel — un appel entrant commence, un appel se termine, une exécution
d'évaluation se termine, une alerte est déclenchée, etc. Il existe **deux modèles
de livraison** :

<CardGroup cols={2}>
  <Card title="Points de terminaison de webhook (recommandé)" icon="bolt" href="/fr/webhooks/endpoints">
    Plusieurs URL, des secrets par point de terminaison, des filtres d'événements par point de terminaison
    et des nouvelles tentatives automatiques.
    Gérez-les via `GET/POST/PATCH/DELETE /v1/developer/webhook-endpoints`.
  </Card>

  <Card title="Webhook historique à URL unique" icon="link" href="/api-reference/organizations#legacy-single-url-webhook">
    Une URL par organisation. Transmet les événements du cycle de vie des appels, y compris les
    échanges de configuration **bloquants**. Géré via `GET/PUT /v1/webhook`.
  </Card>
</CardGroup>

Les dix types d'événements du [catalogue des événements](/fr/webhooks/events) sont
transmis via les points de terminaison de 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 historique à URL unique — si vous disposez à la fois d'une URL historique 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`](/fr/webhooks/call-incoming)
et la [répartition des outils](/fr/tools/overview) en
mode webhook) existe exclusivement sur le chemin historique ; chaque livraison à un
point de terminaison est une notification sans attente de réponse.

## Format de la charge utile

Les livraisons aux points de terminaison sont un objet JSON contenant `data`, `event_id` et
`type` :

```json theme={null}
{
  "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 entre les nouvelles tentatives
**et** entre tous les points de terminaison qui reçoivent l'événement — utilisez-le pour dédupliquer.

Le webhook historique à URL unique envoie les mêmes `type` et `data`, mais
**sans** `event_id` :

```json theme={null}
{
  "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, UTF-8. Les exemples mis en forme dans
cette documentation sont fournis uniquement pour faciliter la lecture.

Consultez le [catalogue des événements](/fr/webhooks/events) 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 une signature HMAC-SHA256 calculée sur le **corps brut de la requête** dans l’en-tête `X-ThunderPhone-Signature`. La clé de signature est le `secret` du point de terminaison (ou le `secret` webhook au niveau de votre organisation pour les livraisons héritées).

### Étapes

1. Lisez le corps brut de la requête **avant** toute analyse.
2. Calculez `hmac_sha256(secret, body).hexdigest()`.
3. Comparez-le en temps constant à l’en-tête `X-ThunderPhone-Signature`.

Nous signons exactement les octets que nous transmettons, et ces octets correspondent à la sérialisation JSON canonique (clés triées, séparateurs compacts). La vérification sur le corps brut fonctionne donc toujours — et si votre framework ne vous fournit que le JSON analysé, le re-sérialiser avec des clés triées et des séparateurs compacts produit des octets identiques. Les deux méthodes sont décrites dans le [guide de vérification](/fr/guides/verify-webhook-signatures).

<CodeGroup>
  ```python Python theme={null}
  import hmac
  import hashlib

  def verify_signature(body: bytes, signature: str, secret: str) -> bool:
      expected = hmac.new(
          secret.encode("utf-8"),
          body,
          hashlib.sha256,
      ).hexdigest()
      return hmac.compare_digest(expected, signature or "")

  # Example Flask handler
  from flask import Flask, request, abort
  app = Flask(__name__)

  @app.post("/thunderphone-webhook")
  def handle():
      body = request.get_data()
      sig = request.headers.get("X-ThunderPhone-Signature", "")
      if not verify_signature(body, sig, WEBHOOK_SECRET):
          abort(401)
      event = request.get_json()
      # dispatch on event["type"] …
      return "", 204
  ```

  ```javascript Node.js (Express) theme={null}
  import crypto from "node:crypto";
  import express from "express";

  function verifySignature(body, signature, secret) {
    const expected = crypto
      .createHmac("sha256", secret)
      .update(body)
      .digest("hex");
    if (!signature || expected.length !== signature.length) return false;
    return crypto.timingSafeEqual(
      Buffer.from(expected),
      Buffer.from(signature),
    );
  }

  const app = express();
  app.post(
    "/thunderphone-webhook",
    express.raw({ type: "application/json" }),
    (req, res) => {
      const sig = req.header("X-ThunderPhone-Signature") || "";
      if (!verifySignature(req.body, sig, process.env.WEBHOOK_SECRET)) {
        return res.sendStatus(401);
      }
      const event = JSON.parse(req.body.toString("utf8"));
      // dispatch on event.type …
      res.sendStatus(204);
    },
  );
  ```
</CodeGroup>

## Sémantique de livraison

Cette sémantique s’applique aux livraisons vers les **endpoints**. Le webhook
historique à URL unique effectue une seule tentative synchrone, sans nouvelles tentatives.

<AccordionGroup>
  <Accordion title="Nouvelles tentatives">
    Chaque événement fait l’objet d’une tentative immédiate. Toute réponse `2xx`
    accuse réception de la livraison. Dans tout autre cas (hors 2xx,
    erreur de connexion, délai d’expiration), nous effectuons une nouvelle tentative après **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 l’endpoint
    est marqué `status="failing"` dans les
    [endpoints de webhook](/fr/webhooks/endpoints). Renvoyez `2xx` dès que
    la charge utile est acceptée de manière durable ; traitez-la de façon asynchrone.
  </Accordion>

  <Accordion title="Ordre">
    L’ordre de livraison est assuré dans la mesure du possible. En pratique, nous livrons les événements dans
    l’ordre de leur émission, mais les nouvelles tentatives peuvent modifier cet ordre en cas d’échec.
    Dédupliquez et réconciliez toujours par `call_id` / identifiant d’objet.
  </Accordion>

  <Accordion title="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 porte le même
    `event_id` ; stockez donc les identifiants traités et ignorez les doublons. `event_id` est
    également partagé entre les endpoints — deux endpoints abonnés au
    même événement reçoivent le même `event_id`.
  </Accordion>

  <Accordion title="Délais d’expiration">
    Les livraisons vers les endpoints ont un délai d’expiration de **30 s** par tentative. Sur le
    chemin historique, les requêtes bloquantes qui pilotent le comportement des appels en direct — l’échange de
    configuration [`telephony.incoming` / `web.incoming`](/fr/webhooks/call-incoming) —
    expirent après **10 s**, mais une réponse lente retarde la prise d’appel ;
    visez donc une réponse en quelques secondes. La répartition d’outils en mode webhook
    [tool dispatch](/fr/tools/overview) autorise 20 s.
  </Accordion>

  <Accordion title="Adresses IP sources">
    Les webhooks sortants proviennent de la plage d’adresses IP cloud de ThunderPhone.
    Si votre pare-feu nécessite une liste d’autorisation, contactez le support et nous
    vous communiquerons les plages actuelles.
  </Accordion>
</AccordionGroup>

## Choisir entre les webhooks historiques et les webhooks basés sur des endpoints

| Fonctionnalité                    | Historique (`/v1/webhook`)                                                | Endpoints (`/v1/developer/webhook-endpoints`) |
| --------------------------------- | ------------------------------------------------------------------------- | --------------------------------------------- |
| Nombre d’URL                      | 1 par organisation                                                        | Plusieurs par organisation                    |
| Couverture des événements         | `telephony.*` / `web.*` uniquement                                        | Les 10 types d’événements                     |
| Filtre d’événements               | —                                                                         | Par endpoint                                  |
| Nouvelles tentatives              | Aucune                                                                    | 8 tentatives sur 24 h                         |
| Enveloppe                         | `type` + `data`                                                           | `type` + `data` + `event_id`                  |
| Rotation du secret                | Remplace le secret unique                                                 | Secret par endpoint                           |
| Désactivation sans suppression    | —                                                                         | `status=disabled`                             |
| Visibilité du statut              | —                                                                         | `active` / `disabled` / `failing`             |
| Échange de configuration bloquant | Oui ([`telephony.incoming` / `web.incoming`](/fr/webhooks/call-incoming)) | Jamais — notifications uniquement             |
| Idéal pour                        | Configuration dynamique des appels                                        | Consommation d’événements en production       |

Les nouvelles intégrations doivent consommer les événements via des webhooks basés sur des
endpoints. 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.

***

## Ressources associées

<CardGroup cols={2}>
  <Card title="Catalogue des événements" icon="list" href="/fr/webhooks/events">
    Tous les types d’événements et leurs charges utiles.
  </Card>

  <Card title="Endpoints de webhook" icon="bolt" href="/fr/webhooks/endpoints">
    Gérez plusieurs endpoints, filtres d’événements et secrets.
  </Card>

  <Card title="telephony.incoming / web.incoming" icon="phone" href="/fr/webhooks/call-incoming">
    La requête bloquante à laquelle votre serveur doit répondre pour configurer les appels.
  </Card>

  <Card title="telephony.complete / web.complete" icon="phone" href="/fr/webhooks/call-complete">
    Charge utile post-appel avec transcription, enregistrement et métriques.
  </Card>
</CardGroup>
