ThunderPhone 2.0 è arrivato.Attivazione self-service, 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 su 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 un'esecuzione di valutazione, viene attivato un avviso e così via. Esistono due modelli di consegna:

Tutti i tipi di evento elencati nel catalogo eventi 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 inoltre inviati al webhook legacy a URL singolo: se hai sia un URL legacy sia 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) risiede 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 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:

{
  "type": "telephony.incoming",
  "data": { "call_id": 987654321, "from_number": "+14155550199", "to_number": "+15551234567" }
}

In transito, ogni body viene serializzato in modo canonico: chiavi ordinate alfabeticamente, nessuno spazio vuoto, UTF-8. Gli esempi formattati nelle presenti documentazioni sono solo a scopo di leggibilità.

Consulta il catalogo eventi per l'elenco completo dei tipi di evento e dei campi del payload.

Verifica della firma

Ogni richiesta include due firme, entrambe HMAC-SHA256 con chiave nel secret dell'endpoint (oppure nel secret del webhook a livello di organizzazione per le consegne legacy):

  • X-ThunderPhone-Signature-V2: t=<unix seconds>,v1=<hex> firma il timestamp, il metodo, il percorso e la query, nonché il corpo non elaborato. Verifica questa firma e rifiuta le richieste il cui t dista più di 5 minuti dal tuo orologio, in modo che una richiesta intercettata non possa essere riprodotta in seguito. I tentativi vengono firmati di nuovo quando vengono inviati.
  • X-ThunderPhone-Signature firma solo il corpo non elaborato. Viene comunque inviata, invariata, affinché i verificatori esistenti continuino a funzionare.

Passaggi

  1. Leggi l'header, il metodo, il percorso e la query non elaborati e il corpo non elaborato prima di qualsiasi analisi.
  2. Calcola l'HMAC-SHA256 esadecimale di <t>\n<METHOD>\n<path and query>\n<body> con il tuo segreto.
  3. Rifiuta la richiesta se t differisce di oltre 300 secondi o se il risultato non corrisponde a v1 in un confronto a tempo costante.

I corpi sono la serializzazione JSON canonica (chiavi ordinate, separatori compatti) e firmiamo esattamente i byte che trasmettiamo. La guida alla verifica contiene la stringa firmata esatta, un esempio completo, codice senza SDK e l'header legacy.

Python (Flask)
from flask import Flask, abort, request
from thunderphone import verify_signature
 
app = Flask(__name__)
 
@app.post("/thunderphone-webhook")
def handle():
    query = request.query_string.decode()
    if not verify_signature(
        request.headers.get("X-ThunderPhone-Signature-V2"),
        secret=WEBHOOK_SECRET,
        method=request.method,
        path=request.path + (f"?{query}" if query else ""),
        body=request.get_data(),  # raw bytes
    ):
        abort(401)
    event = request.get_json()
    # dispatch on event["type"] …
    return "", 204
Node.js (Express)
import express from "express";
import { verifySignature } from "@thunderphone/sdk";
 
const app = express();
app.post(
  "/thunderphone-webhook",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const ok = verifySignature({
      header: req.header("X-ThunderPhone-Signature-V2"),
      secret: process.env.WEBHOOK_SECRET,
      method: req.method,
      path: req.originalUrl,
      body: req.body,
    });
    if (!ok) return res.sendStatus(401);
    const event = JSON.parse(req.body.toString("utf8"));
    // dispatch on event.type …
    res.sendStatus(204);
  },
);

Semantica di consegna

Queste semantiche si applicano alle consegne degli endpoint. Il webhook legacy con un singolo URL effettua un unico tentativo sincrono senza nuovi tentativi, fatta eccezione per lo scambio di configurazione delle chiamate telefoniche telephony.incoming, che viene ritentato una volta.

Nuovi tentativi

Ogni evento viene tentato immediatamente una volta. Qualsiasi risposta 2xx conferma la consegna. In caso di qualsiasi altro esito (non-2xx, errore di connessione, timeout), ritentiamo dopo 1 m, 5 m, 30 m, 2 h, 6 h, 12 h e 24 h dal primo tentativo — 8 tentativi nell'arco di 24 ore. Se tutti i tentativi falliscono, la consegna si interrompe e l'endpoint viene contrassegnato con status="failing" negli endpoint webhook; inviamo un'email ai proprietari e agli amministratori della tua organizzazione e lo mostriamo nella dashboard. Restituisci 2xx non appena il payload viene accettato in modo duraturo; elaboralo in modo asincrono. Gli invii di test dall'API di test dell'endpoint ricevono un nuovo tentativo circa un minuto dopo il primo tentativo e non modificano mai lo stato dell'endpoint.

Ordinamento

L'ordinamento delle consegne è best-effort. In pratica consegniamo gli eventi nell'ordine in cui vengono emessi, ma i ritentativi possono modificarne l'ordine in caso di errore. Esegui sempre deduplicazione e riconciliazione tramite 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 contiene lo stesso event_id, quindi memorizza gli id elaborati e ignora le ripetizioni. event_id è inoltre condiviso tra gli endpoint — due endpoint iscritti allo stesso evento ricevono lo stesso event_id.

Timeout

Le consegne degli endpoint hanno un timeout di 30 s per tentativo. Nel percorso legacy, le richieste bloccanti che determinano il comportamento delle chiamate in corso — lo scambio di configurazione telephony.incoming / web.incoming — sono limitate a 10 s per il primo tentativo di una chiamata telefonica e a 13 s per l'intero scambio, incluso l'unico nuovo tentativo, e a 10 s per una sessione widget. Una risposta lenta ritarda la risposta alla 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 IP cloud di ThunderPhone. Se il tuo firewall richiede un elenco di autorizzazione, 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 eventi—Per endpoint
TentativiNessuno, tranne uno per lo scambio di configurazione della chiamata telefonica8 tentativi nell'arco di 24 h
Bustatype + datatype + data + event_id
Rotazione del segretoSostituisce il singolo segretoSegreto per endpoint
Disabilitare senza eliminarePUT /v1/webhook con {"url": ""}status=disabled
Visibilità dello stato—active / disabled / failing
Scambio di configurazione bloccanteSì (telephony.incoming / web.incoming)Mai — solo notifiche
Ideale perConfigurazione dinamica delle chiamateElaborazione degli 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 risposta o utilizzi l'invio di strumenti in modalità webhook — questi scambi richiesta/risposta vengono eseguiti solo sul percorso legacy.


Correlati