# Webhook, обяснен просто: подпис, повторение, сигурност

> Какво е Webhook, с какво се различава от API заявката и как да обработвате подпис, повторение и дубликати. С curl и Node.

Quelle: https://whovisit.app/bg/blog/uebhuk-webhook-obyasnen-prosto-podpis-sigurnost
Veröffentlicht: 2026-11-07
Herausgeber: Whovisit (ARAVILO, Innsbruck)

Webhook е обаждане в обратната посока. Вместо Вашата система да пита една услуга на всеки няколко минути дали нещо се е случило, услугата вика един адрес при Вас, щом се случи. Това спестява заявки и доставя новините по-бързо. За това трябва да осигурите и защитите приемник. Тази статия обяснява и двете.

## Webhook или API заявка

| | API заявка (Polling) | Webhook |
|---|---|---|
| Кой започва? | Вашата система | Изпращачът |
| Кога? | По график, например всеки час | Щом настъпи събитие |
| Усилия | Заявки и без новини | Нужен е достъпен приемник |
| Подходящ за | Отчети, съпоставяне, дозареждане | Известяване, препращане |
| Риск | Забавяне до следващата заявка | Съобщението се губи, ако приемникът не отговори |

На практика мнозина комбинират двете: Webhook съобщава за събитието, API извлича при нужда подробностите или запълва пропуските след срив.

## Как протича една доставка

1. Настъпва събитие, например „постъпило плащане“.
2. Изпращачът изпраща POST с JSON към вписания Ваш адрес (HTTPS).
3. Вашият приемник проверява подписа, запомня съобщението и отговаря бързо с код от диапазона 2xx.
4. Същинската работа Вашата система върши след това.

