---
title: "Configurazione dinamica per chiamata"
description: "Scegli l'agente che risponde — oppure riscrivi il suo prompt e le sue impostazioni — separatamente per ogni chiamata in arrivo, in base a una logica personalizzata in un webhook che controlli."
---

Per impostazione predefinita, a ogni numero di telefono e chiave pubblicabile è assegnato un agente statico. Quando ti serve una personalizzazione **per chiamante** o **per visitatore** — instradamento VIP, contesto dell'utente connesso, test A/B dei prompt — passa alla modalità webhook e lascia decidere al tuo server.

## Come funziona

1. Ti iscrivi all'evento [`telephony.incoming`](/it/webhooks/events)
   (telefono) o [`web.incoming`](/it/webhooks/events) (widget).
   Entrambi sono webhook **bloccanti**: ThunderPhone attende la tua
   risposta prima di proseguire la chiamata, fino a 13 secondi per una
   chiamata telefonica (incluso un tentativo aggiuntivo) e 10 secondi per una sessione widget.
2. ThunderPhone ti invia `{call_id, from_number, to_number}` (le sessioni
   widget includono campi specifici del widget invece dei numeri: consulta lo
   [schema della richiesta](/it/webhooks/call-incoming)).
3. Il tuo server risponde con una configurazione dell'agente (prompt, voce,
   prodotto, strumenti). ThunderPhone utilizza tale configurazione per la chiamata.
4. Non esiste un agente di fallback. Se restituisci `{}` o una configurazione
   non valida, oppure la richiesta continua a non riuscire dopo il relativo
   [ritentativo](/it/webhooks/call-incoming), la chiamata
   telefonica viene terminata immediatamente e registrata con `end_reason`
   `inbound_config_webhook_failed`; una richiesta di sessione widget non riesce.

<Note>
  Funziona in modo identico per le chiamate telefoniche (`telephony.incoming`) e le
  sessioni widget (`web.incoming`), sia che vengano recapitate a un endpoint webhook
  sia al webhook legacy con URL singolo.
</Note>

<Warning>
  **Nelle chiamate configurate tramite webhook inline non viene riprodotto alcun annuncio di consenso di ThunderPhone.** Questo percorso aggira l'annuncio di inizio chiamata a livello di agente
  ed è espressamente escluso dal framework di ThunderPhone per gli annunci di consenso
  (Termini di servizio, sezione "Registrazione e consenso"). La tua
  organizzazione è responsabile di tutti gli avvisi e consensi relativi a registrazione,
  monitoraggio, partecipazione dell'IA e identificazione del chiamante in queste chiamate. Forniscili
  nel tuo flusso — ad esempio nel copione iniziale del prompt —
  prima di abilitare la modalità webhook inline. Una risposta webhook che fa riferimento a un
  `agent_id` salvato usa la normale Politica di registrazione e divulgazione di quell'agente.
</Warning>

## 1. Configura la destinazione webhook

