Översikt över webhooks
Så levererar ThunderPhone händelser i realtid, så verifierar du signaturer och så jämförs de äldre och slutpunktsbaserade leveransmodellerna.
ThunderPhone skickar HTTP-POST-begäranden till din server när saker
händer under ett samtal — ett inkommande samtal startar, ett samtal avslutas, en utvärderingskörning slutförs, en avisering utlöses och så vidare. Det finns två leveransmodeller:
Flera URL:er, hemligheter per slutpunkt, händelsefilter per slutpunkt
och automatiska återförsök.
Hantera via GET/POST/PATCH/DELETE /v1/developer/webhook-endpoints.
En URL per organisation. Innehåller händelser för samtalets livscykel, inklusive de
blockerande konfigurationsutbytena. Hanteras via GET/PUT /v1/webhook.
Alla tio händelsetyper i händelsekatalogen
levereras via webhook-slutpunkter. De sex händelserna för samtalets livscykel
(telephony.incoming, telephony.complete, telephony.tool,
web.incoming, web.complete, web.tool) skickas även till den
äldre webhooken med en URL — om du har både en äldre URL och en
matchande slutpunkt får du händelsen på båda sökvägarna. Blockerande
beteende (telephony.incoming / web.incoming-konfigurationsutbytet och verktygsdirigering
i webhook-läge) finns endast på den äldre sökvägen; varje leverans till en slutpunkt är en
envägsavisering.
Payloadformat
Leveranser till slutpunkter är ett JSON-objekt med data, event_id och
type:
{
"data": {
"call_id": 987654321,
"from_number": "+14155550199",
"to_number": "+15551234567"
},
"event_id": "3f6b2ad0-1c9e-4a57-9f2b-8f6f0f9d2f11",
"type": "telephony.incoming"
}event_id är unikt för varje utsänd händelse. Det är identiskt vid återförsök
och för varje slutpunkt som tar emot händelsen — deduplicera utifrån det.
Den äldre webhooken med en URL skickar samma type och data, men
utan event_id:
{
"type": "telephony.incoming",
"data": { "call_id": 987654321, "from_number": "+14155550199", "to_number": "+15551234567" }
}Vid överföring serialiseras varje brödtext kanoniskt — nycklar sorteras alfabetiskt, utan blanksteg, UTF-8. De formaterade exemplen i den här dokumentationen är endast för läsbarhet.
Se händelsekatalogen för en fullständig lista över händelsetyper och payloadfält.
Signaturverifiering
Varje begäran innehåller en HMAC-SHA256-signatur över den råa
begärandetexten i rubriken X-ThunderPhone-Signature. Signeringsnyckeln är
slutpunktens secret (eller din organisationsövergripande webhook-secret
för äldre leveranser).
Steg
- Läs den råa begärandetexten före all tolkning.
- Beräkna
hmac_sha256(secret, body).hexdigest(). - Jämför med rubriken
X-ThunderPhone-Signaturei konstant tid.
Vi signerar exakt de byte vi överför, och dessa byte är den kanoniska JSON-serialiseringen (sorterade nycklar, kompakta avgränsare). Därför fungerar verifiering mot den råa texten alltid — och om ditt ramverk endast ger dig tolkad JSON, producerar en ny serialisering med sorterade nycklar och kompakta avgränsare identiska byte. Båda metoderna beskrivs i verifieringsguiden.
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);
},
);Leveranssemantik
Dessa semantiker gäller leveranser till slutpunkter. Den äldre webhooken med en enda URL är ett enda synkront försök utan återförsök.
Återförsök
Varje händelse försöks levereras en gång direkt. Alla 2xx-svar
bekräftar leveransen. Vid alla andra utfall (icke-2xx,
anslutningsfel, tidsgräns) försöker vi igen 1 min, 5 min, 30 min, 2 tim, 6 tim,
12 tim och 24 tim efter det första försöket — 8 försök under
24 timmar. Om alla försök misslyckas stoppas leveransen och slutpunkten
markeras med status="failing" i
webhookslutpunkter. Returnera 2xx så snart som
nyttolasten har accepterats varaktigt; bearbeta den asynkront.
Ordning
Leveransordningen sker efter bästa förmåga. I praktiken levererar vi i den
ordning som händelser genereras, men återförsök kan ändra ordningen vid fel.
Deduplicera alltid och stäm av efter call_id / objekt-id.
Dubbletter
Leverans sker minst en gång: ett återförsök efter ett svar som vi aldrig
tog emot kan skapa en dubblett av en händelse. Varje återförsök innehåller samma
event_id, så lagra bearbetade id:n och hoppa över upprepningar. event_id delas
också mellan slutpunkter — två slutpunkter som prenumererar på samma händelse får
samma event_id.
Tidsgränser
Leveranser till slutpunkter har en tidsgräns på 30 s per försök. På den
äldre sökvägen får blockerande begäranden som styr beteendet för aktiva samtal —
konfigurationsutbytet för
telephony.incoming / web.incoming —
tidsgräns efter 10 s, men ett långsamt svar fördröjer besvarandet av samtalet,
så sikta på att svara inom ett par sekunder. Verktygsdirigering
i webhook-läge tillåter 20 s som standard, och verktygsdeklarationer kan ange en
timeout på toppnivå.
Käll-IP-adresser
Utgående webhooks kommer från ThunderPhones IP-intervall i molnet. Om din brandvägg kräver en tillåtelselista kontaktar du supporten, så delar vi de aktuella intervallen.
Välja mellan äldre och slutpunktsbaserade webhooks
| Funktion | Äldre (/v1/webhook) | Slutpunkter (/v1/developer/webhook-endpoints) |
|---|---|---|
| Antal URL:er | 1 per organisation | Flera per organisation |
| Händelsetäckning | Endast telephony.* / web.* | Alla 10 händelsetyper |
| Händelsefilter | — | Per slutpunkt |
| Återförsök | Inga | 8 försök under 24 tim |
| Kuvert | type + data | type + data + event_id |
| Hemlighetsrotation | Ersätter en enda hemlighet | Hemlighet per slutpunkt |
| Inaktivera utan att radera | — | status=disabled |
| Statusvisning | — | active / disabled / failing |
| Blockerande konfigurationsutbyte | Ja (telephony.incoming / web.incoming) | Aldrig — endast aviseringar |
| Bäst för | Dynamisk samtalskonfiguration | Händelsehantering i produktion |
Nya integrationer bör ta emot händelser via slutpunktsbaserade webhooks. Behåll (eller lägg till) en äldre URL endast om du konfigurerar samtal dynamiskt när de besvaras eller använder verktygsdirigering i webhook-läge — dessa begäran/svar-utbyten körs endast på den äldre sökvägen.
Relaterat
Alla händelsetyper och deras nyttolaster.
Hantera flera slutpunkter, händelsefilter och hemligheter.
Den blockerande begäran som din server måste besvara för att konfigurera samtal.
Nyttolast efter samtalet med transkription, inspelning och mätvärden.