Migrate from NOWPayments

Move from NOWPayments to CoinCircuit: endpoint mapping, webhook re-signing, and a step-by-step checklist.

Open the interactive version or read this guide as markdown.

CoinCircuit and NOWPayments share the same core model — you create a payment, your customer pays a crypto address, and you receive a signed webhook when it settles. The concepts map directly, so migration is mostly a find-and-replace of endpoints, field names, and the signing algorithm.

Concept mapping

NOWPayments CoinCircuit Notes
Payment Payment (checkout session) Same idea: one charge, one address, one webhook.
Invoice Invoice Itemized, multi-line billing with a pay link.
IPN callback Webhook Same delivery model; different signing algorithm.
IPN secret key Webhook secret Used to verify the signature on each event.
x-nowpayments-sig header x-coincircuit-signature header Header name changes; see signing below.
Primary balance Balance Per-asset balance credited after each payment.
Mass payouts Payouts Send crypto to any wallet in a single API call.
Custodial recurring payments Use invoices + webhooks to rebuild recurring billing.

Step 1 — Create payments

NOWPayments flow (before)

NOWPayments required two pre-flight calls — check the minimum amount, get an estimated price — before creating a payment.

GET /v1/min-amount?currency_from=btc&currency_to=eth
GET /v1/estimate?amount=100&currency_from=usd&currency_to=btc
POST /v1/payment
{
  "price_amount": 100,
  "price_currency": "usd",
  "pay_currency": "btc",
  "ipn_callback_url": "https://your-site.com/webhook",
  "order_id": "order_123",
  "order_description": "T-shirt"
}

CoinCircuit flow (after)

One call. CoinCircuit prices the payment and returns the crypto amount to collect in the payment response. You set the asset and chain, or omit them to let the customer pick at checkout.

POST https://api.coincircuit.io/api/v1/payments
x-api-key: sk_live_your_key
Content-Type: application/json
{
  "title": "T-shirt",
  "description": "Order #order_123",
  "amount": "100.00",
  "currency": "USD",
  "customer": { "email": "customer@example.com" },
  "webhookUrl": "https://your-site.com/webhook",
  "metadata": { "orderId": "order_123" }
}

The response includes checkoutUrl — redirect the customer there. CoinCircuit locks the crypto rate for the asset and chain, whether you set them or the customer picks at checkout.

Field mapping

NOWPayments field CoinCircuit field Notes
price_amount amount Pass as a string, e.g. "100.00".
price_currency currency NGN or USD.
pay_currency asset Optional. Omit to let the customer choose.
ipn_callback_url webhookUrl Per-payment webhook URL.
order_id / order_description metadata Store any key-value data you need in webhooks.
success_url successUrl Redirect after successful payment.
cancel_url cancelUrl Redirect after cancellation.

Step 2 — Replace invoices

NOWPayments (before)

POST /v1/invoice
{
  "price_amount": 500,
  "price_currency": "usd",
  "order_id": "inv_001",
  "order_description": "Consulting — June",
  "success_url": "https://your-site.com/thank-you",
  "ipn_callback_url": "https://your-site.com/webhook"
}

CoinCircuit (after)

POST https://api.coincircuit.io/api/v1/invoices
x-api-key: sk_live_your_key
Content-Type: application/json
{
  "description": "Consulting — June",
  "currency": "USD",
  "expiresAt": "2026-08-01T00:00:00Z",
  "customer": { "email": "client@example.com", "firstName": "Ada" },
  "reference": "inv_001",
  "successUrl": "https://your-site.com/thank-you",
  "items": [
    { "name": "Consulting", "quantity": 10, "unitPrice": 50 }
  ]
}

CoinCircuit invoices are line-item based. If NOWPayments was your invoicing layer, map each product or service into an items entry. The reference field replaces order_id.


Step 3 — Migrate webhooks

Both platforms POST a JSON body to your callback URL when a payment status changes. The main difference is the signature algorithm.

NOWPayments signature (before)

  1. Sort the request body by keys recursively.
  2. Serialize with JSON.stringify(sortedBody).
  3. HMAC-SHA512 the string with your IPN secret.
  4. Compare against the x-nowpayments-sig header.
