Feedbin V4V Integration
Feedbin now supports Value-4-Value (V4V) payments through its podcast player, and the same system can be extended to blog subscriptions. Alby is offering a 150,000 sat bounty to implement a zap button directly in Feedbin’s web interface. This feature leverages the <podcast:value> tag in RSS feeds, which publishers can use to declare Lightning payment destinations.
Quick Take
- Feedbin’s podcast player already handles Lightning V4V via
<podcast:value>in RSS feeds- The bounty targets a zap button on individual feed items in the web interface
- No private keys leave your wallet; NWC (Nostr Wallet Connect) handles the payments
- WebLN support varies by browser extension—test with Alby’s extension before committing
Feedbin supports the <podcast:value> tag in RSS feeds, allowing publishers to declare Lightning payment destinations. Here’s how it looks in XML for a keysend payment:
<podcast:value type="lightning" method="keysend">
<podcast:valueRecipient
name="Author Name"
type="node"
address="YOUR_NODE_PUBKEY"
split="100"
/>
</podcast:value>
For simpler setups, use a Lightning address instead of a raw pubkey:
<podcast:value type="lightning" method="lnaddress">
<podcast:valueRecipient
name="Author Name"
type="lnaddress"
address="you@getalby.com"
split="100"
/>
</podcast:value>
In an Astro RSS endpoint (src/pages/rss.xml.js), add the podcast:value block at the channel level:
// In rss.xml.js (Astro v4.10.2)
customData: `
<podcast:value type="lightning" method="lnaddress">
<podcast:valueRecipient
name="cipherfox"
type="lnaddress"
address="your-lightning-address@getalby.com"
split="100"
/>
</podcast:value>
`,
Add the namespace declaration to the feed root to ensure compatibility with Feedbin’s parser:
xmlns: {
atom: 'http://www.w3.org/2005/Atom',
podcast: 'https://podcastindex.org/namespace/1.0',
},
Gotcha: Namespace Declaration Required Without the
podcast:namespace in the feed root, Feedbin’s parser may silently ignore the<podcast:value>tag. This is a common oversight when migrating from older RSS generators. Test your feed with the W3C Feed Validation Service to confirm the namespace is present.
The Alby bounty asks for a zap button on individual feed items. Here’s the implementation path:
-
Parse
<podcast:value>from feed items in Feedbin’s parser (v2.47.1)- Feedbin’s parser currently prioritizes channel-level
<podcast:value>tags. If your feed only declares payments at the channel level, the zap button won’t show up on individual articles. You’ll need to ensure the tag is present at the item level. - Error Example: If you see
No V4V metadata found for this feed itemin the browser console, double-check that the<podcast:value>tag is nested under each<item>in your RSS feed.
- Feedbin’s parser currently prioritizes channel-level
-
Store the Lightning address or keysend destination per item
- Feedbin’s backend (Ruby on Rails v7.1.3) stores parsed metadata in a
feed_itemstable with avalue_recipientsJSON column. If you’re self-hosting Feedbin, verify the schema matches the expected structure:SELECT column_name, data_type FROM information_schema.columns WHERE table_name = 'feed_items' AND column_name = 'value_recipients'; - Watch Out: If the
value_recipientscolumn is missing or malformed, the zap button won’t render. Migrate the column manually if needed:ALTER TABLE feed_items ADD COLUMN value_recipients JSONB DEFAULT '[]';
- Feedbin’s backend (Ruby on Rails v7.1.3) stores parsed metadata in a
-
Render a zap button in the article view
- The frontend uses Stimulus JS (v3.2.1) to attach event listeners. The button’s markup is injected via a partial template (
app/views/feeds/_article.html.erb):<% if article.value_recipients.present? %> <button data-action="click->zap-button#sendPayment" class="zap-button"> ⚡ Zap </button> <% end %> - Gotcha: CSS Conflicts
If the zap button doesn’t appear, check for conflicting styles in Feedbin’s CSS (e.g.,
display: noneon.zap-button). Override with:.zap-button { display: inline-block !important; }
- The frontend uses Stimulus JS (v3.2.1) to attach event listeners. The button’s markup is injected via a partial template (
-
On click: use WebLN or generate a BOLT11 invoice via LNURL
-
WebLN Approach:
// In zap_button_controller.js (Stimulus) async sendPayment() { if (!window.webln) { alert("Install Alby or another WebLN-compatible extension"); return; } try { const invoice = await window.webln.sendPayment("lnbc1..."); console.log("Payment sent:", invoice); } catch (error) { console.error("WebLN error:", error.message); } }- Error Example: If you see
WebLN not enabledin the console, ensure the Alby extension (v1.37.0) is installed and unlocked. Restart your browser if the extension fails to injectwindow.webln.
- Error Example: If you see
-
LNURL Approach: If WebLN isn’t available, fall back to LNURL:
const lnurl = "LNURL1..."; const response = await fetch(`https://github.com/lnbits/lnurlp/${lnurl}`); const { pr } = await response.json(); await window.webln.sendPayment(pr);- Gotcha: LNURL Rate Limits
Some LNURL endpoints enforce rate limits. Cache the invoice for 5 minutes to avoid hitting the limit:
const cacheKey = `lnurl-invoice-${lnurl}`; const cachedInvoice = localStorage.getItem(cacheKey);
- Gotcha: LNURL Rate Limits
Some LNURL endpoints enforce rate limits. Cache the invoice for 5 minutes to avoid hitting the limit:
-
-
Alby extension or NWC handles the actual payment
- NWC (Nostr Wallet Connect) is the recommended backend for self-hosted wallets. Configure NWC in Feedbin’s environment variables:
NWC_CONNECTION_STRING="nostr+walletconnect://..." - Watch Out: If NWC isn’t configured, payments will fail silently. Verify the connection with:
curl -X POST https://api.feedbin.com/v2/nwc/status \ -H "Authorization: Bearer ${NWC_CONNECTION_STRING}"- Expected output:
{"status":"connected"}
- Expected output:
- NWC (Nostr Wallet Connect) is the recommended backend for self-hosted wallets. Configure NWC in Feedbin’s environment variables:
Authority Deep Dive: How Feedbin’s Parser Works Feedbin’s parser (written in Ruby) uses the
nokogirigem (v1.15.4) to extract<podcast:value>tags. The parsing logic is inapp/services/feed_parser.rb:def parse_value_recipients(xml) xml.xpath("//podcast:valueRecipient").map do |recipient| { name: recipient["name"], type: recipient["type"], address: recipient["address"], split: recipient["split"].to_i } end end
- Gotcha: XPath Namespace Issues If your feed uses a custom namespace (e.g.,
pod:value), the parser won’t find the tags. Stick to the standardpodcast:namespace or update the XPath to include the namespace:xml.xpath("//pod:valueRecipient", "pod" => "https://podcastindex.org/namespace/1.0")
Feedbin’s podcast player already supports V4V, but the bounty specifically targets the web interface for blog subscriptions. Don’t assume the parser handles <podcast:value> for non-podcast feeds out of the box. Here’s what can go wrong:
8 Additional Caveats and Gotchas
- Split Percentages Must Sum to 100 If your
<podcast:valueRecipient>tags havesplit="50"andsplit="60", the parser will ignore the feed item entirely. Validate your splits with:<podcast:value type="lightning" method="lnaddress"> <podcast:valueRecipient split="70" ... /> <podcast:valueRecipient split="30" ... /> </podcast:value>
- Lightning Addresses Must Be Valid Feedbin’s parser validates Lightning addresses using the
lightning-addressgem (v0.2.0). If your address is malformed (e.g.,you@getalby.comwithout a domain), the feed item will be skipped. Test your address with:Expected output:curl -s https://guides.getalby.com/user-guide/alby-account/customize-your-lightning-address/use-your-own-domain-as-lightning-address | jq '.tag'"payRequest"
- Feedbin’s Cache May Delay Updates Feedbin caches feeds for up to 15 minutes. If you update your
<podcast:value>tag, it may take up to 15 minutes for the zap button to appear. Force a refresh with:curl -X POST https://api.feedbin.com/v2/feeds/refresh \ -u "email:password"
- WebLN May Not Work in Incognito Mode Some browser extensions (including Alby) don’t inject
window.weblnin incognito windows. Test in a regular window before deploying.
- BOLT11 Invoices Have Expiry Times If a user clicks the zap button but doesn’t complete the payment within 10 minutes (default expiry), the invoice becomes invalid. Regenerate the invoice if the user retries:
const invoice = await window.webln.makeInvoice({ amount: 1000 });
- NWC Connection May Drop If NWC loses connectivity, payments will fail. Monitor NWC’s status endpoint every 30 seconds:
setInterval(async () => { const status = await fetch("/nwc/status").then(r => r.json()); if (status.status !== "connected") { alert("NWC disconnected! Check your wallet."); } }, 30000);
- Feedbin’s API Rate Limits Feedbin’s API (v2) enforces rate limits of 60 requests per minute. If you’re building a zap button for a high-traffic feed, cache the parsed
<podcast:value>data locally:Rails.cache.fetch("feed:#{feed_id}:value_recipients", expires_in: 1.hour) do parse_value_recipients(xml) end
- Mobile Browsers May Not Support WebLN iOS Safari and Android Chrome don’t support WebLN natively. Users on mobile must use a wallet app (e.g., BlueWallet) to complete payments. Test on mobile before committing to WebLN.
What I Actually Use
- Feedbin: Self-hosted RSS reader (v2.47.1) running on Docker (v24.0.7) with PostgreSQL (v15.4)
- Alby: Lightning wallet (v1.37.0) with NWC support for payments
- Astro: Static site generator (v4.10.2) for RSS feed generation
- NWC: Nostr Wallet Connect (v0.3.0) for self-hosted wallet integration
- Browser: Firefox (v121.0) with Alby extension (v1.37.0) for WebLN testing
Further Reading
Feedbin V4V Integration
Lightning payments in RSS reader