Open in
Преглед вебхукова
Како ThunderPhone испоручује догађаје у реалном времену, како да проверите потписе и како се пореде застарели и модели испоруке засновани на крајњим тачкама.
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 "", 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);
},
);Семантика испоруке
Ова семантика се примењује на испоруке до крајњих тачака. Наслеђени вебхук са једним URL-ом има један синхрони покушај без поновних покушаја.
Поновни покушаји
Сваки догађај се одмах покушава једном. Сваки одговор 2xx
потврђује испоруку. За сваки други исход (који није 2xx,
грешка везе, истек времена) поново покушавамо 1 м, 5 м, 30 м, 2 ч, 6 ч,
12 ч и 24 ч након првог покушаја — 8 покушаја током
24 часа. Ако сваки покушај не успе, испорука се зауставља и крајња тачка
се означава као status="failing" у
крајњим тачкама вебхука. Вратите 2xx чим се
корисни терет трајно прихвати; обрадите га асинхроно.
Редослед
Редослед испоруке је по принципу најбољег напора. У пракси испоручујемо
редоследом којим се догађаји емитују, али поновни покушаји могу променити редослед при неуспеху.
Увек уклоните дупликате и усагласите податке на основу call_id / ИД-а објекта.
Дупликати
Испорука је најмање једном: поновни покушај након одговора који никада нисмо
примили може дуплирати догађај. Сваки поновни покушај носи исти
event_id, зато сачувајте обрађене ИД-ове и прескочите понављања. event_id се
такође дели између крајњих тачака — две крајње тачке претплаћене на
исти догађај примају исти event_id.
Истек времена
Испоруке до крајњих тачака имају истек времена од 30 с по покушају. На
наслеђеној путањи, блокирајући захтеви који управљају понашањем активног позива — размена
конфигурације telephony.incoming / web.incoming —
истичу након 10 с, али спор одговор одлаже јављање на позив, зато
настојте да одговорите у року од неколико секунди. Испорука алатки у режиму вебхука
дозвољава 20 с
подразумевано, а декларације алатки могу поставити timeout највишег нивоа.
Изворне IP адресе
Одлазни вебхукови потичу из ThunderPhone опсега IP адреса у облаку. Ако ваш заштитни зид захтева листу дозвољених адреса, обратите се подршци и доставићемо тренутне опсеге.
Избор између наслеђених вебхукова и вебхукова заснованих на крајњим тачкама
| Функција | Наслеђени (/v1/webhook) | Крајње тачке (/v1/developer/webhook-endpoints) |
|---|---|---|
| Број URL-ова | 1 по организацији | Више по организацији |
| Обухват догађаја | Само telephony.* / web.* | Свих 10 типова догађаја |
| Филтер догађаја | — | По крајњој тачки |
| Поновни покушаји | Нема | 8 покушаја током 24 ч |
| Омотач | type + data | type + data + event_id |
| Ротација тајне | Замењује једну тајну | Тајна по крајњој тачки |
| Онемогућавање без брисања | PUT /v1/webhook са {"url": ""} | status=disabled |
| Видљивост статуса | — | active / disabled / failing |
| Блокирајућа размена конфигурације | Да (telephony.incoming / web.incoming) | Никада — само обавештења |
| Најбоље за | Динамичку конфигурацију позива | Обраду догађаја у продукцији |
Нове интеграције треба да примају догађаје преко вебхукова заснованих на крајњим тачкама. Задржите (или додајте) наслеђени URL само ако динамички конфигуришете позиве у тренутку јављања или користите испоруку алатки у режиму вебхука — те размене захтева и одговора извршавају се само на наслеђеној путањи.
Повезано
Сви типови догађаја и њихови корисни терети.
Управљајте вишеструким крајњим тачкама, филтерима догађаја и тајнама.
Блокирајући захтев на који ваш сервер мора да одговори да би конфигурисао позиве.
Корисни терет након позива са транскриптом, снимком и метрикама.