---
title: "Configuration dynamique par appel"
description: "Choisissez l’agent qui répond — ou réécrivez son prompt et ses paramètres — séparément pour chaque appel entrant, selon une logique personnalisée dans un webhook que vous contrôlez."
---

Par défaut, un agent statique est attribué à chaque numéro de téléphone et clé publiable. Lorsque vous avez besoin d'une personnalisation **par appelant** ou **par visiteur** — routage VIP, contexte d'utilisateur connecté, tests A/B de prompt — passez en mode webhook et laissez votre serveur décider.

## Fonctionnement

1. Vous vous abonnez à l’événement [`telephony.incoming`](/fr/webhooks/events)
   (téléphone) ou [`web.incoming`](/fr/webhooks/events) (widget).
   Les deux sont des webhooks **bloquants** : ThunderPhone attend votre
   réponse avant de poursuivre l’appel, jusqu’à 13 secondes pour un appel
   téléphonique (une nouvelle tentative incluse) et 10 secondes pour une session widget.
2. ThunderPhone vous envoie `{call_id, from_number, to_number}` (les sessions widget
   contiennent des champs spécifiques au widget au lieu de numéros — consultez le
   [schéma de requête](/fr/webhooks/call-incoming)).
3. Votre serveur répond avec une configuration d’agent (prompt, voix,
   produit, outils). ThunderPhone utilise cette configuration pour l’appel.
4. Il n’existe aucun agent de secours. Si vous renvoyez `{}` ou une configuration
   non valide, ou si la requête échoue toujours après sa
   [nouvelle tentative](/fr/webhooks/call-incoming), l’appel
   téléphonique est immédiatement terminé et enregistré avec `end_reason`
   `inbound_config_webhook_failed` ; une requête de session widget échoue.

<Note>
  Fonctionne de manière identique pour les appels téléphoniques (`telephony.incoming`) et les
  sessions de widget (`web.incoming`), qu'ils soient envoyés à un point de terminaison webhook
  ou au webhook monourl hérité.
</Note>

