Документация API
Всё, что нужно, чтобы принимать криптоплатежи через Moonvex: от первого ключа до выплат. Базовый адрес API — https://api.moonvex.org, формат — JSON, кодировка — UTF-8.
Как это работает
- Ваш сервер создаёт платёж: номер заказа, сумма, монета и сеть. В ответ — адрес для оплаты и
paymentUrl, готовая страница оплаты. - Клиент платит со своего кошелька: на странице оплаты есть QR-код, адрес, точная сумма и официальный контракт токена.
- Moonvex следит за сетью: замечает перевод, ждёт нужного числа подтверждений и сверяет транзакцию у двух независимых узлов.
- Вы получаете webhook
payment.confirmed— можно выдавать товар. На баланс проекта зачисляется ваша сумма: комиссию Moonvex клиент платит сверху. - Баланс выводится выплатой на ваш адрес — из кабинета или через API.
Быстрый старт
- Зарегистрируйтесь в кабинете, подтвердите email и включите двухфакторную аутентификацию.
- «Интеграция» → «Создать ключ» → тип «Секретный». Ключ показывается один раз — сохраните его на сервере в переменной
MOONVEX_SECRET_KEY. - Там же добавьте webhook: адрес на вашем сервере (только https). Сохраните секрет подписи в
MOONVEX_WEBHOOK_SECRET. - Создайте платёж и отправьте клиента на
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" }
}'- Примите webhook
payment.confirmed, проверьте подпись (раздел Webhooks) и отметьте заказ оплаченным. - Перед выдачей дорогого товара можно перепроверить статус запросом
GET /v1/payments/:id.
SDK для Node.js
Официальный пакет @moonvex/node: все методы API, типы TypeScript, проверка подписи webhooks, повторы при сбоях сети с тем же Idempotency-Key. Без зависимостей, Node.js 18+.
npm install @moonvex/node
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 | Сеть | Монеты | Подтверждений до зачисления |
|---|---|---|---|
TRON | TRON (TRC-20) | TRX, USDT | 19 |
ETHEREUM | Ethereum (ERC-20) | ETH, USDT, USDC | 64 (финализация сети) |
BSC | BNB Smart Chain (BEP-20) | BNB, USDT, USDC | 15 |
SOLANA | Solana (SPL) | SOL, USDT, USDC | 32 (finalized) |
TON | TON (Jetton) | TON, USDT | 1 (мастерчейн) |
BITCOIN | Bitcoin | BTC | 2 — до $1 000, 3 — до $10 000, 6 — больше или сумма в BTC |
Официальные контракты токенов
Зачисляются только токены с этими контрактами. Токен с тем же названием, но другим контрактом — подделка, он не зачисляется.
| Монета | Сеть | Контракт |
|---|---|---|
| USDT | TRON | TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t |
| USDT | ETHEREUM | 0xdAC17F958D2ee523a2206206994597C13D831ec7 |
| USDC | ETHEREUM | 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 |
| USDT | BSC | 0x55d398326f99059fF775485246999027B3197955 |
| USDC | BSC | 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d |
| USDT | SOLANA | Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB |
| USDC | SOLANA | EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v |
| USDT | TON | EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs |
Платежи
Создать платёж
/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 — объект платежа:
{
"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:
- Цена в монете. Для
currency: "USD"— по зафиксированному курсу, с округлением вверх до наименьшей единицы монеты. - Для USDT и USDC — вверх до целых центов.
- Делим на (1 − ставка) и округляем вверх до наименьшей единицы монеты.
- Для 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.
Узнать итоговую сумму заранее (например, чтобы показать её в корзине) — без создания платежа:
/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:
{
"orderId": "order-1002",
"amount": "49.90",
"currency": "USD",
"asset": "BTC",
"network": "BITCOIN"
}Получить платёж
/v1/payments/:idправо ключа: payments:readСписок платежей
/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Отменить платёж
/v1/payments/:id/cancelправо ключа: payments:writeТолько пока клиент не начал платить (статусы CREATED и PENDING). После отмены по тому же orderId можно создать новый платёж — так же, как после истечения срока.
Депозитные адреса
Для сервисов, где у клиента есть баланс или счёт: маркетплейсы и сервисы с предоплатой, SaaS и API с оплатой по использованию, хостинг и облака, рекламные кабинеты, кошельки и обменники, игры и цифровые товары. У каждого вашего клиента свой постоянный адрес. Клиент пополняет его когда угодно и на любую сумму, без счёта и срока. Каждое подтверждённое поступление зачисляется вам отдельно, с комиссией, и приходит webhook deposit.confirmed с customerId — по нему вы пополняете баланс клиента у себя.
/v1/deposit-addressesправо ключа: wallets:writecurl 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);
}/v1/deposit-addresses?customerId=право ключа: payments:read/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). Встраивать её во фрейм на своём сайте нельзя — страница это запрещает из соображений безопасности.
Если хотите свой дизайн оплаты, есть публичные данные без ключа (только то, что видит клиент):
/v1/public/checkout/:id/v1/public/checkout/:id/eventsПервый возвращает сумму, монету, сеть, адрес, контракт, статус, подтверждения и срок; второй — поток обновлений статуса (Server-Sent Events). Лимит — 60 запросов в минуту с одного IP.
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 получает все события; при создании можно выбрать нужные.
Выплаты
Выплата отправляет деньги с баланса проекта на любой адрес в поддерживаемой сети.
/v1/payoutsправо ключа: payouts:writecurl 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 — сколько ровно спишется с баланса. |
feeMode | add — комиссия сверху суммы; deduct — комиссия из суммы. Необязательно, по умолчанию add. |
externalId | Ваш уникальный идентификатор выплаты (повтор — 409 DUPLICATE_EXTERNAL_ID). |
metadata | Необязательно, ваши данные. |
Ответ 202 — выплата принята и выполняется асинхронно; о ходе приходят события 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. |
Расчёт выплаты заранее
Сколько получит получатель, какая будет комиссия, сколько спишется и хватит ли баланса — ничего не создаётся:
/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.
/v1/payouts/:idправо ключа: payouts:read/v1/payouts/:id/cancelправо ключа: payouts:writeОтменить можно до подписи (статусы до QUEUED включительно).
Балансы и курсы
/v1/wallets/balancesправо ключа: balances:readavailable — можно выводить; 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" }
]
}/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 или в приложении).
/v1/plansправо ключа: payments:write/v1/subscriptionsправо ключа: payments:write/v1/subscriptions/:idправо ключа: payments:read/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
/v1/webhooks/endpointsправо ключа: webhooks:manage/v1/webhooks/endpointsправо ключа: webhooks:manage/v1/webhooks/endpoints/:id/rotate-secretправо ключа: webhooks:manage/v1/webhooks/endpoints/:idправо ключа: webhooks:manage/v1/webhooks/deliveries?limit=50право ключа: webhooks:manage/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" }
}
}| HTTP | code | Когда |
|---|---|---|
| 400 | VALIDATION_ERROR | Неверные параметры. Подробности — в message и details. |
| 401 | UNAUTHENTICATED | Нет ключа, ключ неверный, отозван или истёк. |
| 403 | FORBIDDEN | Нет права у ключа, IP не разрешён, проект неактивен. |
| 404 | NOT_FOUND | Нет такого платежа/выплаты в вашем проекте. |
| 409 | CONFLICT | Конфликт состояния: см. details.code. |
| 429 | RATE_LIMITED | Слишком много запросов — повторите позже. |
| 500 | INTERNAL_ERROR | Ошибка на нашей стороне — повторите с тем же Idempotency-Key. |
| details.code | Что значит |
|---|---|
DUPLICATE_ORDER | По этому orderId уже есть активный или оплаченный платёж. |
IDEMPOTENCY_KEY_REUSED | Idempotency-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.
Перед запуском
- Секретный ключ только на сервере; права — минимально нужные; по возможности ограничение по IP.
- Каждый POST — с Idempotency-Key, построенным из вашего идентификатора.
- Подпись webhook проверяется по сырому телу; события старше 5 минут отклоняются.
- Каждое событие обрабатывается один раз (по
id); ответ 2xx — быстро, обработка — в очереди. - Товар выдаётся только по
payment.confirmed/payment.overpaid(или статусу из API), не по возврату клиента на сайт. - Для
payment.underpaidесть понятный сценарий для клиента. - Клиенту показывается
amountиз платежа (с комиссией), а сверка с заказом идёт поorderIdиpricing.amount, а не поamount. - Проверен реальный платёж на небольшую сумму в каждой сети, которую вы включаете.