ThunderPhone 2.0 jest już dostępny.Uruchom samodzielnie — od 2 centów/min.Przeczytaj komunikat

Webhooks

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:

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

  1. Odczytaj surową treść żądania przed jakimkolwiek parsowaniem.
  2. Oblicz hmac_sha256(secret, body).hexdigest().
  3. 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.

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);
  },
);

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

FunkcjaStarsze (/v1/webhook)Punkty końcowe (/v1/developer/webhook-endpoints)
Liczba adresów URL1 na organizacjęWiele na organizację
Zakres zdarzeńTylko telephony.* / web.*Wszystkie 10 typów zdarzeń
Filtr zdarzeńDla każdego punktu końcowego
PonowieniaBrak8 prób w ciągu 24 h
Obwiedniatype + datatype + data + event_id
Rotacja sekretuZastępuje pojedynczy sekretSekret dla każdego punktu końcowego
Wyłączenie bez usuwaniastatus=disabled
Widoczność stanuactive / disabled / failing
Blokująca wymiana konfiguracjiTak (telephony.incoming / web.incoming)Nigdy — tylko powiadomienia
Najlepsze zastosowanieDynamiczna 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