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-код, офіційний контракт токена, таймер, кроки підтвердження. Статус оновлюється автоматично. Відкрийте її в новій вкладці або надішліть клієнтові посилання (у месенджері, електронною поштою). Вбудовувати її у фрейм на своєму сайті не можна — сторінка це забороняє з міркувань безпеки.

Якщо хочете власний дизайн оплати, є публічні дані без ключа (лише те, що бачить клієнт):

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 (електронною поштою або в застосунку).

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. Перевірено реальний платіж на невелику суму в кожній мережі, яку ви вмикаєте.