This guide is not translated yet. The up-to-date version is in Russian: /ru/guides/api-payouts
Гайд · Выплаты

Выплаты крипты по API: автоматизация для обменника

Обновлено: 4 сентября 2026

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

Почему выплата — самая опасная операция

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

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

Идемпотентность: один ключ — одна отправка

Правильный API выплат принимает от вас ключ идемпотентности — ваш собственный идентификатор вывода. Сколько бы раз вы ни повторили запрос с тем же ключом, отправка произойдёт один раз:

  • первый запрос → 202: выплата принята в очередь;
  • повтор с тем же idempotencyKey → 200: вернётся та же выплата, второй отправки не будет.

Поэтому ретрай при обрыве связи безопасен по построению. Правило на вашей стороне одно: ключ идемпотентности — это id вывода в вашей системе (например, withdraw-91422), а не случайная строка, сгенерированная на каждый HTTP-запрос. Случайный ключ превращает защиту в бутафорию: каждый ретрай выглядит новой выплатой.

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

Запрос выплаты

Выплата — один POST. Получатель получает ровно amount: комиссия сети берётся сверху из баланса проекта, добавлять её к сумме не нужно.

curl -X POST https://coinrail.net/v1/payouts \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "coin": "BTC",
    "toAddress": "bc1q...",
    "amount": "0.0009",
    "feeRate": 8,
    "idempotencyKey": "withdraw-91422"
  }'

Нюансы по монетам:

  • BTC и LTC: комиссию сети (feeRate, сат/vByte) выбираете вы — шлюз её не навязывает. Все адреса проекта — один HD-кошелёк: входы для транзакции собираются автоматически, крупные первыми, сдача возвращается на внутренний адрес. Заниженный feeRate отвергается до отправки — узел такую транзакцию не принял бы.
  • USDT (TRC20): комиссию сети платит шлюз, поэтому feeRate нет вовсе. За выплату удерживается фикс — 2.5 USDT в типовом случае (комиссия закрыта арендованной энергией) или 4 USDT при сжигании TRX; обе ставки видны заранее в POST /v1/payouts/estimate. Почему ставок две — в гайде про энергию TRON.

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

Статусы и ошибки: чему верить

Жизненный цикл: QUEUED → BROADCASTING → SENT (есть txHash) либо FAILED — средства не ушли. Финал приходит вебхуком; поллинг не нужен.

У неудачи всегда есть машинный failureCode — контракт, по которому ветвится ваша автоматика. Несколько кодов, на которые стоит запрограммировать реакцию заранее:

  • SEND_LIMIT_EXCEEDED — упёрлись в лимит на выплату или суточный. Не ошибка, а сработавшая страховка: см. следующий раздел.
  • RECIPIENT_BLACKLISTED — адрес получателя заблокирован эмитентом USDT. Перевод бы откатился, а комиссия сгорела — шлюз проверяет заранее. Верните вывод клиенту на уточнение реквизитов.
  • GAS_UNAVAILABLE — шлюз не смог оплатить комиссию сети TRON. Деньги не двигались, повтор безопасен.
  • BROADCAST_STUCK — единственный код, требующий ручной сверки: отправка зависла и снята по таймауту, транзакция могла уйти. Не ретрайте вслепую — сверьте по txHash.

Поле failureReason — текст для человека и логов. Показывайте оператору, но не парсьте: его формулировки — не контракт.

Лимиты — страховка от угона ключа

Представьте худшее: ключ с правом выплат утёк. Что остановит вывод всего кошелька? Только лимиты, настроенные до инцидента:

  • Лимит на одну выплату и на сутки — по каждой монете отдельно. В кабинете их можно только ужесточить, ослабить выше глобальных нельзя. Настройте под свой реальный оборот: лимит «с запасом в десять раз» — это не страховка, а её имитация.
  • Скоуп ключа. Ключ с payouts:write держите только на бэкенде казны. Фронтенду, боту поддержки и аналитике достаточно read — утечка такого ключа неприятна, но деньги не двигает.
  • IP-allowlist — включается в кабинете под PIN: даже украденный ключ бесполезен с чужого адреса.
  • Не храните лишнего. Кошелёк шлюза — операционная касса, а не сейф: накопленное выводите на собственное холодное хранилище. Это главная защита, которая работает даже при полной компрометации.

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

Что будет, если я отправлю один запрос выплаты дважды?
С одинаковым idempotencyKey — ничего: второй запрос вернёт ту же самую выплату со статусом 200, повторной отправки не произойдёт. Именно поэтому ключ должен быть id вывода в вашей системе, а не случайной строкой на каждый запрос.
Нужно ли прибавлять комиссию сети к сумме выплаты?
Нет. Получатель получает ровно amount, комиссия списывается сверху из баланса проекта. Если баланса не хватает на сумму плюс комиссию — выплата уходит в FAILED целиком, частичной отправки не бывает.
Сколько стоит выплата?
В BTC и LTC — только комиссия сети, которую вы сами задаёте через feeRate. В USDT комиссию сети платит шлюз, а с проекта удерживается фикс: 2.5 USDT в типовом случае или 4 USDT, если энергию пришлось покупать сжиганием TRX. Это не процент — сумма выплаты на плату не влияет.
Выплата ушла в FAILED — деньги в безопасности?
Да: FAILED означает, что средства не ушли. Единственное исключение — код BROADCAST_STUCK: отправка снята по таймауту, но транзакция могла успеть уйти в сеть, поэтому этот код требует ручной сверки перед повтором.
Как ограничить ущерб, если API-ключ украдут?
Три уровня, настраиваются заранее: лимиты на выплату и на сутки (в кабинете, только ужесточение), отдельный ключ с payouts:write только на бэкенде казны, IP-allowlist под PIN. И главное — не держать на шлюзе больше, чем нужно для текущего оборота.

Дальше: полный справочник выплат — в документации API; как устроен приём — в гайде про USDT TRC20; кто держит ключи от кошелька — в гайде про кастодиальность.