Перейти к содержанию
STONS
Разделы документации

Выплаты

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

Откуда платятся выплаты

Только с отдельного горячего кошелька. Не с адресов приёма и не из холодного хранилища. Баланс горячего кошелька ограничен сверху и пополняется вручную из холодного хранилища: это потолок потерь, если сервер подписи будет скомпрометирован.

Создать выплату

POST /v1/payouts

Запрос

Поле Тип Обязательно Описание
reference string да Идентификатор выплаты на стороне мерчанта, уникальный в пределах мерчанта: 1–200 видимых ASCII-символов без пробелов. Ключ идемпотентности
asset string да Актив, например USDT_TRON. Сервис платит не во всех активах, в которых принимает
to_address string да Адрес получателя целиком, 20–100 букв и цифр. Должен быть в вайтлисте мерчанта по этому активу
amount string да Сумма в единицах актива: "100.5". Не больше знаков после запятой, чем у актива, без знака, экспоненты и пробелов. Ноль не принимается

Ответ

202 — выплата создана, 200 — выплата с этим reference уже была создана раньше. Тело в обоих случаях одинаковое:

Поле Тип Описание
payout_id string (UUID) Идентификатор выплаты
reference string Идентификатор выплаты на стороне мерчанта
status string Статус, см. ниже
asset string Актив
network string Сеть актива: tron
to_address string Адрес получателя целиком
amount string Сумма со всеми знаками актива: "100.500000"
review_reasons array Какие правила выплата перешла и потому ждёт человека. Пусто у выплаты, прошедшей по правилам
tx_hash string или null Хэш транзакции, когда она отправлена
created_at string Время создания, ISO 8601 в UTC
updated_at string Время последнего изменения, ISO 8601 в UTC

Созданная выплата получает статус queued, если прошла все правила, и pending_review, если нет. Второй случай — не ошибка: выплата принята и ждёт решения человека, а review_reasons говорит, чего именно.

Получить выплату

GET /v1/payouts/{id}

Отвечает тем же телом. Выплаты других мерчантов для вызывающего не существуют: ответ 404 payout_not_found.

Статусы

pending_review → approved → queued → signing → broadcast → confirmed
       ↓                                            ↓
   rejected                                      failed
Статус Смысл
pending_review Выплата перешла лимит или порог и ждёт подтверждения человека
approved Человек подтвердил выплату. Ставится только ручным подтверждением: выплата в пределах правил через этот статус не проходит
queued Выплата принята к исполнению и ждёт подписи
signing Сервер подписи взял выплату в работу
broadcast Транзакция отправлена в сеть и ждёт подтверждений
confirmed Транзакция подтверждена сетью. Окончательный статус
rejected Человек отклонил выплату. Окончательный статус: повтор требует нового reference
failed Отправка не удалась. Окончательный статус: автоматических повторов нет

Выплата в failed не повторяется автоматически — сначала проверяется, что транзакция действительно не попала в сеть, иначе повтор означал бы двойную выплату.

Переход в confirmed и в failed сопровождается вебхуком payout.confirmed или payout.failed — см. события. Комиссию сети платит горячий кошелёк сервиса, и из суммы выплаты она не вычитается: получатель получает ровно amount (ADR-0023).

Куда можно платить

Только на адреса из вайтлиста мерчанта. Вайтлистом управляет оператор командами, см. команды оператора.

  • Адрес, добавленный в вайтлист, можно использовать только после задержки: WHITELIST_COOLDOWN_HOURS, по умолчанию 24 часа. Момент «использовать с» считается при добавлении и хранится в строке, поэтому смена настройки не ускоряет уже добавленные адреса. До этого момента выплата отклоняется кодом address_not_whitelisted, и сообщение называет момент, с которого адрес станет годен.
  • Добавление подписывается операторским ключом офлайн. Подпись покрывает мерчанта, актив, адрес целиком и момент добавления, поэтому её нельзя переставить на другой адрес или другого мерчанта. Строку без верной подписи не примет ни команда, ни сервер подписи — обоснование в ADR-0021.
  • Добавление адреса, у которого первые и последние символы совпадают с уже внесённым, блокируется до явного подтверждения с показом обоих адресов целиком. Так отсекается подмена адреса похожим (address poisoning).
  • Адрес для выплаты никогда не берётся из истории транзакций.
  • Адрес сверяется с вайтлистом дважды: при создании выплаты в API и ещё раз, независимо, сервером подписи перед подписью — и Signer проверяет не только наличие строки, но и подпись оператора под ней. Запись вайтлиста, разрешившая выплату, хранится на самой выплате, поэтому проверять нечего заново выводить.
  • Отзыв адреса подписи не требует: он только убирает разрешение. Отозванный адрес можно добавить заново, и задержка начнётся сначала.

Лимиты

