NOWPayments Integration: Accept Cryptocurrencies Under Key

Without IPN signature verification, clients lose up to 15% of revenue — fake webhooks with `finished` status drain inventory without real payment. Over three years, we intercepted 27 such attacks on client projects. With our crypto integration under key, clients typically save $2,500 per month in pr

Blockchain Development Services

Frequently Asked Questions

Latest works

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1450
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1308
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    1003
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1269
  • image_logo-advance_0.webp
    B2B Advance company logo design
    717
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    1009

Without IPN signature verification, clients lose up to 15% of revenue — fake webhooks with finished status drain inventory without real payment. Over three years, we intercepted 27 such attacks on client projects. With our crypto integration under key, clients typically save $2,500 per month in prevented fraud. Turnkey NOWPayments integration in 2–3 days with mandatory HMAC verification is not an option but a necessity. Our stack: TypeScript, ethers.js/viem, PostgreSQL. With 10+ years of blockchain development experience, we have implemented over 50 crypto gateways. Our turnkey solution is 2 times more reliable than standard integrations — we guarantee stable operation and post-launch support.

Why NOWPayments integration is trickier than it seems

NOWPayments is a hosted payment gateway that handles address generation, blockchain monitoring, and conversion. But without proper API handling, you get a vulnerable system. Key complexities:

  • Choosing pay_currency — not just a ticker, but a ticker in a specific network: usdterc20, usdttrc20, usdtbsc. We always fetch the current currencies via /v1/currencies, never hardcode.
  • Partial payments — the user may send less than required. The partially_paid status requires manual decision: accept, request extra, or cancel.
  • Webhook idempotency — NOWPayments retries on errors. Without an idempotency key, you risk double crediting. On average, 97% of payments are processed without issues after implementing idempotency.

How we implement turnkey integration

Our process includes five stages.

Analysis and design

  • Determine necessary currencies and networks.
  • Design architecture: where to store payment_id, how to handle statuses.

Implementation

  • Write code in TypeScript using ethers.js or viem.
  • Implement HMAC-SHA512 verification (see code below).
  • Add support for partial payments and automatic exchange rate updates. We use retry with exponential backoff, maximum 3 retries.

Testing

Use NOWPayments sandbox with separate keys. Locally run a webhook receiver via ngrok. Check all statuses: waiting, confirming, finished, partially_paid. On average, we find and fix 3–5 bugs during testing.

Deployment and monitoring

  • Set up alerts for critical statuses (partial payments, errors).
  • Log all raw webhooks for debugging.
  • Add polling as fallback if webhook doesn't arrive within 30 minutes.

Documentation and training

  • Deliver API description, status handling scheme.
  • Consult the team on typical scenarios.

Payment flow

1. Your backend → POST /v1/payment → NOWPayments Receive: payment_id, pay_address, pay_amount, expiration_estimate_date 2. Show user QR code and payment address 3. NOWPayments monitors blockchain 4. NOWPayments → IPN Webhook → Your backend payment_status: waiting → confirming → finished/failed/expired 5. Your backend verifies signature, updates order 

Creating a payment

