---
title: "Преглед вебхукова"
description: "Како ThunderPhone испоручује догађаје у реалном времену, како да проверите потписе и како се пореде застарели и модели испоруке засновани на крајњим тачкама."
---

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

<CardGroup cols={2}>
  <Card title="Крајње тачке веб-кука (препоручено)" icon="bolt" href="/sr/webhooks/endpoints">
    Више URL-ова, тајне по крајњој тачки, филтери догађаја по крајњој тачки
    и аутоматски поновни покушаји.
    Управљајте преко `GET/POST/PATCH/DELETE /v1/developer/webhook-endpoints`.
  </Card>
  <Card title="Застарела веб-кука са једним URL-ом" icon="link" href="/api-reference/organizations#legacy-single-url-webhook">
    Један URL по организацији. Садржи догађаје животног циклуса позива, укључујући
    **блокирајуће** размене конфигурације. Њоме се управља преко `GET/PUT /v1/webhook`.
  </Card>
</CardGroup>

Свих десет типова догађаја из [каталога догађаја](/sr/webhooks/events)
испоручује се преко крајњих тачака веб-кука. Шест догађаја животног циклуса позива
(`telephony.incoming`, `telephony.complete`, `telephony.tool`,
`web.incoming`, `web.complete`, `web.tool`) се **такође** шаљу на
застарелу веб-куку са једним URL-ом — ако имате и застарели URL и
одговарајућу крајњу тачку, догађај примате на **обе** путање. Блокирајуће
понашање ([размена конфигурације `telephony.incoming` / `web.incoming`](/sr/webhooks/call-incoming)
и [прослеђивање алатке](/sr/tools/overview) у
режиму веб-куке) постоји искључиво на застарелој путањи; свака испорука
крајњој тачки је обавештење без чекања одговора.

## Формат корисног терета

Испоруке крајњим тачкама су JSON објекат са `data`, `event_id` и
`type`:

```json
{
  "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`:

```json
{
  "type": "telephony.incoming",
  "data": { "call_id": 987654321, "from_number": "+14155550199", "to_number": "+15551234567" }
}
```

При преносу, свако тело се серијализује канонски — кључеви су сортирани
азбучно, без размака, у UTF-8 формату. Примери са форматираним приказом у
овој документацији служе само ради читљивости.

Погледајте [Каталог догађаја](/sr/webhooks/events) за комплетну листу типова
догађаја и поља корисног терета.

## Верификација потписа

Сваки захтев садржи HMAC-SHA256 потпис над **сировим телом
захтева** у заглављу `X-ThunderPhone-Signature`. Кључ за потписивање је
`secret` крајње тачке (или `secret` веб-куке на нивоу Ваше организације за
застареле испоруке).

### Кораци

1. Прочитајте сирово тело захтева **пре** било каквог рашчлањивања.
2. Израчунајте `hmac_sha256(secret, body).hexdigest()`.
3. Упоредите у константном времену са заглављем `X-ThunderPhone-Signature`.

Потписујемо тачно бајтове које шаљемо, а ти бајтови су
канонска JSON серијализација (сортирани кључеви, сажети раздвајачи). Зато
верификација према сировом телу увек функционише — а ако Вам оквир
прослеђује само рашчлањени JSON, поновна серијализација са сортираним
кључевима и сажетим раздвајачима производи идентичне бајтове. Оба поступка
су обухваћена у [водичу за верификацију](/sr/guides/verify-webhook-signatures).

<CodeGroup>
```python Python
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
```

```javascript Node.js (Express)
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);
  },
);
```
</CodeGroup>

## Семантика испоруке

Ова семантика се примењује на испоруке до **крајњих тачака**. Наслеђени вебхук са једним URL-ом
има један синхрони покушај без поновних покушаја.

<AccordionGroup>
  <Accordion title="Поновни покушаји">
    Сваки догађај се одмах покушава једном. Сваки одговор `2xx`
    потврђује испоруку. За сваки други исход (који није 2xx,
    грешка везе, истек времена) поново покушавамо **1 м, 5 м, 30 м, 2 ч, 6 ч,
    12 ч и 24 ч након првог покушаја** — 8 покушаја током
    24 часа. Ако сваки покушај не успе, испорука се зауставља и крајња тачка
    се означава као `status="failing"` у
    [крајњим тачкама вебхука](/sr/webhooks/endpoints). Вратите `2xx` чим се
    корисни терет трајно прихвати; обрадите га асинхроно.
  </Accordion>

  <Accordion title="Редослед">
    Редослед испоруке је по принципу најбољег напора. У пракси испоручујемо
    редоследом којим се догађаји емитују, али поновни покушаји могу променити редослед при неуспеху.
    Увек уклоните дупликате и усагласите податке на основу `call_id` / ИД-а објекта.
  </Accordion>

  <Accordion title="Дупликати">
    Испорука је **најмање једном**: поновни покушај након одговора који никада нисмо
    примили може дуплирати догађај. Сваки поновни покушај носи исти
    `event_id`, зато сачувајте обрађене ИД-ове и прескочите понављања. `event_id` се
    такође дели између крајњих тачака — две крајње тачке претплаћене на
    исти догађај примају исти `event_id`.
  </Accordion>

  <Accordion title="Истек времена">
    Испоруке до крајњих тачака имају истек времена од **30 с** по покушају. На
    наслеђеној путањи, блокирајући захтеви који управљају понашањем активног позива — размена
    конфигурације [`telephony.incoming` / `web.incoming`](/sr/webhooks/call-incoming) —
    истичу након **10 с**, али спор одговор одлаже јављање на позив, зато
    настојте да одговорите у року од неколико секунди. Испорука алатки у режиму вебхука
    [дозвољава](/sr/tools/overview) 20 с
    подразумевано, а декларације алатки могу поставити `timeout` највишег нивоа.
  </Accordion>

  <Accordion title="Изворне IP адресе">
    Одлазни вебхукови потичу из ThunderPhone опсега IP адреса у облаку.
    Ако ваш заштитни зид захтева листу дозвољених адреса, обратите се подршци и доставићемо
    тренутне опсеге.
  </Accordion>
</AccordionGroup>

## Избор између наслеђених вебхукова и вебхукова заснованих на крајњим тачкама

| Функција | Наслеђени (`/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`](/sr/webhooks/call-incoming)) | Никада — само обавештења |
| Најбоље за | Динамичку конфигурацију позива | Обраду догађаја у продукцији |

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

---

## Повезано

<CardGroup cols={2}>
  <Card title="Каталог догађаја" icon="list" href="/sr/webhooks/events">
    Сви типови догађаја и њихови корисни терети.
  </Card>
  <Card title="Крајње тачке вебхука" icon="bolt" href="/sr/webhooks/endpoints">
    Управљајте вишеструким крајњим тачкама, филтерима догађаја и тајнама.
  </Card>
  <Card title="telephony.incoming / web.incoming" icon="phone" href="/sr/webhooks/call-incoming">
    Блокирајући захтев на који ваш сервер мора да одговори да би конфигурисао позиве.
  </Card>
  <Card title="telephony.complete / web.complete" icon="phone" href="/sr/webhooks/call-complete">
    Корисни терет након позива са транскриптом, снимком и метрикама.
  </Card>
</CardGroup>
