How I Built a $0/Month NIP-05 Service Using Cloudflare Workers + Lightning
How I Built a $0/Month NIP-05 Service Using Cloudflare Workers + Lightning
If you want to offer NIP-05 verification on your own domain, you don’t need a server. Here’s how to build one for free using Cloudflare Workers, D1 (SQLite), and LNbits.
The Problem
NIP-05 verification helps people find you on Nostr. But most verification services either:
- Cost money to host (VPS, database)
- Require manual administration
- Don’t accept Bitcoin
I wanted a service that runs for $0/month, accepts Lightning payments, and is fully automated.
Architecture
Three components, all free tier:
- Cloudflare Worker — handles HTTP requests, serves the landing page, and processes registrations
- D1 Database — Cloudflare’s SQLite-at-the-edge, stores identities
- LNbits — creates and verifies Lightning invoices
The flow:
User → Worker → creates invoice via LNbits API
→ stores pending registration in D1
User → pays invoice
User → polls /check/{hash}
Worker → checks payment status with LNbits
→ if paid, moves identity from pending → active
→ /.well-known/nostr.json now returns the identity
The Code
The entire service is ~290 lines of JavaScript. Here are the key parts.
Serving NIP-05 JSON
The NIP-05 spec requires a JSON response at /.well-known/nostr.json:
async function handleNostrJSON(url, env) {
const name = (url.searchParams.get("name") || "").toLowerCase();
const result = { names: {} };
if (name) {
const row = await env.DB.prepare(
"SELECT pubkey FROM identities WHERE name = ?"
).bind(name).first();
if (row) result.names[name] = row.pubkey;
}
return jsonResponse(result);
}
Registration Flow
When someone registers, we create a Lightning invoice and store the pending identity:
async function handleRegister(request, env) {
const body = await request.json();
const name = body.name.toLowerCase().trim();
const pubkey = body.pubkey.toLowerCase().trim();
// Check name availability
const existing = await env.DB.prepare(
"SELECT pubkey FROM identities WHERE name = ?"
).bind(name).first();
if (existing) return jsonResponse({ error: "name already taken" }, 409);
// Create Lightning invoice
const invoice = await createInvoice(env, priceSats, `NIP-05: ${name}`);
// Store pending registration
await env.DB.prepare(
"INSERT INTO pending (payment_hash, name, pubkey, created) VALUES (?, ?, ?, datetime('now'))"
).bind(invoice.payment_hash, name, pubkey).run();
return jsonResponse({
payment_request: invoice.payment_request,
payment_hash: invoice.payment_hash,
amount_sats: priceSats,
});
}
Payment Verification
The frontend polls until payment is confirmed:
async function handleCheck(hash, env) {
const pending = await env.DB.prepare(
"SELECT name, pubkey FROM pending WHERE payment_hash = ?"
).bind(hash).first();
const paid = await checkPayment(env, hash);
if (!paid) return jsonResponse({ paid: false });
// Move from pending to active
await env.DB.prepare(
"INSERT INTO identities (name, pubkey, created) VALUES (?, ?, datetime('now'))"
).bind(pending.name, pending.pubkey).run();
await env.DB.prepare(
"DELETE FROM pending WHERE payment_hash = ?"
).bind(hash).run();
return jsonResponse({ paid: true, registered: true });
}
Deployment
With Wrangler CLI:
# Create D1 database
wrangler d1 create nip05-db
# Apply schema
wrangler d1 execute nip05-db --remote --file=schema.sql
# Set LNbits API key as secret
echo "your-key" | wrangler secret put LNBITS_KEY
# Deploy
wrangler deploy
Total deployment time: about 10 minutes.
What It Costs
- Cloudflare Workers free tier: 100k requests/day
- D1 free tier: 5 million reads/day, 100k writes/day
- LNbits: self-hosted or use a public instance
For a NIP-05 service, you’ll never hit these limits. Effectively $0/month.
What I’d Do Differently
- Add Nostr Connect (NIP-46) so users can sign up without manually entering their hex pubkey
- Add relay hints to the NIP-05 response (the spec supports this)
- Build an admin dashboard to monitor registrations
Try It
The service is live at satoshis.lol — 100 sats for a verified address like yourname@satoshis.lol.
The pattern works for any domain. If you control a domain and run LNbits (or any Lightning node with an API), you can deploy your own in under an hour.
Highlights (1)
NIP-05 verification helps people find you on Nostr. But most verification services either: