moonvex

Документация API

Всё, что нужно, чтобы принимать криптоплатежи через Moonvex: от первого ключа до выплат. Базовый адрес API — https://api.moonvex.org, формат — JSON, кодировка — UTF-8.

Как это работает

  1. Ваш сервер создаёт платёж: номер заказа, сумма, монета и сеть. В ответ — адрес для оплаты и paymentUrl, готовая страница оплаты.
  2. Клиент платит со своего кошелька: на странице оплаты есть QR-код, адрес, точная сумма и официальный контракт токена.
  3. Moonvex следит за сетью: замечает перевод, ждёт нужного числа подтверждений и сверяет транзакцию у двух независимых узлов.
  4. Вы получаете webhook payment.confirmed — можно выдавать товар. На баланс проекта зачисляется ваша сумма: комиссию Moonvex клиент платит сверху.
  5. Баланс выводится выплатой на ваш адрес — из кабинета или через API.
Тестовой сети нет: Moonvex работает в основных сетях. Проверяйте интеграцию на небольших суммах (например, 1 USDT в TRON) — после проверки такой платёж обычный, деньги зачисляются на баланс.

Быстрый старт

  1. Зарегистрируйтесь в кабинете, подтвердите email и включите двухфакторную аутентификацию.
  2. «Интеграция» → «Создать ключ» → тип «Секретный». Ключ показывается один раз — сохраните его на сервере в переменной MOONVEX_SECRET_KEY.
  3. Там же добавьте webhook: адрес на вашем сервере (только https). Сохраните секрет подписи в MOONVEX_WEBHOOK_SECRET.
  4. Создайте платёж и отправьте клиента на paymentUrl:
curl https://api.moonvex.org/v1/payments \
  -H "Authorization: Bearer $MOONVEX_SECRET_KEY" \
  -H "Idempotency-Key: order-1001-attempt-1" \
  -H "Content-Type: application/json" \
  -d '{
    "orderId": "order-1001",
    "amount": "25",
    "currency": "USDT",
    "network": "TRON",
    "customerId": "user-42",
    "description": "Заказ №1001",
    "expiresInMinutes": 30,
    "metadata": { "cart": "A17" }
  }'
  1. Примите webhook payment.confirmed, проверьте подпись (раздел Webhooks) и отметьте заказ оплаченным.
  2. Перед выдачей дорогого товара можно перепроверить статус запросом GET /v1/payments/:id.

SDK для Node.js

Официальный пакет @moonvex/node: все методы API, типы TypeScript, проверка подписи webhooks, повторы при сбоях сети с тем же Idempotency-Key. Без зависимостей, Node.js 18+.

npm install @moonvex/node
Node.js
import { Moonvex, verifyWebhook } from '@moonvex/node';

const moonvex = new Moonvex({ apiKey: process.env.MOONVEX_SECRET_KEY });

// платёж
const payment = await moonvex.payments.create(
  { orderId: 'order-1001', amount: '25', currency: 'USDT', network: 'TRON' },
  { idempotencyKey: 'order-1001-attempt-1' },
);
res.redirect(payment.paymentUrl);

// webhook (express.raw: подпись считается по сырому телу)
app.post('/moonvex/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const event = verifyWebhook(req.body, req.get('Moonvex-Signature'), process.env.MOONVEX_WEBHOOK_SECRET);
  if (event.type === 'payment.confirmed') markOrderPaid(event.data.orderId);
  res.sendStatus(200);
});

Методы: payments.create / get / quote / list / listAll / cancel, depositAddresses.create / list, payouts.create / get / quote / cancel, balances.list, rates.get, plans.upsert, subscriptions.create / get / cancel, webhooks.endpoints.*, webhooks.deliveries.*, verifyWebhook. Ошибки — MoonvexError с полями status, code, detailsCode, requestId.

Ключи API

Каждый запрос передаёт ключ в заголовке:

Authorization: Bearer sk_live_ruahuFSGZyeu4rI5_…
ТипНачинается сДля чего
Секретныйsk_live_Сервер: создание платежей, выплаты, webhooks — всё по выданным правам.
Публичныйpk_live_Только чтение (права на запись ему выдать нельзя). Видит все платежи проекта, поэтому его тоже лучше держать на сервере.

Права ключа

При создании ключа выберите права (без выбора — все). Запрос без нужного права получает 403 FORBIDDEN.