interface CreatePaymentRequest { price_amount: number; // amount in price_currency price_currency: string; // 'usd', 'eur' pay_currency: string; // 'btc', 'eth', 'usdterc20', 'usdttrc20' order_id: string; // your internal ID order_description?: string; ipn_callback_url: string; // URL for webhook success_url?: string; cancel_url?: string; } async function createPayment( orderData: CreatePaymentRequest ): Promise<NOWPaymentsPayment> { const response = await fetch('https://api.nowpayments.io/v1/payment', { method: 'POST', headers: { 'x-api-key': process.env.NOWPAYMENTS_API_KEY!, 'Content-Type': 'application/json', }, body: JSON.stringify(orderData), }); if (!response.ok) { const error = await response.json(); throw new Error(`NOWPayments error: ${error.message}`); } return response.json(); } 

Note: pay_currency is not just a coin name, but a specific coin in a specific network. usdterc20 — USDT on Ethereum, usdttrc20 — USDT on TRON, usdtbsc — on BNB Chain. Always get the current pay_currency list from /v1/currencies, never hardcode.

Ensuring IPN signature verification

NOWPayments signs each webhook with HMAC-SHA512 using your IPN secret (separate from API key). Without signature verification, an attacker can send a fake finished status and get the product for free.

import * as crypto from 'crypto'; function verifyIPNSignature( payload: string, // raw request body, not parsed receivedSignature: string, ipnSecret: string ): boolean { const hmac = crypto.createHmac('sha512', ipnSecret); hmac.update(payload); const computedSignature = hmac.digest('hex'); // Constant-time comparison — protection against timing attacks return crypto.timingSafeEqual( Buffer.from(computedSignature), Buffer.from(receivedSignature) ); } // Express middleware app.post('/webhook/nowpayments', express.raw({ type: 'application/json' }), // raw body! (req, res) => { const signature = req.headers['x-nowpayments-sig'] as string; if (!verifyIPNSignature( req.body.toString(), signature, process.env.NOWPAYMENTS_IPN_SECRET! )) { return res.status(401).json({ error: 'Invalid signature' }); } const payment = JSON.parse(req.body.toString()); handlePaymentUpdate(payment); res.status(200).json({ ok: true }); } ); 

Important: For HMAC verification you need the raw body. If express.json() middleware already parsed the body — the signature won't match due to possible JSON serialization differences. Use express.raw() for the webhook endpoint.

How to protect against fake webhooks?

Besides signature verification, you can check the sender IP addresses. NOWPayments publishes its IP list in the documentation. But the primary protection remains HMAC. Additionally:

  • Store payment_id and don't process duplicate webhooks with the same status if the payment is already completed.
  • Use an idempotency key (e.g., based on payment_id and status).

Statuses and idempotent handling

NOWPayments sends a webhook on every status change. The same statuses may arrive multiple times (retry when your server is unreachable).

Status Description Action
waiting Awaiting funds Show address and QR code
confirming Transaction found, awaiting confirmations Update UI, don't credit
confirmed Confirmed (enough network confirmations) Can prepare order
sending NOWPayments converts and sends Wait for finish
partially_paid Partial amount received Notify admin, request top-up
finished Successfully completed Credit funds
failed Processing error Refund or request retry
expired Payment time expired Cancel order
refunded Refund issued Update status
type PaymentStatus = | 'waiting' // awaiting payment | 'confirming' // transaction found, waiting for confirmations | 'confirmed' // confirmed | 'sending' // NOWPayments converts and sends | 'partially_paid' // partial amount received | 'finished' // successfully completed | 'failed' // error | 'refunded' // refund | 'expired'; // wait time expired async function handlePaymentUpdate(data: IPNPayload): Promise<void> { // Idempotency: check if already processed const existing = await db.query( 'SELECT status FROM payments WHERE nowpayments_id = $1', [data.payment_id] ); if (existing.rows[0]?.status === 'finished') { return; // Already processed, ignore } await db.query( `UPDATE payments SET status = $1, updated_at = NOW(), raw_webhook = $2 WHERE nowpayments_id = $3`, [data.payment_status, JSON.stringify(data), data.payment_id] ); if (data.payment_status === 'finished') { await fulfillOrder(data.order_id); } if (data.payment_status === 'partially_paid') { await notifyPartialPayment(data.order_id, data.actually_paid, data.pay_amount); } } 

Sandbox for testing

NOWPayments provides sandbox: https://api-sandbox.nowpayments.io. Separate API keys, test transactions don't hit real networks. For local webhook testing — ngrok or Cloudflare Tunnel to get a public URL.

# Test via curl curl -X POST https://api-sandbox.nowpayments.io/v1/payment \ -H "x-api-key: YOUR_SANDBOX_KEY" \ -H "Content-Type: application/json" \ -d '{"price_amount":10,"price_currency":"usd","pay_currency":"btc","order_id":"test-001","ipn_callback_url":"https://your-ngrok-url/webhook/nowpayments"}' 

What's included in the work

When you order a turnkey NOWPayments integration, we provide:

  • Ready TypeScript code with signature verification and status handling.
  • Integration with your database (PostgreSQL, MySQL, MongoDB).
  • Setup of sandbox testing and webhook logging.
  • Deployment to production (AWS, DigitalOcean, any VPS).
  • API documentation and error handling.
  • 30 days of support after launch.

Additional: what you should implement

  • Polling as fallback: if no webhook arrives within 30 minutes after payment creation — poll /v1/payment/{id} yourself.
  • Store NOWPayments payment_id in your orders table — needed for reconciliation.
  • Log all raw webhook payloads — helps with debugging and disputes.
  • Alert on partially_paid — requires manual decision: accept, request top-up, or refund.
Tool Purpose Effect
NOWPayments sandbox Safe testing Reduces debugging time by 40%
ngrok / Cloudflare Tunnel Local webhook endpoint Allows debugging verification without deployment
Polling Fallback for lost webhook Guarantees 99.9% payment processing

Our gateway reliability guarantee ensures that integration is 3 times faster than in-house development. Contact us to evaluate your project — we'll determine the scope and timeline individually. Order integration and get ready code in 2 days.