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¤cy_to=eth
GET /v1/estimate?amount=100¤cy_from=usd¤cy_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)
- Sort the request body by keys recursively.
- Serialize with
JSON.stringify(sortedBody). - HMAC-SHA512 the string with your IPN secret.
- Compare against the
x-nowpayments-sigheader.
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.