← All Posts

Add Lightning Payments to a React App with Bitcoin Connect

2026-02-09 — lightning, tutorial, alby, bitcoin-connect, react

What is Bitcoin Connect?

Bitcoin Connect is an open-source library from Alby that adds Lightning wallet connectivity to any web app. Users click a button, connect their wallet (Alby Hub, NWC, or any WebLN-compatible wallet), and your app can send and receive Lightning payments.

No wallet-specific code. No custody. The user brings their own wallet, and Bitcoin Connect handles the protocol layer.

What You'll Build

A React app that:

  1. Shows a "Connect Wallet" button
  2. Creates Lightning invoices and displays a payment modal
  3. Sends outgoing payments via the connected wallet
  4. Persists the wallet connection across page reloads

Prerequisites

Step 1: Install Bitcoin Connect

npm install @getalby/bitcoin-connect-react

For a plain HTML/JS project, use the web components package instead:

npm install @getalby/bitcoin-connect

Step 2: Initialize Bitcoin Connect

Call init() once, early in your app's lifecycle. This sets up the global configuration that all Bitcoin Connect components use.

// src/main.tsx (or App.tsx)
import { init } from '@getalby/bitcoin-connect-react';

init({
  appName: 'My Lightning App',   // shown to the user during connection
  showBalance: true,              // display wallet balance after connecting
  persistConnection: true,        // remember the wallet across sessions
});

Configuration options:

Step 3: Add the Connect Button

The <Button /> component renders a connect/disconnect button. When clicked, it opens a modal where the user picks their wallet.

import { Button } from '@getalby/bitcoin-connect-react';

function App() {
  return (
    <div>
      <h1>My Lightning App</h1>
      <Button
        onConnect={(provider) => {
          console.log('Wallet connected!', provider);
        }}
        onDisconnect={() => {
          console.log('Wallet disconnected');
        }}
      />
    </div>
  );
}

The provider object implements the WebLN interface. You can use it to send payments, create invoices, and check balances.

Step 4: Accept Payments with PayButton

The <PayButton /> shows a payment modal for a specific invoice. Use this when you want users to pay for something.

import { PayButton } from '@getalby/bitcoin-connect-react';

function PaymentPage({ invoice }) {
  return (
    <PayButton
      invoice={invoice}
      onPaid={(response) => {
        console.log('Payment confirmed! Preimage:', response.preimage);
        // Grant access, ship the product, etc.
      }}
    />
  );
}

The payment modal handles everything: QR code, copy button, wallet payment prompt, and confirmation. Your code only needs to react to the onPaid callback.

Step 5: Create Invoices Programmatically

For dynamic pricing (tips, donations, pay-per-use), create invoices on the fly using the launchPaymentModal function:

import { launchPaymentModal } from '@getalby/bitcoin-connect-react';

async function handleTip() {
  // Create an invoice server-side (your API)
  const res = await fetch('/api/create-invoice', {
    method: 'POST',
    body: JSON.stringify({ amount: 1000, memo: 'Tip for great content' }),
  });
  const { invoice } = await res.json();

  // Launch the payment modal
  const { setPaid } = launchPaymentModal({
    invoice,
    onPaid: ({ preimage }) => {
      console.log('Tip received!', preimage);
    },
    onCancelled: () => {
      console.log('User cancelled');
    },
  });
}

Step 6: Send Payments from the Connected Wallet

Once a user connects their wallet, you can send outgoing payments using the provider:

import { requestProvider } from '@getalby/bitcoin-connect-react';

async function payInvoice(bolt11Invoice) {
  try {
    const provider = await requestProvider();
    const result = await provider.sendPayment(bolt11Invoice);
    console.log('Paid! Preimage:', result.preimage);
    return result;
  } catch (err) {
    console.error('Payment failed:', err);
    throw err;
  }
}

The requestProvider() call will prompt the user to connect if they haven't already. Once connected, sendPayment() sends the payment through their wallet.

Step 7: Listen for Connection State

React to connection state changes anywhere in your app:

import { onConnected, onDisconnected } from '@getalby/bitcoin-connect-react';

// Fires whenever a wallet connects
onConnected((provider) => {
  console.log('Connected with provider:', provider);
});

// Fires when the wallet disconnects
onDisconnected(() => {
  console.log('Disconnected');
});

Next.js: Dynamic Import

Bitcoin Connect uses browser APIs that don't work during server-side rendering. In Next.js, use dynamic imports:

import dynamic from 'next/dynamic';

const BitcoinConnectButton = dynamic(
  () => import('@getalby/bitcoin-connect-react').then((mod) => mod.Button),
  { ssr: false }
);

const BitcoinConnectPayButton = dynamic(
  () => import('@getalby/bitcoin-connect-react').then((mod) => mod.PayButton),
  { ssr: false }
);

// Use in your component:
// <BitcoinConnectButton onConnect={...} />
// <BitcoinConnectPayButton invoice={invoice} onPaid={...} />

Call init() inside a useEffect to ensure it runs client-side only:

import { useEffect } from 'react';

export default function Layout({ children }) {
  useEffect(() => {
    import('@getalby/bitcoin-connect-react').then(({ init }) => {
      init({ appName: 'My Next.js App', persistConnection: true });
    });
  }, []);

  return <div>{children}</div>;
}

Complete Example: Tip Jar

Here's a minimal tip jar component that creates invoices and handles payments:

import { useState } from 'react';
import { Button, launchPaymentModal } from '@getalby/bitcoin-connect-react';

function TipJar() {
  const [amount, setAmount] = useState(1000);

  async function handleTip() {
    // Replace with your actual invoice creation endpoint
    const res = await fetch('/api/invoice', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ amount, memo: 'Lightning tip' }),
    });
    const { payment_request } = await res.json();

    launchPaymentModal({
      invoice: payment_request,
      onPaid: () => alert('Thanks for the tip!'),
    });
  }

  return (
    <div>
      <Button />
      <div style={{ marginTop: '1rem' }}>
        {[100, 1000, 5000].map((sats) => (
          <button
            key={sats}
            onClick={() => { setAmount(sats); handleTip(); }}
            style={{ margin: '0 0.5rem', padding: '0.5rem 1rem' }}
          >
            {sats} sats
          </button>
        ))}
      </div>
    </div>
  );
}

Available Components

ComponentPurpose
<Button />Connect/disconnect wallet (opens modal)
<PayButton />Pay a specific invoice (opens payment modal)
<Connect />Inline wallet connection UI (no modal)
<Payment />Inline payment UI (no modal)

What's Next

Bitcoin Connect gives your users wallet choice without vendor lock-in. One library, every Lightning wallet, zero custody.

Found this useful?

Send a tip via Lightning. One click, no account needed.

Tip 100 sats ⚡