Loading...
Loading...
Docs / Migration guide / BitPay
Use this page to replace BitPay's token-pairing, invoice naming, and SDK-heavy webhook assumptions with a cleaner Payvra payment path that you can verify in sandbox before any live cutover.
Keep the rollout narrow: switch payment creation first, prove the signed Payvra webhook handler in sandbox, and only then retire the BitPay-specific token and invoice ceremony.
What to prove before cutover
Naming reset
Credential simplification
Webhook portability
Cutover facts
Primary object
Authentication
Settlement posture
Go-live gate
BitPay's age shows up in its contract surface: invoice terminology, a pairing ceremony for credentials, and webhook verification that usually stays tied to the vendor SDK. Payvra keeps the same business outcome but exposes it through a cleaner payment-first API.
Treat this migration as a simplification of your payment codebase. The goal is not just lower fees; it is fewer provider-specific concepts in the runtime path that creates, settles, and reconciles customer payments.
Lower cost
Simpler auth
Standard signatures
No KYC stall before testing
| BitPay | Maps to | Payvra | Notes |
|---|---|---|---|
POST /invoices | POST /v1/payments | BitPay invoices become Payvra payments. Use `amount` instead of `price`. | |
GET /invoices/{id} | GET /v1/payments/{id} | Direct equivalent. | |
GET /invoices | GET /v1/payments | Payvra adds cursor pagination plus payment filters. | |
POST /refunds | POST /v1/refunds | Payvra supports full and partial refund execution directly. | |
POST /payouts | POST /v1/withdrawals | Payvra keeps payouts inside the same API surface without KYC-gated setup. | |
POST /tokens | POST /v1/api-keys | Replace BitPay token pairing with Payvra secret keys and explicit test/live prefixes. | |
- - | POST /v1/payment-links | Hosted payment links are built in. | |
- - | POST /v1/batch-payouts | Payvra adds multi-recipient payout batching. |
Create a payment
BitPay
// BitPayconst bitpay = new BitPaySDK.Client( "YOUR_CONFIG_PATH", BitPaySDK.Env.Prod);const invoice = await bitpay.CreateInvoice( new BitPaySDK.Models.Invoice(49.99, "USD"));invoice.setNotificationURL("https://yoursite.com/webhooks");invoice.setOrderId("order_123");invoice.setRedirectURL("https://yoursite.com/success");const result = await bitpay.createInvoice(invoice);// Redirect to: result.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
BitPay webhook
// BitPay webhook verificationapp.post("/webhooks", (req, res) => { // BitPay does not sign webhooks by default. const isValid = bitpay.verifyNotification(req.body); if (!isValid) { return res.status(401).send("Invalid"); } const { event } = req.body; if (event.name === "invoice_confirmed") { // 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);});| BitPay event | Maps to | Payvra | Notes |
|---|---|---|---|
IPN invoice_created | Webhook payment.created | Invoice created. | |
IPN invoice_paidInFull | Webhook payment.confirmed | Payment received and confirmed. | |
IPN invoice_confirmed | Webhook payment.completed | Payment settled and completed. | |
IPN invoice_completed | Webhook payment.completed | Payvra keeps the completed terminal event explicit. | |
IPN invoice_expired | Webhook payment.expired | Payment expired. | |
IPN invoice_declined | Webhook payment.failed | Payment failed. | |
IPN refund_created | Webhook refund.initiated | Refund processing started. | |
IPN refund_completed | Webhook refund.completed | Refund completed. |
Provision sandbox keys and freeze the scope of the cutover
sk_test_... and keep existing BitPay invoice traffic on BitPay until the replacement flow is fully proven. Do not combine this with a broader payment refactor.Replace the BitPay client and invoice call
metadata.# Remove BitPay SDKnpm uninstall bitpay-sdk# Install Payvra SDKnpm install payvraReplace webhook verification before changing paid-state logic
payment.completed or the other typed events your order system needs.Run a sandbox order from checkout to completion
Promote to live with the same request shape
Can I run BitPay and Payvra in parallel during cutover?
What happens to BitPay's invoice language in my code?
Can I keep the same webhook endpoint URL?
Does Payvra settle to fiat the same way BitPay does?
Cut the migration along one clean seam: replace invoice creation, prove the new signed webhook path, and then swap traffic over without dragging the old token and invoice concepts into the new runtime.