Stop bots without reCAPTCHA or Cloudflare

Drop-in CAPTCHA that prices the work, not the user. SHA-256 proof in the browser, or pay 3 sats over Lightning to skip. No tracking, no fingerprinting, no surveillance vendor in the loop.
An ornate brass scale balanced perfectly with a glowing bitcoin sigil ingot on the left pan and a rolled scroll of cryptographic hashes on the right pan, both pans illuminated equally, deep indigo background with floating motes of light, candle-amber chiaroscuro. The text "PRICE THE WORK" is rendered at the bottom in clean white sans-serif.
Live ⚡ 3 sats to skip (L402) SHA-256 fallback, difficulty 18 No build step required
Install — 60 seconds, copy-pasteable
npm install @powforge/captcha

Or use the CDN drop-in below — no bundler required. Jump to integration flows ↓

Live demo, try it

Pay 3 sats over Lightning, or compute a SHA-256 proof. Either path mints a short-lived token your server can verify.

Access granted. Token verified server-side. No cookies. No tracking.

1Drop-in script tag (zero build step)

Fastest path. Paste two tags into your form page. No npm install, no bundler, nothing to configure on the server until you want to verify the token.

<script src="https://unpkg.com/@powforge/captcha/dist/powforge-captcha.min.js"
        data-target="#my-captcha"
        data-server="https://captcha.powforge.dev"
        data-theme="dark"
        data-callback="onVerified">
</script>
<div id="my-captcha"></div>
<input type="hidden" name="pf_token">

Wire the callback

<script>
function onVerified(token, method) {
  // token  = short-lived JWT (5 min TTL)
  // method = 'pow' | 'lightning'
  document.querySelector('input[name=pf_token]').value = token;
  document.getElementById('submit-btn').disabled = false;
}
</script>
Why unpkg.com? Public CDN backed by npm. The script is the same artifact published at npmjs.com/package/@powforge/captcha. Subresource Integrity hashes are pinned per release in the package README.

2npm install + ES module

Use this when you have a bundler (Vite, webpack, esbuild, Next, Remix, SvelteKit). You get tree-shaking, types, and version pinning in package.json.

npm install @powforge/captcha
import { PowCaptcha } from '@powforge/captcha';

const captcha = new PowCaptcha({
  target: '#my-captcha',
  server: 'https://captcha.powforge.dev'
});

captcha.on('verified', ({ token, method }) => {
  // token  = short-lived JWT (5 min TTL)
  // method = 'pow' | 'lightning'
  document.querySelector('input[name=pf_token]').value = token;
});

React / Next.js

import { useEffect, useRef } from 'react';
import { PowCaptcha } from '@powforge/captcha';

export function Captcha({ onToken }) {
  const ref = useRef(null);
  useEffect(() => {
    const captcha = new PowCaptcha({
      target: ref.current,
      server: 'https://captcha.powforge.dev'
    });
    captcha.on('verified', ({ token }) => onToken(token));
    return () => captcha.destroy();
  }, []);
  return <div ref={ref} />;
}

3Server-side verification

Verify the token on your server before trusting the form submission. Six lines. Works with Express, Fastify, Hono, Next API routes, anything that hands you the request body.

const { verifyToken } = require('@powforge/captcha/verify');

const result = await verifyToken(req.body.pf_token, {
  server: 'https://captcha.powforge.dev'
});

if (!result.valid) return res.status(403).json({ error: 'CAPTCHA failed' });
// result.method === 'pow' | 'lightning'
// result.difficulty === 18  (PoW path)
// result.amountSats === 3   (Lightning path)

ES module / TypeScript

import { verifyToken } from '@powforge/captcha/verify';

const result = await verifyToken(token, { server: 'https://captcha.powforge.dev' });
if (!result.valid) throw new Error('Invalid CAPTCHA token');
What gets verified? Signature, expiry (5 min), and one-time-use. Tokens cannot be replayed. The verify endpoint is rate-limited to 100 req/sec per origin. No keys to rotate, no quota to manage.

LLightning-skip tier (L402)

Want visitors to skip the PoW entirely by paying 3 sats? Set l402: true. The widget shows both options side by side: compute the proof for free, or pay over Lightning for instant pass.

// Optional: configure L402 endpoint for Lightning-skip tier
const captcha = new PowCaptcha({
  target: '#my-captcha',
  server: 'https://captcha.powforge.dev',
  l402: true  // shows Lightning invoice option alongside PoW
});

The same on script-tag flow:

<script src="https://unpkg.com/@powforge/captcha/dist/powforge-captcha.min.js"
        data-target="#my-captcha"
        data-server="https://captcha.powforge.dev"
        data-l402="true"
        data-callback="onVerified">
</script>
Why both paths? PoW priced in CPU-seconds is fair to humans, brutal to high-volume scrapers. Lightning priced in sats is fair to legitimate agents (3 sats < 1 millisecond of compute), brutal to a swarm of cheap bots. Charging the work in either denomination is what makes the gate work.

You're done

If your widget is not rendering, check the browser console — the script logs the exact mount-target selector it tried. If verification fails on the server, hit https://captcha.powforge.dev/health to confirm the verify endpoint is reachable from your network.

every gate opens with energy. watts or sats.