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

Почему заказ уходит за платёж, которого не было?

15 сент. 2026 г. 11 мин чтения Paymos Tech Paymos Tech
Пять оранжевых пунктирных линий подходят слева и обрываются у грани высокой белой панели; за ней нетронутой стоит панель поменьше

Кратко

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

Обмануть криптоплатёж откатом не получится. После финальности перевода апеллировать не к кому и списать деньги с продавца некому.

Остаётся окно до отгрузки: минуты между словами «я оплатил» и действием склада, лицензионного сервера или оператора поддержки.

В этом окне живут семь схем. Каждая ниже стоит рядом с защитой, которая её ломает, и с местом, где эта защита заканчивается. Защиту, названную без предела, обязательно применят за пределом. Блокчейна в большинстве схем нет вовсе: подделанное сообщение, доверие к браузеру, перенаправленный вывод — приёмы из атак на форму входа, наведённые на платёж.

Почему хеш транзакции ничего не доказывает?

Покупатель присылает скриншот и хеш, и хеш проверяется.

В этом и ловушка. Вставьте его в обозреватель блоков — вернётся настоящий перевод, а рядом с ним сумма и символ токена. Ни одно из трёх — ни сам перевод, ни сумма, ни символ — не привязывает его к вашему счёту, и слабее всех тут символ: в EIP-20 поля name и symbol необязательны, и стандарт прямо требует от интерфейсов на них не рассчитывать.

Символ — это строка, которую выбрал автор контракта.

Защита — состояние самого счёта. Платёж засчитывают, когда перевод пришёл на адрес этого счёта и подтвердился на глубину, заданную для его сети и суммы: на Ethereum это два блока на суммах до 100 долларов и тридцать два выше 10 000, а шесть сетей закрываются финальностью сети, а не счётом блоков.

Каждый замеченный перевод показан на странице оплаты, пока плательщик смотрит, — неподтверждённые тоже.

Глубина — это размен, а не доказательство окончательности. Уже засчитанный перевод, который позже выпадет из цепочки при реорганизации, у мерчанта не отбирают: недостача ложится на счета платформы.

На вопрос, дошли ли деньги, счёт отвечает. На вопрос, заслуживает ли этот заказ отгрузки, — никогда.

Деньги на балансе есть, а счёт не оплачен — как так?

Потому что перевод доходит до адреса и не оплачивает ни одного счёта.

Четыре формы этого покупатель делает руками: перевод после закрытия окна оплаты; перевод на счёт, уже оплаченный, просроченный или отменённый; перевод в другом активе — USDC на счёт, выставленный в USDT; и перевод на адрес, за которым счёта нет вовсе. Каждый зачисляется на баланс мерчанта за вычетом обычной комиссии.

Самого счёта это не касается. Он остаётся неоплаченным, и вебхука по такому зачислению не будет: собственного события у него нет вовсе, поэтому система, которая слушает только поток, не узнает о нём никогда. Человек узнаёт: уведомление уходит на почту и в колокольчик. А на странице баланса зачисление стоит отдельным блоком — причина, сумма, комиссия и, когда они известны, отправитель с хешем транзакции.

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

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

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

Кто сказал магазину, что счёт оплачен?

Обычно браузер — свидетель, заинтересованный в исходе.

Заказ помечают оплаченным по адресу возврата или по событию из формы оплаты. Low-Code SDK отправляет пять таких событий, все — CustomEvent на window, и все рассказывают, что сделала накладка поверх страницы.

Чтобы погасить индикатор загрузки, годится. Состояние платежа они не описывают, и покупатель с открытой консолью отправляет любое из них сам.

Это первая из двух версий одной ошибки. Вторая — про сумму.

Откуда ваш виджет берёт сумму?

Из пяти источников на выбор, и безопасны они по-разному.

Виджет разбирает их в порядке приоритета: price_id — цена, лежащая на сервере, и тогда суммы в запросе нет вовсе; статичная сумма; обработчик get_amount на JavaScript; селектор amount_selector, который читает .value у элемента страницы; и, наконец, pos_url, до которого доходят, когда четыре предыдущих ничего не дали.

На странице, которую покупатель может править, четвёртый вариант означает, что цену называет он сам. MITRE держит для этого класса отдельную запись, CWE-602, клиентская проверка серверной защиты: сервер «полагается на то, что клиент реализует механизм, предназначенный для защиты сервера».

