Регулярные крипто-платежи без кастодиана: гайд 2026

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

Воксельные круговые стрелки соединяют светящиеся платёжные плитки в изумрудных и золотых тонах на тёмном фоне

У крипты нет аналога сохранённого номера карты — нет кнопки «списать автоматически», потому что регулярный платёж может произойти сам только двумя способами: процессор держит деньги на кастодии, либо клиент явно выдаёт on-chain allowance (а это работает только для токен-стандартов, которые его поддерживают, — не для BTC или XMR). Любая крипто-подписка, за которую ты когда-либо платил, незаметно построена на одном из этих двух механизмов. Этот гайд — про третий вариант: полностью non-custodial архитектуру link-per-cycle, которая автоматизирует создание payment request и сверку поверх API SwapZilla Pay, без постоянного разрешения на кошелёк клиента.

Почему крипта не умеет списывать деньги как карта

Сохранённая карта работает, потому что Visa и Mastercard построили рельсы специально под то, чтобы мерчант мог списывать деньги по расписанию, а карточная сеть выступает уровнем принуждения, если что-то идёт не так. У Bitcoin, Monero и большинства base-layer крипто-активов ничего аналогичного нет. Адрес кошелька — не токен разрешения: если ты один раз отправил BTC на адрес, это не даёт никому права списать с этого адреса ещё раз в следующем месяце.

Есть ровно два способа обойти этот разрыв, и оба меняют модель доверия:

  • Custodial-биллинг. Процессор держит деньги клиента (или заранее пополненный баланс) и сам инициирует списание по твоему расписанию. Именно так под красивым брендингом работает большинство «крипто-подписок» — регулярность здесь запись в custodial-леджере, а не on-chain событие.
  • On-chain allowance. Некоторые токен-стандарты — ERC-20 самый известный — поддерживают паттерн approve/transferFrom, где клиент выдаёт смарт-контракту постоянное разрешение списывать до установленного лимита. Это существует только для токенов, построенных с таким интерфейсом; аналога для BTC или XMR нет, и это значит, что клиент выдаёт именно постоянный allowance, а не подтверждает каждый платёж отдельно.

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

Ни один вариант не «плохой», но оба чем-то жертвуют ради удобства авто-списания. Кастодия означает, что третья сторона контролирует settlement и может его заморозить или задержать. Allowance означает, что клиент передаёт постоянное разрешение, которое живёт, пока не будет явно отозвано, — это реальная поверхность атаки, если контракт или сторона, держащая allowance, будут скомпрометированы. Стоит сказать прямо: SwapZilla Pay не реализует модель on-chain allowance; паттерн approve/transferFrom выше приведён как контраст, а не как то, что делает API.

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

Замени «карту на файле» на «ссылку на цикл». Вместо того чтобы хранить способ оплаты и списывать с него, ты создаёшь новый payment request на каждый биллинг-период, и клиент оплачивает его так же, как разовый инвойс — одной on-chain транзакцией с кошелька, которым он владеет.

Это сохраняет все non-custodial свойства, описанные в гайде о приёме криптоплатежей без аккаунта мерчанта: ни одна третья сторона не держит settlement-баланс, деньги клиента идут напрямую на твой адрес через routing агрегатора, и между циклами нет постоянного разрешения ни в контракте, ни в леджере процессора.

Здесь важна точность: у SwapZilla Pay нет нативного объекта subscription. Нет endpoint’а «создать подписку», нет встроенного cadence, нет dunning-движка. Pay даёт тебе тот же payment-request API, что описан в /pay/docs и используется для разовых инвойсов — регулярное поведение ты строишь сам, в своём приложении, вызывая этот API по расписанию, которое контролируешь ты.

Это и есть trade-off по сравнению с custodial-продуктом подписок: больше интеграционной работы на старте в обмен на то, что деньги клиента никогда не оседают у тебя на балансе, и никакого постоянного on-chain разрешения не требуется.

Автоматизация создания payment request по расписанию через Pay API

