AGORA.GRIPE
Lightningalphav0.2.0

Thunder Bridge

Route Lightning sat to any wallet. Interactive demo of the Thunder Bridge npm package.

// gateway

Age-gating financial rails kills innovation and stifles creativity for everyone locked out. So here comes Thunder Bridge.

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.

// why this existsthe problem, the use cases, and where this is going
// problem

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.

// approach

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.

// goal

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).

01

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.

02

Triggers

Webhook and WebSocket event on every payment. Drive whatever.

03

Programmatic

A few lines of JS. Runs in browsers, Node, Bun, and edge runtimes.

What you can build

// use cases

Stream alerts

Live overlay on every donation for Twitch, YouTube, or OBS. Trigger plus streamTrigger over WebSocket deliver events in real time.

createTrigger + streamTrigger

IoT 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 + streamTrigger

Pay-per-use API

Unlock an API key, an AI generation, or a file. Donation confirmed by webhook equals access granted.

createDonation + webhook

Tip jar widget

Drop a donate form on your blog. createDonation renders the QR, waitForFulfillment shows status live.

createDonation + waitForFulfillment

Game 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 + streamTrigger

Anti-spam paywall

Send 1 sat to comment, submit a form, or hit an API. Bot economics collapse, humans barely notice, no captchas.

createDonation + waitForFulfillment
// roadmapthunder-bridge@0.2.0

This 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. ๐Ÿงก

Alpha
Trustless verification
Beta
Open-source / self-host
// sandbox

Try the API

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.

// which path do I use

Create a donation

createDonation(destination)

the gateway resolves the address

use whena browser with no backend, or the simplest path

  • no backend, a single call
  • the gateway does the LNURL resolution
  • you lean on the gateway's resolution, guarded by identity binding
  • on a shared custodial wallet the check confirms only the provider, not the exact account

Bring your own invoice

createDonationForInvoice(invoice)

you resolve the invoice

use whenyou have a backend and want the gateway to choose nothing

  • no substitution, the gateway relays your exact invoice
  • works for any wallet, even unverifiable ones
  • you need a backend to resolve the invoice yourself

Both are trustless (hash reuse), trackable with waitForFulfillment, and throw GatewayCheatError if the gateway misbehaves.

// the donation flowcreate a donation, track it live, confirm it on your server
step 01
1

Create a donation

Generate a BOLT11 invoice that pays out to a Lightning address. Scan it with any wallet.

code
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.

step 02
2

Track payment in real time

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.

code
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);
}
Create a donation above to track it here.
step 03
3

Verify a webhook

Receive POST notifications on your own server with HMAC-SHA256 signatures you can trust.

code
// 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");
}
Server-only. Drop this into your route handler.
parseWebhookRequest verifies the X-Signature-256 header. Returns null on bad signature.
Retries on failure: 10s, 1m, 2m, 5m, 10m, 30m, 1h, then hourly up to 1 week.
Sample verified payload
{
  "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"
  }
}
// building blocksQR codes, type checks, fee estimates, bring-your-own-invoice
step 04
4

QR code from any Lightning destination

`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.

code
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;
Type a Lightning destination: an LN address (user@domain) or a BOLT11 invoice (lnbc...). The SVG renders below.
step 05
5

Naive type detection

Quick format check that recognises a Lightning address. Just a regex over the input. No network call, no domain lookup, no signature check.

code
import { detectDestinationType } from "thunder-bridge";

detectDestinationType("you@your-wallet.com");
// "lnAddress"
input(empty)
detected typenull

Useful for client-side validation before you call createDonation. Always re-validate on the server.

step 06
6

Estimate fees

Ask the gateway how many sat will reach the recipient before you create a donation.

code
import { ThunderBridge } from "thunder-bridge";

const tb = new ThunderBridge("https://thunder-bridge.agora.gripe");

const fees = await tb.estimateFees(1000);

console.log(fees);
Click Run to fetch a fee estimate from the gateway.
// triggers and live streamsreusable addresses and real-time donation events
step 08
8

Create a trigger

A trigger is a reusable Lightning address with a fixed price. Share the QR, donations stream into your app.

code
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 streamTrigger
step 09
9

Stream donations to your app

Open 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.

code
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();
Create a trigger above to stream its donations here.

// guide

Use it in your project

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.

Install

bash
npm install thunder-bridge

Claude Code skill

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.

bash
# 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