Tools
Tools: Accept Bitcoin Payments in Next.js — 5 Minute Guide
2026-02-13
0 views
admin
Accept Bitcoin Payments in Next.js — 5 Minute Guide ## What You Get ## 1. Install ## 2. Create a Payment (API Route) ## 3. Display the QR Code in React ## 4. Handle the Webhook ## 5. Environment Variables ## That's It 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. Create app/api/payments/route.ts: Create app/api/webhooks/coinpay/route.ts: 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 Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse COMMAND_BLOCK:
npm install @profullstack/coinpay Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
npm install @profullstack/coinpay COMMAND_BLOCK:
npm install @profullstack/coinpay CODE_BLOCK:
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, });
} Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
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, });
} CODE_BLOCK:
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, });
} COMMAND_BLOCK:
'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> );
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
'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> );
} COMMAND_BLOCK:
'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> );
} CODE_BLOCK:
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 });
} Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
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 });
} CODE_BLOCK:
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 });
} CODE_BLOCK:
COINPAY_API_KEY=your_api_key_here
COINPAY_WEBHOOK_SECRET=your_webhook_secret_here
NEXT_PUBLIC_BASE_URL=https://yourapp.com Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
COINPAY_API_KEY=your_api_key_here
COINPAY_WEBHOOK_SECRET=your_webhook_secret_here
NEXT_PUBLIC_BASE_URL=https://yourapp.com CODE_BLOCK:
COINPAY_API_KEY=your_api_key_here
COINPAY_WEBHOOK_SECRET=your_webhook_secret_here
NEXT_PUBLIC_BASE_URL=https://yourapp.com - Non-custodial — your 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
how-totutorialguidedev.toaiserverswitch