<Warning>
  **Aucune annonce de consentement ThunderPhone n'est diffusée lors des appels
  configurés via webhook inline.** Ce parcours contourne l'annonce de début d'appel
  au niveau de l'agent et est expressément exclu du cadre d'annonce de consentement
  de ThunderPhone (Conditions d'utilisation, section « Enregistrement et consentement »).
  Votre organisation est responsable de tous les avis et consentements relatifs à
  l'enregistrement, la surveillance, la participation de l'IA et l'identification
  de l'appelant pour ces appels. Fournissez-les dans votre propre flux — par exemple
  dans le script d'ouverture du prompt — avant d'activer le mode webhook inline. Une
  réponse webhook qui référence un `agent_id` enregistré utilise la politique habituelle
  d'enregistrement et de divulgation de cet agent.
</Warning>

## 1. Configurer la destination du webhook

<Tabs>
<Tab title="Appels téléphoniques">
Pour les numéros de téléphone, abonnez votre point de terminaison à `telephony.incoming` :

```bash
curl -X POST https://api.thunderphone.com/v1/developer/webhook-endpoints \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "label":  "Prod call-incoming",
    "url":    "https://example.com/thunderphone/incoming",
    "events": ["telephony.incoming"]
  }'
```

La réponse inclut un `secret` à usage unique — enregistrez-le ; vous l'utiliserez
pour la vérification de signature.
</Tab>
<Tab title="Widget web">
Pour les sessions de widget, créez une clé publiable en `mode="webhook"`
avec l'URL de votre point de terminaison intégrée :

```bash
curl -X POST https://api.thunderphone.com/v1/publishable-key \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name":            "Dynamic widget",
    "mode":            "webhook",
    "webhook_url":     "https://example.com/thunderphone/widget-incoming",
    "allowed_domains": ["example.com"]
  }'
```

Le widget enverra une requête POST vers cette URL à chaque démarrage de session.
</Tab>
</Tabs>

## 2. Implémentez le gestionnaire

Trois règles de base :

- **Vérifiez la signature** à chaque requête (voir
  [Vérifier les signatures de webhook](/fr/guides/verify-webhook-signatures)).
  Ne sautez pas cette étape en développement : faites-le correctement une fois et réutilisez-la.
- **Répondez rapidement**. La première tentative d'un appel téléphonique est interrompue au bout de 10
  secondes, et l'échange complet au bout de 13 ; chaque seconde est un silence pour l'appelant. Effectuez des recherches dans la base de données si nécessaire, mais n'appelez pas les LLM en aval de manière synchrone : pour générer un prompt dynamique, pré-calculez et mettez en cache.
- **Prévoyez un repli propre**. Pour tout état inattendu, répondez avec un `prompt` et une `voice` par défaut (ou un `agent_id` enregistré). Renvoyer `{}` ou un statut d'erreur met fin à l'appel.

<CodeGroup>
```python FastAPI
import hashlib
import hmac
import json
import os

from fastapi import FastAPI, HTTPException, Request

app = FastAPI()
SECRET = os.environ["THUNDERPHONE_WEBHOOK_SECRET"]

def verify(body: bytes, sig: str) -> bool:
    expected = hmac.new(SECRET.encode(), body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig or "")

@app.post("/thunderphone/incoming")
async def incoming(request: Request):
    body = await request.body()
    if not verify(body, request.headers.get("X-ThunderPhone-Signature", "")):
        raise HTTPException(401)

    event = json.loads(body)
    if event["type"] not in ("telephony.incoming", "web.incoming"):
        return {}  # fall back to default

    caller = event["data"]["from_number"]
    # Cheap DB lookup: is this a known VIP?
    customer = lookup_customer(caller)
    if customer and customer.tier == "vip":
        return {
            "prompt":  f"You are a VIP concierge for {customer.name}. Be proactive…",
            "voice":   "john",
            "product": "storm-base",
        }
    return {}  # default agent handles non-VIPs

def lookup_customer(phone: str):
    # ... your CRM integration ...
    pass
```

```javascript Express
import crypto from "node:crypto";
import express from "express";

const app = express();
const SECRET = process.env.THUNDERPHONE_WEBHOOK_SECRET;

function verify(body, sig) {
  const expected = crypto.createHmac("sha256", SECRET).update(body).digest("hex");
  return sig &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig));
}

app.post(
  "/thunderphone/incoming",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    if (!verify(req.body, req.header("X-ThunderPhone-Signature"))) {
      return res.sendStatus(401);
    }
    const event = JSON.parse(req.body.toString("utf8"));

    const IMPORTANT_TYPES = new Set([
      "telephony.incoming",
      "web.incoming",
    ]);
    if (!IMPORTANT_TYPES.has(event.type)) return res.json({});

    const customer = await lookupCustomer(event.data.from_number);
    if (customer?.tier === "vip") {
      return res.json({
        prompt:  `You are a VIP concierge for ${customer.name}. Be proactive…`,
        voice:   "john",
        product: "storm-base",
      });
    }
    res.json({}); // fall back to default agent
  },
);
```
</CodeGroup>

## 3. Schéma de réponse

Le corps de la réponse correspond exactement au
[schéma de réponse des appels entrants](/fr/webhooks/call-incoming).
Les champs les plus utilisés :

| Champ | Type | Description |
|-------|------|-------------|
| `prompt` | chaîne (obligatoire) | Prompt système de l’agent |
| `voice` | chaîne (obligatoire) | ID de voix de [`GET /v1/voices`](/api-reference/agents#voices) |
| `product` | chaîne | Utilise `spark` par défaut. Les produits expérimentaux sont disponibles uniquement pour les organisations autorisées. |
| `background_track` | chaîne \| null | ID de l’audio ambiant |
| `acknowledgement_prompt_mode` | chaîne | `auto` ou `manual` (Storm avec acquiescements verbaux uniquement) |
| `acknowledgement_prompt` | chaîne | Obligatoire lorsque le mode est `manual` |
| `language_switching` | chaîne | Pour les agents multilingues, `ask_first` ou `follow_caller` |
| `tools` | tableau | Schémas d’outils de fonction intégrés — voir [Outils de fonction](/fr/tools/overview) |

<Note>
  L'ordre de parole par appel et `max_hold_seconds` ne sont pas disponibles dans
  la réponse du webhook. Définissez-les sur l'[agent](/api-reference/agents)
  référencé.
</Note>

### Conserver un agent enregistré et fournir des variables

Renvoyez `{"agent_id": 12, "variables": {"name": "Ada"}}` pour utiliser l'agent
enregistré de cette organisation avec des données propres à l'appel. Son prompt peut contenir `{{name}}` ou
`{{name|Friend}}`. Les variables de webhook fusionnent avec les variables au niveau de la requête ; la valeur null
utilise la valeur par défaut de l'espace réservé, ou un texte vide si aucune n'est fournie. Les valeurs finales et les noms non résolus apparaissent dans les détails de l'appel et les webhooks de fin d'appel.
Les réponses utilisant un agent enregistré acceptent uniquement `agent_id`, `variables` et `timezone`. Si `prompt` est
présent, la réponse utilise une configuration inline et ignore `agent_id` (y compris
les métadonnées nulles ou non entières) ; le prompt inline doit tout de même être valide. Les réponses avec configuration inline peuvent également inclure `variables`. Les réponses utilisant un agent enregistré appliquent la répartition A/B déployée de l'agent aux appels téléphoniques et aux appels depuis le widget, puis rendent les variables. Consultez les [variables d'appel](/fr/guides/call-variables)
pour connaître les limites et la prise en charge de l'API de session. La configuration bloquante provient de l'URL historique du numéro de téléphone/de l'organisation ou d'une clé de widget en mode webhook ; les événements entrants du système de points de terminaison sont uniquement des notifications.

### Définir le fuseau horaire par appel

Un agent peut répondre à des contacts situés dans différents fuseaux horaires. Ajoutez `timezone`, un nom IANA, à une réponse d’agent enregistré ou intégrée, et l’agent indique la date et l’heure, puis propose des créneaux dans ce fuseau horaire pour cet appel, avant son propre fuseau horaire :

```json
{"agent_id": 12, "timezone": "America/Chicago", "variables": {"name": "Ada"}}
```

Envoyez le fuseau plutôt qu’un horodatage dans `variables` : l’horloge de l’agent continue de fonctionner pendant l’appel, et une heure transmise devient obsolète. Une valeur qui n’est pas un fuseau IANA est ignorée et l’appel se connecte dans le fuseau horaire de l’agent.

## Modèles

### Contexte d'utilisateur connecté

Dans les widgets en mode webhook, la page du visiteur sait déjà qui il est.
Appelez votre webhook avec un paramètre de chaîne de requête que le SDK du widget
transmet (`?customer_id=123`), puis recherchez le client côté serveur.

### Déploiement progressif de prompt A/B

Avant de le développer vous-même, notez que ThunderPhone propose une fonctionnalité
native [Expériences](/fr/guides/concepts)
(`/dashboard/experiments` et l'onglet **A/B** du générateur d'agents) qui
définit les variantes, répartit le trafic et compare les résultats par variante —
sans webhook.

Si vous avez tout de même besoin d'un contrôle côté webhook : hachez `call_id` → bucket ;
servez le prompt A pour `0..49` et le prompt B pour `50..99`. Enregistrez le bucket
choisi dans votre propre base de données, puis corrélez-le ultérieurement avec la
note de l'appel terminé.

### Routage selon l'heure

Heures d'ouverture → agent « support en direct » ; hors horaires → agent « prise de
message ». Effectuez simplement une bascule sur `new Date().getUTCHours()` dans votre gestionnaire.

---

## Étapes suivantes

<CardGroup cols={2}>
  <Card title="Référence du webhook d'appel entrant" icon="phone" href="/fr/webhooks/call-incoming">
    Schémas exacts des requêtes et réponses, y compris chaque clé de configuration.
  </Card>
  <Card title="Vérifier les signatures de webhook" icon="shield-check" href="/fr/guides/verify-webhook-signatures">
    Configurez correctement le HMAC une fois, puis réutilisez-le partout.
  </Card>
  <Card title="Créer une intégration d'outil" icon="screwdriver-wrench" href="/fr/guides/build-tool-integration">
    Combinez le routage dynamique avec des outils par agent.
  </Card>
  <Card title="Sémantique de livraison" icon="bolt" href="/fr/webhooks/overview">
    Nouvelles tentatives, ordre, délais d'expiration.
  </Card>
</CardGroup>
