Преглед вебхукова

ThunderPhone шаље HTTP POST захтеве вашем серверу када се догађаји одвијају током позива — започне се долазни позив, позив се заврши, покретање оцењивања се доврши, активира се упозорење и тако даље. Постоје два модела испоруке:

Свих десет типова догађаја из каталога догађаја испоручује се преко крајњих тачака веб-хука. Шест догађаја животног циклуса позива (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 веб-хука на нивоу ваше организације за застареле испоруке).

Кораци

  1. Прочитајте необрађено тело захтева пре било каквог рашчлањивања.
  2. Израчунајте hmac_sha256(secret, body).hexdigest().
  3. Упоредите у константном времену са заглављем 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 + datatype + data + event_id
Ротација тајнеЗамењује јединствену тајнуТајна по крајњој тачки
Онемогућавање без брисањаstatus=disabled
Видљивост статусаactive / disabled / failing
Блокирајућа размена конфигурацијеДа (telephony.incoming / web.incoming)Никада — само обавештења
Најбоље заДинамичку конфигурацију позиваПотрошњу догађаја у продукцији

Нове интеграције треба да примају догађаје путем webhook-ова заснованих на крајњим тачкама. Задржите (или додајте) наслеђени URL само ако конфигуришете позиве динамички у тренутку јављања или користите прослеђивање алатки у webhook режиму — те размене захтева и одговора покрећу се само на наслеђеној путањи.


Повезано