Коротко
Сначала откройте журнал вебхуков в кабинете: отправили ли мы уведомление и что ответил ваш сервер — всё записано там. Если в журнале «отправлено, ответа нет» или 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
Понять, на чьей стороне проблема, можно за две минуты.
- Откройте webhook.site — он выдаст временный адрес
- Кабинет → Интеграции → добавьте этот адрес как вебхук
- В песочнице создайте счёт и симулируйте оплату
- Появилось ли уведомление на странице webhook.site?
Появилось — значит мы отправляем корректно, и дело в вашем сервере: адрес закрыт, маршрута нет или ошибка в коде. Не появилось — проверьте список адресов и выбор событий, затем напишите в поддержку.
После проверки не забудьте удалить временный адрес: он публичный, и всё пришедшее на него видит кто угодно.
Частые ошибки
- Проверять адрес открытием в браузере. Браузер шлёт GET, а мы шлём POST. Успешный GET ничего не говорит о POST.
- Указать локальный адрес.
localhost,127.0.0.1или адрес внутренней сети снаружи не видны. - Слишком сузить список событий. Выбрали только
invoice.paid, а потом ждётеinvoice.expired. - Полагаться только на вебхук. Там, где важна задержка, стоит параллельно опрашивать статус счёта.
- Путать песочницу и боевой режим. Если в песочнице один адрес, а в боевом другой, в одном режиме всё работает, а в другом нет.
Вопросы и ответы
Можно обойтись без вебхука и опрашивать статус самому? Можно, но лучше сочетать: вебхук быстрее, опрос надёжнее.
Уведомление пришло несколько раз — это сбой? Нет, это нормально, так устроены повторы. Если обработчик идемпотентен, вреда нет.
Потерял секрет, можно его посмотреть? Нет, секрет показывается один раз. Создайте в кабинете новый и обновите значение в коде.
Если сервер полежал, оплаты потеряются? Нет. Повторы идут до часа, а статусы счетов хранятся у нас — их всегда можно прочитать через API.
За сколько обычно приходит вебхук? Обычно в пределах пяти секунд после оплаты. Если дольше: Счёт завис в статусе pending.