Your CAPTCHA is good at stopping bots. But it stops real users too. The ones who are in a hurry, on mobile, or just sick of clicking traffic lights. Here is a way to let the real ones skip it for 3 sats.
Three sats. About a third of a cent. Enough to filter automation running at scale. Cheap enough that humans pay without noticing.
What you are gonna build
A self-hosted CAPTCHA widget with two tiers running side by side:
- Free path: SHA-256 proof-of-work in a Web Worker. Pure JavaScript, no WASM, no tracking, no third-party calls.
- Skip tier: click the lightning bolt, scan a bolt11 invoice in any wallet, ship.
Same widget. Same token format on the server. The user picks the path that fits their hurry.
How the skip tier works
The flow is dead simple:
- User clicks the
⚡ Skip (3 sats)button on the widget. - Browser hits
POST /api/skipon your server. - Your server asks LNBits for a bolt11 invoice and returns it with a
payment_hash. - Widget pops a modal with the invoice. Click-to-copy bolt11. Cancel button to fall back to PoW.
- Browser polls
GET /api/skip/check/<payment_hash>every 2 seconds. - Soon as LNBits says paid, the server hands back a verification token. Same shape as the PoW path.
Most Lightning wallets confirm in under 2 seconds. The user is through the form before they would have finished one round of "click all the buses."
Step 1 — Install
Two ways. Pick whichever fits your stack.
NPM:
npm install @powforge/captcha
Or the drop-in script tag (UMD bundle, no build step):
<script src="https://captcha.powforge.dev/widget.js"></script>
The bundle is tiny and self-contained. No webpack config, no peer deps, no React. Drop it on any HTML page.
Step 2 — Add the data-l402 attribute
Here is the whole thing on a contact form:
<form action="/submit" method="post">
<input name="email" type="email" required>
<textarea name="message" required></textarea>
<div id="pow-captcha"
data-server="https://your-server.com"
data-l402="true">
</div>
<input type="hidden" name="pf_token">
<button type="submit">Send</button>
</form>
<script src="https://captcha.powforge.dev/widget.js"
data-target="#pow-captcha"></script>
The data-l402="true" is the whole switch. Without it you get the standard PoW-only widget. With it, the lightning bolt button shows up next to the spinner the moment the challenge loads.
The hidden pf_token input gets filled by the widget once verification finishes. PoW path or Lightning path, same token, same name. Your form handler does not care which way the user came in.
Step 3 — Server-side setup
The widget hits two endpoints on your server: /api/challenge (the existing PoW endpoint) and /api/skip plus /api/skip/check/<hash> (new for the L402 tier).
You need an LNBits node anywhere reachable. Self-host it, run a Voltage instance, or use any LNBits-compatible wallet. Two env vars:
LNBITS_URL=https://your-lnbits.example.com
LNBITS_INVOICE_KEY=your-readwrite-invoice-key
The server pattern is the same one used in the mcp-l402-gate example: mint an invoice with a memo, hand back { bolt11, payment_hash }, then on each /api/skip/check/<hash> poll, ask LNBits if that payment hash settled. When it has, sign and return a token in the same shape your /api/verify endpoint already returns.
Reference implementation lives at powforge.dev/captcha with the full server route handlers spelled out. The widget code lives in @powforge/captcha on npm if you want to read the client side.
What the user actually sees
The widget loads its normal CAPTCHA challenge. Spinner spins, SHA-256 cooks, progress bar fills. Same as ALTCHA or any other PoW CAPTCHA.
But there is a small lightning bolt button right beside the spinner that says ⚡ Skip (3 sats). Tap it. A modal pops with a QR code area showing the bolt11 invoice. Open Phoenix, Wallet of Satoshi, Zeus, Alby, whatever you have. Scan or paste. Hit pay.
The modal closes itself the second LNBits confirms. Token fills the hidden form input. Submit goes through. Most wallets settle the invoice in under two seconds. The whole skip flow is faster than the PoW would have been on a phone.
If the user changes their mind mid-pay, the modal has a Cancel — use PoW instead button that drops them right back into the proof-of-work path. No state lost.
Why 3 sats
Three sats is roughly $0.003 at current prices. That is on purpose.
Too cheap to be a paywall. Real humans hitting a contact form will pay 3 sats every time without thinking, the way you tap-to-pay for transit without reading the price.
But for automation? At 3 sats per request, a scraper running 100k captures costs 300,000 sats. Around $300. Suddenly the math on solving CAPTCHAs at 2c each through a CAPTCHA-farm is competitive again, and your form is no longer the cheap target. You did not block the bot. You priced it.
That is the whole game with PoW and L402 captchas. You are not stopping the bot, you are making the bot pay enough that it picks somebody else's form.
Tweaking the price
3 sats is the default in @powforge/captcha. If your form gets aggressive bot traffic, dial it up to 10 or 50 sats. If you want the skip tier to feel free for users, run a 1-sat tier.
The price lives in your server's /api/skip handler in the LNBits invoice amount field. Change one number, redeploy, done.
What about wallets without Lightning
The free PoW path always runs. The lightning bolt is opt-in. Users without a Lightning wallet just ignore it and let the SHA-256 finish, same as if you never enabled L402. No degradation, no exclusion.
Going further
- Docs and full server reference: powforge.dev/captcha
- ALTCHA side-by-side comparison: powforge.dev/captcha/compare/altcha
- npm package:
@powforge/captcha
If you add this to something, drop the URL in the comments. I want to see what folks build with sub-cent skip tiers. I have a hunch the form-spam economics flip in interesting ways once your honeypot can charge.
Top comments (0)