Преглед вебхукова
ThunderPhone шаље HTTP POST захтеве вашем серверу када се догађаји
одвијају током позива — започне се долазни позив, позив се заврши, покретање
оцењивања се доврши, активира се упозорење и тако даље. Постоје два модела
испоруке:
Више URL-ова, тајне по крајњој тачки, филтери догађаја по крајњој тачки
и аутоматски поновни покушаји.
Управљајте путем GET/POST/PATCH/DELETE /v1/developer/webhook-endpoints.
Један URL по организацији. Садржи догађаје животног циклуса позива, укључујући
блокирајуће размене конфигурације. Управља се путем GET/PUT /v1/webhook.
Свих десет типова догађаја из каталога догађаја
испоручује се преко крајњих тачака веб-хука. Шест догађаја животног циклуса позива
(telephony.incoming, telephony.complete, telephony.tool,
web.incoming, web.complete, web.tool) се такође шаље на
застарели веб-хук са једним URL-ом — ако имате и застарели URL и
одговарајућу крајњу тачку, догађај примате на обе путање. Блокирајуће
понашање (размена конфигурације telephony.incoming / web.incoming
и прослеђивање алата у режиму
веб-хука) постоји искључиво на застарелој путањи; свака испорука на
крајњу тачку је обавештење без чекања на одговор.
Формат садржаја
Испоруке на крајњу тачку су JSON објекат са data, event_id и
type:
{
"data": {
"call_id": 987654321,
"from_number": "+14155550199",
"to_number": "+15551234567"
},
"event_id": "3f6b2ad0-1c9e-4a57-9f2b-8f6f0f9d2f11",
"type": "telephony.incoming"
}
event_id је јединствен за сваки емитовани догађај. Идентичан је при поновним покушајима
и на свакој крајњој тачки која прими догађај — користите га за уклањање дупликата.
Застарели веб-хук са једним URL-ом шаље исти type и data, али
без event_id:
{
"type": "telephony.incoming",
"data": { "call_id": 987654321, "from_number": "+14155550199", "to_number": "+15551234567" }
}
При преносу, свако тело се серијализује канонски — кључеви су сортирани абецедно, без белина, UTF-8. Примери са форматирањем у овој документацији служе само ради читљивости.
Погледајте Каталог догађаја за комплетну листу типова догађаја и поља садржаја.
Верификација потписа
Сваки захтев садржи HMAC-SHA256 потпис над необрађеним телом
захтева у заглављу X-ThunderPhone-Signature. Кључ за потписивање је
secret крајње тачке (или secret веб-хука на нивоу ваше организације за
застареле испоруке).
Кораци
- Прочитајте необрађено тело захтева пре било каквог рашчлањивања.
- Израчунајте
hmac_sha256(secret, body).hexdigest(). - Упоредите у константном времену са заглављем
X-ThunderPhone-Signature.
Потписујемо тачно бајтове које шаљемо, а ти бајтови су канонска JSON серијализација (сортирани кључеви, компактни раздвајачи). Зато верификација у односу на необрађено тело увек функционише — а ако вам оквир прослеђује само рашчлањени JSON, поновна серијализација са сортираним кључевима и компактним раздвајачима производи идентичне бајтове. Оба поступка су обухваћена у водичу за верификацију.
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
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);
},
);
Семантика испоруке
Ова семантика се примењује на испоруке крајњим тачкама. Наслеђени webhook са једним URL-ом је један синхрони покушај без поновних покушаја.
Поновни покушаји
Сваки догађај се одмах покушава једном. Сваки одговор 2xx
потврђује испоруку. При сваком другом исходу (који није 2xx,
грешка везе, истек времена) поново покушавамо 1 мин, 5 мин, 30 мин, 2 ч, 6 ч,
12 ч и 24 ч након првог покушаја — 8 покушаја у периоду од
24 часа. Ако сваки покушај не успе, испорука се зауставља и крајња тачка
се означава као status="failing" у
webhook крајњим тачкама. Вратите 2xx чим
је payload трајно прихваћен; обраду вршите асинхроно.
Редослед
Редослед испоруке је најбољи могући. У пракси испоручујемо оним
редоследом којим се догађаји емитују, али поновни покушаји могу променити редослед при неуспеху.
Увек уклањајте дупликате и усаглашавајте по call_id / ID-ју објекта.
Дупликати
Испорука је најмање једном: поновни покушај након одговора који никада
нисмо примили може дуплирати догађај. Сваки поновни покушај носи исти
event_id, зато чувајте обрађене ID-јеве и прескачите понављања. event_id је
такође заједнички за све крајње тачке — две крајње тачке претплаћене на
исти догађај примају исти event_id.
Истек времена
Испоруке крајњим тачкама имају истек времена од 30 с по покушају. На
наслеђеној путањи, блокирајући захтеви који управљају понашањем активног позива — размена
конфигурације telephony.incoming / web.incoming —
истичу након 10 с, али спор одговор одлаже јављање на позив, зато
настојте да одговорите у року од неколико секунди. Прослеђивање алатки у webhook режиму
tool dispatch дозвољава 20 с.
Изворне IP адресе
Одлазни webhook-ови потичу из ThunderPhone опсега IP адреса у облаку. Ако ваш заштитни зид захтева листу дозвољених адреса, контактирајте подршку и поделићемо тренутне опсеге.
Избор између наслеђених webhook-ова и webhook-ова заснованих на крајњим тачкама
| Функција | Наслеђени (/v1/webhook) | Крајње тачке (/v1/developer/webhook-endpoints) |
|---|---|---|
| Број URL-ова | 1 по организацији | Више по организацији |
| Обухват догађаја | Само telephony.* / web.* | Свих 10 типова догађаја |
| Филтер догађаја | — | По крајњој тачки |
| Поновни покушаји | Нема | 8 покушаја током 24 ч |
| Омотач | type + data | type + data + event_id |
| Ротација тајне | Замењује јединствену тајну | Тајна по крајњој тачки |
| Онемогућавање без брисања | — | status=disabled |
| Видљивост статуса | — | active / disabled / failing |
| Блокирајућа размена конфигурације | Да (telephony.incoming / web.incoming) | Никада — само обавештења |
| Најбоље за | Динамичку конфигурацију позива | Потрошњу догађаја у продукцији |
Нове интеграције треба да примају догађаје путем webhook-ова заснованих на крајњим тачкама. Задржите (или додајте) наслеђени URL само ако конфигуришете позиве динамички у тренутку јављања или користите прослеђивање алатки у webhook режиму — те размене захтева и одговора покрећу се само на наслеђеној путањи.
Повезано
Сви типови догађаја и њихови payload-ови.
Управљајте са више крајњих тачака, филтерима догађаја и тајнама.
Блокирајући захтев на који ваш сервер мора да одговори како би конфигурисао позиве.
Payload након позива са транскриптом, снимком и метрикама.