Биллинг-цикл живёт в твоей базе, а не в Pay. Смоделируй его как простую запись на клиента: customer_id, cadence (monthly, weekly и так далее), amount, receive_asset и next_due_date. У Pay нет понятия подписки, так что всё это состояние — целиком твоя ответственность.

Scheduler — cron-задача, serverless-функция по таймеру, воркер очереди — обходит эту таблицу ежедневно и для каждой строки, где next_due_date попадает в твоё окно упреждения (обычно несколько дней), вызывает Pay API и создаёт новый payment request на сумму и актив получения этого цикла. Сохрани возвращённый id запроса в записи клиента сразу, ещё до отправки чего-либо клиенту — он понадобится для сверки webhook позже.

Как именно выдаётся доступ к API — формат ключа, аутентификация, rate limits — смотри в /pay/docs как в источнике истины, а не в пересказе здесь: детали provisioning могут меняться, и на момент интеграции нужно ориентироваться именно на документацию.

Минимальная запись cadence на практике выглядит так:

ПолеПримерКомментарий
customer_idcust_1284твой собственный идентификатор
cadencemonthlyинтервал, который следит scheduler
amount49.00 USDTфиксирована на цикл, если не версионируешь прайсинг
next_due_date2026-08-08сдвигается только после сверки
last_request_idreq_9f2a…Pay-запрос, привязанный к текущему циклу

Изменения цены делай явными — если поднял цену плана, обнови сохранённый amount у себя, и следующий сгенерированный запрос отразит это. Внутри уже созданного payment request нет живого прайсинг-хука.

Доставка ссылки на продление и разумное окно expiry

Как только запрос создан, отправляй ссылку с запасом времени до истечения — по email, push-уведомлением или и тем и другим. Ссылка на продление, которая истекает в день создания, почти не оставляет шанса клиенту с медленным кошельком, занятыми выходными или пропущенным уведомлением.

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

Две привычки здесь помогают. Первая — отправляй ссылку за несколько дней до next_due_date, а не в саму дату платежа: это оставляет запас на неудачную первую попытку. Вторая — считай каждую ссылку на продление одноразовой: если она истекла неоплаченной, создавай новую, а не пытайся «оживить» старый запрос. У payment request нет действия re-open; свежий запрос со свежим expiry — правильный следующий шаг.

Сверка продлений через HMAC-верифицированные webhook

Это шаг, который превращает link-per-cycle flow из ручной гонки за инвойсами в реально автоматизированный процесс. Pay отправляет три события webhook в течение жизни payment request: payment.created при создании запроса, payment.updated при промежуточных изменениях статуса по мере продвижения свапа, и payment.completed, когда платёж клиента settled.

Твоя интеграция должна продвигать next_due_date подписки и открывать доступ на следующий цикл только по верифицированному событию payment.completed — никогда по payment.created, и никогда просто потому что клиент вернулся на твой redirect URL. Каждая доставка webhook несёт заголовок X-SwapZillaPay-Signature — HMAC от payload; сверяй его на сервере со своим webhook-секретом, прежде чем доверять чему-либо внутри body.

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

Неверифицированный webhook endpoint — открытое приглашение: любой, кто угадает URL твоего endpoint, может отправить POST с поддельным payment.completed и открыть себе доступ. Сначала проверяй, потом действуй — каждый раз, без исключений для «доверенного» трафика.

Обработка пропущенных платежей и dunning без кнопки auto-retry

Поскольку карту нельзя повторить, а постоянного allowance для списания нет — если только ты не построил что-то custodial, чего эта архитектура намеренно избегает, — пропущенное продление это по-настоящему пропущенный платёж, а не временный отказ, который сеть тихо повторит за тебя. Твоя dunning-логика должна полностью заменить этот отсутствующий retry.

