Перейти к содержимому

Надёжность интеграции

Платёжная интеграция работает поверх сети, которая теряет пакеты, и очередей, которые доставляют сообщения дважды. Эта страница — про то, как построить обработку так, чтобы ни одна из этих особенностей не превратилась в потерянный или задвоенный платёж.

Ключ идемпотентности — ваш merchantTransactionId. Он должен быть уникален в рамках кабинета.

Если сделка с таким идентификатором уже есть, повторный запрос вернёт ORDER_WITH_SPECIFIED_MERCHANT_TRANSACTION_ID_ALREADY_EXISTS, а не создаст вторую. Это не ошибка вашей логики — это защита, и обрабатывать её нужно как «сделка уже создана».

POST /payment/pay-in { merchantTransactionId: "order-1001" } → сделка создана
POST /payment/pay-in { merchantTransactionId: "order-1001" } → ORDER_..._ALREADY_EXISTS

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

Правильный порядок:

  1. Не повторять запрос вслепую.

  2. Запросить сделку по вашему идентификатору:

    GET /payment/merchant/{merchantTransactionId}
  3. Если сделка вернулась — работать с ней, запрос удался.

  4. Если пришло TRADE_NOT_FOUND — повторить создание с тем же merchantTransactionId.

Тот же порядок применим к отмене и к любым другим изменяющим запросам.

Уведомления доставляются как минимум один раз. Практические следствия:

Одно и то же уведомление придёт повторно. Например, если ваш сервер ответил 200, но соединение оборвалось до того, как ответ дошёл до нас. Обработчик должен быть безопасен при повторном вызове.

Уведомления могут прийти не по порядку. Сначала success, потом active — нормальная ситуация при ретраях. Не выстраивайте логику на последовательности; смотрите на текущий статус.

Уведомление может прийти после того, как вы уже узнали результат из GET. Это не повод считать его лишним — обработайте так же, как первое.

Рабочая схема обработчика:

1. Прочитать raw body
2. Проверить x-signature → не сошлось: 401, выход
3. Ответить 200 → сразу, до бизнес-логики
4. Положить событие в очередь
5. В фоне:
- взять блокировку по id сделки
- если сохранённый статус уже финальный — ничего не делать
- иначе применить новый статус
ЧтоЗначение
Тайм-аут на ваш ответ по webhook10 секунд
Успешный ответ200 или 201
Попыток доставки6
Паузы между попытками~5 с, ~30 с, ~2 мин, ~10 мин, ~30 мин, ~2 ч, с разбросом ±20 %

Если обработка не укладывается в 10 секунд, отвечайте 200 сразу и обрабатывайте асинхронно. Иначе доставка будет засчитана как неудачная, и вы получите то же уведомление ещё раз — уже параллельно первой обработке.

После шести неудачных попыток доставка прекращается. Восстановить её можно только через поддержку, поэтому надёжнее иметь собственную сверку.

Webhook — основной канал, но не единственный источник истины. Раз в сутки полезно сверять состояние сделок за период.

GET /payment?begin=2026-08-22T00:00:00.000Z&end=2026-08-23T00:00:00.000Z&blockIndex=1&blockCapacity=100
ПараметрОписание
begin, endГраницы периода, ISO 8601
statusФильтр по статусу
blockIndexНомер страницы, с 1
blockCapacityРазмер страницы, по умолчанию 10
sortKey, sortDirectionПоле и направление сортировки, ASC или DESC

Ответ содержит массив result и блок elements с метаданными пагинации: aggregateVolume — всего записей, segmentCount — всего страниц, activeSegment — текущая.

Сверяйте по трём признакам: у сделки, которая у нас финальная, должен быть финальный статус и у вас; суммы должны совпадать (с учётом success_changed); сделок, которых нет у вас, быть не должно.

  • createdAt приходит в UTC в формате ISO 8601 с миллисекундами.
  • paymentLifetime — в минутах, отсчёт от createdAt.
  • Не полагайтесь на локальное время сервера при вычислении момента истечения: считайте от createdAt.
  • Часовой пояс, выбранный в кабинете, влияет только на отображение и выгрузки, но не на значения в API.
  • crypto и fee приходят строками, в минимальных единицах USDT. Делите на 1 000 000.
  • amount — фиатная сумма. При success_changed она отличается от той, что вы отправляли при создании.
  • Не приводите денежные значения к числам с плавающей точкой в промежуточных расчётах: используйте decimal или целые числа минимальных единиц.
ОшибкаК чему приводит
Ретрай создания с новым merchantTransactionIdДвойная сделка, для Pay-Out — двойная выплата
Обработка success без обработки success_changedЧасть успешных платежей зависает необработанной
Отгрузка на статусе checking или activeОтгрузка по неподтверждённому платежу
Пересборка JSON перед проверкой подписиПодпись не сходится никогда
Тяжёлая логика внутри обработчика webhookТайм-аут 10 с, лавина повторных доставок
Отсутствие блокировки по сделкеГонка при параллельной доставке одного события
Доверие порядку уведомленийФинальный статус перезаписывается промежуточным
  • merchantTransactionId уникален и стабилен при ретраях
  • Проверка x-signature выполняется на raw body до бизнес-логики
  • Обработчик webhook отвечает 200 быстрее 10 секунд
  • Повторная доставка одного события не меняет результат
  • success_changed обрабатывается наравне с success
  • При тайм-ауте выполняется поиск сделки, а не повторное создание
  • Финальные статусы не перезаписываются промежуточными
  • Настроена ежедневная сверка через GET /payment
  • Все IP-адреса ваших серверов согласованы и внесены в whitelist
  • API-ключ хранится только на сервере