ПравоЧто разрешает
payments:readчитать платежи и подписки
payments:writeсоздавать и отменять платежи, планы и подписки
payouts:readчитать выплаты
payouts:writeсоздавать и отменять выплаты
balances:readчитать балансы
webhooks:manageуправлять адресами webhooks и журналом доставок
wallets:writeвыдавать клиентам постоянные депозитные адреса
  • Разрешённые IP. Можно ограничить ключ списком IP или подсетей — запросы с других адресов будут отклонены.
  • Ключ показывается один раз. Потеряли — создайте новый и отзовите старый в кабинете; отзыв действует сразу.
  • Никогда не кладите секретный ключ в код сайта, мобильного приложения или репозиторий.

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

Создание платежа, выплаты и подписки требует заголовок Idempotency-Key — уникальный ключ операции (8–128 символов: буквы, цифры, _ - : .). Он защищает от двойных платежей и выплат при повторе запроса после таймаута.

  • Повтор с тем же ключом и тем же телом вернёт тот же ответ и заголовок Idempotent-Replayed: true — второй платёж не создастся.
  • Тот же ключ с другим телом — 409 с кодом IDEMPOTENCY_KEY_REUSED.
  • Ключи хранятся 30 дней.
Хорошая практика — строить ключ из вашего идентификатора: order-1001-attempt-1. Тогда повтор после сбоя сети гарантированно не создаст второй счёт.

Суммы, монеты и сети

  • Все суммы — строки с десятичной точкой: "25", "0.00125". Не передавайте числа с плавающей точкой.
  • В ответах суммы приходят со всеми знаками монеты: USDT в TRON — 6 знаков (25.000000), BTC — 8.
  • currency — в чём выражена сумма: монета (USDT, BTC…) или USD. Для USD обязателен asset — монета оплаты; курс фиксируется при создании счёта и виден в pricing.rate.
  • Цена в одной криптовалюте с оплатой в другой не поддерживается.
  • USDT и USDC — всегда целые центы. Сумма к оплате округляется вверх до 0.01 (например, 26.05, а не 26.041667). BTC, ETH, BNB, SOL, TON и TRX — с полной точностью монеты: 0.01 BTC — слишком крупный шаг.
  • Комиссия сверху. amount в платеже — итог для клиента, ваша цена + комиссия проекта. Ваша цена остаётся в pricing.amount, ваш доход при полной оплате — в merchantAmount. Подробнее — Комиссия и итоговая сумма.

Сети, монеты и подтверждения

networkСетьМонетыПодтверждений до зачисления
TRONTRON (TRC-20)TRX, USDT19
ETHEREUMEthereum (ERC-20)ETH, USDT, USDC64 (финализация сети)
BSCBNB Smart Chain (BEP-20)BNB, USDT, USDC15
SOLANASolana (SPL)SOL, USDT, USDC32 (finalized)
TONTON (Jetton)TON, USDT1 (мастерчейн)
BITCOINBitcoinBTC2 — до $1 000, 3 — до $10 000, 6 — больше или сумма в BTC

Официальные контракты токенов

Зачисляются только токены с этими контрактами. Токен с тем же названием, но другим контрактом — подделка, он не зачисляется.

МонетаСетьКонтракт
USDTTRONTR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t
USDTETHEREUM0xdAC17F958D2ee523a2206206994597C13D831ec7
USDCETHEREUM0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
USDTBSC0x55d398326f99059fF775485246999027B3197955
USDCBSC0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d
USDTSOLANAEs9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB
USDCSOLANAEPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
USDTTONEQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs

Платежи

Создать платёж