const hmac = crypto.createHmac('sha512', ipnSecret);
hmac.update(JSON.stringify(sortObject(body)));
const expected = hmac.digest('hex');
const isValid = expected === req.headers['x-nowpayments-sig'];

CoinCircuit signature (after)

CoinCircuit signs the timestamped payload with HMAC-SHA256 and sends the result in the x-coincircuit-signature header as v1=<hex>, alongside an x-coincircuit-timestamp header. Rebuild the signed string as timestamp.payload, recompute the HMAC, strip the v1= prefix, and compare in constant time.

const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret, timestamp) {
  try {
    const signedPayload = timestamp ? `${timestamp}.${payload}` : payload;
    const expectedSignature = crypto.createHmac('sha256', secret).update(signedPayload, 'utf8').digest('hex');
    // The signature header is "v1=<hex>"; strip the version prefix before comparing.
    const received = signature.replace(/^v1=/, '');
    return crypto.timingSafeEqual(Buffer.from(received, 'hex'), Buffer.from(expectedSignature, 'hex'));
  } catch (_) {
    return false;
  }
}

// Express example
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const payload = req.body.toString('utf8');
  const signature = req.headers['x-coincircuit-signature'];
  const timestamp = req.headers['x-coincircuit-timestamp'];
  if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET, timestamp)) {
    return res.status(401).send('Invalid signature');
  }
  const event = JSON.parse(payload);
  // handle event
  res.sendStatus(200);
});

Use express.raw() (or equivalent) so you verify the exact bytes CoinCircuit signed, before any JSON parsing.

Webhook event mapping

NOWPayments status CoinCircuit event Trigger
waiting CoinCircuit omits pre-deposit waiting events.
confirming transaction.received Deposit detected on-chain, awaiting confirmation.
confirmed transaction.confirmed Transaction confirmed on the blockchain.
finished payment.completed Payment settled in full — fulfil the order on this.
partially_paid payment.partial Partial deposit received.
failed / expired payment.expired Session expired or payment could not be completed.
refunded refund.success Refund confirmed on-chain.

Fulfil orders on payment.completed (was finished in NOWPayments). Store your internal order ID in metadata when creating the payment — it comes back on every event.


Step 4 — Migrate payouts

NOWPayments mass payout (before)

NOWPayments batched payouts in a single /payout call with an array of withdrawals and a separate IPN secret.

CoinCircuit payout (after)

Save the recipient once, then initiate per payout. Check fees before sending.

POST https://api.coincircuit.io/api/v1/recipients
x-api-key: sk_live_your_key
Content-Type: application/json

{
  "type": "crypto_address",
  "label": "Vendor A",
  "details": { "chain": "base", "address": "0xRecipientAddress" }
}
GET https://api.coincircuit.io/api/v1/payouts/fees
x-api-key: sk_live_your_key
POST https://api.coincircuit.io/api/v1/payouts
x-api-key: sk_live_your_key
Content-Type: application/json

{
  "method": "crypto",
  "currency": "USDC",
  "amount": "500.00",
  "recipientId": "recipient-id-from-step-above"
}

Listen for payout.success or payout.failed webhooks to confirm the result.


Step 5 — Check payment status

NOWPayments (before)

GET /v1/payment/{paymentId}

CoinCircuit (after)

GET https://api.coincircuit.io/api/v1/payments/reference/{reference}
x-api-key: sk_live_your_key

Retrieve by the reference returned when you created the payment. To list all payments:

GET https://api.coincircuit.io/api/v1/payments?page=1&size=20

Step 6 — Test in the sandbox

CoinCircuit has a full sandbox environment on testnet. Point your integration at the sandbox base URL with test API keys before going live.

- https://api.coincircuit.io/api/v1
+ https://sandbox-api.coincircuit.io/api/v1

Use the Simulate payment button on the sandbox checkout page to trigger payment outcomes (full, partial, overpayment, AML failure) without moving real funds. See the Sandbox Environment guide for faucet links and testnet details.