NOWPayments-compatible API

Open in ChatGPTOpen in Claude

Paths, the x-api-key header, request and response fields, statuses and the x-nowpayments-sig IPN signature follow NOWPayments’ docs, so existing code usually needs only a new base URL, API key and IPN secret. Coming from somewhere else? See the migration overview.

Underneath it is a regular blockwallets bill: the receiving addresses are yours, the payment page is our hosted checkout, and a payment only counts once it is confirmed on chain and cross-checked against a second node. Below: three steps, then what each API supports and how it differs from the original — parameters we don't implement return an error instead of being silently ignored.

Three steps

  1. Get a merchant account (apply on the home page) and add your domains under “Website domains” on “Developers › Integration & keys” in the dashboard. Callback URLs (ipn_callback_url) and redirect URLs (success_url / cancel_url / redirect_url) must be on those domains or your callback URL's domain — a leaked key can't point webhooks elsewhere.
  2. Generate compatible credentials in the merchant dashboard under “Developers › Integration & keys”, one set for live and one for test: NOWPayments gets an API key + IPN secret, Coinbase Commerce an API key + webhook shared secret. They are shown once; we only keep hashes / ciphertext.
  3. Point your code at the base URL below and use the new keys. Run it on testnets with the test credentials first, then switch to live.
BeforeAfterAuth headerWebhook signature
https://api.nowpayments.io/v1https://blockwallets.io/nowpayments/v1x-api-keyx-nowpayments-sig (HMAC-SHA512)

NOWPayments

JavaScript
- 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();

Supported endpoints

EndpointNotes
GET /status{"message":"OK"}, no key needed
GET /currencies · /merchant/coins · /full-currenciesCoins you can accept right now (tickers below); fixed_rate is ignored
GET /min-amount · GET /estimateMinimums and estimates; USD pricing only (USDT / USDC count 1:1)
POST /paymentCreate a payment; returns pay_address / pay_amount right away (white-label)
POST /invoiceCreate an invoice; invoice_url is our hosted checkout where the customer picks coin and network
POST /invoice-paymentPick the coin for an invoice, returns the payment
GET /payment/:idPayment status
GET /payment/List: limit (1–500) / page / orderBy / dateFrom / dateTo / invoiceId; sortBy only created_at
POST /authThe list only needs x-api-key, no JWT; this returns an unchecked placeholder token so “log in, then list” code keeps working (email / password are not checked)

Not available: payouts, conversions, custody sub-partners, subscriptions, update-merchant-estimate, fiat payments. Those paths return 404.

IPN callbacks

When a payment reaches a final status we POST to the ipn_callback_url given at creation, with x-nowpayments-sig = HMAC-SHA512(IPN secret, the JSON sorted by key recursively) — exactly NOWPayments' verification code. The body we send is already sorted, compact JSON, so an HMAC over the raw body works too (and is the safest choice in PHP / Python, whose re-encoding of very small decimals can differ from JS). Failed callbacks are retried after 1 min, 5 min, 30 min, … — 8 attempts in total.

Node.js
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)
PHP
$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_statusWhenIPN sent
waitingWaiting for payment (also an invoice with no coin picked yet)—
confirmingPayment seen on chain, waiting for confirmations— (visible when you query)
finishedPaid (including overpaid; actually_paid is what arrived)✓
partially_paidShort: the customer can still top up to the same address before it expires (sent once at the first short payment), and again if still short at expiry✓
expiredExpired unpaid✓
refundedYou refunded the whole payment from the dashboard and it was sent— (visible when you query)

confirmed, sending and failed never appear: once confirmed it is finished and credited to your balance; nothing is forwarded.

Currency tickers

CoinNetworkpay_currencynetworkMinimum
USDTTRONusdttrc20trx$1
USDTBNB Chainusdtbsc usdtbep20bsc$1
USDCBNB Chainusdcbsc usdcbep20bsc$1
USDTEthereumusdterc20eth$5
USDCEthereumusdc usdcerc20eth$5
USDCBaseusdcbasebase$1
USDTPolygonusdtmatic usdtpolygonmatic$1
USDCPolygonusdcmatic usdcpolygonmatic$1
USDTArbitrumusdtarbarbitrum$1
USDCArbitrumusdcarbarbitrum$1
USDTOptimismusdtopop$1
USDCOptimismusdcopop$1
USDTAvalancheusdtarc20 usdtavax / usdtavaxcavaxc$1
USDCAvalancheusdcarc20 usdcavax / usdcavaxcavaxc$1
USDTX Layerusdtxlayerxlayer$1
USDCX Layerusdcxlayerxlayer$1
USD1BNB Chainusd1bscbsc$1
FDUSDBNB Chainfdusdbscbsc$1
DAIEthereumdai daierc20eth$5
DAIArbitrumdaiarbarbitrum$1
DAIBasedaibasebase$1
BTCBitcoinbtcbtc$5
LTCLitecoinltcltc$1
DOGEDogecoindogedoge$1
BCHBitcoin Cashbchbch$1
TRXTRONtrxtrx$1
BNBBNB Chainbnbbsc bnbbsc$1
ETHEthereumetheth$5
ETHBaseethbasebase$1
ETHArbitrumetharbarbitrum$1
ETHOptimismethopop$1
POLPolygonmaticmainnet pol / polmainnet / maticmatic$1
AVAXAvalancheavaxc avaxavaxc$1
OKBX Layerokbxlayerxlayer$1
USDTSolanausdtsolsol$1
USDCSolanausdcsolsol$1
SOLSolanasolsol$1
USDTTONusdttonton$1
TONTONtonton$1

Differences from NOWPayments

Test mode

Charges and payments created with test credentials run on each chain's testnet and their webhooks are signed with the test IPN secret / shared secret — fully separate from live: a test key can't see live payments.

Everything else (checkout, confirmations, networks and minimums, refunds) works as in the main docs.