POST/v1/paymentsправо ключа: payments:write
ПолеОбязательноОписание
orderIdдаНомер заказа у вас (до 128 символов: буквы, цифры, - _ . : / #). Один активный платёж на заказ.
amountдаСумма строкой: "25", "49.90".
currencyдаВ чём сумма: монета или USD.
networkдаСеть оплаты: TRON, ETHEREUM, BSC, SOLANA, TON, BITCOIN.
assetдля USDМонета оплаты, если сумма в USD.
customerIdнетВаш идентификатор клиента — для поиска и подписок.
customerEmailнетEmail клиента.
expiresInMinutesнетСрок оплаты: 5–1440 минут, по умолчанию 30.
descriptionнетДо 500 символов.
metadataнетВаши данные: до 50 ключей, значения — строка, число, true/false или null. Возвращаются в платеже и webhooks.

Ответ 201 — объект платежа:

Payment
{
  "id": "pay_UsLiLbyGM7YUbdy6BsUL5a",
  "object": "payment",
  "status": "PENDING",
  "mode": "INVOICE",
  "orderId": "order-1001",
  "customerId": "user-42",
  "amount": "26.050000",
  "merchantAmount": "25.008000",
  "asset": "USDT",
  "network": "TRON",
  "contract": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
  "tokenStandard": "TRC-20",
  "address": "TCnQo6fMf9LL8bjhiJMPq6DZ38GbAEoeDs",
  "paymentUrl": "https://pay.moonvex.org/pay/pay_UsLiLbyGM7YUbdy6BsUL5a",
  "pricing": { "currency": "USDT", "amount": "25", "rate": null },
  "receivedAmount": "0.000000",
  "fee": { "percent": "4", "expectedAmount": "1.042000", "amount": null },
  "netAmount": null,
  "confirmations": 0,
  "requiredConfirmations": 19,
  "txHashes": [],
  "expiresAt": "2026-11-10T19:30:00.000Z",
  "settledAt": null,
  "createdAt": "2026-11-10T19:00:00.000Z",
  "metadata": { "cart": "A17" }
}
ПолеЧто значит
idИдентификатор платежа (pay_…). Сохраните его.
addressАдрес для оплаты. Свой для каждого платежа.
paymentUrlГотовая страница оплаты для клиента.
amountИтого к оплате для клиента: ваша сумма + комиссия вашего проекта. USDT и USDC — с точностью до центов (округление вверх).
merchantAmountСколько получите вы, если клиент заплатит полностью.
receivedAmountСколько уже пришло.
contract / tokenStandardОфициальный контракт токена и стандарт (null для нативной монеты).
feeКомиссия Moonvex по ставке вашего проекта (у каждого проекта своя). Её платит клиент сверху. expectedAmount — комиссия при полной оплате, amount — фактическая после оплаты.
netAmountСколько зачислено на ваш баланс после комиссии.
confirmations / requiredConfirmationsПодтверждения сети: сейчас / нужно.
txHashesХэши транзакций клиента.
pricingИсходная цена: валюта, сумма и курс (для USD).

Комиссия и итоговая сумма

Комиссию Moonvex платит клиент сверху. Ставка проекта применяется к итоговой сумме, которую платит клиент (комиссия = ставка × итог), поэтому итог = цена / (1 − ставка), а не цена × (1 + ставка): при 4 % клиент платит 26.05 за цену 25, а не 26.00. Ставка у каждого проекта своя и фиксируется в платеже при создании.

Порядок расчёта amount:

  1. Цена в монете. Для currency: "USD" — по зафиксированному курсу, с округлением вверх до наименьшей единицы монеты.
  2. Для USDT и USDC — вверх до целых центов.
  3. Делим на (1 − ставка) и округляем вверх до наименьшей единицы монеты.
  4. Для USDT и USDC — снова вверх до целых центов. Это и есть amount.

При оплате комиссия = amount × ставка с округлением вниз, вам — amount минус комиссия. Все округления идут в вашу пользу, поэтому вы получаете не меньше цены, обычно на доли цента больше. Пример при 4 %: 25 / 0.96 = 26.041666… → 26.05; комиссия 26.05 × 0.04 = 1.042; вам 25.008 вместо 25 — лишние 0.008 из-за округления до центов. Точная сумма, которую получите вы, — в merchantAmount.

Узнать итоговую сумму заранее (например, чтобы показать её в корзине) — без создания платежа:

GET/v1/payments/quoteправо ключа: payments:read
Ответ
{
  "object": "payment_quote",
  "asset": "USDT",
  "network": "TRON",
  "merchantAmount": "25.008000",
  "fee": { "percent": "4", "amount": "1.042000" },
  "total": "26.050000",
  "pricing": { "currency": "USDT", "amount": "25", "rate": null }
}

Для цены в USD курс в ответе текущий; созданный платёж фиксирует свой курс.

Сумма в USD с оплатой в BTC:

POST /v1/payments
{
  "orderId": "order-1002",
  "amount": "49.90",
  "currency": "USD",
  "asset": "BTC",
  "network": "BITCOIN"
}

Получить платёж

GET/v1/payments/:idправо ключа: payments:read

Список платежей

GET/v1/payments?status=&orderId=&limit=&cursor=право ключа: payments:read

Сначала новые. limit — 1–100 (по умолчанию 20). Для следующей страницы передайте nextCursor из ответа.

curl "https://api.moonvex.org/v1/payments?status=CONFIRMED&limit=50" \
  -H "Authorization: Bearer $MOONVEX_SECRET_KEY"

# ответ: { "data": [ ...платежи... ], "hasMore": true, "nextCursor": "eyJ..." }
# следующая страница: ?cursor=eyJ...&status=CONFIRMED&limit=50

Отменить платёж

POST/v1/payments/:id/cancelправо ключа: payments:write

Только пока клиент не начал платить (статусы CREATED и PENDING). После отмены по тому же orderId можно создать новый платёж — так же, как после истечения срока.

Депозитные адреса

Для сервисов, где у клиента есть баланс или счёт: маркетплейсы и сервисы с предоплатой, SaaS и API с оплатой по использованию, хостинг и облака, рекламные кабинеты, кошельки и обменники, игры и цифровые товары. У каждого вашего клиента свой постоянный адрес. Клиент пополняет его когда угодно и на любую сумму, без счёта и срока. Каждое подтверждённое поступление зачисляется вам отдельно, с комиссией, и приходит webhook deposit.confirmed с customerId — по нему вы пополняете баланс клиента у себя.

POST/v1/deposit-addressesправо ключа: wallets:write
curl https://api.moonvex.org/v1/deposit-addresses \
  -H "Authorization: Bearer $MOONVEX_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "customerId": "user-42", "network": "TRON" }'

# 201 при первом запросе, 200 — если у клиента адрес уже есть (тот же адрес)
{
  "object": "deposit_address",
  "customerId": "user-42",
  "address": "TJ8nqPbE2s4yLxq6mJ7vYpB9cX3dQkWz1a",
  "networks": ["TRON"],
  "assets": [
    { "asset": "TRX", "network": "TRON", "contract": null },
    { "asset": "USDT", "network": "TRON", "contract": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t" }
  ],
  "createdAt": "2026-11-11T10:00:00.000Z"
}
  • Один клиент — один адрес в сети. Повторный запрос для того же customerId возвращает тот же адрес (Idempotency-Key не нужен). У разных клиентов адреса разные.
  • Адрес на семейство сетей. Адрес TRON принимает TRX и USDT; один адрес EVM принимает монеты Ethereum и BNB Smart Chain (в ответе networks — обе сети). Для Bitcoin, Solana и TON — свои адреса.
  • Зачисляются только монеты из assets с официальными контрактами. Поддельные токены и пыль не зачисляются.
  • Подтверждения — как у обычных платежей; для Bitcoin — 6 (сумма пополнения заранее неизвестна).
  • Адрес постоянный: показывайте его клиенту в профиле, им можно пользоваться годами.

Как приходят пополнения

Каждое поступление — отдельный объект платежа с mode: "DEPOSIT" (без orderId и срока). События: deposit.detected — перевод замечен в сети (можно показать «пополнение в пути»), deposit.confirmed — подтверждён и зачислен. Сумма — receivedAmount, после комиссии — netAmount. У депозита нет заранее известной суммы, поэтому здесь комиссия не добавляется сверху, а удерживается из поступления по ставке проекта.

// webhook deposit.confirmed: event.data — объект платежа с mode = "DEPOSIT"
{
  "id": "01a1c8…",
  "type": "deposit.confirmed",
  "data": {
    "id": "pay_9Qm…", "mode": "DEPOSIT", "status": "CONFIRMED",
    "customerId": "user-42", "asset": "USDT", "network": "TRON",
    "address": "TJ8nqPbE2s4yLxq6mJ7vYpB9cX3dQkWz1a",
    "receivedAmount": "50.000000", "fee": { "percent": "4", "amount": "2.000000" }, "netAmount": "48.000000",
    "txHashes": ["7f3a…"], "orderId": null, "...": "…"
  }
}

// пополните баланс клиента у себя — ровно один раз на event.id:
if (event.type === 'deposit.confirmed') {
  await creditCustomerOnce(event.id, event.data.customerId, event.data.asset, event.data.receivedAmount);
}
GET/v1/deposit-addresses?customerId=право ключа: payments:read
GET/v1/payments?customerId=user-42&mode=DEPOSITправо ключа: payments:read

Список адресов клиента и история его пополнений.

Статусы платежа

СтатусЧто произошлоЧто делать
CREATEDСчёт создан, ждём перевод.Показать клиенту страницу оплаты.
PENDINGЖдём перевод (так платёж возвращается сразу после создания).—
DETECTEDПеревод замечен в сети.Можно показать «оплата получена, ждём подтверждения». Товар не выдавать.
PARTIALLY_PAIDПришла часть суммы.Клиент может доплатить на тот же адрес.
CONFIRMINGТранзакция в блоке, набираются подтверждения.Ждать.
CONFIRMEDОплачено полностью, деньги зачислены.Выдать товар.
OVERPAIDПришло больше суммы (сверх допуска). Зачислено фактически пришедшее.Выдать товар; излишек — на ваше решение.
UNDERPAIDОкно доплаты (30 минут после срока) закрылось, пришло меньше. Зачислено фактически пришедшее.Решить: выдать часть, попросить доплатить отдельным счётом или вернуть.
EXPIREDСрок вышел без оплаты.Создать новый платёж по тому же orderId при необходимости.
CANCELLEDВы отменили платёж.—
FAILEDТранзакция в сети не удалась.—
REFUNDEDПлатёж возвращён.—
  • Допуск 0,5 %. Сумма в пределах ±0,5 % от счёта считается точной оплатой (кошельки иногда округляют). Комиссия всегда считается от фактически пришедшей суммы, поэтому при недоплате в пределах допуска вы получите чуть меньше merchantAmount; точная сумма — в netAmount.
  • Поздняя оплата. Деньги, пришедшие после EXPIRED, автоматически не зачисляются — их разбирает поддержка Moonvex и зачисляет вручную. Поставьте срок с запасом, если клиенты платят с бирж.
  • Не та сеть или монета. Перевод не той монетой или в другой сети на адрес платежа не зачисляется автоматически и тоже уходит в ручной разбор.
  • Откат блоков. Если транзакция исчезла из сети до финализации, платёж вернётся к ожиданию. Поэтому выдавайте товар только по CONFIRMED / OVERPAID, а не по DETECTED.

Страница оплаты

paymentUrl (https://pay.moonvex.org/pay/pay_…) — готовая страница: сумма, адрес, QR-код, официальный контракт токена, таймер, шаги подтверждения. Статус обновляется сам. Откройте её в новой вкладке или отправьте клиенту ссылкой (в мессенджере, по email). Встраивать её во фрейм на своём сайте нельзя — страница это запрещает из соображений безопасности.

Если хотите свой дизайн оплаты, есть публичные данные без ключа (только то, что видит клиент):

GET/v1/public/checkout/:id
GET/v1/public/checkout/:id/events

Первый возвращает сумму, монету, сеть, адрес, контракт, статус, подтверждения и срок; второй — поток обновлений статуса (Server-Sent Events). Лимит — 60 запросов в минуту с одного IP.

Возврат клиента на ваш сайт после оплаты — не подтверждение оплаты. Отмечайте заказ оплаченным только по webhook или по статусу из GET /v1/payments/:id.

Webhooks

Moonvex отправляет POST с JSON на ваш адрес при каждом изменении платежа, выплаты или подписки. Адрес задаётся в кабинете («Интеграция» → «Webhooks») или через API; только https.

Запрос к вашему серверу
POST /moonvex/webhook HTTP/1.1
Content-Type: application/json
User-Agent: Moonvex-Webhooks/1.0
Moonvex-Event-Id: 01a1c34d-0e9f-721e-8579-db6f436c4a38
Moonvex-Delivery-Id: 01a1c34d-153c-7989-8fdf-517de0d81590
Moonvex-Signature: t=1794279267,v1=5f2b…c81e

{
  "id": "01a1c34d-0e9f-721e-8579-db6f436c4a38",
  "type": "payment.confirmed",
  "apiVersion": "2026-11-01",
  "createdAt": "2026-11-10T19:04:12.000Z",
  "data": { "id": "pay_UsLiLbyGM7YUbdy6BsUL5a", "status": "CONFIRMED", "orderId": "order-1001", "...": "весь объект платежа" }
}
  • id — идентификатор события. При повторной доставке он тот же: обрабатывайте каждое событие один раз.
  • data — объект целиком: для payment.* — платёж (как в GET /v1/payments/:id), для payout.* — выплата.
  • Moonvex-Signature — подпись. Всегда проверяйте её.

Проверка подписи

Заголовок: t=<unix-время>,v1=<подпись>. Подпись — HMAC-SHA256 от строки <t>.<сырое тело запроса> секретом webhook, в hex. Проверяйте по сырому телу (до разбора JSON), сравнивайте за постоянное время и отклоняйте события старше 5 минут. После смены секрета в течение 24 часов в заголовке два v1 — со старым и новым секретом; подходит любой.

import crypto from 'node:crypto';
import express from 'express';

/** true, если подпись верна и событию не больше 5 минут */
function verifyMoonvex(rawBody, header, secret, toleranceSec = 300) {
  const parts = (header || '').split(',').map((p) => p.split('='));
  const t = Number((parts.find(([k]) => k === 't') || [])[1]);
  if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
  const expected = crypto.createHmac('sha256', secret).update(t + '.').update(rawBody).digest();
  return parts
    .filter(([k]) => k === 'v1')
    .some(([, sig]) => {
      const got = Buffer.from(sig, 'hex');
      return got.length === expected.length && crypto.timingSafeEqual(got, expected);
    });
}

const app = express();
// важно: подпись считается по «сырому» телу, поэтому express.raw, а не express.json
app.post('/moonvex/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
  if (!verifyMoonvex(req.body, req.get('Moonvex-Signature'), process.env.MOONVEX_WEBHOOK_SECRET)) {
    return res.status(400).send('bad signature');
  }
  const event = JSON.parse(req.body.toString('utf8'));
  if (await alreadyProcessed(event.id)) return res.sendStatus(200); // повторная доставка
  if (event.type === 'payment.confirmed') await markOrderPaid(event.data.orderId, event.data);
  await rememberProcessed(event.id);
  res.sendStatus(200); // ответьте 2xx в течение 10 секунд
});

Ответ и повторы

  • Ответьте любым кодом 2xx в течение 10 секунд. Долгую обработку делайте после ответа (в очереди).
  • При ошибке (5xx, 429, таймаут, сеть) повторы: через 10 с, 1 мин, 5 мин, 30 мин, 2 ч, 6 ч, 12 ч, 24 ч и ещё 24 ч (±20 %) — около трёх суток. Ответ 4xx (кроме 408 и 429) повторяется только 3 раза.
  • Если адрес долго не отвечает, доставки на него ненадолго приостанавливаются и затем продолжаются.
  • Журнал доставок с кодами ответов и кнопкой «Повторить» — в кабинете, «Интеграция». Там же кнопка «Отправить тест» — событие webhook.test.

События

СобытиеКогда
payment.createdПлатёж создан.
payment.detectedПеревод замечен в сети (в том числе частичный).
payment.confirmingТранзакция в блоке, набираются подтверждения.
payment.confirmedОплачено полностью — выдавайте товар.
payment.overpaidОплачено больше суммы.
payment.underpaidОкно доплаты закрылось, оплачено меньше.
payment.expiredСрок оплаты вышел.
payment.cancelledПлатёж отменён.
payment.failedТранзакция не удалась.
deposit.detectedПополнение постоянного адреса замечено в сети.
deposit.confirmedПополнение подтверждено и зачислено — пополните баланс клиента.
payout.createdВыплата принята.
payout.processingВыплата проверена и готовится к отправке.
payout.broadcastedТранзакция выплаты отправлена в сеть.
payout.confirmedВыплата подтверждена сетью.
payout.failedВыплата не удалась или отклонена — сумма вернулась на баланс.
subscription.createdПодписка создана.
subscription.renewedПериод подписки оплачен и продлён.
subscription.past_dueПериод закончился, но не оплачен: отправьте клиенту pendingPayment.url.
subscription.expiredПодписка не оплачена после льготного периода.
subscription.cancelledПодписка отменена.

По умолчанию webhook получает все события; при создании можно выбрать нужные.

Выплаты

Выплата отправляет деньги с баланса проекта на любой адрес в поддерживаемой сети.

POST/v1/payoutsправо ключа: payouts:write
curl https://api.moonvex.org/v1/payouts \
  -H "Authorization: Bearer $MOONVEX_SECRET_KEY" \
  -H "Idempotency-Key: withdraw-7781" \
  -H "Content-Type: application/json" \
  -d '{
    "asset": "USDT",
    "network": "TRON",
    "address": "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
    "amount": "150",
    "externalId": "withdraw-7781"
  }'
ПолеОписание
assetМонета.
networkСеть.
addressАдрес получателя. Проверяется под сеть; адрес контракта токена отклоняется.
amountС feeMode add (по умолчанию) — сколько получит получатель; с deduct — сколько ровно спишется с баланса.
feeModeadd — комиссия сверху суммы; deduct — комиссия из суммы. Необязательно, по умолчанию add.
externalIdВаш уникальный идентификатор выплаты (повтор — 409 DUPLICATE_EXTERNAL_ID).
metadataНеобязательно, ваши данные.

Ответ 202 — выплата принята и выполняется асинхронно; о ходе приходят события payout.*.

Payout
{
  "id": "po_8Fq2mXr0kD1sVb7nLtYw3c",
  "object": "payout",
  "externalId": "withdraw-7781",
  "status": "CREATED",
  "asset": "USDT",
  "network": "TRON",
  "address": "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
  "amount": "150.000000",
  "fee": { "percent": "2", "amount": "3.000000" },
  "total": "153.000000",
  "txHash": null,
  "failureReason": null,
  "createdAt": "2026-11-10T19:10:00.000Z",
  "completedAt": null
}
  • Комиссия за выплату = сумма получателю × ставка проекта, с округлением вниз до наименьшей единицы монеты (в вашу пользу). С feeMode: "add" она удерживается сверх суммы: с баланса списывается total = amount + fee.amount. С feeMode: "deduct" списывается ровно переданная сумма: получатель получает её / (1 + ставка) с округлением вниз, комиссия — остаток (может быть на 1 наименьшую единицу больше, чтобы списание было ровно вашей суммой). Пока выплата идёт, total в «резерве».
  • Не хватает баланса — 409 с кодом INSUFFICIENT_BALANCE.
  • Крупные выплаты проверяются вручную (статус PENDING_APPROVAL) — это может занять время.
  • Сетевая комиссия оплачивается Moonvex; получатель получает ровно amount.
СтатусЗначение
CREATED / RISK_CHECKПринята, идёт проверка.
PENDING_APPROVALЖдёт ручного одобрения.
APPROVED / QUEUED / SIGNINGГотовится к отправке.
BROADCASTED / CONFIRMINGОтправлена, ждём подтверждения сети.
COMPLETEDВыполнена. txHash — хэш транзакции.
FAILED / REJECTED / CANCELLEDНе выполнена; сумма вернулась на баланс. Причина — в failureReason.

Расчёт выплаты заранее

Сколько получит получатель, какая будет комиссия, сколько спишется и хватит ли баланса — ничего не создаётся:

GET/v1/payouts/quote?asset=USDT&network=TRON&amount=100&feeMode=deductправо ключа: payouts:read
Ответ
{
  "object": "payout_quote",
  "asset": "USDT",
  "network": "TRON",
  "feeMode": "deduct",
  "amount": "98.039215",
  "fee": { "percent": "2", "amount": "1.960785" },
  "total": "100.000000",
  "available": "500.000000",
  "sufficient": true
}

Пример при ставке 2 % и feeMode: "deduct": со 100 USDT получатель получит 98.039215, комиссия 1.960785, с баланса уйдёт ровно 100.

GET/v1/payouts/:idправо ключа: payouts:read
POST/v1/payouts/:id/cancelправо ключа: payouts:write

Отменить можно до подписи (статусы до QUEUED включительно).

Балансы и курсы

GET/v1/wallets/balancesправо ключа: balances:read

available — можно выводить; reserved — удерживается под выплаты в процессе.

curl https://api.moonvex.org/v1/wallets/balances -H "Authorization: Bearer $MOONVEX_SECRET_KEY"

{
  "data": [
    { "asset": "USDT", "network": "TRON", "available": "1240.500000", "reserved": "153.000000" },
    { "asset": "BTC", "network": "BITCOIN", "available": "0.01840000", "reserved": "0.00000000" }
  ]
}
GET/v1/rates?assets=BTC,ETH

Курсы к USD (медиана нескольких бирж) для BTC, ETH, BNB, TRX, SOL, TON, USDT, USDC — до 8 монет за запрос. Нужен любой действующий ключ. Обновляются раз в ~2 минуты.

curl "https://api.moonvex.org/v1/rates?assets=BTC,ETH" -H "Authorization: Bearer $MOONVEX_SECRET_KEY"

{
  "data": [
    { "asset": "BTC", "quote": "USD", "price": "73189.15", "sources": ["binance", "coinbase", "kraken"] },
    { "asset": "ETH", "quote": "USD", "price": "2026.00", "sources": ["binance", "coinbase", "kraken"] }
  ],
  "ttlSeconds": 120
}

Подписки

Регулярные платежи: каждый период — отдельный счёт, который клиент оплачивает по ссылке. Автоматически списать криптовалюту с кошелька клиента нельзя, поэтому отправляйте клиенту ссылку pendingPayment.url (по email или в приложении).

PUT/v1/plansправо ключа: payments:write
POST/v1/subscriptionsправо ключа: payments:write
GET/v1/subscriptions/:idправо ключа: payments:read
POST/v1/subscriptions/:id/cancelправо ключа: payments:write
# 1. План (создаётся или обновляется по code)
curl -X PUT https://api.moonvex.org/v1/plans \
  -H "Authorization: Bearer $MOONVEX_SECRET_KEY" -H "Content-Type: application/json" \
  -d '{ "code": "pro-monthly", "name": "Pro, месяц", "priceAmount": "15", "currency": "USDT",
        "asset": "USDT", "network": "TRON", "intervalDays": 30, "trialDays": 0, "graceDays": 3 }'

