Przegląd webhooków
Jak ThunderPhone dostarcza zdarzenia w czasie rzeczywistym, jak weryfikować podpisy oraz jak porównać starsze modele dostarczania z modelami opartymi na punktach końcowych.
ThunderPhone wysyła żądania HTTP POST do Twojego serwera, gdy podczas połączenia
wystąpią określone zdarzenia — rozpocznie się połączenie przychodzące, zakończy się połączenie, ukończone zostanie
ocenianie, zostanie wyzwolony alert itd. Dostępne są dwa modele
dostarczania:
Wiele adresów URL, sekrety dla poszczególnych endpointów, filtry zdarzeń dla poszczególnych endpointów
oraz automatyczne ponawianie prób.
Zarządzaj za pomocą GET/POST/PATCH/DELETE /v1/developer/webhook-endpoints.
Jeden adres URL na organizację. Obsługuje zdarzenia cyklu życia połączenia, w tym
blokujące wymiany konfiguracji. Zarządzany za pomocą GET/PUT /v1/webhook.
Wszystkie dziesięć typów zdarzeń z katalogu zdarzeń jest
dostarczanych przez endpointy webhooków. Sześć zdarzeń cyklu życia połączenia
(telephony.incoming, telephony.complete, telephony.tool,
web.incoming, web.complete, web.tool) jest również wysyłanych do
starszego webhooka z jednym adresem URL — jeśli masz zarówno starszy adres URL, jak i
pasujący endpoint, otrzymasz zdarzenie na obu ścieżkach. Zachowanie blokujące
(wymiana konfiguracji telephony.incoming / web.incoming
oraz wysyłanie wywołań narzędzi w trybie webhooka)
występuje wyłącznie na starszej ścieżce; każde dostarczenie do endpointu jest
powiadomieniem bez oczekiwania na odpowiedź.
Format ładunku
Dostarczenia do endpointów mają postać obiektu JSON z polami 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 jest unikalne dla każdego wyemitowanego zdarzenia. Jest identyczne przy ponawianiu prób
oraz we wszystkich endpointach, które otrzymują zdarzenie — używaj go do deduplikacji.
Starszy webhook z jednym adresem URL wysyła te same pola type i data, ale
bez event_id:
{
"type": "telephony.incoming",
"data": { "call_id": 987654321, "from_number": "+14155550199", "to_number": "+15551234567" }
}Podczas transmisji każde body jest serializowane kanonicznie — klucze są sortowane alfabetycznie, bez białych znaków, w UTF-8. Przykłady sformatowane dla czytelności w tej dokumentacji służą wyłącznie ułatwieniu lektury.
Pełną listę typów zdarzeń i pól ładunku znajdziesz w katalogu zdarzeń.
Weryfikacja podpisu
Każde żądanie zawiera podpis HMAC-SHA256 dla surowego treści
żądania w nagłówku X-ThunderPhone-Signature. Kluczem podpisującym jest
secret punktu końcowego (lub secret webhooka na poziomie organizacji w przypadku starszych
dostaw).
Kroki
- Odczytaj surową treść żądania przed jakimkolwiek parsowaniem.
- Oblicz
hmac_sha256(secret, body).hexdigest(). - Porównaj w czasie stałym z nagłówkiem
X-ThunderPhone-Signature.
Podpisujemy dokładnie te bajty, które przesyłamy, a są nimi kanoniczna serializacja JSON (posortowane klucze, zwarte separatory). Dlatego weryfikacja względem surowej treści zawsze działa — a jeśli framework udostępnia tylko sparsowany JSON, ponowna serializacja z posortowanymi kluczami i zwartymi separatorami daje identyczne bajty. Oba sposoby opisano w przewodniku po weryfikacji.
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);
},
);Semantyka dostarczania
Ta semantyka dotyczy dostarczania do punktów końcowych. Starszy webhook z jednym adresem URL wykonuje pojedynczą synchroniczną próbę bez ponowień.
Ponowienia
Każde zdarzenie jest podejmowane natychmiast jeden raz. Każda odpowiedź
2xx potwierdza dostarczenie. Przy każdym innym wyniku (nie-2xx,
błąd połączenia, przekroczenie limitu czasu) ponawiamy próbę po 1 min, 5 min, 30 min, 2 h, 6 h,
12 h i 24 h od pierwszej próby — 8 prób w ciągu
24 godzin. Jeśli każda próba się nie powiedzie, dostarczanie zostaje zatrzymane, a punkt końcowy
otrzymuje oznaczenie status="failing" w
punktach końcowych webhooków. Zwróć 2xx, gdy tylko
dane zostaną trwale przyjęte; przetwarzaj je asynchronicznie.
Kolejność
Kolejność dostarczania jest realizowana według najlepszych starań. W praktyce dostarczamy zdarzenia w
kolejności ich emisji, ale ponowienia mogą zmienić kolejność po błędzie.
Zawsze usuwaj duplikaty i uzgadniaj dane według call_id / identyfikatora obiektu.
Duplikaty
Dostarczanie odbywa się co najmniej raz: ponowienie po odpowiedzi, której nigdy
nie otrzymaliśmy, może powielić zdarzenie. Każde ponowienie zawiera ten sam
event_id, dlatego przechowuj przetworzone identyfikatory i pomijaj powtórzenia. event_id jest
również wspólny dla punktów końcowych — dwa punkty końcowe subskrybujące to
samo zdarzenie otrzymują ten sam event_id.
Limity czasu
Dostarczanie do punktów końcowych ma limit czasu 30 s na próbę. W
starszej ścieżce blokujące żądania sterujące zachowaniem aktywnego połączenia —
wymiana konfiguracji telephony.incoming / web.incoming —
przekraczają limit czasu po 10 s, ale wolna odpowiedź opóźnia
odebranie połączenia, dlatego staraj się odpowiadać w ciągu kilku
sekund. Wysyłanie narzędzi w trybie webhooków tool dispatch domyślnie pozwala na 20 s,
a deklaracje narzędzi mogą ustawiać timeout najwyższego poziomu.
Źródłowe adresy IP
Wychodzące webhooki pochodzą z zakresu adresów IP chmury ThunderPhone. Jeśli zapora wymaga listy dozwolonych adresów, skontaktuj się z pomocą techniczną, a udostępnimy aktualne zakresy.
Wybór między starszymi webhookami a webhookami opartymi na punktach końcowych
| Funkcja | Starsze (/v1/webhook) | Punkty końcowe (/v1/developer/webhook-endpoints) |
|---|---|---|
| Liczba adresów URL | 1 na organizację | Wiele na organizację |
| Zakres zdarzeń | Tylko telephony.* / web.* | Wszystkie 10 typów zdarzeń |
| Filtr zdarzeń | — | Dla każdego punktu końcowego |
| Ponowienia | Brak | 8 prób w ciągu 24 h |
| Obwiednia | type + data | type + data + event_id |
| Rotacja sekretu | Zastępuje pojedynczy sekret | Sekret dla każdego punktu końcowego |
| Wyłączenie bez usuwania | — | status=disabled |
| Widoczność stanu | — | active / disabled / failing |
| Blokująca wymiana konfiguracji | Tak (telephony.incoming / web.incoming) | Nigdy — tylko powiadomienia |
| Najlepsze zastosowanie | Dynamiczna konfiguracja połączeń | Odbieranie zdarzeń w środowisku produkcyjnym |
Nowe integracje powinny odbierać zdarzenia za pośrednictwem webhooków opartych na punktach końcowych. Zachowaj (lub dodaj) starszy adres URL tylko wtedy, gdy konfigurujesz połączenia dynamicznie w momencie odebrania lub używasz wysyłania narzędzi w trybie webhooków — te wymiany żądań i odpowiedzi działają wyłącznie w starszej ścieżce.
Powiązane
Wszystkie typy zdarzeń i ich dane.
Zarządzaj wieloma punktami końcowymi, filtrami zdarzeń i sekretami.
Blokujące żądanie, na które serwer musi odpowiedzieć, aby skonfigurować połączenia.
Dane po połączeniu z transkrypcją, nagraniem i metrykami.