Accept Bitcoin Payments in Next.js — 5 Minute Guide
Source: Dev.to
Overview
Accept cryptocurrency payments in your Next.js app without third‑party custody, KYC paperwork, or lengthy approvals. By installing @profullstack/coinpay, you can receive BTC, ETH, SOL, POL, BCH, USDC, and more directly to wallets you control.
- Non‑custodial – your private keys never leave your server
- 6 chains – BTC, ETH, SOL, POL, BCH, USDC
- HD wallet derivation – unique address per payment
- Automatic USD conversion rates
- Webhook notifications on payment confirmation
Installation
npm install @profullstack/coinpay
Server‑side API Route
Create app/api/payments/route.ts:
import { NextRequest, NextResponse } from 'next/server';
const COINPAY_API = 'https://coinpayportal.com/api';
const API_KEY = process.env.COINPAY_API_KEY!;
export async function POST(req: NextRequest) {
const { amount, currency, metadata } = await req.json();
const res = await fetch(`${COINPAY_API}/payments`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_KEY}`,
},
body: JSON.stringify({
amount, // amount in USD
currency, // 'BTC', 'ETH', 'SOL', 'POL', 'BCH', 'USDC'
metadata, // your order ID, user info, etc.
webhook_url: `${process.env.NEXT_PUBLIC_BASE_URL}/api/webhooks/coinpay`,
}),
});
const payment = await res.json();
return NextResponse.json({
paymentId: payment.id,
address: payment.address,
amount: payment.crypto_amount,
currency: payment.currency,
qr: payment.qr_code_url,
expires_at: payment.expires_at,
});
}
Client Component
Create a React component that calls the API and displays the payment details.
'use client';
import { useState } from 'react';
interface PaymentData {
paymentId: string;
address: string;
amount: string;
currency: string;
qr: string;
expires_at: string;
}
export default function PaymentButton({ amount }: { amount: number }) {
const [payment, setPayment] = useState(null);
const [loading, setLoading] = useState(false);
async function handlePay(currency: string) {
setLoading(true);
const res = await fetch('/api/payments', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ amount, currency, metadata: { orderId: '123' } }),
});
const data = await res.json();
setPayment(data);
setLoading(false);
}
if (payment) {
return (
<>
<h3>Send {payment.amount} {payment.currency}</h3>
<p>{payment.address}</p>
<button onClick={() => navigator.clipboard.writeText(payment.address)}>
Copy Address
</button>
</>
);
}
return (
<>
<button onClick={() => {/* trigger payment UI */}}>
Pay ${amount}
</button>
{['BTC', 'ETH', 'SOL', 'USDC'].map((c) => (
<button
key={c}
onClick={() => handlePay(c)}
disabled={loading}
>
{c}
</button>
))}
</>
);
}
Webhook Handler
Create app/api/webhooks/coinpay/route.ts to process payment notifications.
import { NextRequest, NextResponse } from 'next/server';
import crypto from 'crypto';
const WEBHOOK_SECRET = process.env.COINPAY_WEBHOOK_SECRET!;
function verifySignature(payload: string, signature: string): boolean {
const expected = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
export async function POST(req: NextRequest) {
const body = await req.text();
const signature = req.headers.get('x-coinpay-signature') || '';
if (!verifySignature(body, signature)) {
return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
}
const event = JSON.parse(body);
switch (event.status) {
case 'confirmed':
// Payment confirmed on‑chain
// Update your order, grant access, send receipt
console.log(`Payment ${event.payment_id} confirmed for ${event.amount} ${event.currency}`);
break;
case 'expired':
// Payment window expired
break;
}
return NextResponse.json({ received: true });
}
Environment Variables
Add the following variables to your .env (or hosting platform’s secret store):
COINPAY_API_KEY=your_api_key_here
COINPAY_WEBHOOK_SECRET=your_webhook_secret_here
NEXT_PUBLIC_BASE_URL=https://yourapp.com
Conclusion
In roughly five minutes and four files, your Next.js application can accept crypto payments directly, without a middleman holding your funds. Each payment is sent to an address derived from your own HD wallet, giving you full control.
What sets this apart from services like BitPay or Coinbase Commerce?
Your private keys stay on your server, so no custodian can freeze or withhold your funds, and no KYC is required to release them.
Further reading:
📖 CoinPay documentation
Package: @profullstack/coinpay