Рабочий паттерн: напоминание до даты платежа, второе напоминание если ссылка истекла неоплаченной, и создание свежего запроса вместо переиспользования истёкшего. Дай клиенту короткий grace period с сохранённым доступом после даты платежа — достаточно, чтобы покрыть «собирался оплатить вчера», и достаточно коротко, чтобы неплательщики не катались бесплатно целый лишний цикл. Если grace period прошёл без верифицированного payment.completed — понижай или приостанавливай доступ на своей стороне; это изменение состояния целиком живёт в твоём приложении, потому что у Pay нет понятия о том, что такое «доступ» для твоего продукта.

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

Частые ошибки и когда этот подход не подходит

Ошибки, которые чаще всего всплывают, когда команда строит это в первый раз:

  • Предположение, что у Pay есть объект subscription. Его нет — нет endpoint’а подписок, нет встроенного cadence, нет dunning-движка. Всё это состояние и логику строишь сам, опираясь на payment-request API из /pay/docs.
  • Пропуск верификации подписи «пока что». Это самый частый способ, которым эксплуатируют link-per-cycle биллинг. Проверяй X-SwapZillaPay-Signature на каждом webhook, в каждом окружении, с первого дня.
  • Слишком короткое окно expiry, из-за которого честных клиентов гоняют за уже мёртвой ссылкой. Давай реальным плательщикам реальный запас времени.
  • Считать фиксированную сумму инвойса неуязвимой к движению курса. Если цена задана в волатильной монете оплаты, floating-rate запрос может settle на другую сумму, чем ты ожидал, из-за движения рынка между котировкой и оплатой. Реши заранее, ценообразуешь ли ты в стабильном активе получения или закладываешь variance, и посмотри как работают routing и типы курса в агрегаторе перед тем как фиксировать подход.
  • Строить весь цикл сверки, не протестировав scheduler изолированно. Сначала добейся правильной работы создания запросов самих по себе — обработку webhook и dunning-логику проще отлаживать на системе, в создании запросов которой ты уже уверен.

Эта архитектура хорошо подходит для SaaS, платной подписки на контент и регулярного биллинга услуг, где клиенты уже crypto-native и готовы каждый цикл проходить короткий платёжный flow. Она плохо подходит, если тебе нужен невидимый, нулевого трения auto-renew, неотличимый от сохранённой карты, — это custodial trade-off, которого этот гайд намеренно избегает, а не пробел, который можно обойти non-custodial способом. Также не подходит для usage-based биллинга с сильно варьирующимися крошечными суммами за цикл, где overhead создания и отслеживания ссылки на каждое списание перевешивает ценность самого платежа.

Если хочешь self-host весь стек вместо того чтобы строить поверх API, Greenfield API BTCPay Server поддерживает похожий подход «построй свой cadence сам» на инфраструктуре, которую держишь сам, — trade-off там в операционном overhead в обмен на полный суверенитет над каждым запросом в потоке.

FAQ

