---
title: "Общ преглед на уебкуките"
description: "Как ThunderPhone доставя събития в реално време, как да проверявате подписи и как се сравняват наследеният и базираният на крайни точки модели за доставка."
---

ThunderPhone изпраща HTTP `POST` заявки към вашия сървър, когато по време на разговор
се случат събития — започва входящо обаждане, разговор приключва, изпълнение
за оценяване завършва, задейства се предупреждение и т.н. Има **два модела
за доставяне**:

<CardGroup cols={2}>
  <Card title="Webhook крайни точки (препоръчително)" icon="bolt" href="/bg/webhooks/endpoints">
    Множество URL адреси, тайни за всяка крайна точка, филтри за събития за всяка крайна точка
    и автоматични повторни опити.
    Управлявайте чрез `GET/POST/PATCH/DELETE /v1/developer/webhook-endpoints`.
  </Card>
  <Card title="Наследен webhook с един URL адрес" icon="link" href="/api-reference/organizations#legacy-single-url-webhook">
    Един URL адрес за организация. Съдържа събитията от жизнения цикъл на разговора, включително
    **блокиращите** обмени на конфигурация. Управлява се чрез `GET/PUT /v1/webhook`.
  </Card>
</CardGroup>

Всички десет типа събития в [каталога на събитията](/bg/webhooks/events) се
доставят чрез webhook крайни точки. Шестте събития от жизнения цикъл на разговора
(`telephony.incoming`, `telephony.complete`, `telephony.tool`,
`web.incoming`, `web.complete`, `web.tool`) се изпращат **също** към
наследения webhook с един URL адрес — ако имате както наследен URL адрес, така и
съвпадаща крайна точка, получавате събитието и по **двата** пътя. Блокиращото
поведение ([обменът на конфигурация за `telephony.incoming` / `web.incoming`](/bg/webhooks/call-incoming)
и [изпращането на инструменти](/bg/tools/overview) в режим webhook)
е налично единствено по наследения път; всяко доставяне до крайна точка е
известие без изчакване на отговор.

## Формат на полезния товар

Доставките до крайни точки са 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` е уникален за всяко излъчено събитие. Той е идентичен при повторни опити
**и** за всяка крайна точка, която получава събитието — използвайте го за премахване на дубликати.

Наследеният webhook с един URL адрес изпраща същите `type` и `data`, но
**без** `event_id`:

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

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

Вижте [Каталога на събитията](/bg/webhooks/events) за пълния списък с типове
събития и полета на полезния товар.

## Проверка на подписа

Всяка заявка съдържа HMAC-SHA256 подпис върху **суровото тяло на
заявката** в заглавката `X-ThunderPhone-Signature`. Ключът за подписване е
`secret` на крайната точка (или `secret` на webhook на ниво организация за
наследени доставки).

### Стъпки

1. Прочетете суровото тяло на заявката **преди** какъвто и да е анализ.
2. Изчислете `hmac_sha256(secret, body).hexdigest()`.
3. Сравнете го в константно време със заглавката `X-ThunderPhone-Signature`.

Подписваме точно байтовете, които изпращаме, а тези байтове са
каноничната JSON сериализация (сортирани ключове, компактни разделители). Затова
проверката спрямо суровото тяло винаги работи — а ако вашата рамка ви предоставя
само анализиран JSON, повторното му сериализиране със сортирани ключове и
компактни разделители създава идентични байтове. И двата подхода са
описани в [ръководството за проверка](/bg/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"` в
    [крайните точки за уебкуки](/bg/webhooks/endpoints). Върнете `2xx` веднага щом
    полезният товар бъде надеждно приет; обработвайте асинхронно.
  </Accordion>

  <Accordion title="Подреждане">
    Подреждането на доставките се извършва с максимално усилие. На практика доставяме в
    реда, в който се генерират събитията, но повторните опити могат да променят реда при неуспех.
    Винаги премахвайте дубликатите и съгласувайте по `call_id` / id на обекта.
  </Accordion>

  <Accordion title="Дубликати">
    Доставянето е **поне веднъж**: повторен опит след отговор, който не сме
    получили, може да дублира събитие. Всеки повторен опит съдържа същия
    `event_id`, затова съхранявайте обработените id и пропускайте повторенията. `event_id` се
    споделя и между крайните точки — две крайни точки, абонирани за едно и също
    събитие, получават един и същ `event_id`.
  </Accordion>

  <Accordion title="Изчаквания">
    Доставките към крайни точки имат изчакване от **30 сек.** за всеки опит. По
    наследения път блокиращите заявки, които управляват поведението на разговори на живо — обменът за
    конфигурация [`telephony.incoming` / `web.incoming`](/bg/webhooks/call-incoming) —
    изтичат след **10 сек.**, но бавният отговор забавя приемането на разговора, затова
    се стремете да отговорите в рамките на няколко секунди. [Извикването на инструменти](/bg/tools/overview) в режим на уебкуки
    позволява 20 сек. по подразбиране, а декларациите на инструменти могат да задават `timeout` от най-горно ниво.
  </Accordion>

  <Accordion title="Изходящи IP адреси">
    Изходящите уебкуки произхождат от диапазона облачни IP адреси на ThunderPhone.
    Ако защитната ви стена изисква списък с разрешени адреси, свържете се с поддръжката и ще
    споделим текущите диапазони.
  </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`](/bg/webhooks/call-incoming)) | Никога — само известия |
| Най-подходящо за | Динамична конфигурация на разговори | Обработване на събития в продукционна среда |

Новите интеграции трябва да обработват събития чрез уебкуки, базирани на
крайни точки. Запазете (или добавете) наследен URL само ако конфигурирате разговори
динамично при приемане или използвате извикване на инструменти в режим на уебкуки — тези
обмени заявка/отговор се изпълняват само по наследения път.

---

## Свързани

<CardGroup cols={2}>
  <Card title="Каталог на събитията" icon="list" href="/bg/webhooks/events">
    Всички типове събития и техните полезни товари.
  </Card>
  <Card title="Крайни точки за уебкуки" icon="bolt" href="/bg/webhooks/endpoints">
    Управлявайте множество крайни точки, филтри за събития и тайни.
  </Card>
  <Card title="telephony.incoming / web.incoming" icon="phone" href="/bg/webhooks/call-incoming">
    Блокиращата заявка, на която сървърът ви трябва да отговори, за да конфигурира разговори.
  </Card>
  <Card title="telephony.complete / web.complete" icon="phone" href="/bg/webhooks/call-complete">
    Полезен товар след разговор с транскрипция, запис и показатели.
  </Card>
</CardGroup>
