How to Accept Bitcoin Payments in Node.js
Accepting Bitcoin payments in a Node.js application should not require running a full payment infrastructure stack from day one. Most developers want a simple flow: create an invoice, send the customer to checkout, detect payment, and update the order automatically.
Nijipe provides a developer-friendly Bitcoin and Lightning payment API for modern internet businesses. It is designed around merchant-controlled settlement, meaning payments go directly to the merchant’s Bitcoin or Lightning infrastructure while Nijipe handles invoices, checkout, monitoring, and signed webhooks.
In this tutorial, we will build a simple Node.js integration that creates a Bitcoin payment invoice, redirects the customer to a hosted checkout page, and listens for payment confirmation through a webhook.
What You Will Build
By the end of this guide, your Node.js app will be able to:
- Create a Bitcoin or Lightning invoice
- Redirect a customer to a hosted payment checkout
- Receive a signed webhook when payment is detected
- Verify the webhook signature
- Mark the order as paid in your application
This tutorial uses Express and native fetch, available in modern Node.js versions.
1. Install Dependencies
Create a new Node.js project:
mkdir nijipe-nodejs-demo
cd nijipe-nodejs-demo
npm init -y
npm install express dotenvCreate a .env file:
NIJIPE_API_KEY=your_nijipe_api_key
NIJIPE_WEBHOOK_SECRET=your_webhook_secret
NIJIPE_API_BASE=https://www.nijipe.com
APP_URL=https://yourdomain.comYour API key is used to create invoices. Your webhook secret is used to verify that payment events actually came from Nijipe.
2. Create a Basic Express Server
Create a file called server.js:
import express from "express";
import crypto from "crypto";
import dotenv from "dotenv";
dotenv.config();
const app = express();
app.use(express.json({
verify: (req, res, buf) => {
req.rawBody = buf.toString();
}
}));
const PORT = process.env.PORT || 3000;
app.get("/", (req, res) => {
res.send("Nijipe Node.js Bitcoin payment demo is running.");
});
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});Update package.json to use ES modules:
{
"type": "module",
"scripts": {
"start": "node server.js"
}
}Run the server:
npm start3. Create a Bitcoin Payment Invoice
Now add an endpoint that creates a payment invoice through Nijipe.
app.post("/create-invoice", async (req, res) => {
try {
const { amount, orderId } = req.body;
const response = await fetch(`${process.env.NIJIPE_API_BASE}/v1/invoices`, {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.NIJIPE_API_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
amount: amount,
currency: "USD",
description: `Order ${orderId}`,
metadata: {
orderId: orderId
},
successUrl: `${process.env.APP_URL}/payment-success?orderId=${orderId}`,
cancelUrl: `${process.env.APP_URL}/payment-cancelled?orderId=${orderId}`,
webhookUrl: `${process.env.APP_URL}/webhooks/nijipe`,
bitcoin: true,
lightning: true,
expiresIn: 600
})
});
const invoice = await response.json();
if (!response.ok) {
return res.status(response.status).json({
error: "Failed to create invoice",
details: invoice
});
}
return res.json({
invoiceId: invoice.id,
checkoutUrl: invoice.checkoutUrl,
status: invoice.status
});
} catch (error) {
console.error("Invoice creation failed:", error);
return res.status(500).json({
error: "Internal server error"
});
}
});This endpoint receives an amount and order ID from your application, creates a Nijipe invoice, and returns a hosted checkout URL.
Example request:
curl -X POST http://localhost:3000/create-invoice \
-H "Content-Type: application/json" \
-d '{"amount": 1500, "orderId": "order_123"}'In this example, 1500 represents the amount in cents, equal to $15.00.
A successful response may look like:
{
"invoiceId": "inv_01HX2J...",
"checkoutUrl": "https://pay.nijipe.com/i/inv_01HX2J...",
"status": "pending"
}Your frontend can redirect the customer to checkoutUrl.
4. Redirect the Customer to Checkout
In your frontend, call /create-invoice and redirect the customer:
async function payWithBitcoin() {
const response = await fetch("/create-invoice", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
amount: 1500,
orderId: "order_123"
})
});
const data = await response.json();
if (data.checkoutUrl) {
window.location.href = data.checkoutUrl;
}
}The customer will see a Bitcoin or Lightning payment page. They can scan the QR code or copy the invoice into a compatible wallet.
5. Receive Payment Webhooks
After payment is detected, Nijipe sends a webhook to your server. A webhook allows your app to update the order without asking the customer to refresh the page.
Create a webhook endpoint:
app.post("/webhooks/nijipe", (req, res) => {
const signature = req.headers["x-nijipe-signature"];
const timestamp = req.headers["x-nijipe-timestamp"];
const isValid = verifyNijipeSignature({
rawBody: req.rawBody,
signature,
timestamp,
secret: process.env.NIJIPE_WEBHOOK_SECRET
});
if (!isValid) {
return res.status(401).json({
error: "Invalid webhook signature"
});
}
const event = req.body;
console.log("Webhook received:", event.type);
switch (event.type) {
case "invoice.payment_detected":
console.log("Payment detected for invoice:", event.data.invoiceId);
break;
case "invoice.confirmed":
console.log("Invoice confirmed:", event.data.invoiceId);
console.log("Order ID:", event.data.metadata?.orderId);
// TODO: Mark order as paid in your database
// await markOrderAsPaid(event.data.metadata.orderId);
break;
case "invoice.underpaid":
console.log("Invoice underpaid:", event.data.invoiceId);
break;
case "invoice.overpaid":
console.log("Invoice overpaid:", event.data.invoiceId);
break;
case "invoice.expired":
console.log("Invoice expired:", event.data.invoiceId);
break;
default:
console.log("Unhandled event:", event.type);
}
return res.json({
received: true
});
});6. Verify the Webhook Signature
Webhook verification is important. Without it, anyone could send a fake request to your server claiming an invoice was paid.
Add this helper function:
function verifyNijipeSignature({ rawBody, signature, timestamp, secret }) {
if (!rawBody || !signature || !timestamp || !secret) {
return false;
}
const payload = `${timestamp}.${rawBody}`;
const expectedSignature = crypto
.createHmac("sha256", secret)
.update(payload)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}This uses HMAC SHA-256 verification and timingSafeEqual to reduce timing attack risks.
7. Recommended Invoice Status Flow
A typical Bitcoin payment lifecycle looks like this:
pending
↓
payment_detected
↓
confirmedOther possible states may include:
underpaid
overpaid
expired
canceled
failedFor most e-commerce use cases, you should fulfill the order only after receiving the invoice.confirmed webhook.
For Lightning payments, confirmation can happen almost instantly. For on-chain Bitcoin payments, confirmation depends on the Bitcoin network and your configured confirmation threshold.
8. Why Use Nijipe Instead of a Custodial Gateway?
Traditional crypto payment gateways often receive funds into platform-controlled wallets and require merchants to withdraw later. Nijipe is designed differently.
With Nijipe, Bitcoin payments are routed to merchant-controlled infrastructure. Nijipe coordinates the payment experience, invoice creation, monitoring, and webhook delivery, but does not custody merchant funds.
This gives developers a managed API experience without forcing merchants to surrender control of their settlement layer.
9. Production Checklist
Before going live, make sure you have:
- Added your production API key
- Configured your webhook secret
- Stored order IDs in your database
- Verified webhook signatures
- Handled underpaid and overpaid invoices
- Set a sensible invoice expiry time
- Tested both Bitcoin and Lightning payment flows
- Added retry logic for webhook processing
- Logged invoice and payment events for audit purposes
Conclusion
Accepting Bitcoin payments in Node.js does not need to be complicated. With Nijipe, developers can create invoices, launch checkout pages, receive signed webhooks, and monitor settlement through a simple API.
The result is a clean payment experience for customers and a non-custodial settlement model for merchants.
Nijipe gives you the developer experience of a modern payment API while preserving merchant control over funds.
Next Steps
- Create a Nijipe account
- Generate an API key
- Connect your merchant-controlled Bitcoin or Lightning infrastructure
- Build your first invoice flow
- Test webhooks in sandbox mode
Start with a single invoice endpoint, verify webhooks correctly, and then connect the flow to your production checkout.
Explore the API reference for detailed endpoints. See webhooks for the complete list of event types, and read about test mode for details on sandbox testing.