Перенос цены на сервер не делает оплату безопасной — он убирает из рук покупателя одно решение. Корзину, у которой сумма меняется от заказа к заказу, по-прежнему считает ваш код, а состояние «оплачено» по-прежнему должно приходить оттуда, куда покупатель не пишет.

Кто ещё может прислать вам событие об оплате?

Адрес обратного вызова стоит в публичном интернете и принимает JSON.

Подделанному invoice.paid не нужен доступ ни к чему: верная форма, верный идентификатор счёта и обработчик, который читает тело. У CWE-345 запись ровно об этом — продукт «недостаточно проверяет происхождение или подлинность данных, из-за чего принимает недостоверные данные».

Каждая доставка подписана HMAC-SHA256, и заголовок X-Webhook-Signature составной: метка времени и одно или несколько значений v1. Во время смены секрета их там два в течение 24 часов, и подойти может любое, поэтому приёмник разбирает v1 как список, а не как поле.

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

Дальше повтор, и вот тут обычно удивляются. X-Webhook-Timestamp ставится на каждой доставке, а собственного окна на исходящей стороне не применяют: допуск выбирает тот, кто проверяет.

Документация просит отклонять доставки старше 300 секунд, и официальные SDK по умолчанию берут пять минут, так что интеграция на любом из них получает проверку в наследство.

Проверка, написанная руками по одному HMAC, примет перехваченную доставку через неделю, и подпись на ней будет настоящая.

Почему одно событие приходит дважды?

Большинство повторов — не атака. Поэтому обработчик так легко написать неправильно.

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

Веерная рассылка добавляет ещё: два адреса, подписанных на одну категорию, дают две доставки одного факта.

Атакующему достаточно переслать одну перехваченную.

В одном запросе едут два идентификатора, и вся защита в том, чтобы дать каждому свою работу. X-Webhook-Id — тот самый evt_… — это личность доставки, по ней и отсекают повторы. inv_… внутри data — личность заказа, одинаковая у всех адресов и во всех состояниях, по ней и привязывают заказ.

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

Отсечение останавливает второй прогон, а не второе прибытие. Доставки идут, пока идёт цикл, и исчерпанный цикл валит событие, а не адрес.

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

Куда уйдёт возврат?

На тот адрес, который вставил человек, разбирающий обращение.

Эта схема не стоит атакующему ничего и ваших систем не касается вовсе. Запрос на возврат приходит с адресом внутри — из почтового ящика клиента, который читает кто-то ещё, или от коллеги, которого уговорили.

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

Добавление адреса — привилегированное действие, и оно попадает в этот же след.

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

Читать его как уже имеющуюся защиту не стоит. Оно появляется тогда, когда кто-то включит TOTP или заведёт passkey.

Второе место — размер одного одобрения. За один запрос под одним подтверждением в список уходит до тысячи адресов, а импорт CSV этим и пользуется: одно уведомление на весь файл вместо одного на адрес.

Удобство и радиус поражения тут — одно и то же число, поэтому читать нужно сам список. О том, чей кошелёк на том конце, список тоже молчит: мерчант вправе внести адрес клиента намеренно.

Кому положен возврат, в какие сроки и кто его одобряет — вопрос политики возвратов.

Что остаётся, когда атакующий уже вошёл?

Все защиты выше подчиняются аккаунту, который их настроил.

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

Пароля, который можно выманить, у аккаунта нет. Вход — ссылка на почту, Google, Telegram OIDC там, где он включён, или passkey, а вторым фактором доступен TOTP.

Против этой схемы сильнее всех passkey, потому что он привязан к месту, где его завели: WebAuthn ограничивает учётные данные той стороной, на которой их зарегистрировали, и включает происхождение в каждый подписанный ответ.

Убедительный домен-двойник не соберёт ничего пригодного.

Подтверждение вторым фактором стоит перед тремя операциями — отключением второго фактора, изменением белого списка адресов, правкой списка разрешённых IP у API-ключа, — и у каждой свой билет, поэтому код, перехваченный для одной, не разрешает другую.

