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 sugli endpoint.
ThunderPhone invia richieste HTTP POST al tuo server quando si
verificano eventi durante una chiamata: inizia una chiamata in entrata, termina una chiamata, viene completata
una valutazione, si attiva un avviso e così via. Esistono due modelli
di consegna:
Più URL, segreti per endpoint, filtri degli eventi per endpoint
e nuovi tentativi automatici.
Gestisci tramite GET/POST/PATCH/DELETE /v1/developer/webhook-endpoints.
Un URL per organizzazione. Trasporta gli eventi del ciclo di vita delle chiamate, inclusi gli
scambi di configurazione bloccanti. Gestito in GET/PUT /v1/webhook.
Tutti e dieci i tipi di evento nel catalogo degli eventi vengono
consegnati tramite endpoint webhook. I sei eventi del ciclo di vita delle chiamate
(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 e l'
instradamento degli strumenti in modalità webhook) è disponibile
esclusivamente nel percorso legacy; ogni consegna a un endpoint è una
notifica fire-and-forget.
Formato del payload
Le consegne agli endpoint sono un oggetto JSON con data, event_id e
type:
{
"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: esegui la deduplicazione su di esso.
Il webhook legacy a URL singolo invia gli stessi type e data, ma
senza event_id:
{
"type": "telephony.incoming",
"data": { "call_id": 987654321, "from_number": "+14155550199", "to_number": "+15551234567" }
}Durante la trasmissione, ogni corpo viene serializzato in modo canonico: chiavi ordinate alfabeticamente, nessuno spazio vuoto, UTF-8. Gli esempi formattati nelle presenti documentazione servono solo a facilitarne la lettura.
Consulta il Catalogo degli eventi 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 del webhook a livello di organizzazione per le
consegne legacy).
Passaggi
- Leggi il corpo della richiesta non elaborato prima di qualsiasi analisi.
- Calcola
hmac_sha256(secret, body).hexdigest(). - Confrontalo a tempo costante con l'header
X-ThunderPhone-Signature.
Firmiamo esattamente i byte che trasmettiamo, che corrispondono alla serializzazione JSON canonica (chiavi ordinate, separatori compatti). Pertanto, la verifica rispetto al corpo non elaborato funziona sempre — e se il tuo framework ti fornisce solo JSON già analizzato, serializzarlo nuovamente con chiavi ordinate e separatori compatti produce byte identici. Entrambi i metodi sono descritti nella guida alla verifica.
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 "", 204import 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);
},
);Semantica di consegna
Questa semantica si applica alle consegne agli endpoint. Il webhook legacy a URL singolo esegue un unico tentativo sincrono senza ritentativi.
Ritentativi
Ogni evento viene tentato una volta immediatamente. Qualsiasi risposta 2xx
conferma la consegna. Per qualsiasi altro esito (non-2xx,
errore di connessione, timeout), riproviamo 1 min, 5 min, 30 min, 2 h, 6 h,
12 h e 24 h dopo il 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" in
endpoint webhook. Restituisci 2xx non appena
il payload viene accettato in modo durevole; elaboralo in modo asincrono.
Ordinamento
L'ordinamento delle consegne è gestito secondo il principio del massimo impegno. In pratica consegniamo nell'
ordine in cui gli eventi vengono emessi, ma i ritentativi possono riordinare gli eventi in caso di errore.
Esegui sempre la deduplicazione e la riconciliazione in base a call_id / ID oggetto.
Duplicati
La consegna è almeno una volta: un ritentativo dopo una risposta che non
abbiamo ricevuto può duplicare un evento. Ogni ritentativo include 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.
Timeout
Le consegne agli endpoint hanno un timeout di 30 s per tentativo. Nel
percorso legacy, le richieste bloccanti che determinano il comportamento della chiamata in tempo reale — lo
scambio di configurazione telephony.incoming / web.incoming —
scadono dopo 10 s, ma una risposta lenta ritarda la presa in carico della chiamata,
quindi cerca di rispondere entro un paio di secondi. L'invio di strumenti
in modalità webhook consente 20 s per impostazione predefinita e le dichiarazioni degli strumenti
possono impostare un timeout di primo livello.
IP di origine
I webhook in uscita provengono dall'intervallo di IP cloud di ThunderPhone. Se il firewall richiede una lista consentita, contatta il supporto e condivideremo gli intervalli attuali.
Scelta 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 degli eventi | Solo telephony.* / web.* | Tutti i 10 tipi di evento |
| Filtro eventi | — | Per endpoint |
| Ritentativi | Nessuno | 8 tentativi in 24 h |
| Busta | type + data | type + data + event_id |
| Rotazione del segreto | Sostituisce il singolo segreto | Segreto per endpoint |
| Disattivazione senza eliminazione | — | status=disabled |
| Visibilità dello stato | — | active / disabled / failing |
| Scambio di configurazione bloccante | Sì (telephony.incoming / web.incoming) | Mai — solo notifiche |
| Ideale per | Configurazione dinamica delle chiamate | Consumo di eventi in produzione |
Le nuove integrazioni devono 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
Tutti i tipi di evento e i relativi payload.
Gestisci endpoint multipli, filtri eventi e segreti.
La richiesta bloccante a cui il tuo server deve rispondere per configurare le chiamate.
Payload post-chiamata con trascrizione, registrazione e metriche.