<Tabs>
<Tab title="Chiamate telefoniche">
Per i numeri di telefono, iscrivi il tuo endpoint a `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 risposta include un `secret` monouso: salvalo; ti servirà
per la verifica della firma.
</Tab>
<Tab title="Widget web">
Per le sessioni widget, crea una chiave pubblicabile in `mode="webhook"`
con l'URL del tuo endpoint incorporato:

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

Il widget eseguirà una richiesta POST a questo URL all'avvio di ogni sessione.
</Tab>
</Tabs>

## 2. Implementa il gestore

Tre regole pratiche:

- **Verifica la firma** su ogni richiesta (consulta
  [Verifica le firme dei webhook](/it/guides/verify-webhook-signatures)).
  Non saltare questo passaggio in sviluppo: configurarlo correttamente una volta e riutilizzarlo.
- **Rispondi rapidamente**. Il primo tentativo di una chiamata telefonica viene interrotto dopo 10
  secondi e l'intero scambio dopo 13, e ogni secondo è silenzio per il chiamante.
  Esegui ricerche nel database se necessario, ma non chiamare LLM a valle in modo sincrono: se vuoi
  generare prompt dinamici, precalcolali e memorizzali nella cache.
- **Usa un fallback pulito**. Per qualsiasi stato imprevisto, rispondi con un valore predefinito per
  `prompt` e `voice` (oppure con un `agent_id` salvato). Restituire `{}`
  o uno stato di errore termina la chiamata.

<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. Schema della risposta

Il corpo della risposta corrisponde esattamente allo
[schema della risposta per le chiamate in entrata](/it/webhooks/call-incoming).
I campi più usati:

| Campo | Tipo | Descrizione |
|-------|------|-------------|
| `prompt` | stringa (obbligatorio) | Prompt di sistema per l'agente |
| `voice` | stringa (obbligatorio) | ID della voce da [`GET /v1/voices`](/api-reference/agents#voices) |
| `product` | stringa | Il valore predefinito è `spark`. I prodotti sperimentali sono disponibili solo per le organizzazioni autorizzate. |
| `background_track` | stringa \| null | ID dell'audio ambientale |
| `acknowledgement_prompt_mode` | stringa | `auto` o `manual` (solo Storm-with-ack) |
| `acknowledgement_prompt` | stringa | Obbligatorio quando la modalità è `manual` |
| `language_switching` | stringa | Per gli agenti multilingue, `ask_first` o `follow_caller` |
| `tools` | array | Schemi degli strumenti funzione in linea — vedi [Strumenti funzione](/it/tools/overview) |

<Note>
  L'ordine di conversazione per chiamata e `max_hold_seconds` non sono disponibili
  nella risposta del webhook. Impostali sull'[agente](/api-reference/agents)
  a cui fai riferimento.
</Note>

### Mantieni un agente salvato e fornisci variabili

Restituisci `{"agent_id": 12, "variables": {"name": "Ada"}}` per usare l'agente
salvato di quell'organizzazione con dati per chiamata. Il relativo prompt può contenere `{{name}}` o
`{{name|Friend}}`. Le variabili del webhook vengono unite alle variabili a livello di richiesta; null
usa il valore predefinito del segnaposto oppure testo vuoto se non ne viene fornito alcuno. I valori finali
e i nomi non risolti vengono visualizzati nei dettagli della chiamata e nei webhook di completamento.
Le risposte dell'agente salvato accettano solo `agent_id`, `variables` e `timezone`. Se `prompt` è
presente, la risposta usa la configurazione inline e ignora `agent_id` (inclusi
metadati null o non interi); il prompt inline deve comunque essere valido. Le risposte con configurazione inline
possono includere anche `variables`. Le risposte dell'agente salvato usano la suddivisione A/B distribuita dell'agente
sia nelle chiamate telefoniche sia nelle chiamate dal widget, quindi eseguono il rendering delle variabili. Consulta [variabili di chiamata](/it/guides/call-variables)
per i limiti e il supporto dell'API di sessione. La configurazione bloccante proviene dall'URL legacy
del numero di telefono/dell'organizzazione o da una chiave del widget in modalità webhook; gli eventi in entrata del sistema endpoint
sono solo notifiche.

### Imposta il fuso orario per chiamata

Un agente può rispondere a contatti in fusi orari diversi. Aggiungi `timezone`, un
nome IANA, a una risposta di agente salvato o inline e l'agente indica la data
e l'ora e propone orari in quel fuso per questa chiamata, anziché nel fuso
orario dell'agente:

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

Invia il fuso anziché un timestamp in `variables`: l'orologio dell'agente continua
a scorrere durante la chiamata e l'orario trasmesso diventa obsoleto. Un valore
che non corrisponde a un fuso IANA viene ignorato e la chiamata si connette nel
fuso dell'agente.

## Pattern

### Contesto dell'utente autenticato

Nei widget in modalità webhook, la pagina del visitatore sa già chi è.
Chiama il tuo webhook con un parametro della stringa di query che l'SDK
del widget inoltra (`?customer_id=123`) e cerca il cliente lato server.

### Distribuzione A/B dei prompt

Prima di implementarlo manualmente, tieni presente che ThunderPhone include una funzionalità nativa
di [Esperimenti](/it/guides/concepts)
(`/dashboard/experiments` e la scheda **A/B** del builder dell'agente) che
definisce varianti, divide il traffico e confronta i risultati per variante —
senza webhook.

Se hai comunque bisogno del controllo lato webhook: calcola l'hash di `call_id` → bucket;
fornisci il prompt A per `0..49` e il prompt B per `50..99`. Registra il
bucket scelto nel tuo database e in seguito correlalo alla valutazione della
chiamata completata.

### Routing basato sull'orario

Orario lavorativo → agente di "assistenza dal vivo"; fuori orario → agente per
"prendere un messaggio". Semplice switch su `new Date().getUTCHours()` nel tuo handler.

---

## Passaggi successivi

<CardGroup cols={2}>
  <Card title="Riferimento webhook per chiamate in entrata" icon="phone" href="/it/webhooks/call-incoming">
    Schemi esatti di richiesta e risposta, incluse tutte le chiavi di configurazione.
  </Card>
  <Card title="Verifica le firme dei webhook" icon="shield-check" href="/it/guides/verify-webhook-signatures">
    Configura correttamente l'HMAC una volta; riutilizzalo ovunque.
  </Card>
  <Card title="Crea un'integrazione con strumenti" icon="screwdriver-wrench" href="/it/guides/build-tool-integration">
    Combina il routing dinamico con strumenti per agente.
  </Card>
  <Card title="Semantica di consegna" icon="bolt" href="/it/webhooks/overview">
    Riprova, ordinamento, timeout.
  </Card>
</CardGroup>
