Webhook

Повторы webhook в платёжных системах

Как Qvard повторяет webhook платежа, что считается неудачной доставкой и как собрать обработчик, который переживает повторы, дубли и простой, не выполняя заказ дважды.

Webhook платежа это сообщение, которое нельзя потерять и на которое нельзя среагировать дважды. Сети падают, серверы перезапускаются, поэтому отправитель повторяет доставку; именно поэтому обработчик нужно писать с расчётом на повторы. В материале описано, как повторяет Qvard, что он считает ошибкой и как построить вокруг этого обработчик и процедуру восстановления.

Когда отправляется webhook

Qvard отправляет webhook, когда платёж завершён. Это POST на callback URL, сохранённый в вашем проекте, с небольшим JSON, где ваш id (идентификатор, который вы передали при создании платежа) и status, с подписью в заголовке X-Qvard-Signature.

Неполный, отклонённый или ещё ожидающий платёж webhook о завершении не порождает. Если о таких платежах нужно знать, смотрите их в кабинете; не ждите сообщения, которое не отправляется.

Что считается доставленным

Qvard считает доставку успешной, только когда ваша точка отвечает ровно HTTP 200. Всё остальное ошибка:

  • любой другой код ответа, включая 201, 202, 204 и все 4xx и 5xx;
  • таймаут, потому что сервер был медленным или недоступным;
  • редирект: Qvard не следует за 301 и 302, поэтому правило, перенаправляющее http на https или добавляющее слеш в конце, ломает доставку.

Эта точность намеренная. 204 No Content выглядит вполне приличным ответом и всё равно считается неудачной доставкой. Возвращайте 200, при желании с пустым телом.

Как работают повторы

После неудачной доставки Qvard пробует снова. Всего пять попыток примерно за шесть минут. Отсюда два следствия:

  • Короткий простой покрыт. Если сервер перезапускается или деплой занимает минуту-две, одна из следующих попыток пройдёт, и вы ничего не потеряете.
  • Долгий простой нет. После последней попытки Qvard останавливается. Если сервер лежал дольше этих шести минут, уведомление само не придёт; отправьте его по платежу ещё раз из кабинета.

Из-за повторов обработчик иногда увидит одно и то же событие несколько раз, например когда сервер сделал работу, а ответ потерялся по дороге. Пример ниже построен с расчётом на это: обновление меняет только ещё не оплаченный заказ, выполнение ставится в очередь, только если строка изменилась, и ответ в любом случае 200.

Pythonwebhook.py
import os
from fastapi import FastAPI, Request, Response
from qvard import Qvard

app = FastAPI()
SECRET = os.environ["QVARD_SECRET_KEY"]
qvard = Qvard(SECRET)

@app.post("/webhooks/qvard")
async def qvard_webhook(request: Request):
    try:
        body = qvard.webhooks.construct_event(await request.body(), request.headers.get("X-Qvard-Signature", ""), SECRET)
    except ValueError:
        return Response(status_code=400)

    order_id, status = body.get("id"), body.get("status")

    # Idempotent: the UPDATE changes a row only for an order that is not paid yet.
    if status == "completed":
        changed = await db.execute(
            "UPDATE orders SET state = 'paid' WHERE id = :id AND state <> 'paid'", {"id": order_id}
        )
        if changed:
            await queue.enqueue("fulfil", order_id)   # slow work after the answer

    return Response(status_code=200)   # exactly 200, also for repeats and unknown orders

Что обязан делать обработчик

Четыре правила закрывают почти все проблемы:

  1. Проверка подписи. Проверяйте заголовок X-Qvard-Signature (HMAC-SHA256 вашим секретным ключом по сырому телу) за постоянное время и иначе отвечайте 400. Делайте это до того, как действовать по платежу. См. как защитить webhook криптоплатежей.
  2. Идемпотентность. Делайте смену состояния условной, «пометить оплаченным если ещё не оплачен», лучше одним запросом к базе, чтобы две параллельные доставки не победили обе.
  3. Быстрый ответ. Проверить, обновить, поставить в очередь, ответить ровно 200. Письма, генерацию файлов и вызовы других систем делайте из очереди, чтобы медленный смежный сервис не превратился в таймаут webhook.
  4. Отвечайте 200 на то, что никогда не обработаете. Неизвестный заказ или повтор тоже должны возвращать 200, иначе Qvard продолжит повторять сообщение, с которым вы уже справились или справитесь никогда.

Сбои, похожие на баги Qvard

Большинство жалоб «webhook не пришёл» имеют причину на принимающей стороне:

  • Файрвол или WAF, блокирующий запросы с незнакомых адресов, либо страница «скоро открытие» или обслуживания.
  • Редирект с http на https, с www на голый домен или на адрес со слешем.
  • Ошибка фреймворка после того, как ответ уже решён, например исключение в middleware, превращающее ваш 200 в 500.
  • Медленный обработчик, который делает работу до ответа и упирается в таймаут.
  • Кэш или CDN перед маршрутом.
  • Неверный адрес, сохранённый в проекте, или опечатка в маршруте.

Проверьте точку снаружи вашей сети запросом с валидным заголовком X-Qvard-Signature и смотрите в журнале доступа сервера код ответа, а не только факт прихода запроса.

Процедура восстановления при пропущенных уведомлениях

Повторы покрывают короткие простои; для остального добавьте страховку.

  • Логируйте каждую доставку: id, status, ваше решение (применено, повтор, неизвестный заказ, ключ отклонён) и время. Пропуск в логе так вы замечаете разрыв.
  • Сверяйтесь по расписанию. Раз в сутки сравнивайте заказы, которые всё ещё считаете неоплаченными, с платежами и балансом в кабинете Qvard. Всё, что в кабинете завершено, а у вас не оплачено, это пропущенное уведомление.
  • Отправляйте повторно из кабинета по таким платежам и давайте идемпотентному обработчику их применить.
  • Настройте оповещение, когда серия запросов получает ошибку, чтобы узнать о сломанной точке за минуты, а не в конце дня.

Остальная интеграция, включая создание платежа, в материалах как принимать криптоплатежи на сайте и API для криптоплатежей.

FAQ

Сколько раз Qvard повторяет webhook?

Всего пять попыток примерно за шесть минут. После этого Qvard останавливается, а уведомление по платежу можно отправить повторно из кабинета.

Какой код ответа должна вернуть моя точка?

Ровно 200. Любой другой статус, включая 204, таймаут или редирект, считается неудачной доставкой и повторяется.

Может ли один и тот же webhook прийти дважды?

Да, например когда сервер обработал первую доставку, но ответ потерялся. Делайте обработчик идемпотентным и на повторы всё равно отвечайте 200.

Приходит ли webhook при неполном или отклонённом платеже?

Нет. Webhook отправляется, когда платёж завершён. Остальные платежи проверяйте в кабинете.

Похожие статьи

Похожие статьи