Loading...
Loading...
Docs / Migration guide / NOWPayments
Use this page to run a controlled provider cutover: verify the pricing difference, map the request contract, replace the IPN logic with signed Payvra webhooks, and move traffic only after the sandbox path is repeatable.
The safe rollout is simple: keep existing NOWPayments invoices draining where they are, point new payment creation at Payvra in sandbox, prove the signed webhook path, and then switch only the API key prefix when you promote to live.
What to prove before cutover
Real fee delta
Webhook safety
Cutover reversibility
Cutover facts
Primary naming change
Auth change
Refund posture
Promotion rule
NOWPayments and Payvra both process crypto payments, but the operational contract is different. NOWPayments leans on weaker webhook verification and can hide exchange-rate spread inside settlement. Payvra exposes the rate and fee structure directly in the payment flow.
Treat this migration as a production hardening exercise, not just an endpoint rename. You are replacing the payment create path, the webhook contract, and the settlement assumptions merchants rely on when reconciling incoming volume.
Lower real cost
Stronger webhooks
Built-in refunds
Usable sandbox
| NOWPayments | Maps to | Payvra | Notes |
|---|---|---|---|
POST /v1/payment | POST /v1/payments | Use `amount` + `currency` instead of `price_amount` + `price_currency`. | |
GET /v1/payment/{id} | GET /v1/payments/{id} | Same read path with richer settlement and fee detail. | |
GET /v1/payment/ | GET /v1/payments | Payvra adds cursor pagination plus filters. | |
GET /v1/min-amount | - - | No minimum-amount endpoint is required on Payvra. | |
GET /v1/estimate | - - | Live exchange-rate information comes back in payment creation. | |
POST /v1/payout | POST /v1/withdrawals | Payvra uses "withdrawals" and returns chain execution detail. | |
- - | POST /v1/refunds | Payvra supports API refunds instead of manual refund handling. | |
- - | POST /v1/payment-links | Hosted payment links are built in. |
Create a payment
NOWPayments
// NOWPaymentsconst response = await fetch("https://api.nowpayments.io/v1/payment", { method: "POST", headers: { "x-api-key": "YOUR_NOWPAYMENTS_KEY", "Content-Type": "application/json", }, body: JSON.stringify({ price_amount: 49.99, price_currency: "usd", pay_currency: "sol", ipn_callback_url: "https://yoursite.com/webhooks", order_id: "order_123", }),});const payment = await response.json();// Redirect to: payment.invoice_urlPayvra
// Payvraimport Payvra from "payvra";const payvraSecretKey = process.env.PAYVRA_SECRET_KEY;if (!payvraSecretKey) { throw new Error("Set PAYVRA_SECRET_KEY before running this example.");}const payvra = new Payvra(payvraSecretKey);const payment = await payvra.payments.create({ amount: "49.99", currency: "USDC", chain: "solana", metadata: { orderId: "order_123" },});// Redirect to: payment.checkoutUrlVerify webhooks
NOWPayments IPN
// NOWPayments IPN handlerapp.post("/webhooks", (req, res) => { const hmac = crypto .createHmac("sha512", IPN_SECRET) .update(JSON.stringify(sortKeys(req.body))) .digest("hex"); if (hmac !== req.headers["x-nowpayments-sig"]) { return res.status(401).send("Invalid"); } const { payment_status, order_id } = req.body; if (payment_status === "finished") { // Mark order paid } res.sendStatus(200);});Payvra webhook
// Payvra webhook handlerapp.post("/webhooks", (req, res) => { const signature = req.headers["webhook-signature"]; const timestamp = req.headers["webhook-timestamp"]; const webhookId = req.headers["webhook-id"]; const signedContent = `${webhookId}.${timestamp}.${req.rawBody}`; const expected = crypto .createHmac("sha256", WEBHOOK_SECRET) .update(signedContent) .digest("base64"); if (signature !== `v1,${expected}`) { return res.status(401).send("Invalid"); } const { type, data } = req.body; if (type === "payment.completed") { // Mark order paid - data.metadata.orderId } res.sendStatus(200);});| NOWPayments status | Maps to | Payvra | Notes |
|---|---|---|---|
IPN waiting | Webhook payment.created | Payment created and awaiting deposit. | |
IPN confirming | Webhook payment.confirming | Deposit detected and waiting on chain confirmations. | |
IPN confirmed / sending | Webhook payment.confirmed | Payment confirmed on-chain. | |
IPN finished | Webhook payment.completed | Payment settled and completed. | |
IPN failed | Webhook payment.failed | Payment failed. | |
IPN expired | Webhook payment.expired | Payment window expired. |
Create sandbox credentials and keep live traffic where it is
sk_test_... and leave existing NOWPayments invoices running to completion. The first milestone is proving the new Payvra path, not forcing an all-at-once cutover.Install the Payvra SDK and replace payment creation
price_amount to amount, pay_currency to chain, and order_id to metadata.orderId.npm install payvraReplace the IPN verifier before you trust paid-state updates
Run one full sandbox order to completion
Promote to live by swapping only the credential prefix
Can I run both providers in parallel?
What changes for webhook handling?
What happens to exchange-rate markup?
Start in sandbox, prove the signed webhook path, and only then shift live traffic. The migration stays reversible if you treat it as a controlled cutover instead of a same-day rewrite.