# 2. Подписка клиента
curl https://api.moonvex.org/v1/subscriptions \
  -H "Authorization: Bearer $MOONVEX_SECRET_KEY" -H "Idempotency-Key: sub-user-42-pro" \
  -H "Content-Type: application/json" \
  -d '{ "planCode": "pro-monthly", "customerId": "user-42" }'

# ответ
{
  "id": "sub_3Hk9…", "object": "subscription", "status": "PAST_DUE", "plan": "pro-monthly",
  "customerId": "user-42", "currentPeriodEnd": null,
  "pendingPayment": { "id": "pay_…", "url": "https://pay.moonvex.org/pay/pay_…" }
}
СтатусЗначение
TRIALПробный период (trialDays), платить не нужно.
ACTIVEОплачено до currentPeriodEnd. Счёт на следующий период выставляется заранее.
PAST_DUEПериод не оплачен; есть pendingPayment для оплаты.
EXPIREDНе оплачено за льготный период (graceDays, по умолчанию 3 дня).
CANCELLEDОтменена вами.

Webhooks через API

GET/v1/webhooks/endpointsправо ключа: webhooks:manage
POST/v1/webhooks/endpointsправо ключа: webhooks:manage
POST/v1/webhooks/endpoints/:id/rotate-secretправо ключа: webhooks:manage
DELETE/v1/webhooks/endpoints/:idправо ключа: webhooks:manage
GET/v1/webhooks/deliveries?limit=50право ключа: webhooks:manage
POST/v1/webhooks/deliveries/:id/replayправо ключа: webhooks:manage
# создать адрес для уведомлений (секрет подписи вернётся один раз)
curl https://api.moonvex.org/v1/webhooks/endpoints \
  -H "Authorization: Bearer $MOONVEX_SECRET_KEY" -H "Content-Type: application/json" \
  -d '{ "url": "https://shop.example.com/moonvex/webhook", "events": ["payment.confirmed", "payment.expired"] }'

