ThunderPhone 2.0 è arrivato.Parti in autonomia, da 2¢/min.Leggi l’annuncio

Webhooks

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:

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

  1. Leggi il corpo della richiesta non elaborato prima di qualsiasi analisi.
  2. Calcola hmac_sha256(secret, body).hexdigest().
  3. 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.

Python
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
Node.js (Express)
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);
  },
);

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 URL1 per organizzazioneMolti per organizzazione
Copertura degli eventiSolo telephony.* / web.*Tutti i 10 tipi di evento
Filtro eventiPer endpoint
RitentativiNessuno8 tentativi in 24 h
Bustatype + datatype + data + event_id
Rotazione del segretoSostituisce il singolo segretoSegreto per endpoint
Disattivazione senza eliminazionestatus=disabled
Visibilità dello statoactive / disabled / failing
Scambio di configurazione bloccanteSì (telephony.incoming / web.incoming)Mai — solo notifiche
Ideale perConfigurazione dinamica delle chiamateConsumo 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