Learn how to implement Value-4-Value payments in Feedbin using podcast:value tags and the Alby bounty for a zap button.

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:

  1. 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 item in the browser console, double-check that the <podcast:value> tag is nested under each <item> in your RSS feed.
  2. Store the Lightning address or keysend destination per item

    • Feedbin’s backend (Ruby on Rails v7.1.3) stores parsed metadata in a feed_items table with a value_recipients JSON 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_recipients column 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 '[]';
  3. 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: none on .zap-button). Override with:
      .zap-button { display: inline-block !important; }
  4. 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 enabled in the console, ensure the Alby extension (v1.37.0) is installed and unlocked. Restart your browser if the extension fails to inject window.webln.
    • 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);
  5. 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"}

Authority Deep Dive: How Feedbin’s Parser Works Feedbin’s parser (written in Ruby) uses the nokogiri gem (v1.15.4) to extract <podcast:value> tags. The parsing logic is in app/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 standard podcast: 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

  1. Split Percentages Must Sum to 100 If your <podcast:valueRecipient> tags have split="50" and split="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>
  1. Lightning Addresses Must Be Valid Feedbin’s parser validates Lightning addresses using the lightning-address gem (v0.2.0). If your address is malformed (e.g., you@getalby.com without a domain), the feed item will be skipped. Test your address with:
    curl -s https://guides.getalby.com/user-guide/alby-account/customize-your-lightning-address/use-your-own-domain-as-lightning-address | jq '.tag'
    Expected output: "payRequest"
  1. 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"
  1. WebLN May Not Work in Incognito Mode Some browser extensions (including Alby) don’t inject window.webln in incognito windows. Test in a regular window before deploying.
  1. 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 });
  1. 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);
  1. 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
  1. 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

Flow

Feedbin V4V Integration

Lightning payments in RSS reader

1
Parse Feed Extract podcast:value metadata
2
Store Metadata Save per feed item
3
Render Zap Button Add UI element
4
Initiate Payment WebLN/LNURL request
5
Handle Payment NWC processes transaction