# { "id": "…", "url": "…", "events": [...], "secret": "whsec_…" }

Секрет подписи возвращается только при создании и при смене (rotate-secret). После смены старый секрет действует ещё 24 часа — успейте обновить его у себя.

Ошибки

Ошибка всегда приходит в одном формате. requestId пришлите в поддержку — по нему находится запрос.

{
  "error": {
    "code": "CONFLICT",
    "message": "Order order-1001 already has a payment in progress or is closed",
    "requestId": "req_9e6c8eb939aa40b28d689df40f7d51a6",
    "details": { "code": "DUPLICATE_ORDER" }
  }
}
HTTPcodeКогда
400VALIDATION_ERRORНеверные параметры. Подробности — в message и details.
401UNAUTHENTICATEDНет ключа, ключ неверный, отозван или истёк.
403FORBIDDENНет права у ключа, IP не разрешён, проект неактивен.
404NOT_FOUNDНет такого платежа/выплаты в вашем проекте.
409CONFLICTКонфликт состояния: см. details.code.
429RATE_LIMITEDСлишком много запросов — повторите позже.
500INTERNAL_ERRORОшибка на нашей стороне — повторите с тем же Idempotency-Key.
details.codeЧто значит
DUPLICATE_ORDERПо этому orderId уже есть активный или оплаченный платёж.
IDEMPOTENCY_KEY_REUSEDIdempotency-Key уже использован с другим телом.
ASSET_NETWORK_NOT_ENABLEDЭта монета в этой сети не принимается.
SYSTEM_PAUSEDПриём или выплаты временно приостановлены.
INSUFFICIENT_BALANCEНе хватает баланса на выплату с комиссией.
INVALID_ADDRESSАдрес не подходит для сети (или это адрес контракта).
DUPLICATE_EXTERNAL_IDВыплата с таким externalId уже есть.
PAYMENT_NOT_FOUND / PAYOUT_NOT_FOUNDНет такого объекта в вашем проекте.

