Route Lightning sat to any wallet. Interactive demo of the Thunder Bridge npm package.
Thunder Bridge is a thin wrapper on top of a Lightning node. A few lines of code and your app starts taking sat straight into your own LN wallet. Hook anything you want onto every donation.
You've got an idea and you know it'll need to take sound money. Now let's run through who the standard payment rails kick out on day one. Under 18? Stripe, PayPal and your bank send you home. Anything in a vertical the regulator doesn't currently like? Account terminated. Live in a country that happens to be on the wrong list this week? Cheers, take care. "Consumer protection," "destination principle," "level playing field," "fair taxation of the digital economy," "preventing market distortion," the regulators call it, but what they're mostly protecting you from is earning your own money. Cool, thanks.
Spinning up a Lightning node for the occasion, playing treasurer, babysitting channels and liquidity? Probably not either. First you want to find out if the thing has any pulse, and for that you need the simplest possible end-to-end path so it actually runs. Complications show up on their own, once they're really needed.
Thunder Bridge is that shortcut. The goal is to lower the barrier and get to where, by design, a person cannot be easily cut off from the payment rail. Not by the operator's promise, but by how it is built.
To be fair, there are two things here. Integrity is on Lightning already today. On a trustless setup the gateway cannot keep your sats or send them elsewhere, even if it desperately wanted to. That is not our good heart, it is math. Censorship is a different league, and I won't sugarcoat it. Every middleman can say no, this gateway included.
So how do you beat it? Not with a promise not to. With exit. The code will be open, you can self-host the gateway, the protocol is permissionless. Part of the vision is multiple independent hosts, so the payment does not stand or fall with one node or one person. One gateway sends you home? Move to another, or run your own. The point is not that the gateway won't stop you. The point is that you don't need it. Sat in, sat out. No paperwork, no age gate, no "sorry, kid", no "not that country". By design.
A technically savvy person can already pull this off today via Breez SDK, phoenixd, their own BTCPay Server, ... The point of this project is to open the same door to people who aren't quite that technical, who only need a one-off or stop-gap solution, or who simply prefer simplicity. Just bring an idea and a few lines of code (or an agent that writes them for you).
Routing
Sats from an LN address reach your wallet through a relay that cannot keep them on the trustless path. No node of your own.
Triggers
Webhook and WebSocket event on every payment. Drive whatever.
Programmatic
A few lines of JS. Runs in browsers, Node, Bun, and edge runtimes.
Stream alerts
Live overlay on every donation for Twitch, YouTube, or OBS. Trigger plus streamTrigger over WebSocket deliver events in real time.
createTrigger + streamTriggerIoT triggers
Light a lamp, open a door, drive a vending machine. Your ESP or Pi connects over WebSocket and the hardware reacts to every payment.
createTrigger + streamTriggerPay-per-use API
Unlock an API key, an AI generation, or a file. Donation confirmed by webhook equals access granted.
createDonation + webhookTip jar widget
Drop a donate form on your blog. createDonation renders the QR, waitForFulfillment shows status live.
createDonation + waitForFulfillmentGame server triggers
Minecraft, Counter-Strike: send sats = kill a random player, drop loot, summon a mob, change the weather. Lightning-funded chaos for your server.
createTrigger + streamTriggerAnti-spam paywall
Send 1 sat to comment, submit a form, or hit an API. Bot economics collapse, humans barely notice, no captchas.
createDonation + waitForFulfillmentThis is alpha. The vision is clean programmatic access, no fluff. Before you pay, the SDK verifies the gateway trustlessly on the client. It checks the amount and chain. It binds the recipient invoice to the address you chose. A swap to another account gets caught, even on a shared custodial wallet like Wallet of Satoshi. None of those checks rests on trusting the gateway, that part is math, not a promise. What it cannot prove on its own is that the payout actually settled. The preimage that would show it is the value the gateway reports, so confirming it really landed means trusting the gateway, unless you cross-check the preimage your own paying wallet got. That last piece is in progress. The catch is the SDK running this check ships from us, so the strongest move is to run it on your own backend or audit it. As of 0.2.0 the SDK is strict by default. Anything it cannot confirm is refused unless you opt into the risk. Next up are beta, open source, and self-hosting. ๐งก
Nine short examples that walk you through the API. Each card shows real code on the left and a live preview on the right. Start with the donation flow, or jump to the part you need.
createDonation(destination)the gateway resolves the address
use whena browser with no backend, or the simplest path
createDonationForInvoice(invoice)you resolve the invoice
use whenyou have a backend and want the gateway to choose nothing
Both are trustless (hash reuse), trackable with waitForFulfillment, and throw GatewayCheatError if the gateway misbehaves.
Generate a BOLT11 invoice that pays out to a Lightning address. Scan it with any wallet.
import {
ThunderBridge,
GatewayCheatError,
UnverifiedRecipientError,
} from "thunder-bridge";
// Trustless by default. The SDK refuses anything it can't verify.
const tb = new ThunderBridge("https://thunder-bridge.agora.gripe");
try {
const { id, bolt11, forwardingMode } = await tb.createDonation({
destination: "you@your-wallet.com",
amountSat: 21,
});
} catch (e) {
if (e instanceof UnverifiedRecipientError) {
// recipient not verified, retry with allow: {...}
} else if (e instanceof GatewayCheatError) {
// gateway provably cheated
}
}The invoice is real. Scanning it triggers an actual Lightning payment.
Opens a WebSocket to the gateway and subscribes to status events for one donationId. `onStatusChange` fires on every transition through `invoice_created โ payment_received โ paying_out`. Resolves with the terminal state (`success | failed | expired`); rejects when `timeoutMs` runs out.
import { ThunderBridge } from "thunder-bridge";
const tb = new ThunderBridge("https://thunder-bridge.agora.gripe");
const finalStatus = await tb.waitForFulfillment(donationId, {
onStatusChange(status) {
console.log("status:", status);
// "invoice_created" -> "payment_received"
// -> "paying_out" -> "success"
},
timeoutMs: 600_000,
});
// On success the preimage is verified proof of payment.
if (finalStatus === "success") {
const { preimage } = await tb.getDonation(donationId);
}Receive POST notifications on your own server with HMAC-SHA256 signatures you can trust.
// app/api/webhook/route.ts (Next.js App Router)
import { parseWebhookRequest } from "thunder-bridge";
export async function POST(req: Request) {
const payload = await parseWebhookRequest(
req,
process.env.WEBHOOK_SECRET!,
);
// returns null on a bad signature
if (!payload) {
return new Response("Bad signature", { status: 401 });
}
console.log(payload.event); // "donation.success"
console.log(payload.donation.amount_received_sat);
return new Response("OK");
}{
"event": "donation.success",
"signal_only": false,
"donation": {
"id": "d_a1b2c3...",
"destination": "you@your-wallet.com",
"amount_received_sat": 1000,
"amount_sent_sat": 995,
"network_fee_sat": 5,
"status": "success",
"error_message": null,
"created_at": "2026-04-25T14:32:01Z",
"updated_at": "2026-04-25T14:32:08Z"
}
}`invoiceToSvg` returns a deterministic SVG string for BOLT11 invoices and LN addresses (LUD-17). Encoded with the `LIGHTNING:` URI scheme and uppercased for QR alphanumeric compression and wallet compatibility. Pure client-side. No canvas, no network call.
import { invoiceToSvg } from "thunder-bridge";
// Works for a BOLT11 invoice or an LN address.
const svg = invoiceToSvg("you@your-wallet.com", {
size: 220,
color: "#1a1a2e",
});
document.body.innerHTML = svg;Quick format check that recognises a Lightning address. Just a regex over the input. No network call, no domain lookup, no signature check.
import { detectDestinationType } from "thunder-bridge";
detectDestinationType("you@your-wallet.com");
// "lnAddress"Useful for client-side validation before you call createDonation. Always re-validate on the server.
Ask the gateway how many sat will reach the recipient before you create a donation.
import { ThunderBridge } from "thunder-bridge";
const tb = new ThunderBridge("https://thunder-bridge.agora.gripe");
const fees = await tb.estimateFees(1000);
console.log(fees);A trigger is a reusable Lightning address with a fixed price. Share the QR, donations stream into your app.
import { ThunderBridge } from "thunder-bridge";
const tb = new ThunderBridge("https://thunder-bridge.agora.gripe");
const trigger = await tb.createTrigger({
priceSat: 21,
});
console.log(trigger.ln_address);
// "ln-XXXXX@thunder-bridge.agora.gripe"
console.log(trigger.hash);
// share this hash with streamTriggerOpen a WebSocket and react to every donation as it lands. Replays history on connect, auto-reconnects. Raw URL is `wss://thunder-bridge.agora.gripe/ws/triggers/'<hash>'`, so you can verify it in any generic WS client (e.g. websocketking.com). From that stream you can drive whatever: a Slack or Discord alert, an LED blinking on an ESP32, a door opener, a donation overlay in your livestream, an in-game event, a push notification, a database write, basically anything that listens.
import { ThunderBridge } from "thunder-bridge";
const tb = new ThunderBridge("https://thunder-bridge.agora.gripe");
const stop = tb.streamTrigger(triggerHash, {
onDonation(d) {
console.log("+" + d.amount_sat + " sat");
},
onConnect: () => console.log("WS open"),
onDisconnect: () => console.log("reconnecting"),
});
// later: stop the stream
// stop();Install the Thunder Bridge npm package and start accepting Lightning donations in a few lines of code. Works in Node.js, Bun, Deno, browsers, and edge runtimes.
npm install thunder-bridge
If you build with Claude Code, the npm package ships a skill that teaches Claude the current API and integration patterns. Install it into your repo and Claude picks it up automatically when you write Thunder Bridge code.
# Per project (./.claude/skills/) npx thunder-bridge install-skill # Global for your user (~/.claude/skills/) npx thunder-bridge install-skill --global # Overwrite existing copy npx thunder-bridge install-skill --force