HexблогПолучить ключ

Идемпотентность, вебхуки и ретраи: как не потерять деньги

Reseller API21 августа 2026 г. 7 мин чтения

Любая интеграция с платным API однажды упирается в один и тот же вопрос: запрос ушёл, ответа нет, повторять или нет. Ответ зависит от того, как устроена идемпотентность. Разберём механику и места, где на ней теряют деньги.

Ключ идемпотентности

Идентификатор заказа придумываете вы, удобнее всего брать UUID. Повторный запрос на создание с тем же идентификатором вернёт тот же заказ, даже если параметры в теле другие, а повторное подтверждение оплаты не спишет депозит второй раз. Уникальность держит база платформы, а не её память о недавних запросах, то есть ретрай безопасен и через час.

Отсюда простое правило: новый заказ это новый идентификатор, а повтор упавшего запроса это старый идентификатор. Нарушение первой половины даёт клиенту чужой заказ, нарушение второй даёт двойное списание.

order_id = uuid4()          # генерируем ОДИН раз и сохраняем у себя
save(order_id, status="new")

for attempt in range(3):    # ретраим с тем же order_id
    try:
        r = post("/order/create", {"custom_id": order_id, ...})
        break
    except Timeout:
        sleep(2 ** attempt)

Ключевая деталь в первой строке: идентификатор генерируется до запроса и сохраняется в вашей базе. Если генерировать его внутри цикла повторов, идемпотентности не будет, сколько бы её ни обещало API.

Вебхук это ускорение, а не истина

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

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

Три ловушки, на которых теряют деньги

Первая: признак возврата у успешного заказа. При частичной выдаче, когда поставщик отдал меньше позиций, чем заказано, статус остаётся «доставлен», а разница за невыданное возвращается на депозит. Если ветвиться по одному признаку возврата, такой заказ будет посчитан провальным, и покупателю вернут деньги за товар, который он уже получил. Смотрите на статус, а сумму берите из поля возврата.

Вторая: состояние «доставка на проверке». Это означает, что доставка идёт дольше обычного и её смотрит человек, а не что она провалилась. Деньги не возвращены, состояние не терминальное, возвращать оплату покупателю рано.

Третья: свой таймаут на асинхронных товарах. У части позиций доставка занимает часы, а в отдельных режимах ещё и ждёт действия покупателя. Жёсткий таймаут в пятнадцать минут превратит нормальный заказ в возврат, а клиент получит и деньги, и товар.

Что делать с незнакомым

Список товаров, событий и полей растёт. Незнакомое событие уведомления игнорируйте, незнакомый код провала считайте обычным провалом, новые поля в ответах не считайте нарушением схемы. Интеграция, которая падает на новом поле, будет ломаться при каждом обновлении на стороне платформы.

Ретраи и лимиты

  • Повторяйте только сетевые сбои и пятисотые, но не 400 и не 409.
  • На 429 ждите столько, сколько сказано в заголовке, и не быстрее.
  • Ставьте потолок на число повторов, дальше пишите в свою очередь на разбор.
  • Логируйте свой идентификатор заказа рядом с ответом, иначе разбор инцидента невозможен.

Минимальный чек-лист перед продом

  • Идентификатор заказа генерируется до запроса и лежит в вашей базе.
  • Опрос статуса работает и без уведомлений.
  • Подпись уведомления проверяется до разбора тела.
  • Возврат покупателю делается по статусу, а не по одному признаку возврата.
  • Суммы считаются в целых единицах, округление одно и на выводе.

Ключ выдаётся через Telegram

Семь эндпоинтов, оптовые цены с депозита, идемпотентные заказы. Регистрация и KYC не нужны.

Читайте также

Hex

© Hex, white-label франшиза

Собираем анонимную аналитику посещений. Подробнее