We build custom applications 5x faster and cheaper 🚀
Book a Free Consultation
Stuck on an error? Book a 30-minute call with an engineer and get a direct fix + next steps. No pressure, no commitment.
To integrate an LNbits wallet into an OpenClaw flow so users can receive a Lightning invoice as a QR code, build an explicit skill in ClawHub that holds the LNbits API key as a secret, have the skill call your LNbits REST API to create an invoice (receive a BOLT11 / payment request), render that BOLT11 as a QR (data URL or file) and return it to the user, and confirm payment either via a validated webhook or safe polling to your LNbits instance. Keep stateful / public HTTP endpoints (webhook receiver, long-running poller, database) outside the agent runtime, validate all webhooks and secrets, and debug by inspecting agent logs, API responses, and credential scopes.
Replace placeholders with your LNbits base URL, API key, and confirm the exact LNbits endpoint/field names from LNbits docs. This example uses fetch (Node 18+) and the qrcode package to create a data URL for a PNG.
<b>// install:</b>
<b>// npm install qrcode</b>
import QRCode from "qrcode";
const LNBITS_BASE_URL = process.env.LNBITS_BASE_URL; <b>// e.g. https://lnbits.example</b>
const LNBITS_API_KEY = process.env.LNBITS_API_KEY; <b>// store securely in ClawHub</b>
// Create invoice via LNbits REST API (confirm the exact path/fields in your LNbits instance docs)
export async function createInvoice({sats, memo}) {
const body = {
<b>// Confirm exact field names for your LNbits instance; common fields are amount/memo.</b>
amount: sats,
memo: memo || ""
};
const res = await fetch(`${LNBITS_BASE_URL}/api/v1/payments`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Api-Key": LNBITS_API_KEY
},
body: JSON.stringify(body)
});
if (!res.ok) {
const text = await res.text();
throw new Error(`LNBITS create-invoice failed: ${res.status} ${text}`);
}
const data = await res.json();
<b>// Look for the BOLT11 / payment_request field in the response (names vary: 'payment_request', 'bolt11', etc.)</b>
const bolt11 = data.payment_request || data.bolt11 || data.payment_request_bech32 || data.bolt_11;
const invoiceId = data.id || data.uid || data.payment_hash;
if (!bolt11) throw new Error("No payment request found in LNbits response; inspect response shape.");
// Create a PNG data URL for the QR
const qrDataUrl = await QRCode.toDataURL(bolt11, {type: "image/png"});
return {invoiceId, bolt11, qrDataUrl, raw: data};
}
Example webhook handler (minimal Express):
import express from "express";
const app = express();
app.use(express.json());
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;
app.post("/lnbits-webhook", (req, res) => {
<b>// Verify signature / secret depending on how you configured it with LNbits.</b>
const incomingSecret = req.header("X-Webhook-Secret");
if (WEBHOOK_SECRET && incomingSecret !== WEBHOOK_SECRET) {
return res.status(403).send("Forbidden");
}
<b>// Inspect payload and (optionally) re-query LNbits to confirm status</b>
const payload = req.body;
console.log("LNbits webhook:", payload);
<b>// Acknowledge quickly</b>
res.status(200).send("ok");
<b>// Then process asynchronously: map invoice -> user, mark paid, notify user</b>
});
Example polling snippet (safe rate, run in external worker):
<b>// Periodically check invoice status and notify when paid</b>
async function pollInvoice(invoiceId) {
const res = await fetch(`${LNBITS_BASE_URL}/api/v1/payments/${invoiceId}`, {
headers: { "X-Api-Key": LNBITS_API_KEY }
});
const data = await res.json();
<b>// Inspect data.status (or equivalent) for 'paid' / 'settled'</b>
return data;
}
Speak one‑on‑one with a senior engineer about your no‑code app, migration goals, and budget. In just half an hour you’ll leave with clear, actionable next steps—no strings attached.
1
Direct answer: A 401 usually means the request didn’t present the LNBits API key the endpoint expects (wrong header, wrong key, wrong wallet/instance or revoked key). Confirm you send the correct key in the header the LNBits server expects and call the correct base URL.
X-Api-Key (not Bearer/Authorization) unless your deployment documents otherwise.curl example:
# <b>//</b> send invoice creation with X-Api-Key
curl -X POST https://lnbits.example/api/v1/payments -H "X-Api-Key: YOUR_ADMIN_KEY" -H "Content-Type: application/json" -d '{"amount":100,"memo":"test"}'
Node fetch:
/* <b>//</b> Node fetch example */
fetch(url, {method:'POST', headers:{'X-Api-Key':key,'Content-Type':'application/json'}, body:JSON.stringify(body)})
2
Most often the QR modal doesn’t appear because the frontend never receives a properly shaped LN invoice or the UI event that opens the modal isn’t triggered (network/CORS, runtime error, missing invoice field like payment_request/bolt11). Check the LNBits response, the agent/skill logs, and the browser console/network for the invoice payload and modal trigger.
// verify invoice payload and open modal
fetch('/api/latest-invoice').then(r=>r.json()).then(inv=>{
// ensure expected field exists
if(inv && (inv.payment_request || inv.bolt11)){
openQrModal(inv.payment_request || inv.bolt11);
} else {
console.error('Missing payment_request/bolt11', inv);
}
});
3
The direct fix: point LNBits' webhook/callback setting to a publicly reachable HTTPS endpoint that your OpenClaw skill or your external webhook receiver exposes, set a shared webhook secret in the LNBits webhook configuration and put the identical secret into your OpenClaw skill's environment (ClawHub or env var). Make sure the receiver validates the secret/signature, returns HTTP 200 quickly, and that the URL is reachable (use ngrok for local dev) and not blocked by firewall.
Follow these concrete steps:
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.raw({type: '*/*'}));
app.post('/webhook', (req, res) => {
<b>//</b> compute HMAC using shared secret from process.env.WEBHOOK_SECRET
const sig = req.headers['x-signature'] || '';
const h = crypto.createHmac('sha256', process.env.WEBHOOK_SECRET).update(req.body).digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(h), Buffer.from(sig))) return res.status(401).end();
<b>//</b> process paid invoice payload
res.status(200).end();
});
app.listen(3000);
4
Direct answer: a PENDING invoice after LNBits shows PAID usually means OpenClaw (or your skill) never received the payment preimage or failed to validate it, so it can’t mark the invoice settled. Resolve by retrieving the preimage from LNBits/your node, re-delivering it to the OpenClaw skill or settlement endpoint, and fixing webhook/auth failures so future preimages arrive reliably.
Check these places:
This prompt helps an AI assistant understand your setup and guide you through the fix step by step, without assuming technical knowledge.
From startups to enterprises and everything in between, see for yourself our incredible impact.
Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We’ll discuss your project and provide a custom quote at no cost.Â