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

# Panoramica dei webhook

> Come ThunderPhone invia eventi in tempo reale, come verificare le firme e come si confrontano i modelli di consegna legacy e basati su endpoint.

ThunderPhone invia richieste HTTP `POST` al tuo server quando accadono eventi
durante una chiamata: inizia una chiamata in entrata, termina una chiamata, si completa
un'esecuzione di valutazione, si attiva un avviso e così via. Esistono **due modelli
di consegna**:

<CardGroup cols={2}>
  <Card title="Endpoint webhook (consigliati)" icon="bolt" href="/it/webhooks/endpoints">
    URL multipli, secret per endpoint, filtri degli eventi per endpoint
    e nuovi tentativi automatici.
    Gestisci tramite `GET/POST/PATCH/DELETE /v1/developer/webhook-endpoints`.
  </Card>

  <Card title="Webhook legacy a URL singolo" icon="link" href="/api-reference/organizations#legacy-single-url-webhook">
    Un URL per organizzazione. Trasporta gli eventi del ciclo di vita della chiamata, inclusi gli
    scambi di configurazione **bloccanti**. Gestito tramite `GET/PUT /v1/webhook`.
  </Card>
</CardGroup>

Tutti e dieci i tipi di evento nel [catalogo degli eventi](/it/webhooks/events) vengono
consegnati tramite endpoint webhook. I sei eventi del ciclo di vita della chiamata
(`telephony.incoming`, `telephony.complete`, `telephony.tool`,
`web.incoming`, `web.complete`, `web.tool`) vengono **anche** inviati al
webhook legacy a URL singolo: se disponi sia di un URL legacy sia di un endpoint
corrispondente, ricevi l'evento su **entrambi** i percorsi. Il comportamento bloccante
(lo [scambio di configurazione `telephony.incoming` / `web.incoming`](/it/webhooks/call-incoming)
e l'[inoltro degli strumenti](/it/tools/overview) in modalità webhook)
è disponibile esclusivamente sul percorso legacy; ogni consegna agli endpoint è una
notifica fire-and-forget.

## Formato del payload

Le consegne agli endpoint sono un oggetto JSON con `data`, `event_id` e
`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` è univoco per ogni evento emesso. È identico tra i nuovi tentativi
**e** tra tutti gli endpoint che ricevono l'evento: usalo per la deduplicazione.

Il webhook legacy a URL singolo invia gli stessi `type` e `data`, ma
**senza** `event_id`:

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

Sulla rete, ogni corpo viene serializzato in modo canonico: chiavi ordinate
alfabeticamente, nessuno spazio bianco, UTF-8. Gli esempi formattati in modo leggibile in
questa documentazione sono solo a scopo di leggibilità.

Consulta il [Catalogo degli eventi](/it/webhooks/events) per l'elenco completo dei tipi di evento
e dei campi del payload.

## Verifica della firma

Ogni richiesta include una firma HMAC-SHA256 sul **corpo della richiesta
non elaborato** nell'header `X-ThunderPhone-Signature`. La chiave di firma è il
`secret` dell'endpoint (oppure il `secret` webhook a livello di organizzazione per le
consegne legacy).

### Passaggi

1. Leggi il corpo della richiesta non elaborato **prima** di qualsiasi analisi.
2. Calcola `hmac_sha256(secret, body).hexdigest()`.
3. Confrontalo in tempo costante con l'header `X-ThunderPhone-Signature`.