Стъпка 3 е важна. Stripe препоръчва в [документацията за Webhook](https://docs.stripe.com/webhooks) веднага да се върне код 2xx, преди да тече сложна логика, която може да предизвика изтичане на времето. Който първо осчетоводява фактурата и после отговаря, рискува излишни повторения.

## Подпис: как да проверите произхода

Вашият адрес е публично достъпен. Всеки би могъл да му изпраща измислени съобщения. Затова сериозните изпращачи подписват всяко съобщение със споделена тайна. Обичаен е HMAC със SHA-256 върху съдържанието, често заедно с времеви печат. [GitHub описва метода](https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries) така: HMAC-SHA256 върху съдържанието, представен шестнадесетично, със заглавката `X-Hub-Signature-256`. Сравнението трябва да се извършва с функция с постоянно време на изпълнение, за да не могат нападателите да изведат нищо от времето за отговор.

Важни точки за Вашия приемник:

- **Използвайте суровото съдържание.** Изчислявайте подписа върху получените байтове, а не върху наново сериализирания JSON. Дори друг нов ред променя подписа.
- **Проверявайте времевия печат.** Stripe използва времеви печат в подписа и стандартен толеранс от пет минути, за да предотврати атаки с повторение. Толеранс 0 изключва проверката.
- **Пазете тайната в тайна.** Не в кода, не в хранилището, а в променлива на средата или в трезор.

Насоки с отворен код за изпращачи и приемници обединява инициативата [Standard Webhooks](https://www.standardwebhooks.com/). Как точно всеки доставчик образува своя подпис, е записано в неговата документация.

## Повторения и двойна доставка

Ако Вашият приемник не отговори или отговори с код за грешка, изпращачът опитва отново. Stripe например повтаря според собствената си документация с нарастващи интервали до три дни. Други доставчици имат други правила, някои изключват Webhook след твърде много неуспехи. От това следва:

- **Смятайте с дубликати.** Едно и също събитие може да пристигне многократно, дори Вашият приемник да работи правилно. Stripe изрично посочва това.
- **Направете обработването идемпотентно.** Това означава: който обработи същото съобщение два пъти, не създава втори резултат. Съхранявайте идентификатора на събитието и прескачайте познатото.
- **Не разчитайте на реда.** Събитията могат да пристигнат разменени. При съмнение проверете текущото състояние чрез API.

## Сигурност от двете страни

**За приемниците:** само HTTPS, проверявайте подписа и възрастта, ограничавайте размера на съобщението, не изпълнявайте команди от съдържанието. Третирайте съдържанието като всеки вход отвън.

**За изпращачите, които викат адреси на клиенти:** Който предлага Webhook, вика адреси, вписани от други. Това е класически път за Server-Side Request Forgery. Нападателят вписва вътрешен адрес. Сървърът го вика от защитената мрежа. [Описанието на OWASP](https://community.owasp.org/attacks/Server_Side_Request_Forgery) посочва като цели например вътрешни услуги и метаданни адреси на облачни доставчици. Разумни защитни мерки:

- допускайте само `https://`,
- разрешавайте адреса при вписването и преди всяко извикване и отхвърляйте частни, локални и резервирани диапазони,
- не следвайте слепешком пренасочванията,
- задавайте къси времеви ограничения.

## Пример: изпращане на тестово съобщение с curl

С curl и openssl можете да тествате Вашия приемник без истинския изпращач. Скриптът изпраща подписано съобщение, каквото би изпратил изпращач с времеви печат:

```bash
GEHEIMNIS="test-geheimnis"
ZEIT=$(date +%s)
INHALT='{"id":"evt_1","typ":"besuch.begonnen","firma":"beispiel-firma.example"}'
SIGNATUR="sha256=$(printf '%s.%s' "$ZEIT" "$INHALT" | openssl dgst -sha256 -hmac "$GEHEIMNIS" | sed 's/^.* //')"

curl -X POST http://localhost:3000/webhook \
  -H "Content-Type: application/json" \
  -H "X-Zeit: $ZEIT" \
  -H "X-Signatur: $SIGNATUR" \
  -d "$INHALT"
```

## Пример: малък приемник в Node.js

```js
const http = require('http')
const crypto = require('crypto')
const GEHEIMNIS = process.env.WEBHOOK_GEHEIMNIS || 'test-geheimnis'
const gesehen = new Set() // в реална среда: база данни

http.createServer((req, res) => {
  if (req.method !== 'POST' || req.url !== '/webhook') { res.writeHead(404); return res.end() }
  const teile = []
  req.on('data', t => teile.push(t))
  req.on('end', () => {
    const roh = Buffer.concat(teile).toString('utf8')
    const zeit = req.headers['x-zeit'] || ''
    const erwartet = 'sha256=' + crypto.createHmac('sha256', GEHEIMNIS).update(zeit + '.' + roh).digest('hex')
    const erhalten = String(req.headers['x-signatur'] || '')
    const a = Buffer.from(erwartet), b = Buffer.from(erhalten)
    const echt = a.length === b.length && crypto.timingSafeEqual(a, b)
    const frisch = Math.abs(Date.now() / 1000 - Number(zeit)) < 300
    if (!echt || !frisch) { res.writeHead(401); return res.end() }
    const ereignis = JSON.parse(roh)
    if (!gesehen.has(ereignis.id)) { gesehen.add(ereignis.id); console.log('neu:', ereignis.typ) }
    res.writeHead(200); res.end('ok')
  })
}).listen(3000)
```

Приемникът проверява подписа и възрастта, прескача познатите идентификатори и отговаря веднага. За производствена работа Ви трябват допълнително HTTPS, ограничение на размера и опашка за по-нататъшната обработка.

## Къде Webhook са разпространени

Платежните услуги, платформите за код и инструментите за формуляри съобщават събития чрез Webhook. Може и разпознаването на посетители: ако системата съобщи за фирма, която посещава Вашата страница, тя попада директно в инструмент по Ваш избор. Как това води до система за продажби, показва [Как да пренесете посещенията на уебсайта в CRM: 4 пътя в сравнение](https://whovisit.app/blog/website-besuche-ins-crm). Алтернативата чрез заявка описва [Кои фирми посещават моя уебсайт? 3 пътя накратко](https://whovisit.app/blog/welche-firmen-besuchen-meine-website). Какви данни текат при това, обяснява [Как работи разпознаването на фирми? Технологията в 6 стъпки](https://whovisit.app/blog/firmenerkennung-website-technik).

## Често задавани въпроси

**Каква е разликата между Webhook и API?** При API Вашата система пита, при Webhook изпращачът се обажда сам. И двете могат да се комбинират.

**Какво се случва, ако моят приемник се срине?** Повечето изпращачи повтарят доставката в продължение на известен период. Колко дълго и колко често, е записано в документацията на доставчика. За пропуските трябва да планирате съпоставяне чрез API.

**Достатъчно ли е да проверя адреса на изпращача?** Не. Адресите на изпращачите могат да се подправят и се променят. Надежден е само подписът със тайна, която знаете само Вие и изпращачът.
