
chovyAccept Bitcoin Payments in Next.js — 5 Minute Guide You want to accept crypto in your...
You want to accept crypto in your Next.js app. No third-party custody, no KYC paperwork, no waiting weeks for approvals. Just install a package, add a few routes, and start receiving BTC, ETH, SOL, and more directly to your own wallets.
Here's how with @profullstack/coinpay.
npm install @profullstack/coinpay
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,
});
}
'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<PaymentData | null>(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 (
<div style={{ textAlign: 'center', padding: '2rem' }}>
<h3>Send {payment.amount} {payment.currency}</h3>
<img src={payment.qr} alt="Payment QR Code" width={256} height={256} />
<p style={{ fontFamily: 'monospace', fontSize: '0.85rem', wordBreak: 'break-all' }}>
{payment.address}
</p>
<button onClick={() => navigator.clipboard.writeText(payment.address)}>
Copy Address
</button>
</div>
);
}
return (
<div>
<p>Pay ${amount}</p>
<div style={{ display: 'flex', gap: '0.5rem' }}>
{['BTC', 'ETH', 'SOL', 'USDC'].map((c) => (
<button key={c} onClick={() => handlePay(c)} disabled={loading}>
{c}
</button>
))}
</div>
</div>
);
}
Create app/api/webhooks/coinpay/route.ts:
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 });
}
COINPAY_API_KEY=your_api_key_here
COINPAY_WEBHOOK_SECRET=your_webhook_secret_here
NEXT_PUBLIC_BASE_URL=https://yourapp.com
Five minutes, four files. You're now accepting crypto payments with no middleman holding your funds. Every payment goes directly to an address derived from your own HD wallet.
What makes this different from BitPay/Coinbase Commerce: Your keys stay on your server. There's no custodian. No one can freeze your funds or require KYC to release them.
📖 Docs: coinpayportal.com
📦 npm: @profullstack/coinpay