Prezentare generală a webhook-urilor
Cum livrează ThunderPhone evenimente în timp real, cum să verificați semnăturile și cum se compară modelele de livrare vechi și bazat pe endpoint-uri.
ThunderPhone trimite cereri HTTP POST către serverul dumneavoastră atunci când au loc evenimente în timpul unui apel — începe un apel de intrare, se încheie un apel, se finalizează o rulare de evaluare, se declanșează o alertă și așa mai departe. Există două modele de livrare:
Mai multe URL-uri, secrete pentru fiecare endpoint, filtre de evenimente pentru fiecare endpoint
și reîncercări automate.
Gestionați prin GET/POST/PATCH/DELETE /v1/developer/webhook-endpoints.
Un URL per organizație. Include evenimentele din ciclul de viață al apelului, inclusiv
schimburile de configurare blocante. Gestionat la GET/PUT /v1/webhook.
Toate cele zece tipuri de evenimente din catalogul de evenimente sunt
livrate prin endpointuri webhook. Cele șase evenimente din ciclul de viață al apelului
(telephony.incoming, telephony.complete, telephony.tool,
web.incoming, web.complete, web.tool) sunt trimise și către
webhook-ul moștenit cu un singur URL — dacă aveți atât un URL moștenit, cât și un
endpoint corespunzător, primiți evenimentul pe ambele căi. Comportamentul
blocant (schimbul de configurare telephony.incoming / web.incoming
și expedierea instrumentelor în modul webhook)
există exclusiv pe calea moștenită; fiecare livrare către endpoint este o
notificare fire-and-forget.
Formatul încărcăturii utile
Livrările către endpoint sunt un obiect JSON cu data, event_id și
type:
{
"data": {
"call_id": 987654321,
"from_number": "+14155550199",
"to_number": "+15551234567"
},
"event_id": "3f6b2ad0-1c9e-4a57-9f2b-8f6f0f9d2f11",
"type": "telephony.incoming"
}event_id este unic pentru fiecare eveniment emis. Este identic între reîncercări
și între toate endpointurile care primesc evenimentul — deduplicați pe baza acestuia.
Webhook-ul moștenit cu un singur URL trimite aceleași type și data, dar
fără event_id:
{
"type": "telephony.incoming",
"data": { "call_id": 987654321, "from_number": "+14155550199", "to_number": "+15551234567" }
}La transmitere, fiecare corp este serializat canonic — cheile sunt sortate alfabetic, fără spații albe, UTF-8. Exemplele formatate pentru lizibilitate din această documentație sunt doar pentru claritate.
Consultați catalogul de evenimente pentru lista completă de tipuri de evenimente și câmpuri ale încărcăturii utile.
Verificarea semnăturii
Fiecare solicitare include o semnătură HMAC-SHA256 calculată asupra corpului brut al solicitării în antetul X-ThunderPhone-Signature. Cheia de semnare este secret al endpointului (sau secret webhook la nivel de organizație pentru livrările vechi).
Pași
- Citiți corpul brut al solicitării înainte de orice analiză.
- Calculați
hmac_sha256(secret, body).hexdigest(). - Comparați în timp constant cu antetul
X-ThunderPhone-Signature.
Semnăm exact octeții pe care îi transmitem, iar acești octeți reprezintă serializarea JSON canonică (chei sortate, separatori compacți). Prin urmare, verificarea față de corpul brut funcționează întotdeauna — iar dacă frameworkul vă oferă doar JSON analizat, reserializarea acestuia cu chei sortate și separatori compacți produce octeți identici. Ambele metode sunt prezentate în ghidul de verificare.
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 livrării
Această semantică se aplică livrărilor către endpoint-uri. Webhook-ul moștenit cu un singur URL efectuează o singură încercare sincronă, fără reîncercări.
Reîncercări
Fiecare eveniment este încercat imediat o dată. Orice răspuns 2xx
confirmă livrarea. Pentru orice alt rezultat (non-2xx,
eroare de conexiune, expirare) reîncercăm la 1 min, 5 min, 30 min, 2 h, 6 h,
12 h și 24 h după prima încercare — 8 încercări pe parcursul a
24 de ore. Dacă toate încercările eșuează, livrarea se oprește, iar endpoint-ul
este marcat cu status="failing" în
endpoint-uri webhook. Returnați 2xx de îndată ce
payload-ul este acceptat în mod durabil; procesați asincron.
Ordonare
Ordonarea livrărilor se face pe baza celui mai bun efort. În practică, livrăm în
ordinea în care sunt emise evenimentele, însă reîncercările pot reordona livrările în caz de eșec.
Eliminați întotdeauna duplicatele și reconciliați după call_id / id-ul obiectului.
Duplicate
Livrarea este cel puțin o dată: o reîncercare după un răspuns pe care nu l-am
primit poate duplica un eveniment. Fiecare reîncercare poartă același
event_id, așadar stocați id-urile procesate și omiteți repetările. event_id este
partajat și între endpoint-uri — două endpoint-uri abonate la același eveniment
primesc același event_id.
Expirări
Livrările către endpoint-uri au o expirare de 30 s pentru fiecare încercare. Pe
calea moștenită, solicitările blocante care controlează comportamentul apelurilor active —
schimbul de configurare telephony.incoming / web.incoming —
expiră după 10 s, însă un răspuns lent întârzie preluarea apelului, așa că
urmăriți să răspundeți în câteva secunde. Expedierea instrumentelor în
modul webhook permite implicit 20 s, iar declarațiile de instrumente pot seta un timeout
la nivel superior.
IP-uri sursă
Webhook-urile de ieșire provin din intervalul de IP-uri cloud al ThunderPhone. Dacă firewall-ul dumneavoastră necesită o listă de permisiuni, contactați asistența, iar noi vă vom comunica intervalele actuale.
Alegerea între webhook-uri moștenite și bazate pe endpoint-uri
| Funcționalitate | Moștenit (/v1/webhook) | Endpoint-uri (/v1/developer/webhook-endpoints) |
|---|---|---|
| Număr de URL-uri | 1 per organizație | Mai multe per organizație |
| Acoperire evenimente | Doar telephony.* / web.* | Toate cele 10 tipuri de evenimente |
| Filtru de evenimente | — | Pentru fiecare endpoint |
| Reîncercări | Niciuna | 8 încercări în 24 h |
| Plic | type + data | type + data + event_id |
| Rotirea secretului | Înlocuiește secretul unic | Secret pentru fiecare endpoint |
| Dezactivare fără ștergere | — | status=disabled |
| Vizibilitatea stării | — | active / disabled / failing |
| Schimb de configurare blocant | Da (telephony.incoming / web.incoming) | Niciodată — doar notificări |
| Cel mai potrivit pentru | Configurarea dinamică a apelurilor | Consumul de evenimente în producție |
Integrările noi ar trebui să consume evenimente prin webhook-uri bazate pe endpoint-uri. Păstrați (sau adăugați) un URL moștenit numai dacă configurați apelurile dinamic la momentul preluării sau utilizați expedierea instrumentelor în modul webhook — aceste schimburi cerere/răspuns rulează numai pe calea moștenită.
Asociate
Toate tipurile de evenimente și payload-urile acestora.
Gestionați mai multe endpoint-uri, filtre de evenimente și secrete.
Solicitarea blocantă la care serverul dumneavoastră trebuie să răspundă pentru a configura apelurile.
Payload după apel, cu transcriere, înregistrare și metrici.