Firmiamo esattamente i byte che trasmettiamo e tali byte corrispondono alla
serializzazione JSON canonica (chiavi ordinate, separatori compatti). Pertanto,
la verifica rispetto al corpo non elaborato funziona sempre — e se il tuo framework
fornisce solo JSON analizzato, serializzarlo nuovamente con chiavi ordinate e
separatori compatti produce byte identici. Entrambi gli approcci sono trattati nella
[guida alla verifica](/it/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>

## Semantica di consegna

Questa semantica si applica alle consegne agli **endpoint**. Il webhook legacy a URL singolo effettua un unico tentativo sincrono senza nuovi tentativi.

<AccordionGroup>
  <Accordion title="Nuovi tentativi">
    Ogni evento viene tentato una volta immediatamente. Qualsiasi risposta `2xx`
    conferma la consegna. Per qualsiasi altro esito (non-2xx,
    errore di connessione, timeout) riproviamo dopo **1 min, 5 min, 30 min, 2 h, 6 h,
    12 h e 24 h dal primo tentativo** — 8 tentativi nell'arco di
    24 ore. Se ogni tentativo fallisce, la consegna si interrompe e l'endpoint
    viene contrassegnato con `status="failing"` negli
    [endpoint webhook](/it/webhooks/endpoints). Restituisci `2xx` non appena
    il payload viene accettato in modo durevole; elaboralo in modo asincrono.
  </Accordion>

  <Accordion title="Ordinamento">
    L'ordinamento delle consegne avviene secondo il principio del massimo impegno. In pratica consegniamo gli eventi nell'
    ordine in cui vengono emessi, ma i nuovi tentativi possono riordinarli in caso di errore.
    Esegui sempre deduplicazione e riconciliazione tramite `call_id` / id oggetto.
  </Accordion>

  <Accordion title="Duplicati">
    La consegna è **almeno una volta**: un nuovo tentativo dopo una risposta che non abbiamo
    ricevuto può duplicare un evento. Ogni nuovo tentativo contiene lo stesso
    `event_id`, quindi archivia gli id elaborati e ignora le ripetizioni. `event_id` è
    condiviso anche tra gli endpoint — due endpoint iscritti allo
    stesso evento ricevono lo stesso `event_id`.
  </Accordion>

  <Accordion title="Timeout">
    Le consegne agli endpoint hanno un timeout di **30 s** per tentativo. Nel
    percorso legacy, le richieste bloccanti che regolano il comportamento delle chiamate in tempo reale — lo
    scambio di configurazione [`telephony.incoming` / `web.incoming`](/it/webhooks/call-incoming) —
    scadono dopo **10 s**, ma una risposta lenta ritarda la presa in carico della chiamata,
    quindi cerca di rispondere entro pochi secondi. L'invio di strumenti in modalità webhook [tool dispatch](/it/tools/overview) consente 20 s.
  </Accordion>

  <Accordion title="IP di origine">
    I webhook in uscita provengono dall'intervallo di IP cloud di ThunderPhone.
    Se il firewall richiede un elenco di autorizzazione, contatta il supporto e condivideremo
    gli intervalli attuali.
  </Accordion>
</AccordionGroup>

## Scegliere tra webhook legacy e basati su endpoint

| Funzionalità                        | Legacy (`/v1/webhook`)                                                   | Endpoint (`/v1/developer/webhook-endpoints`) |
| ----------------------------------- | ------------------------------------------------------------------------ | -------------------------------------------- |
| Numero di URL                       | 1 per organizzazione                                                     | Molti per organizzazione                     |
| Copertura eventi                    | Solo `telephony.*` / `web.*`                                             | Tutti i 10 tipi di evento                    |
| Filtro eventi                       | —                                                                        | Per endpoint                                 |
| Nuovi tentativi                     | Nessuno                                                                  | 8 tentativi in 24 h                          |
| Involucro                           | `type` + `data`                                                          | `type` + `data` + `event_id`                 |
| Rotazione del segreto               | Sostituisce il singolo segreto                                           | Segreto per endpoint                         |
| Disabilitare senza eliminare        | —                                                                        | `status=disabled`                            |
| Visibilità dello stato              | —                                                                        | `active` / `disabled` / `failing`            |
| Scambio di configurazione bloccante | Sì ([`telephony.incoming` / `web.incoming`](/it/webhooks/call-incoming)) | Mai — solo notifiche                         |
| Ideale per                          | Configurazione dinamica delle chiamate                                   | Consumo di eventi in produzione              |

Le nuove integrazioni dovrebbero consumare gli eventi tramite webhook
basati su endpoint. Mantieni (o aggiungi) un URL legacy solo se configuri le chiamate
dinamicamente al momento della presa in carico o usi l'invio di strumenti in modalità webhook — questi
scambi richiesta/risposta vengono eseguiti solo nel percorso legacy.

***

## Correlati

<CardGroup cols={2}>
  <Card title="Catalogo degli eventi" icon="list" href="/it/webhooks/events">
    Tutti i tipi di evento e i relativi payload.
  </Card>

  <Card title="Endpoint webhook" icon="bolt" href="/it/webhooks/endpoints">
    Gestisci più endpoint, filtri eventi e segreti.
  </Card>

  <Card title="telephony.incoming / web.incoming" icon="phone" href="/it/webhooks/call-incoming">
    La richiesta bloccante a cui il tuo server deve rispondere per configurare le chiamate.
  </Card>

  <Card title="telephony.complete / web.complete" icon="phone" href="/it/webhooks/call-complete">
    Payload post-chiamata con trascrizione, registrazione e metriche.
  </Card>
</CardGroup>
