---
title: "telephony.incoming / web.incoming"
description: "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 **bloquante**
`telephony.incoming` / `web.incoming` à votre
[URL de webhook héritée](/api-reference/organizations#legacy-single-url-webhook)
et attend une réponse de configuration : jusqu’à **13 secondes** pour un
appel téléphonique, avec une nouvelle tentative incluse, et jusqu’à **10 secondes** pour une session de widget
(voir [Délais d’expiration et nouvelles tentatives](#timeouts-and-retries)). Utilisez cet
échange pour choisir dynamiquement un prompt, une voix et des outils pour chaque appel —
consultez le [guide de configuration dynamique des appels](/fr/guides/dynamic-call-config)
pour le modèle de bout en bout.

<Note>
  Les [points de terminaison webhook](/fr/webhooks/endpoints) abonnés reçoivent
  également `telephony.incoming` / `web.incoming` — pour **chaque** appel entrant
  et session web, avec ou sans configuration d'agent — mais ces livraisons sont
  des notifications fire-and-forget avec un `event_id`, et ne sont jamais
  bloquantes. Seul le webhook hérité à URL unique assure l'échange de
  configuration présenté sur cette page. Les formats des notifications de point
  de terminaison figurent dans le [catalogue des événements](/fr/webhooks/events).
</Note>

## Délais d’attente et nouvelles tentatives

Répondez rapidement : l’appelant attend en ligne pendant que vous décidez.

Pour un appel téléphonique (`telephony.incoming`) :

- La première tentative peut prendre jusqu’à **10 secondes**.
- En cas de délai d’attente, d’impossibilité de se connecter, ou de réponse
  `5xx`, `408` ou `429`, ThunderPhone effectue une nouvelle tentative. La nouvelle tentative reçoit ce qui reste d’un budget de **13 secondes** pour l’ensemble de l’échange : environ 3 secondes après une première tentative ayant expiré, et presque la totalité des 13 secondes après un échec rapide.
  Aucune nouvelle tentative n’est effectuée s’il reste moins de 2 secondes.
- Toute autre réponse `4xx`, une réponse dépassant la
  [limite de taille](#response-size-limit), ou une réponse `2xx` dont le corps ne contient pas une
  configuration valide est définitive et ne fait pas l’objet d’une nouvelle tentative.
- Les deux tentatives envoient le même corps, le même en-tête `X-ThunderPhone-Signature`,
  et le même en-tête `X-ThunderPhone-Idempotency-Key`
  (`telephony-incoming-<call_id>`), afin qu’un gestionnaire ayant des effets de bord
  puisse reconnaître la nouvelle tentative.

Il n’existe aucun agent de secours. Si aucune configuration utilisable n’arrive, l’appel est
terminé immédiatement plutôt que de laisser l’appelant sur une ligne silencieuse. Il apparaît dans l’historique des appels comme un appel échoué, non facturé, avec
`end_reason` `inbound_config_webhook_failed`, et l’événement habituel
[`telephony.complete`](/fr/webhooks/call-complete) est envoyé.

Une session de widget (`web.incoming`) dispose d’une seule tentative pouvant durer jusqu’à 10
secondes. En cas d’échec, la requête de session échoue avec `502` ou `422`.

<Warning>
  **Les appels configurés par webhook ne comportent aucune annonce de
  consentement ThunderPhone.** Les appels configurés via cet échange contournent
  l'annonce au démarrage de l'appel au niveau de l'agent et sont expressément
  exclus du cadre d'annonce de consentement de ThunderPhone (Conditions
  d'utilisation, section « Enregistrement et consentement »). Votre organisation
  est seule responsable de chaque avis et consentement requis concernant
  l'enregistrement, la surveillance, la participation de l'IA et l'identification
  de l'appelant pour ces appels — les appels peuvent toujours être enregistrés,
  transcrits, analysés et traités par l'IA. Intégrez les divulgations requises à
  votre propre flux d'appel avant d'activer cette option.
</Warning>

## Corps de la requête

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

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

| Champ | Type | Description |
|-------|------|-------------|
| `call_id` | integer | ID de l'appel — stable sur tous les événements de cet appel |
| `from_number` | string | Numéro de l'appelant au format E.164 |
| `to_number` | string | Destination 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 :

```json
{
  "type": "web.incoming",
  "data": {
    "call_id": 987654322,
    "origin_domain": "https://example.com",
    "publishable_key_prefix": "pk_live_a1b2"
  }
}
```

| Champ | Type | Description |
|-------|------|-------------|
| `call_id` | entier | ID de l’appel |
| `origin_domain` | chaîne | L’origine de la page qui héberge le widget, telle qu’envoyée par le navigateur. Un client autre qu’un navigateur peut envoyer n’importe quelle valeur ; ne la considérez donc pas comme une preuve de l’origine de la requête |
| `publishable_key_prefix` | chaîne | Premiers caractères de la clé publiable qui a ouvert la session |
| `language`, `primary_language` | chaîne | Présents lorsque la session du widget a demandé une surcharge de langue |
| `voice` | chaîne | Présent lorsque la session du widget a demandé une surcharge de voix |
| `website_context` | chaîne | Présent lorsque le widget a transmis un contexte de page par session |

Les champs de surcharge (`language`, `voice`, `website_context` et toutes les
`variables` du navigateur) arrivent uniquement lorsque la clé publiable les autorise dans ses
[surcharges du navigateur](/fr/guides/embed-web-widget). Une requête de session contenant une surcharge non autorisée par la clé est rejetée avant l’appel de ce webhook.

<Note>
  Les widgets en mode webhook envoient cette requête à leur propre `webhook_url`
  de clé publiable lorsqu'il est défini, avec repli sur l'URL de webhook au niveau
  de l'organisation. Dans tous les cas, elle est signée avec le `secret` du
  webhook de l'organisation.
</Note>

---

## 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.

```json
{
  "prompt":  "You are a helpful booking assistant for Acme Restaurant.",
  "voice":   "john",
  "product": "spark",
  "background_track": null,
  "tools":   []
}
```

| Champ | Type | Obligatoire | Description |
|-------|------|-------------|-------------|
| `prompt` | chaîne | oui | Prompt système qui pilote l’agent |
| `voice` | chaîne | oui | ID de voix provenant de [`GET /v1/voices`](/api-reference/agents#voices), par ex. `john`. `voice_name` est accepté comme alias. Les voix inconnues échouent à la validation et rejettent l’appel |
| `product` | chaîne | non | Valeur par défaut : `spark`. Valeurs autorisées : `spark`, `bolt`, `storm-base`, `storm-base-with-ack`, `storm-extra`, `storm-extra-with-ack` |
| `thinking_level` | chaîne | non | `minimal`, `base` (par défaut) ou `extra`. Remplacé pour les produits Storm : `storm-extra*` force `extra`, les autres `storm-*` forcent `base` |
| `audio_context_mode` | chaîne | non | `full` (par défaut) ou `reduced` |
| `watchdog_enabled` | booléen | non | Active la supervision pour cet appel. Valeur par défaut : `false` |
| `additional_audio_context` | booléen \| null | non | Inclut 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 comportant beaucoup d’orthographe ou de chiffres, 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_mode` | chaîne | non | `none`, `acknowledgement` (par défaut) ou `tick` |
| `language` | chaîne | non | Raccourci pour `primary_language` |
| `primary_language` | chaîne | non | Code de langue, normalisé (valeur par défaut : `en`). Les codes non résolubles rejettent l’appel |
| `has_additional_languages` | booléen | non | Valeur par défaut : `false` |
| `additional_languages` | tableau de chaînes | non | Langues supplémentaires vers lesquelles l’agent peut basculer |
| `native_voice_switching` | booléen | non | Valeur par défaut : `false`. Lorsque l’appel passe à une autre langue, remplace la voix configurée par une voix native de cette langue (correspondant au genre) |
| `language_switching` | chaîne \| null | non | `ask_first` (par défaut) ou `follow_caller` ; `null` équivaut à `ask_first`. Définit comment un agent multilingue gère l’interlocuteur qui parle une autre de ses langues. `ask_first` bascule immédiatement lorsqu’il demande la langue ou prononce une phrase complète dans celle-ci, et demande s’il faut continuer dans cette langue après quelque chose de court, comme un « oui ». `follow_caller` bascule vers toute langue configurée qu’il parle, même un seul mot. S’applique uniquement lorsque `additional_languages` est défini |
| `background_track` | chaîne \| null | non | ID d’audio ambiant ou `null` |
| `acknowledgement_prompt_mode` | chaîne | non | `auto` (par défaut) ou `manual` (produits Storm-with-ack) |
| `acknowledgement_prompt` | chaîne | non | Utilisé lorsque `acknowledgement_prompt_mode="manual"` |
| `silence_interval_seconds` | entier \| null | non | 5–120. Secondes de silence de l’appelant avant une relance |
| `silence_max_checkins` | entier \| null | non | 1–10 |
| `silence_checkins_enabled` | booléen | non | Valeur par défaut : `true` |
| `connect_tone_enabled` | booléen | non | Valeur par défaut : `false` |
| `pronunciation_dicts` | booléen \| null | non | Valeur par défaut : `false` ; `null` équivaut à `false`. Lorsque la valeur est `true`, la synthèse vocale Cartesia de l’appel utilise le dictionnaire de prononciation ThunderPhone pour la langue de l’appel, lorsqu’il existe. Fonctionne pour les appels téléphoniques et les appels depuis le widget. S’applique uniquement aux propres voix Cartesia de ThunderPhone, et non aux voix utilisant votre propre clé API Cartesia. Non utilisé avec `agent_id`, auquel cas les paramètres de l’agent enregistré s’appliquent |
| `voicemail_action` | chaîne | non | `prompt` (par défaut), `hangup` ou `message` |
| `voicemail_message` | chaîne | non | Utilisé lorsque `voicemail_action="message"` |
| `agent_name` | chaîne | non | Nom d’affichage transmis aux tableaux de bord et au widget |
| `org_name` | chaîne | non | Nom d’affichage de l’organisation pour le persona de l’agent |
| `tools` | tableau | non | Schémas d’outils de fonction intégrés (voir [Outils de fonction](/fr/tools/overview)) |
| `transfer_destinations` | tableau d’objets \| null | non | Jusqu’à 50 entrées `{name, number}` vers lesquelles cet appel peut être transféré, validées comme les [numéros de transfert](/api-reference/agents#transfer-numbers) d’un agent. L’agent transfère par nom et uniquement vers ces entrées ; `[]` signifie aucun transfert. Omettez ce champ ou envoyez `null` pour laisser l’agent transférer vers un numéro de son choix |
| `timezone` | chaîne | non | Fuseau horaire IANA pour cet appel, tel que `America/Chicago`. L’agent indique la date et l’heure dans ce fuseau, avant son propre fuseau horaire. Également autorisé à côté de `agent_id`. Les valeurs qui ne sont pas des fuseaux IANA sont ignorées. Voir [Définir le fuseau horaire par appel](/fr/guides/dynamic-call-config) |
| `call_id` | entier | non | Écho facultatif de l’ID d’appel de la requête ; ignoré |

<Note>
  Les clés de premier niveau inconnues sont silencieusement **ignorées** : un nom
  de champ mal orthographié ne refuse pas la configuration, il ne s’applique
  simplement pas. L’ordre de parole et `max_hold_seconds` ne sont pas acceptés ici ;
  ils sont configurables uniquement sur l’[Agent](/api-reference/agents) lui-même.
</Note>

Comme `prompt` et `voice` sont obligatoires, renvoyer `{}` ou toute
réponse qui échoue à la validation rejette l’appel : un appel téléphonique est terminé
comme décrit dans [Délais d’attente et nouvelles tentatives](#timeouts-and-retries), et une
demande de session de widget échoue avec `422`. Il n’existe pas de solution de repli
vers un agent statique sur ce chemin (un numéro ou une clé en mode webhook n’a aucun agent
attribué).

---

## Limite de taille de réponse

<Warning>
  Les réponses de configuration sont limitées à **5 Mio**. Si un gestionnaire
  renvoie une réponse plus volumineuse, y compris avec un statut `2xx`,
  ThunderPhone indique que la réponse dépasse la limite et
  rejette l'appel ou la session de widget. Limitez la réponse aux champs
  nécessaires à la configuration de l'appel ; hébergez les données volumineuses derrière des outils de fonction ou
  un autre service au lieu de les intégrer à la configuration.
</Warning>

---

## Exemple de gestionnaire

<CodeGroup>
```python 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 {}
```

```javascript 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({});
  },
);
```
</CodeGroup>

---

## Réponse avec des outils de fonction

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

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

<Tip>
  Les requêtes vers les endpoints d'outils sont signées avec le **même secret
  webhook de l'organisation** qui a signé cet échange. Consultez
  [Outils de fonction](/fr/tools/overview) pour connaître la structure exacte et le format de
  requête signée.
</Tip>

---

## Aide-mémoire des niveaux de produit

| Produit | Latence | Raisonnement | Acquiescement |
|---------|---------|-----------|-----------------|
| `spark` | La plus faible | Basique | — |
| `bolt` | Faible | Amélioré | — |
| `storm-base` | Moyenne | Solide | — |
| `storm-base-with-ack` | Moyenne | Solide | Remplissage automatique pendant la réflexion |
| `storm-extra` | Plus élevée | Approfondi | — |
| `storm-extra-with-ack` | Plus élevée | Approfondi | Remplissage automatique pendant la réflexion |

---

## Associé

<CardGroup cols={2}>
  <Card title="telephony.complete / web.complete" icon="phone-slash" href="/fr/webhooks/call-complete">
    L’événement de fin d’appel non bloquant.
  </Card>
  <Card title="Outils de fonction" icon="screwdriver-wrench" href="/fr/tools/overview">
    Schéma JSON complet pour `tools[]` et le contrat d’endpoint signé.
  </Card>
  <Card title="Endpoints de webhook" icon="bolt" href="/fr/webhooks/endpoints">
    Abonnez plusieurs URL à `telephony.incoming` / `web.incoming`.
  </Card>
  <Card title="Configuration dynamique des appels" icon="wand-magic-sparkles" href="/fr/guides/dynamic-call-config">
    Modèles pour les prompts, outils et tests A/B par appelant.
  </Card>
</CardGroup>