Лимиты запросов

ЗапросыЛимит на ключ
Чтение (GET)100 в секунду
Создание и отмена платежей, подписок20 в секунду
Выплаты2 в секунду
Публичные данные страницы оплаты (без ключа)60 в минуту с IP

При превышении — 429 RATE_LIMITED; повторите через секунду. Не опрашивайте статус платежей в цикле — используйте webhooks.

Машиночитаемое описание API (OpenAPI 3): api.moonvex.org/v1/openapi.json.

Перед запуском

  1. Секретный ключ только на сервере; права — минимально нужные; по возможности ограничение по IP.
  2. Каждый POST — с Idempotency-Key, построенным из вашего идентификатора.
  3. Подпись webhook проверяется по сырому телу; события старше 5 минут отклоняются.
  4. Каждое событие обрабатывается один раз (по id); ответ 2xx — быстро, обработка — в очереди.
  5. Товар выдаётся только по payment.confirmed / payment.overpaid (или статусу из API), не по возврату клиента на сайт.
  6. Для payment.underpaid есть понятный сценарий для клиента.
  7. Клиенту показывается amount из платежа (с комиссией), а сверка с заказом идёт по orderId и pricing.amount, а не по amount.
  8. Проверен реальный платёж на небольшую сумму в каждой сети, которую вы включаете.