Skip to main content

Your First Settlement

This tutorial walks through creating, displaying, and confirming your first Lightning Network settlement using Lightning Enable.

What We Will Build

A simple settlement flow that:

  1. Creates a Lightning invoice
  2. Displays a QR code for the payer
  3. Polls for settlement confirmation
  4. Shows a success message

Prerequisites

  • Lightning Enable API key (from your subscription)
  • OpenNode API key configured in your merchant account
  • Basic HTML/JavaScript knowledge

Step 1: Create the Settlement Request

Create a settlement invoice by calling the Lightning Enable API:

async function createSettlement(orderId, amount, description) {
const response = await fetch('https://api.lightningenable.com/api/payments', {
method: 'POST',
headers: {
'X-API-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
orderId: orderId,
amount: amount,
currency: 'USD',
description: description
})
});

if (!response.ok) {
throw new Error('Failed to create settlement');
}

return await response.json();
}

The response includes everything you need:

{
"invoiceId": "inv_abc123",
"status": "unpaid",
"lightningInvoice": "lnbc250n1...",
"onchainAddress": "bc1q...",
"hostedCheckoutUrl": "https://checkout.opennode.com/...",
"expiresAt": "2024-12-29T12:15:00Z"
}

Step 2: Display the QR Code

Use a QR code library to display the Lightning invoice:

<!DOCTYPE html>
<html>
<head>
<title>Complete Settlement</title>
<script src="https://cdn.jsdelivr.net/npm/qrcode@1.5.3/build/qrcode.min.js"></script>
<style>
.settlement-container {
max-width: 400px;
margin: 50px auto;
text-align: center;
font-family: system-ui;
}
.qr-code {
margin: 20px 0;
}
.invoice-text {
word-break: break-all;
font-size: 12px;
color: #666;
background: #f5f5f5;
padding: 10px;
border-radius: 8px;
}
.status {
padding: 10px 20px;
border-radius: 20px;
display: inline-block;
margin-top: 20px;
}
.status-pending { background: #fef3c7; color: #92400e; }
.status-settled { background: #dcfce7; color: #166534; }
.status-expired { background: #fee2e2; color: #991b1b; }
</style>
</head>
<body>
<div class="settlement-container">
<h1>$25.00</h1>
<p>Scan with your Lightning wallet</p>

<div class="qr-code">
<canvas id="qr"></canvas>
</div>

<div class="invoice-text" id="invoice">
Loading...
</div>

<div class="status status-pending" id="status">
Awaiting settlement...
</div>
</div>

<script>
// Settlement data (in production, get from server)
const settlement = {
invoiceId: 'inv_abc123',
lightningInvoice: 'lnbc250n1...'
};

// Display QR code
QRCode.toCanvas(
document.getElementById('qr'),
settlement.lightningInvoice,
{ width: 256 }
);

// Display invoice text
document.getElementById('invoice').textContent = settlement.lightningInvoice;
</script>
</body>
</html>

Step 3: Poll for Settlement Status

Check the settlement status every few seconds:

async function checkSettlementStatus(invoiceId) {
const response = await fetch(
`https://api.lightningenable.com/api/payments/${invoiceId}`,
{
headers: {
'X-API-Key': 'YOUR_API_KEY'
}
}
);

return await response.json();
}

function startPolling(invoiceId) {
const statusEl = document.getElementById('status');

const interval = setInterval(async () => {
const settlement = await checkSettlementStatus(invoiceId);

switch (settlement.status) {
case 'paid':
statusEl.className = 'status status-settled';
statusEl.textContent = 'Settlement complete';
clearInterval(interval);
// Redirect to success page
window.location.href = '/success?order=' + settlement.orderId;
break;

case 'expired':
statusEl.className = 'status status-expired';
statusEl.textContent = 'Invoice expired';
clearInterval(interval);
break;

case 'processing':
statusEl.textContent = 'Settlement detected, confirming...';
break;
}
}, 3000); // Check every 3 seconds
}

// Start polling when page loads
startPolling('inv_abc123');

Step 4: Handle Success

When the settlement is confirmed, redirect to a success page:

<!-- success.html -->
<!DOCTYPE html>
<html>
<head>
<title>Settlement Complete</title>
</head>
<body>
<div style="text-align: center; padding: 50px;">
<h1>Settlement Complete</h1>
<p>Order ID: <span id="orderId"></span></p>
</div>

<script>
const params = new URLSearchParams(window.location.search);
document.getElementById('orderId').textContent = params.get('order');
</script>
</body>
</html>

Using Webhooks Instead of Polling

For production, webhooks are more reliable than polling:

// Express.js webhook handler
app.post('/webhooks/lightning', express.raw({type: '*/*'}), (req, res) => {
const signature = req.headers['x-gateway-signature'];
const payload = req.body.toString();

// Verify signature
if (!verifySignature(payload, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}

const data = JSON.parse(payload);

if (data.status === 'paid') {
// Settlement confirmed - update your records
processSettlement(data.orderId, data.invoiceId);
}

res.status(200).send('OK');
});

Settlement States

StateDescription
unpaidInvoice created, awaiting settlement
processingSettlement detected, confirming
paidSettlement complete
expiredInvoice expired without settlement

Testing with Testnet

  1. Use OpenNode Testnet at dev.opennode.com
  2. Get testnet Bitcoin from a faucet
  3. Use a testnet Lightning wallet (Polar, Thunderhub)
  4. Complete the settlement and watch the status change

Next Steps