API, совместимый с NOWPayments
Пути, заголовок x-api-key, поля запросов и ответов, статусы и подпись IPN x-nowpayments-sig соответствуют документации NOWPayments, поэтому существующему коду обычно нужны только новый базовый URL, API key и IPN secret. Переходите откуда-то ещё? См. обзор перехода.
Внутри это обычный счёт blockwallets: адреса для приёма ваши, страница оплаты — наша размещённая страница, а платёж засчитывается только после подтверждения в блокчейне и сверки со вторым узлом. Далее: три шага, затем что поддерживает каждый API и чем он отличается от оригинала — параметры, которые мы не реализовали, возвращают ошибку, а не молча игнорируются.
Три шага
- Получите аккаунт продавца (запросите доступ на главной странице) и добавьте свои домены в разделе «Домены сайта» на странице «Разработчикам › Интеграция и ключи» в кабинете. URL уведомлений (
ipn_callback_url) и URL перенаправления (success_url/cancel_url/redirect_url) должны находиться на этих доменах или на домене вашего URL уведомлений — утёкший ключ не сможет направить вебхуки в другое место. - Создайте совместимые учётные данные в кабинете продавца в разделе «Разработчикам › Интеграция и ключи», один набор для live и один для test: для NOWPayments — API key + IPN secret, для Coinbase Commerce — API key + webhook shared secret. Они показываются один раз; мы храним только хеши / шифртекст.
- Направьте код на базовый URL ниже и используйте новые ключи. Сначала запустите его в тестовых сетях с тестовыми учётными данными, затем переключитесь на live.
| До | После | Заголовок авторизации | Подпись вебхука |
|---|---|---|---|
https://api.nowpayments.io/v1 | https://blockwallets.io/nowpayments/v1 | x-api-key | x-nowpayments-sig (HMAC-SHA512) |
NOWPayments
- const NP = "https://api.nowpayments.io/v1";
+ const NP = "https://blockwallets.io/nowpayments/v1";
const res = await fetch(`${NP}/payment`, {
method: "POST",
headers: { "x-api-key": process.env.NOWPAYMENTS_API_KEY, "content-type": "application/json" },
body: JSON.stringify({
price_amount: 49.9, price_currency: "usd", pay_currency: "usdttrc20",
order_id: "A-1001", order_description: "Pro plan",
ipn_callback_url: "https://shop.example/ipn/nowpayments",
}),
});
const { payment_id, pay_address, pay_amount } = await res.json();Поддерживаемые эндпоинты
| Эндпоинт | Примечания |
|---|---|
GET /status | {"message":"OK"}, ключ не нужен |
GET /currencies · /merchant/coins · /full-currencies | Монеты, которые можно принимать прямо сейчас (тикеры ниже); fixed_rate игнорируется |
GET /min-amount · GET /estimate | Минимумы и оценки; цены только в USD (USDT / USDC считаются 1:1) |
POST /payment | Создать платёж; сразу возвращает pay_address / pay_amount (white-label) |
POST /invoice | Создать инвойс; invoice_url — наша размещённая страница оплаты, где клиент выбирает монету и сеть |
POST /invoice-payment | Выбрать монету для инвойса, возвращает платёж |
GET /payment/:id | Статус платежа |
GET /payment/ | Список: limit (1–500) / page / orderBy / dateFrom / dateTo / invoiceId; sortBy только created_at |
POST /auth | Для списка нужен только x-api-key, JWT не требуется; здесь возвращается непроверяемый токен-заглушка, чтобы код вида «войти, затем получить список» продолжал работать (email / пароль не проверяются) |
Недоступно: выплаты, конвертации, субпартнёры с кастодиальным хранением, подписки, update-merchant-estimate, фиатные платежи. Эти пути возвращают 404.
IPN-уведомления
Когда платёж достигает конечного статуса, мы отправляем POST на ipn_callback_url, указанный при создании, с x-nowpayments-sig = HMAC-SHA512(IPN secret, JSON, рекурсивно отсортированный по ключам) — в точности как в коде проверки NOWPayments. Тело, которое мы отправляем, уже представляет собой отсортированный компактный JSON, поэтому HMAC по исходному телу тоже подходит (и это самый надёжный вариант в PHP / Python, у которых повторное кодирование очень малых десятичных чисел может отличаться от JS). Неудачные уведомления повторяются через 1 мин, 5 мин, 30 мин, … — всего 8 попыток.
import crypto from "node:crypto";
// Same as the NOWPayments docs (our body is already sorted, so hashing the raw body works too)
function sortObject(obj) {
return Object.keys(obj).sort().reduce((r, k) => {
r[k] = obj[k] && typeof obj[k] === "object" ? sortObject(obj[k]) : obj[k];
return r;
}, {});
}
function verifyIpn(params, sig, ipnSecret) {
const hmac = crypto.createHmac("sha512", ipnSecret).update(JSON.stringify(sortObject(params))).digest("hex");
return crypto.timingSafeEqual(Buffer.from(hmac), Buffer.from(sig || ""));
}
// verifyIpn(req.body, req.headers["x-nowpayments-sig"], process.env.NOWPAYMENTS_IPN_SECRET)$raw = file_get_contents('php://input');
$data = json_decode($raw, true);
tksort($data); // the recursive ksort from the NOWPayments docs
$hmac = hash_hmac('sha512', json_encode($data, JSON_UNESCAPED_SLASHES), trim($ipnSecret));
$ok = hash_equals($hmac, $_SERVER['HTTP_X_NOWPAYMENTS_SIG'] ?? '');| payment_status | Когда | Отправляется ли IPN |
|---|---|---|
waiting | Ожидает оплаты (также инвойс, в котором монета ещё не выбрана) | — |
confirming | Платёж виден в блокчейне, ждём подтверждений | — (виден при запросе) |
finished | Оплачен (в том числе с переплатой; actually_paid — сумма, которая пришла) | ✓ |
partially_paid | Недоплата: клиент ещё может доплатить на тот же адрес до истечения срока (отправляется один раз при первой недоплате), и ещё раз, если к истечению всё ещё не хватает | ✓ |
expired | Истёк без оплаты | ✓ |
refunded | Вы вернули весь платёж из кабинета, и возврат отправлен | — (виден при запросе) |
Статусы confirmed, sending и failed не встречаются: после подтверждения платёж получает finished и зачисляется на ваш баланс; ничего никуда не пересылается.
Тикеры монет
| Монета | Сеть | pay_currency | network | Минимум |
|---|---|---|---|---|
| USDT | TRON | usdttrc20 | trx | $1 |
| USDT | BNB Chain | usdtbsc usdtbep20 | bsc | $1 |
| USDC | BNB Chain | usdcbsc usdcbep20 | bsc | $1 |
| USDT | Ethereum | usdterc20 | eth | $5 |
| USDC | Ethereum | usdc usdcerc20 | eth | $5 |
| USDC | Base | usdcbase | base | $1 |
| USDT | Polygon | usdtmatic usdtpolygon | matic | $1 |
| USDC | Polygon | usdcmatic usdcpolygon | matic | $1 |
| USDT | Arbitrum | usdtarb | arbitrum | $1 |
| USDC | Arbitrum | usdcarb | arbitrum | $1 |
| USDT | Optimism | usdtop | op | $1 |
| USDC | Optimism | usdcop | op | $1 |
| USDT | Avalanche | usdtarc20 usdtavax / usdtavaxc | avaxc | $1 |
| USDC | Avalanche | usdcarc20 usdcavax / usdcavaxc | avaxc | $1 |
| USDT | X Layer | usdtxlayer | xlayer | $1 |
| USDC | X Layer | usdcxlayer | xlayer | $1 |
| USD1 | BNB Chain | usd1bsc | bsc | $1 |
| FDUSD | BNB Chain | fdusdbsc | bsc | $1 |
| DAI | Ethereum | dai daierc20 | eth | $5 |
| DAI | Arbitrum | daiarb | arbitrum | $1 |
| DAI | Base | daibase | base | $1 |
| BTC | Bitcoin | btc | btc | $5 |
| LTC | Litecoin | ltc | ltc | $1 |
| DOGE | Dogecoin | doge | doge | $1 |
| BCH | Bitcoin Cash | bch | bch | $1 |
| TRX | TRON | trx | trx | $1 |
| BNB | BNB Chain | bnbbsc bnb | bsc | $1 |
| ETH | Ethereum | eth | eth | $5 |
| ETH | Base | ethbase | base | $1 |
| ETH | Arbitrum | etharb | arbitrum | $1 |
| ETH | Optimism | ethop | op | $1 |
| POL | Polygon | maticmainnet pol / polmainnet / matic | matic | $1 |
| AVAX | Avalanche | avaxc avax | avaxc | $1 |
| OKB | X Layer | okbxlayer | xlayer | $1 |
| USDT | Solana | usdtsol | sol | $1 |
| USDC | Solana | usdcsol | sol | $1 |
| SOL | Solana | sol | sol | $1 |
| USDT | TON | usdtton | ton | $1 |
| TON | TON | ton | ton | $1 |
Отличия от NOWPayments
price_currency— толькоusd(плюсusdt/usdc1:1); другие фиатные валюты возвращают 400.pay_amount(фиксированная сумма в криптовалюте),payout_address/payout_currency/payout_extra_idиcustomer_emailне поддерживаются и возвращают 400.is_fixed_rateпринимается, но ни на что не влияет: курс мы фиксируем всегда (BTC / LTC и др. — по медиане нескольких бирж, на 30 минут). Еслиis_fee_paid_by_userравен true, комиссия платформы добавляется кpay_amount(комиссия платформы 1%, при больших оборотах ниже; см. цены).- Платежи действительны до 12 часов (NOWPayments: 7 дней).
partially_paid_urlсохраняется и возвращается как есть, но страница оплаты на него не перенаправляет. payment_id— 10-значное число; для инвойсов, созданных через/invoice,payment_idплатежа равенidинвойса, и у инвойса ровно один платёж (повторных пополнений сparent_payment_idнет).- Отправляются только IPN с конечными статусами и
partially_paid(таблица выше), безwaiting/confirming;fee.serviceFee— комиссия платформы (полученная сумма × ставка на момент создания счёта, стандартная 1%), остальные комиссии равны 0.
Тестовый режим
Charge и платежи, созданные с тестовыми учётными данными, работают в тестовой сети каждого блокчейна, а их вебхуки подписываются тестовым IPN secret / shared secret — полностью отдельно от live: тестовый ключ не видит live-платежи.
Всё остальное (страница оплаты, подтверждения, сети и минимумы, возвраты) работает так же, как в основной документации.