У ключа на вывод есть требование, которого у платёжного нет вовсе: без такого списка Payout-ключ не проходит аутентификацию. Незаполненный список означает отказ, а не доступ отовсюду.

Отсутствие пароля — не отсутствие сессии. Ссылка для входа падает в почтовый ящик, а ящик забирают первым; закрывает этот путь второй фактор, и по умолчанию в нём не состоит никто. Читает мерчант потом собственные записанные действия и историю входов — полный журнал платформы доступен только операторам.

Какая половина из этого ваша?

Семь защит лежат по разные стороны API.

Три держат независимо от того, что кто-то сделает: состояние счёта, белый список перед выводом и отказ закрывать счёт переводом, который его не оплатил. Остальные четыре существуют, только если их напишет ваш код, — сверить подпись вместе с меткой времени, отсекать по идентификатору доставки, считать цену на сервере, включить второй фактор, который включает подтверждение. На каждую уходит вечер.

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

Семь схем до отгрузки и предел каждой защиты (сентябрь 2026 года)
СхемаЧто её останавливаетЧто остаётся открытым
Доказательство оплаты приносит сам покупательСостояние счёта, зачисление на заданной глубине подтвержденийОтгружать ли заказ — по-прежнему коммерческое решение
Перевод дошёл до адреса и не оплатил счётСчёт не двигается, зачисление приходит с причиной рядомВебхука нет, и интеграция на одних событиях не узнает
Отгрузку или цену решает браузерСостояние оплаты с сервера, цена на сервереПлавающую корзину всё равно считает ваш код
Подделанный обратный вызовПодпись HMAC-SHA256, сравнение без утечки по времени, v1 как списокПовтор: окно по метке времени выбирает приёмник
Одно событие доставлено больше одного разаОтсечение по X-Webhook-Id, привязка заказа по идентификатору внутри dataДоставки продолжают приходить, останавливается только второй прогон
Возврат на присланный адресВывод только на адрес из белого списка, пустой список выключает выводыОдно подтверждение одобряет до тысячи адресов сразу
Атакующий уже вошёл в аккаунтПароля нет: passkey, TOTP, подтверждение на каждую операциюВторой фактор включают сами, без него подтверждения не будет

Частые вопросы

Что такое мошенничество с криптоплатежами?

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

Доказывает ли хеш транзакции, что счёт оплачен?

Нет. Хеш доказывает, что перевод есть в сети. Дошёл ли он до адреса именно этого счёта, в принимаемом активе, до закрытия окна оплаты и на нужную глубину подтверждений — отвечает состояние счёта.

Почему деньги пришли, а заказ не отметился оплаченным?

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

Можно ли подделать платёжный вебхук?

Отправить POST на адрес обратного вызова может кто угодно — поэтому каждая доставка подписана HMAC-SHA256 по сырому телу. Сверяйте подпись сравнением без утечки по времени, читайте значения v1 в заголовке как список и проверяйте метку времени: одна подпись повтор не ловит.

Требует ли Paymos второй фактор при добавлении адреса для вывода?

Только там, где у пользователя мерчанта уже включён TOTP или заведён passkey. Подтверждение усиливает существующий фактор, поэтому того, у кого его нет, оно не спрашивает. Сам белый список работает всегда, а пустой список выключает выводы совсем.

Как не отгрузить заказ дважды по повторному вебхуку?

Отсекайте повторы по evt-идентификатору из X-Webhook-Id, а заказ ведите по счёту, который назван внутри data. Таблица заказов, построенная на идентификаторе доставки, проведёт два адреса как два платежа.

Когда НЕ стоит использовать собственный антифрод

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

Источники

  1. 1. EIP-20 — Token Standard (accessed 2026-09-15)
  2. 2. CWE-602 — Client-Side Enforcement of Server-Side Security (accessed 2026-09-15)
  3. 3. CWE-345 — Insufficient Verification of Data Authenticity (accessed 2026-09-15)
  4. 4. W3C — Web Authentication: An API for accessing Public Key Credentials Level 2 (accessed 2026-09-15)
  5. 5. Документация Paymos — проверка подписи вебхука (accessed 2026-09-15)
  6. 6. Документация Paymos — безопасность (accessed 2026-09-15)

Последняя проверка: 15 сент. 2026 г.

#мошенничество#вебхуки#безопасность#риски-платежей
Поделиться