Loading...
Loading...
Docs / Migration guide / CoinGate
Use this page to switch from CoinGate's order and callback model to Payvra's payment contract, while making the pricing, settlement, and webhook behavior explicit before any live traffic moves.
Keep the migration narrow and reversible: map the order-create path to Payvra payments, replace the callback verifier, prove one sandbox run end to end, and only then shift production traffic.
What to prove before cutover
Pricing transparency
Webhook hardening
Operational continuity
Cutover facts
Primary naming change
Settlement target
Hosted checkout
Promotion rule
CoinGate and Payvra both let you accept crypto payments, but the production contract differs. CoinGate wraps orders around its own exchange path, while Payvra keeps the payment surface cleaner and makes settlement behavior easier to reason about.
Run this migration as a replacement of assumptions as much as code. The team needs to know what happens to order creation, callback verification, and settlement visibility before moving live merchant traffic.
Lower total cost
Clearer security model
Broader chain posture
Faster sandbox loop
| CoinGate | Maps to | Payvra | Notes |
|---|---|---|---|
POST /v2/orders | POST /v1/payments | CoinGate orders become Payvra payments. Use `amount` + `currency` instead of `price_amount` + `price_currency`. | |
GET /v2/orders/{id} | GET /v1/payments/{id} | Direct equivalent with richer settlement detail. | |
GET /v2/orders | GET /v1/payments | Payvra uses cursor pagination instead of page-number traversal. | |
POST /v2/orders/{id}/checkout | POST /v1/payment-links | Use Payvra payment links for hosted checkout equivalents. | |
GET /v2/ledger/account | GET /v1/ledger/balances | Payvra exposes balance detail by settlement currency and chain. | |
POST /v2/withdrawals | POST /v1/withdrawals | Direct equivalent with on-chain execution detail. | |
- - | POST /v1/refunds | Refunds move from manual operations into a first-class API path. | |
- - | POST /v1/batch-payouts | Payvra adds multi-recipient payouts. |
Create a payment
CoinGate
// CoinGateconst response = await fetch("https://api.coingate.com/v2/orders", { method: "POST", headers: { Authorization: "Token YOUR_COINGATE_TOKEN", "Content-Type": "application/json", }, body: JSON.stringify({ order_id: "order_123", price_amount: 49.99, price_currency: "USD", receive_currency: "USDT", callback_url: "https://yoursite.com/webhooks", success_url: "https://yoursite.com/success", cancel_url: "https://yoursite.com/cancel", }),});const order = await response.json();// Redirect to: order.payment_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.checkoutUrlHandle webhooks
CoinGate callback
// CoinGate callback handlerapp.post("/webhooks", (req, res) => { const { status, order_id, token } = req.body; if (token !== process.env.COINGATE_CALLBACK_TOKEN) { return res.status(401).send("Invalid"); } if (status === "paid") { // 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);});| CoinGate status | Maps to | Payvra | Notes |
|---|---|---|---|
Callback new | Webhook payment.created | Payment created. | |
Callback pending | Webhook payment.confirming | Deposit detected. | |
Callback confirming | Webhook payment.confirming | Payment still waiting on chain confirmation. | |
Callback paid | Webhook payment.confirmed | Payment confirmed on-chain. | |
Callback invalid | Webhook payment.failed | Payment failed. | |
Callback expired | Webhook payment.expired | Payment expired. | |
Callback canceled | Webhook payment.failed | Canceled and invalid both collapse into failed-state handling. | |
Callback refunded | Webhook refund.completed | Refund completed. |
Start with sandbox credentials and keep current orders draining
sk_test_... credentials and leave open CoinGate orders untouched. The first objective is to prove the Payvra path, not to re-open already-issued orders.Replace order creation with Payvra payment creation
price_amount and price_currency into the Payvra payment request, and move your existing order reference into metadata.orderId.npm install payvraReplace the callback verifier before trusting order completion
Run one sandbox payment to the final completed state
Promote to live with the same route shape
What changes about hosted checkout?
What happens to CoinGate's fiat-conversion expectations?
Can I migrate in parallel?
Map the order flow to Payvra payments, prove the signed webhook path in sandbox, and promote to live only after the new settlement path is easy for operators to explain and reconcile.