Можно ли настроить крипто-платежи так, чтобы они продлевались автоматически, как подписка на карте?
Нативно — нет, и ни один non-custodial rail не воспроизведёт невидимое авто-продление, которое даёт сохранённая карта, потому что у крипты нет аналога карточной сети, списывающей деньги от твоего имени. Но можно построить близкое к этому: scheduler создаёт новый payment request на SwapZilla Pay за несколько дней до даты платежа и отправляет клиенту ссылку — продление выглядит автоматическим на твоей стороне, хотя клиент каждый цикл всё равно совершает реальную транзакцию. Если тебе нужен ноль действий клиента за цикл, единственные пути — custodial-биллинг (процессор держит деньги и рассчитывается сам) или on-chain allowance для токен-стандартов, которые его поддерживают. Оба — разные trade-off с доверием, а не фича этой non-custodial модели.
Есть ли способ автоматически списывать деньги с крипто-кошелька клиента каждый месяц?
Не без того, чтобы чем-то пожертвовать. У кошельков BTC и XMR вообще нет механизма allowance — списать с них нельзя, пока держатель сам не инициирует транзакцию. Некоторые токен-стандарты, ERC-20 в их числе, поддерживают паттерн approve/transferFrom, где клиент выдаёт контракту постоянное разрешение списывать средства до лимита. Но это требует, чтобы клиент активно выдал именно постоянный allowance, работает только с совместимыми токенами и несёт свой риск, если контракт или сторона, держащая allowance, будут скомпрометированы. SwapZilla Pay эту модель не использует — это non-custodial система link-per-cycle, где клиент оплачивает каждый платёж напрямую, и постоянного разрешения на списание не существует в принципе.
В чём реальная разница между регулярным крипто-биллингом и подпиской в духе Stripe?
Подписка Stripe списывает деньги с сохранённой карты по твоему расписанию, и клиенту после первой регистрации ничего делать не нужно. Non-custodial крипто-версия работает иначе: твоя система создаёт новый payment request на каждый цикл, и клиент каждый раз совершает реальную транзакцию, потому что сохранённого способа оплаты списывать не с чего. Плюс в том, что ни процессор, ни контракт никогда не держат постоянное разрешение на деньги клиента. Trade-off — интеграционная работа на твоей стороне: scheduler, логика сверки и dunning-flow, которые Stripe даёт из коробки, плюс небольшое активное участие клиента каждый цикл.
Может ли ERC-20 allowance (approve/transferFrom) дать мне авто-списание стейблкоинов без кастодии?
Это ближайший к card-on-file вариант без кастодиана, который держит деньги, но и он не бесплатен по trade-off. Клиент подписывает одну транзакцию approve, выдавая контракту право списывать до установленного лимита, а твой контракт вызывает transferFrom по своему расписанию. Это работает только для токен-стандартов с таким интерфейсом — аналога для BTC или XMR не существует, — и клиент доверяет контракту (и тому, кто им управляет) постоянный allowance вплоть до отзыва. Это принципиально другая архитектура по сравнению с link-per-cycle моделью SwapZilla Pay, которая вообще не запрашивает постоянных разрешений; здесь она упомянута как контраст, а не как то, что реализует Pay.
Есть ли у SwapZilla Pay нативная функция подписок или регулярного биллинга?
Нет, и это стоит сказать прямо. API Pay построен вокруг единичных payment request: ты создаёшь запрос, клиент его оплачивает, ты получаешь webhook при settlement. Нет endpoint'а для подписок, нет встроенного cadence, нет dunning-движка и нет объекта клиента с регулярной суммой. Регулярная архитектура из этого гайда — то, что ты строишь сам поверх payment-request API: scheduler, генерирующий новый запрос каждый цикл, плюс своя база с cadence и датами платежей. Точные форматы запросов и webhook смотри в [/pay/docs](/pay/docs).
Как обрабатывать клиента, который пропустил платёж за продление?
Считай это по-настоящему пропущенным платежом, а не временным отказом — карточная сеть его тихо не повторит за тебя. Рабочий паттерн: напоминание до даты платежа, второе напоминание если ссылка истекла неоплаченной, короткий grace period с сохранённым доступом после даты платежа, и свежий payment request (никогда не переиспользованный истёкший), если клиент хочет закрыть долг. Как только grace period прошёл без верифицированного webhook payment.completed — понижай или приостанавливай доступ на своей стороне; эта логика целиком живёт в твоём приложении, потому что у Pay нет понятия о том, что такое «доступ» для твоего продукта.
Что будет, если курс изменится между циклами биллинга при фиксированной сумме инвойса?
Сумма получения, которую ты задаёшь при создании payment request, — это то, что должно прийти на твой кошелёк; сумма, которую платит клиент, считается по курсу на момент оплаты, а не на момент создания ссылки. Если цена плана указана в стабильном активе получения — например, в стейблкоине, — движение курса в основном влияет на то, сколько отправляет клиент, а не на то, сколько получаешь ты. Если получаешь волатильный актив, реальная ценность может отличаться от цикла к циклу просто из-за движения рынка — это осознанное решение по прайсингу, а не то, что payment-request flow сглаживает за тебя. Смотри [как работают типы курса в агрегаторе](/how-it-works/) перед тем как выбирать подход.