# Вебхук не приходит — как найти причину

> Счёт оплачен, а на ваш сервер уведомление не пришло. С чего начать диагностику, какая причина встречается чаще всего и как проверить её одним запросом.

## Коротко

Сначала откройте **журнал вебхуков** в кабинете: отправили ли мы уведомление и что ответил ваш сервер — всё записано там. Если в журнале «отправлено, ответа нет» или 401/403, проблема на вашем адресе. Самая частая причина — **адрес вебхука закрыт авторизацией**: общий пароль сайта, белый список IP, защита Cloudflare или VPN не пускают нас внутрь. Мы приходим как обычный внешний клиент и не передаём никаких логинов и паролей.

## По симптому

| Что видите | Вероятная причина | Что делать |
|---|---|---|
| В журнале нет ни одной записи | Адрес не добавлен или это событие не выбрано | Кабинет → Интеграции → проверьте адрес и список событий |
| В журнале «отправлено», ответ 401/403 | Адрес закрыт авторизацией | Оставьте именно этот путь открытым |
| Ответ 404 | Неверный путь или маршрут не зарегистрирован | Проверьте полный адрес через curl, а не в браузере |
| Ответ 301/302 | Редирект на другое место — мы его не следуем | Укажите конечный адрес как адрес вебхука |
| Ответ 500 | Падает ваш обработчик | Смотрите свои логи |
| Таймаут | Обработка слишком долгая | Сначала ответьте 200, работу делайте после |
| Уведомление приходит, но код его отвергает | Не сходится подпись | Смотрите шаг 5 ниже |

## Порядок диагностики

Не меняйте порядок — каждый шаг делает следующий осмысленным.

**1. Посмотрите журнал в кабинете.** В разделе Интеграции пишется каждая отправка: тип события, время, HTTP-ответ вашего сервера. Если записей нет вообще — мы не отправляли, значит статус счёта ещё не менялся или адрес не добавлен. Если запись есть, а ответ неуспешный — дело на вашей стороне.

**2. Доступен ли адрес публично?** Это самая частая причина. Адрес вебхука должен быть открыт из интернета для любого клиента. У нас нет вашего пароля, токена и доступа в ваш VPN.

Проверка: отправьте запрос **снаружи**, не со своего же сервера.

```
curl -i -X POST https://vash-domen.kz/webhooks/qutpay -d '{}'
```

Если приходит 401, 403, 302 или соединение вообще не устанавливается — мы попадём ровно в то же самое. Типичные препятствия: базовая авторизация на сайте, защита админ-панели, правило WAF или Cloudflare, белый список IP, окружение staging.

Решение — не открывать весь сайт, а оставить без авторизации **только этот один путь**. Безопасность обеспечивает проверка подписи, она работает вместо пароля.

**3. Это https и настоящий домен?** В боевом режиме принимается только `https`, домен должен быть настоящим. IP-адрес и временные туннельные адреса (вроде ngrok) не принимаются. Сертификат должен быть действующим: просроченный или самоподписанный обрывает соединение.

**4. Уберите редиректы.** Мы следуем редиректам 307 и 308 **только на тот же самый адрес** — например `http` → `https` или косая черта в конце. Редирект на другой домен или другой путь не отрабатывается. Поэтому указывайте конечный, точный адрес: с `www`, если он с `www`, и с косой чертой, если она нужна.

**5. Проверьте проверку подписи.** Бывает, что уведомление доходит, но обработчик его отбрасывает. Подпись считается как `HMAC-SHA256(secret, timestamp + "." + rawBody)`, в hex, с префиксом `sha256=`. Самая частая ошибка — разобрать тело в JSON, а потом собрать обратно в строку и по ней считать подпись. Считайте по **неизменённому телу в исходных байтах**. Метка `timestamp` старше пяти минут приниматься не должна.

**6. Отвечает ли сервер 2xx?** Всё, что не 2xx, мы считаем неудачей. Если обработка долгая, сначала ответьте 200, а работу выполняйте в фоне.

## Повторы

Если вы ответили не 2xx или не ответили вовсе, мы повторяем отправку **11 раз**. Интервал растёт от 10 секунд до часа. То есть даже если сервер упал на час, после восстановления уведомление дойдёт — вручную делать ничего не нужно.

Именно поэтому **обработчик должен быть идемпотентным**: одно событие может прийти несколько раз. Берите пару `(invoice.id, status)` как ключ и не обрабатывайте повторно то, что уже обработали.

## Проверка через webhook.site

Понять, на чьей стороне проблема, можно за две минуты.

1. Откройте [webhook.site](https://webhook.site) — он выдаст временный адрес
2. Кабинет → Интеграции → добавьте этот адрес как вебхук
3. В песочнице создайте счёт и симулируйте оплату
4. Появилось ли уведомление на странице webhook.site?

Появилось — значит мы отправляем корректно, и дело в вашем сервере: адрес закрыт, маршрута нет или ошибка в коде. Не появилось — проверьте список адресов и выбор событий, затем напишите в поддержку.

После проверки не забудьте удалить временный адрес: он публичный, и всё пришедшее на него видит кто угодно.

## Частые ошибки

- **Проверять адрес открытием в браузере.** Браузер шлёт GET, а мы шлём POST. Успешный GET ничего не говорит о POST.
- **Указать локальный адрес.** `localhost`, `127.0.0.1` или адрес внутренней сети снаружи не видны.
- **Слишком сузить список событий.** Выбрали только `invoice.paid`, а потом ждёте `invoice.expired`.
- **Полагаться только на вебхук.** Там, где важна задержка, стоит параллельно опрашивать статус счёта.
- **Путать песочницу и боевой режим.** Если в песочнице один адрес, а в боевом другой, в одном режиме всё работает, а в другом нет.

## Вопросы и ответы

**Можно обойтись без вебхука и опрашивать статус самому?** Можно, но лучше сочетать: вебхук быстрее, опрос надёжнее.

**Уведомление пришло несколько раз — это сбой?** Нет, это нормально, так устроены повторы. Если обработчик идемпотентен, вреда нет.

**Потерял секрет, можно его посмотреть?** Нет, секрет показывается один раз. Создайте в кабинете новый и обновите значение в коде.

**Если сервер полежал, оплаты потеряются?** Нет. Повторы идут до часа, а статусы счетов хранятся у нас — их всегда можно прочитать через API.

**За сколько обычно приходит вебхук?** Обычно в пределах пяти секунд после оплаты. Если дольше: [Счёт завис в статусе pending](/kb/ru/invoice-stuck-pending).
