BitPay Integration: Accept Crypto Payments via API

Integrating a crypto payment gateway into e-commerce is more than just plugging in an SDK. We often encounter situations where after creating an invoice, the webhook doesn't arrive or statuses get duplicated, and the accounting department can't reconcile. BitPay solves these issues, but requires pro

Blockchain Development Services

Frequently Asked Questions

Latest works

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1450
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1308
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    1003
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1269
  • image_logo-advance_0.webp
    B2B Advance company logo design
    717
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    1009

Integrating a crypto payment gateway into e-commerce is more than just plugging in an SDK. We often encounter situations where after creating an invoice, the webhook doesn't arrive or statuses get duplicated, and the accounting department can't reconcile. BitPay solves these issues, but requires proper status handling and idempotency.

We integrate BitPay into your business: configure acceptance of BTC, ETH, USDC, USDT via Ethereum, Polygon, Arbitrum, Base. BitPay takes care of legal documentation and fiat conversion. Our team has completed 25+ cryptocurrency integrations. BitPay is 3 times faster to set up than a custom gateway. We'll evaluate your project for free — contact us.

How Does the Payment Flow Work?

The API works through invoices: your backend creates an invoice on BitPay, gets a URL to redirect the user, BitPay accepts payment and notifies your webhook. Full documentation is available in BitPay API Reference.

Why Is ECDSA Authentication More Complex Than an API Key?

BitPay signs requests with a private key instead of a static token. This is more secure, but requires generating an ECDSA keypair and registering the public key as a token.

For most integrations, it's easier to use the official BitPay SDK (Node.js, PHP, Python, Ruby, Java) — it encapsulates request signing.

const BitPaySDK = require('bitpay-sdk'); const fs = require('fs'); async function setupBitPay() { const client = new BitPaySDK.Client( null, BitPaySDK.Env.Prod, fs.readFileSync('./private.key', 'utf8') ); await client.authorizeClient('your-pairing-code'); return client; } 

Creating an Invoice

const BitPaySDK = require('bitpay-sdk'); async function createInvoice(orderId, amount, currency = 'USD') { const invoice = new BitPaySDK.Models.Invoice(amount, currency); invoice.orderId = orderId; invoice.notificationUrl = `https://yourapp.com/webhooks/bitpay`; invoice.redirectUrl = `https://yourapp.com/orders/${orderId}/success`; invoice.closeUrl = `https://yourapp.com/orders/${orderId}/cancel`; invoice.buyer = new BitPaySDK.Models.Buyer(); invoice.buyer.email = customerEmail; const created = await client.createInvoice(invoice); return { invoiceId: created.id, paymentUrl: created.url, expirationTime: created.expirationTime }; } 

An invoice is valid for 15 minutes by default — the user must pay within that period. The amount in USD is fixed at the BitPay exchange rate at the moment of invoice creation.

Webhook Handling

BitPay sends an IPN (Instant Payment Notification) to the notificationUrl. It is critical to verify the invoice status via the API, not just trust the webhook body.

const express = require('express'); const router = express.Router(); router.post('/webhooks/bitpay', async (req, res) => { const { id: invoiceId, status } = req.body.data || {}; if (!invoiceId) { return res.status(400).json({ error: 'Missing invoice ID' }); } const invoice = await client.getInvoice(invoiceId); switch (invoice.status) { case 'paid': await updateOrderStatus(invoice.orderId, 'paid_unconfirmed'); break; case 'confirmed': await updateOrderStatus(invoice.orderId, 'confirmed'); break; case 'complete': await fulfillOrder(invoice.orderId); break; case 'expired': await updateOrderStatus(invoice.orderId, 'expired'); break; case 'invalid': await handleInvalidPayment(invoice.orderId, invoice); break; } res.json({ success: true }); }); 
Status Description Action
new Invoice created, awaiting payment Wait
paid Paid but unconfirmed Queue
confirmed Minimum confirmations (usually 1) Partially credit
complete All confirmations, funds credited Fulfill order
expired User did not pay within 15 minutes Cancel
invalid Underpayment or error Refund

For fulfillment, use confirmed or complete depending on your risk tolerance. complete is safest but has a longer delay.

Refunds

BitPay requires a return address — you need to request it from the user at the time of payment or when initiating a refund.

async function createRefund(invoiceId, amount, currency) { const refund = new BitPaySDK.Models.Refund(); refund.invoiceId = invoiceId; refund.amount = amount; refund.currency = currency; const created = await client.createRefund(refund); return created; } 

Risks Covered by BitPay

BitPay handles transaction security, guarantees no chargebacks (irreversible payments), and provides certified reports for accounting. According to official documentation, the platform does not require additional regulatory approval.

Comparison: BitPay vs. Custom Gateway

Parameter BitPay Custom Gateway
Time to launch 2–3 days 2–4 weeks
Legal support Ready-made documentation Needs a lawyer
Fiat conversion Automatic Need an exchange
Security ECDSA + PCI-certified Full responsibility

Typical Integration Issues

Webhook not arriving. BitPay requires HTTPS with a valid certificate on the notificationUrl. Localhost is not accessible — for development use ngrok or BitPay Testnet with a public URL.

Duplicate webhooks. BitPay may send multiple notifications for the same status (retry on timeout). Use invoiceId as an idempotency key: INSERT ... ON CONFLICT (invoice_id, status) DO NOTHING.

Partial payment. If the user pays less, the status becomes invalid. BitPay automatically returns the underpayment if the buyer's email is available.

Timezone in expirationTime. The field is returned as a Unix timestamp in milliseconds. new Date(invoice.expirationTime) — remember it's milliseconds, not seconds.

Testing

BitPay provides a Testnet environment (BitPaySDK.Env.Test) with test Bitcoin. Create an invoice, pay with a testnet wallet — the entire flow without real money. The pairing code for the test environment is created separately in the dashboard.

What's Included in the Work

  1. BitPay SDK integration (Node.js, PHP, Python, Ruby, Java)
  2. Idempotent webhook endpoint setup
  3. Status handling and edge cases (expired invoice, partial payment, retry)
  4. Testnet testing and deployment
  5. Documentation of your implementation
  6. 30 days of post-launch support

Estimated Timeline

2–3 days: 1 day for SDK, 1 day for webhook + state machine, 1 day for testing. The exact cost is calculated individually based on integration complexity. Get a consultation — contact us for a project evaluation. Order a BitPay integration, and we'll configure crypto acceptance within 48 hours.

Testing Details In the BitPay Dashboard, create a separate API Token for Testnet. For payment, use a test wallet, e.g., Bitcoin Testnet in Electrum. Test all statuses from new to complete and invalid.