Oversigt over webhooks
Sådan leverer ThunderPhone hændelser i realtid, sådan verificerer du signaturer, og sådan sammenlignes de ældre og endpoint-baserede leveringsmodeller.
ThunderPhone sender HTTP-POST-anmodninger til din server, når der sker
noget under et opkald — et indgående opkald starter, et opkald slutter, en
evalueringskørsel fuldføres, en alarm udløses osv. Der er to
leveringsmodeller:
Flere URL'er, hemmeligheder pr. endepunkt, hændelsesfiltre pr. endepunkt
og automatiske genforsøg.
Administrer via GET/POST/PATCH/DELETE /v1/developer/webhook-endpoints.
Én URL pr. organisation. Indeholder opkaldslivscyklushændelserne, herunder de
blokerende konfigurationsudvekslinger. Administreres på GET/PUT /v1/webhook.
Alle ti hændelsestyper i hændelseskataloget leveres
via webhookendepunkter. De seks opkaldslivscyklushændelser
(telephony.incoming, telephony.complete, telephony.tool,
web.incoming, web.complete, web.tool) sendes også til det
forældede webhook med enkelt URL — hvis du både har en forældet URL og et
matchende endepunkt, modtager du hændelsen på begge ruter. Blokerende
adfærd (konfigurationsudvekslingen for
telephony.incoming / web.incoming og
værktøjsafsendelse i webhooktilstand)
findes udelukkende på den forældede rute; hver levering til et endepunkt er en
fire-and-forget-notifikation.
Payloadformat
Leveringer til endepunkter er et JSON-objekt med data, event_id og
type:
{
"data": {
"call_id": 987654321,
"from_number": "+14155550199",
"to_number": "+15551234567"
},
"event_id": "3f6b2ad0-1c9e-4a57-9f2b-8f6f0f9d2f11",
"type": "telephony.incoming"
}event_id er unik for hver udsendt hændelse. Den er identisk på tværs af genforsøg
og på tværs af alle endepunkter, der modtager hændelsen — dedupliker efter den.
Det forældede webhook med enkelt URL sender samme type og data, men
uden event_id:
{
"type": "telephony.incoming",
"data": { "call_id": 987654321, "from_number": "+14155550199", "to_number": "+15551234567" }
}Ved overførsel serialiseres alle brødtekster kanonisk — nøgler sorteres alfabetisk, uden mellemrum, UTF-8. De pænt formaterede eksempler i denne dokumentation er kun for læsbarhed.
Se hændelseskataloget for den komplette liste over hændelsestyper og payloadfelter.
Signaturverifikation
Hver anmodning indeholder en HMAC-SHA256-signatur over den rå anmodnings-
brødtekst i headeren X-ThunderPhone-Signature. Signeringsnøglen er
endepunktets secret (eller din organisations webhook-secret for ældre
leveringer).
Trin
- Læs den rå anmodningsbrødtekst før enhver parsing.
- Beregn
hmac_sha256(secret, body).hexdigest(). - Sammenlign i konstant tid med headeren
X-ThunderPhone-Signature.
Vi signerer nøjagtigt de bytes, vi sender, og disse bytes er den kanoniske JSON-serialisering (sorterede nøgler, kompakte separatorer). Derfor virker verifikation mod den rå brødtekst altid — og hvis dit framework kun giver dig parset JSON, producerer en genserialisering med sorterede nøgler og kompakte separatorer identiske bytes. Begge fremgangsmåder er beskrevet i verifikationsvejledningen.
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);
},
);Leveringssemantik
Denne semantik gælder for leveringer til endepunkter. Den ældre webhook med én URL er ét enkelt synkront forsøg uden genforsøg.
Genforsøg
Hver hændelse forsøges leveret én gang med det samme. Ethvert 2xx-svar
bekræfter leveringen. Ved ethvert andet udfald (ikke-2xx,
forbindelsesfejl, timeout) forsøger vi igen efter 1 m, 5 m, 30 m, 2 h, 6 h,
12 h og 24 h efter det første forsøg — 8 forsøg fordelt over
24 timer. Hvis alle forsøg fejler, stopper leveringen, og endepunktet
markeres med status="failing" i
webhook-endepunkter. Returnér 2xx, så snart
payloadet er accepteret varigt; behandl det asynkront.
Rækkefølge
Leveringsrækkefølgen er efter bedste evne. I praksis leverer vi i den
rækkefølge, hændelser udsendes, men genforsøg kan ændre rækkefølgen ved fejl.
Dedupliker og afstem altid efter call_id / objekt-id.
Dubletter
Levering er mindst én gang: et genforsøg efter et svar, vi aldrig
modtog, kan duplikere en hændelse. Hvert genforsøg bruger det samme
event_id, så gem behandlede id'er, og spring gentagelser over. event_id deles
også på tværs af endepunkter — to endepunkter, der abonnerer på den
samme hændelse, modtager det samme event_id.
Timeouts
Leveringer til endepunkter har en timeout på 30 s pr. forsøg. På den
ældre sti får blokerende anmodninger, der styrer live opkaldsadfærd — den
telephony.incoming / web.incoming
konfigurationsudveksling — timeout efter 10 s, men et langsomt
svar forsinker opkaldsbesvarelsen, så tilstræb at svare inden for et par
sekunder. Værktøjskald i webhook-tilstand tillader som standard 20 s,
og værktøjsdeklarationer kan angive en timeout på øverste niveau.
Kilde-IP'er
Udgående webhooks kommer fra ThunderPhone's cloud-IP-område. Hvis din firewall kræver en tilladelsesliste, skal du kontakte support, så deler vi de aktuelle områder.
Valg mellem ældre og endepunktbaserede webhooks
| Funktion | Ældre (/v1/webhook) | Endepunkter (/v1/developer/webhook-endpoints) |
|---|---|---|
| Antal URL'er | 1 pr. organisation | Flere pr. organisation |
| Hændelsesdækning | Kun telephony.* / web.* | Alle 10 hændelsestyper |
| Hændelsesfilter | — | Pr. endepunkt |
| Genforsøg | Ingen | 8 forsøg over 24 h |
| Indpakning | type + data | type + data + event_id |
| Rotation af hemmelighed | Erstatter enkelt hemmelighed | Hemmelighed pr. endepunkt |
| Deaktiver uden sletning | — | status=disabled |
| Statussynlighed | — | active / disabled / failing |
| Blokerende konfigurationsudveksling | Ja (telephony.incoming / web.incoming) | Aldrig — kun notifikationer |
| Bedst til | Dynamisk opkaldskonfiguration | Hændelsesforbrug i produktion |
Nye integrationer bør modtage hændelser via endepunktbaserede webhooks. Behold (eller tilføj) kun en ældre URL, hvis du konfigurerer opkald dynamisk ved besvarelse eller bruger værktøjskald i webhook-tilstand — disse anmodnings-/svarudvekslinger kører kun på den ældre sti.
Relateret
Alle hændelsestyper og deres payloads.
Administrer flere endepunkter, hændelsesfiltre og hemmeligheder.
Den blokerende anmodning, din server skal besvare for at konfigurere opkald.
Payload efter opkaldet med transskription, optagelse og målinger.