@nowpaymentsio/nowpayments-sdk-nodejs
v0.2.1
Published
Scenario-first Node.js SDK for NOWPayments hosted checkout and crypto payments.
Readme
NOWPayments Node.js SDK
Scenario-first SDK for accepting crypto payments with NOWPayments from Node.js.
The main SDK scenario is: initialize SDK → create hosted checkout → redirect the customer → receive IPN/webhook status updates or poll payment status. The SDK keeps response bodies close to the NOWPayments API shape, so invoice_url, payment_id, payment_status, pay_address, pay_amount, and other familiar fields stay recognizable.
Features
- Node.js >= 18, ESM, no runtime dependencies.
- Hosted checkout flow through
createCheckout()/createPayment(). - Direct payment flow through
createDirectPayment()for in-page deposit address UI. - API-shaped responses with non-enumerable convenience aliases such as
checkout.checkoutUrlandpayment.id. - Stable SDK payment statuses:
pending,processing,paid,partially_paid,failed,refunded,expired,cancelled,unknown. - Payment status polling with
watchPaymentStatus()andonPaymentStatusChange(). - IPN/webhook signature verification with HMAC SHA-512 and recursively sorted payload keys.
- Consistent
configuration,validation,network,timeout,api, andunknownerror types.
Installation
npm install @nowpaymentsio/nowpayments-sdk-nodejsFor local development from this archive:
npm install /path/to/nowpayments-node-sdkQuick start: hosted checkout
Use this when you want to redirect the customer to the NOWPayments hosted checkout page.
import { NowPaymentsSDK } from '@nowpaymentsio/nowpayments-sdk-nodejs';
const sdk = new NowPaymentsSDK({
apiKey: process.env.NOWPAYMENTS_API_KEY,
ipnSecret: process.env.NOWPAYMENTS_IPN_SECRET,
ipnCallbackUrl: 'https://example.com/webhooks/nowpayments',
successUrl: 'https://example.com/payment/success',
cancelUrl: 'https://example.com/payment/cancel'
});
const checkout = await sdk.createCheckout({
amount: 49.99,
currency: 'usd',
payCurrency: 'btc', // optional — omit to let the customer choose on the invoice page
orderId: 'order-1001',
description: 'Demo order'
});
console.log(checkout.id); // invoice id
console.log(checkout.invoice_url); // redirect customer to this URLcreatePayment(input) and createHostedCheckout(input) are aliases for createCheckout(input).
Checkout is invoice-shaped. Under the hood, hosted checkout calls POST /v1/invoice. The returned object is invoice-shaped — it has invoice_url but no payment_status. A real payment appears only after the customer opens the invoice and sends funds. Track it via IPN/webhook or watchPaymentStatus().
Preflight when payCurrency is set:
GET /v1/estimate— converts the price amount to the crypto equivalentGET /v1/min-amount— checks the minimum payment amount- Throws
ValidationErrorwithcode: BELOW_MINIMUM_PAYMENT_AMOUNTif the estimate is below the minimum POST /v1/invoice
When payCurrency is omitted, all three preflight steps are skipped and only POST /v1/invoice is called.
Example response:
{
id: '4522625843',
order_id: 'order-1001',
order_description: 'Demo order',
price_amount: 49.99,
price_currency: 'usd',
pay_currency: 'btc',
ipn_callback_url: 'https://example.com/webhooks/nowpayments',
invoice_url: 'https://nowpayments.io/payment/?iid=4522625843',
success_url: 'https://example.com/payment/success',
cancel_url: 'https://example.com/payment/cancel',
created_at: '2026-01-01T00:00:00.000Z',
updated_at: '2026-01-01T00:00:00.000Z',
estimate: { currency_from: 'usd', amount_from: 49.99, currency_to: 'btc', estimated_amount: 0.00042 },
minimum: { currency_from: 'btc', min_amount: 0.0001 }
}⚠️ Non-enumerable aliases. Convenience aliases (
id,checkoutUrl,invoiceUrl,amount,order, etc.) are declared non-enumerable at runtime. They are accessible by direct property access (checkout.id,checkout.checkoutUrl), but do not appear inJSON.stringify(checkout)or{ ...checkout }. For serialization use the snake_case fields (checkout.invoice_url,checkout.price_amount, etc.).
Direct payment flow
Use this when you want to show the deposit address in your own UI instead of redirecting.
const payment = await sdk.createDirectPayment({
amount: 100,
currency: 'usd',
payCurrency: 'trx',
orderId: 'order-1002',
ipnCallbackUrl: 'https://example.com/webhooks/nowpayments'
});
console.log(payment.payment_id); // API field
console.log(payment.id); // non-enumerable alias
console.log(payment.pay_address); // deposit address
console.log(payment.deposit.memo); // required for XRP/XLM/MEMO coins
console.log(payment.status); // SDK status: 'pending'Authentication and automatic JWT refresh
Some NOWPayments endpoints, for example GET /v1/payment/, require both x-api-key and a Bearer JWT. POST /v1/auth returns this JWT from dashboard email + password; the token is short-lived, so the SDK now keeps this work under the hood.
Pass apiKey, email, and password to one SDK instance. The first Bearer-protected call automatically obtains a JWT, stores it in memory, reuses it while it is valid, and refreshes it when the token is expired or when the API responds with 401 Unauthorized.
const sdk = new NowPaymentsSDK({
apiKey: process.env.NOWPAYMENTS_API_KEY,
email: process.env.NOWPAYMENTS_EMAIL,
password: process.env.NOWPAYMENTS_PASSWORD
});
// No manual sdk.authenticate() and no manual jwtToken plumbing are needed.
const payments = await sdk.listPayments({
limit: 20,
page: 0,
sortBy: 'created_at',
orderBy: 'desc'
});
console.log(payments.data); // array of Payment objects
console.log(payments.pagesCount); // total pagesManual token mode is still supported for advanced use cases:
const sdk = new NowPaymentsSDK({
apiKey: process.env.NOWPAYMENTS_API_KEY,
jwtToken: process.env.NOWPAYMENTS_JWT_TOKEN
});
const payments = await sdk.listPayments({ limit: 20 });You can still call await sdk.authenticate() explicitly if you need to inspect sdk.jwtToken, but application code normally should not need to pass JWTs between SDK instances anymore.
sortBy is the field name (e.g. created_at, payment_id). orderBy is asc or desc only. Snake-case aliases (sort_by, order_by) are accepted and normalized automatically.
Webhooks / IPN
When creating a checkout or direct payment, pass ipnCallbackUrl. When the payment status changes, NOWPayments sends a POST request to that URL with a JSON body and the signature in the x-nowpayments-sig header.
// Express example
app.post('/webhooks/nowpayments', express.json(), (req, res) => {
try {
const event = sdk.parseWebhook(req.body, req.headers['x-nowpayments-sig']);
if (event.type === 'payment.status_changed') {
const payment = event.payment;
console.log(payment.payment_id, payment.payment_status, '->', payment.status);
// Update your order by payment.order_id or payment.purchase_id
}
res.json({ ok: true });
} catch (error) {
// Signature mismatch throws ValidationError with code INVALID_WEBHOOK_SIGNATURE
res.status(400).json({ ok: false });
}
});Manual signature verification:
const isValid = sdk.verifyWebhookSignature(
req.body,
req.headers['x-nowpayments-sig']
);The SDK signs JSON.stringify(sortObjectDeep(payload)) with HMAC SHA-512, matching NOWPayments IPN format.
Payment status polling
Single check:
const payment = await sdk.getPaymentStatus('5745459419');
console.log(payment.payment_status); // raw API status, e.g. 'waiting'
console.log(payment.status); // SDK status, e.g. 'pending'watchPaymentStatus — EventEmitter
const watcher = sdk.watchPaymentStatus('5745459419', {
intervalMs: 5000,
timeoutMs: 15 * 60 * 1000
});
watcher.on('change', ({ from, to, payment }) => {
console.log(`${from} → ${to}`, payment.id);
});
watcher.on('terminal', (payment) => {
console.log('final status:', payment.status);
});
watcher.on('timeout', ({ paymentId }) => {
console.warn('polling timed out for', paymentId);
});
watcher.on('error', console.error);Events emitted:
| Event | Payload | When |
|---|---|---|
| status | Payment | Every poll cycle |
| change | { from, to, payment } | Status changed from a known previous status |
| terminal | Payment | Terminal status reached |
| timeout | { paymentId } | timeoutMs elapsed without a terminal status |
| error | Error | Poll request failed |
onPaymentStatusChange — callback shorthand
const unsubscribe = sdk.onPaymentStatusChange('5745459419', ({ from, to, payment }) => {
console.log(`${from ?? 'initial'} → ${to}`);
if (to === 'paid') {
console.log('Payment complete!', payment.id);
unsubscribe(); // optional — watcher stops automatically on terminal
}
}, { intervalMs: 5000 });from is null when the payment is already in a terminal status on the first poll (no prior status seen). Otherwise it is the previous SDK status string.
The returned unsubscribe() function stops the underlying watcher. The watcher also stops automatically when a terminal status is reached.
Currencies
// All enabled currencies (GET /v1/full-currencies, filters enabled: true)
const currencies = await sdk.getAvailableCurrencies();
// All currencies including disabled ones
const all = await sdk.getAvailableCurrencies({ onlyEnabled: false });
// Currencies available for fixed-rate payments (GET /v1/currencies?fixed_rate=true)
const fixedRate = await sdk.getFixedRateCurrencies();
// Currencies enabled for this specific merchant account (GET /v1/merchant/coins)
const merchant = await sdk.getMerchantCurrencies();Estimate and minimum amount
const estimate = await sdk.estimatePrice({
amount: 100,
fromCurrency: 'usd',
toCurrency: 'btc'
});
console.log(estimate.estimated_amount); // BTC equivalent of $100
const minimum = await sdk.getMinimumPaymentAmount({
fromCurrency: 'btc'
});
console.log(minimum.min_amount); // minimum BTC payment amountPublic API
Scenario methods
| Method | Description |
|---|---|
| createCheckout(input) | Creates a hosted checkout via POST /v1/invoice. Returns invoice-shaped object with invoice_url. |
| createPayment(input) | Alias for createCheckout(input). |
| createHostedCheckout(input) | Alias for createCheckout(input). |
| createDirectPayment(input) | Calls POST /v1/payment. Returns payment object with deposit address. |
| watchPaymentStatus(paymentId, options) | Starts polling. Emits status, change, terminal, timeout, error. |
| onPaymentStatusChange(paymentId, callback, options) | Convenience subscription. Returns unsubscribe(). |
| parseWebhook(payload, signature, options) | Verifies HMAC signature and returns a normalized event. |
Endpoint coverage
| SDK method | NOWPayments endpoint |
|---|---|
| estimatePrice(input) | GET /v1/estimate |
| getMinimumPaymentAmount(input) | GET /v1/min-amount |
| getAvailableCurrencies(options) | GET /v1/full-currencies |
| getFixedRateCurrencies() | GET /v1/currencies?fixed_rate=true |
| getMerchantCurrencies() | GET /v1/merchant/coins |
| createInvoice(input) | POST /v1/invoice |
| createPaymentFromInvoice(input) | POST /v1/invoice-payment |
| createDirectPayment(input) | POST /v1/payment |
| refreshPaymentEstimate(paymentId) | POST /v1/payment/{id}/update-merchant-estimate |
| getPaymentStatus(paymentId) | GET /v1/payment/{payment_id} |
| listPayments(query) | GET /v1/payment/ |
| authenticate(credentials) | POST /v1/auth |
A raw client is available for direct API access:
// Escape hatch — raw API response, no normalization
await sdk.raw.createInvoice({ price_amount: 10, price_currency: 'usd' });
await sdk.raw.getBalance();Status mapping
| API status | SDK status |
|---|---|
| waiting | pending |
| confirming | processing |
| confirmed | processing |
| sending | processing |
| finished | paid |
| partially_paid | partially_paid |
| failed | failed |
| refunded | refunded |
| expired | expired |
| cancelled / canceled | cancelled |
| unknown / empty | unknown |
Terminal statuses (watcher stops automatically): paid, partially_paid, failed, refunded, expired, cancelled.
Errors
All SDK errors extend SDKError and serialize predictably via .toJSON():
try {
await sdk.createCheckout({ amount: 1, currency: 'usd', payCurrency: 'btc' });
} catch (error) {
if (error.name === 'ValidationError') {
console.log(error.type); // 'validation'
console.log(error.code); // e.g. 'BELOW_MINIMUM_PAYMENT_AMOUNT'
console.log(error.details); // { estimatedPayAmount, minimumPayAmount, ... }
}
if (error.name === 'APIError') {
console.log(error.httpStatus); // e.g. 401
console.log(error.requestId); // Cloudflare ray id if available
}
}| Error class | type | Typical cause |
|---|---|---|
| ConfigurationError | configuration | Missing API key or IPN secret |
| ValidationError | validation | Invalid input or amount below minimum |
| NetworkError | network | Fetch / DNS / connection failure |
| NetworkError | timeout | Request exceeded timeoutMs |
| APIError | api | NOWPayments returned a non-2xx response |
| SDKError | unknown | Unexpected error wrapped by SDK utilities |
Development
npm run build
npm testNo runtime dependencies. Tests use Node's built-in node:test with a mock fetch.