Действуют четыре значения. Все они заданы в одной учётной единице — в долларах, — потому что суточный лимит сервиса складывает выплаты по всем активам, а складывать минимальные единицы активов с разными decimals нельзя. Отсюда правило: сервис платит только в активах, привязанных к доллару (ADR-0022).

Переменная Что ограничивает
PAYOUT_MAX_AMOUNT Одну выплату
PAYOUT_AUTO_APPROVE_LIMIT Порог, ниже которого подтверждение человека не нужно
PAYOUT_DAILY_LIMIT_MERCHANT Сумму выплат одного мерчанта за сутки
PAYOUT_DAILY_LIMIT_TOTAL Сумму выплат всего сервиса за сутки

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

Переход лимита не отклоняет выплату — он переводит решение человеку. В review_reasons появляется одно или несколько значений:

Значение Когда
above_transaction_limit Сумма больше PAYOUT_MAX_AMOUNT
above_auto_approve_limit Сумма больше PAYOUT_AUTO_APPROVE_LIMIT
above_merchant_daily_limit Вместе с этой выплатой мерчант превысит свой суточный лимит
above_service_daily_limit Вместе с этой выплатой сервис превысит суточный лимит целиком

Ручное подтверждение

Подтверждает и отклоняет выплату человек, командой на сервере — маршрута для этого нет, пока нет административных ролей и второго фактора (ADR-0019). Процедура целиком, с параметрами команд, — в командах оператора. Коротко:

  1. Оператор смотрит выплаты в pending_review: payout:list. Вывод показывает адрес целиком, сумму в минимальных единицах актива и действующие лимиты.
  2. Он сверяет адрес получателя символ за символом с тем, что ожидает мерчант, и проверяет причину ожидания.
  3. payout:approve --actor <кто> --id <выплата> переводит выплату в approved. payout:reject --actor <кто> --id <выплата> --note <почему> — в rejected, окончательно.

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

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

Идемпотентность

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

Запрос с тем же reference, но другой суммой, активом или адресом отклоняется кодом 409 reference_conflict: это другая выплата, а не повтор.

Остановка выплат

Выплаты останавливаются автоматически при расхождении реестра с сетью и аварийной командой оператора. Пока остановка действует, новые выплаты не принимаются — код 503 payouts_halted; повтор с тем же reference после снятия остановки создаст выплату. Причина остановки наружу не раскрывается: она описывает внутренний инцидент. Снять остановку можно только вручную, с записью в журнал аудита — см. остановку выплат.

Ошибки

HTTP error.code Когда
400 validation_failed Поле отсутствует, лишнее или неверного формата; у суммы слишком много знаков после запятой; сумма не больше нуля
401 unauthorized Нет ключа API или он неверный
403 merchant_disabled Мерчант отключён
404 payout_not_found Выплаты нет или она принадлежит другому мерчанту
409 reference_conflict Выплата с этим reference уже есть и описывает другую выплату
422 asset_not_payable В этом активе сервис не делает выплат
422 asset_payouts_disabled Оператор временно выключил выплаты в этом активе. Повтор с существующим reference возвращает выплату
422 address_not_whitelisted Адреса нет в вайтлисте по этому активу или его задержка не прошла
429 rate_limited Превышен лимит запросов
503 payouts_halted Выплаты остановлены

Пример

curl -s -X POST https://api.stons.io/v1/payouts \
  -H "X-Api-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"reference": "wd_501", "asset": "USDT_TRON", "to_address": "TUEZSdKsoDHQMeZwihtdoBiN46zxhGWYdH", "amount": "100.5"}'
{
  "payout_id": "4c2f1a7e-93d5-4c0b-8a2e-5f6b7c8d9e01",
  "reference": "wd_501",
  "status": "queued",
  "asset": "USDT_TRON",
  "network": "tron",
  "to_address": "TUEZSdKsoDHQMeZwihtdoBiN46zxhGWYdH",
  "amount": "100.500000",
  "review_reasons": [],
  "tx_hash": null,
  "created_at": "2026-09-12T12:00:00.000Z",
  "updated_at": "2026-09-12T12:00:00.000Z"
}

Выплата, которая ждёт человека, отвечает тем же кодом 202:

{
  "payout_id": "7e1b2c3d-4f5a-6b7c-8d9e-0f1a2b3c4d5e",
  "reference": "wd_502",
  "status": "pending_review",
  "asset": "USDT_TRON",
  "network": "tron",
  "to_address": "TUEZSdKsoDHQMeZwihtdoBiN46zxhGWYdH",
  "amount": "900.000000",
  "review_reasons": ["above_auto_approve_limit"],
  "tx_hash": null,
  "created_at": "2026-09-12T12:05:00.000Z",
  "updated_at": "2026-09-12T12:05:00.000Z"
}