# NOWPayments-compatible API

> Switch from NOWPayments to blockwallets by changing the base URL and keys: supported endpoints, IPN callbacks and signatures, currency tickers and differences.
>
> HTML: https://blockwallets.io/docs/nowpayments?lang=en · 中文: https://blockwallets.io/docs/nowpayments.md

## NOWPayments-compatible API

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](https://blockwallets.io/docs/migrate?lang=en).

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

- Get a merchant account ([apply on the home page](https://blockwallets.io/#join)) 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.

- 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.

- 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.

| Before | After | Auth header | Webhook signature |
| --- | --- | --- | --- |
| `https://api.nowpayments.io/v1` | `https://blockwallets.io/nowpayments/v1` | `x-api-key` | `x-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

| Endpoint | Notes |
| --- | --- |
| `GET /status` | `{"message":"OK"}`, no key needed |
| `GET /currencies` · `/merchant/coins` · `/full-currencies` | Coins you can accept right now (tickers below); `fixed_rate` is ignored |
| `GET /min-amount` · `GET /estimate` | Minimums and estimates; USD pricing only (USDT / USDC count 1:1) |
| `POST /payment` | Create a payment; returns `pay_address` / `pay_amount` right away (white-label) |
| `POST /invoice` | Create an invoice; `invoice_url` is our hosted checkout where the customer picks coin and network |
| `POST /invoice-payment` | Pick the coin for an invoice, returns the payment |
| `GET /payment/:id` | Payment status |
| `GET /payment/` | List: `limit` (1–500) / `page` / `orderBy` / `dateFrom` / `dateTo` / `invoiceId`; `sortBy` only `created_at` |
| `POST /auth` | The 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.

```nodejs
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_status | When | IPN sent |
| --- | --- | --- |
| `waiting` | Waiting for payment (also an invoice with no coin picked yet) | — |
| `confirming` | Payment seen on chain, waiting for confirmations | — (visible when you query) |
| `finished` | Paid (including overpaid; `actually_paid` is what arrived) | ✓ |
| `partially_paid` | Short: 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 | ✓ |
| `expired` | Expired unpaid | ✓ |
| `refunded` | You 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

| Coin | Network | pay_currency | network | Minimum |
| --- | --- | --- | --- | --- |
| 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 |

#### Differences from NOWPayments

- `price_currency` is `usd` only (plus `usdt` / `usdc` at 1:1); other fiat returns 400.

- `pay_amount` (a fixed crypto amount), `payout_address` / `payout_currency` / `payout_extra_id` and `customer_email` are not supported and return 400.

- `is_fixed_rate` is accepted but has no effect: we always lock the rate (BTC / LTC etc. at the median of several exchanges, for 30 minutes). With `is_fee_paid_by_user` true the platform fee is added to `pay_amount` (the platform fee is 1%, lower at higher volume; see [pricing](https://blockwallets.io/pricing)).

- Payments are valid for up to 12 hours (NOWPayments: 7 days). `partially_paid_url` is stored and echoed back, but the checkout does not redirect to it.

- `payment_id` is a 10-digit number; for invoices created with `/invoice` the payment's `payment_id` equals the invoice `id`, and an invoice has exactly one payment (no `parent_payment_id` re-deposits).

- Only final-status and `partially_paid` IPNs are sent (table above), no `waiting` / `confirming` ones; `fee.serviceFee` is the platform fee (the amount received × the rate when the bill was created, 1% standard), other fees are 0.

### 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](https://blockwallets.io/docs/cryptomus?lang=en).
