Lightning Python
Executable
Jan 31, 2026
Pay Lightning Invoices with LNbits
Send Lightning payments by paying BOLT11 invoices via LNbits API
#lnbits
#payment
#bolt11
#api
Overview
Pay Lightning invoices to send instant Bitcoin payments. This requires an Admin key (not just Invoice key) because it spends funds.
The Code
"""
Lightning Invoice Payer
Send payments via LNbits API
Requirements:
- requests (pip install requests)
- LNbits instance with Admin key
Environment variables:
- LNBITS_URL: Your LNbits instance URL
- LNBITS_ADMIN_KEY: Admin API key (required for sending)
SECURITY: Admin key can spend all funds. Never expose it.
"""
import os
import requests
from typing import Optional
from dataclasses import dataclass
# Configuration
LNBITS_URL = os.getenv("LNBITS_URL", "https://legend.lnbits.com")
LNBITS_ADMIN_KEY = os.getenv("LNBITS_ADMIN_KEY", "")
@dataclass
class PaymentResult:
"""Result of a Lightning payment."""
success: bool
payment_hash: str
preimage: Optional[str]
fee_msat: int
error: Optional[str]
def get_wallet_balance() -> dict:
"""
Get current wallet balance.
Returns:
dict with balance in sats and msat
"""
if not LNBITS_ADMIN_KEY:
raise ValueError("LNBITS_ADMIN_KEY not set")
headers = {"X-Api-Key": LNBITS_ADMIN_KEY}
response = requests.get(
f"{LNBITS_URL}/api/v1/wallet",
headers=headers,
timeout=10
)
response.raise_for_status()
data = response.json()
balance_msat = data.get("balance", 0)
return {
"balance_msat": balance_msat,
"balance_sats": balance_msat // 1000,
"name": data.get("name", "Unknown")
}
def decode_invoice(bolt11: str) -> dict:
"""
Decode a BOLT11 invoice to inspect before paying.
Args:
bolt11: BOLT11 invoice string
Returns:
dict with invoice details
"""
if not LNBITS_ADMIN_KEY:
raise ValueError("LNBITS_ADMIN_KEY not set")
headers = {"X-Api-Key": LNBITS_ADMIN_KEY}
response = requests.post(
f"{LNBITS_URL}/api/v1/payments/decode",
headers=headers,
json={"data": bolt11},
timeout=10
)
response.raise_for_status()
data = response.json()
return {
"payment_hash": data.get("payment_hash"),
"amount_msat": data.get("amount_msat", 0),
"amount_sats": data.get("amount_msat", 0) // 1000,
"description": data.get("description", ""),
"expiry": data.get("expiry"),
"timestamp": data.get("date")
}
def pay_invoice(
bolt11: str,
max_fee_sats: Optional[int] = None
) -> PaymentResult:
"""
Pay a Lightning invoice.
Args:
bolt11: BOLT11 invoice string
max_fee_sats: Maximum routing fee (optional)
Returns:
PaymentResult with success status and details
Raises:
ValueError: If API key not configured
"""
if not LNBITS_ADMIN_KEY:
raise ValueError("LNBITS_ADMIN_KEY not set")
headers = {
"X-Api-Key": LNBITS_ADMIN_KEY,
"Content-Type": "application/json"
}
payload = {
"out": True, # True = outgoing payment
"bolt11": bolt11
}
if max_fee_sats:
payload["max_fee"] = max_fee_sats * 1000 # Convert to msat
try:
response = requests.post(
f"{LNBITS_URL}/api/v1/payments",
headers=headers,
json=payload,
timeout=60 # Payments can take time to route
)
response.raise_for_status()
data = response.json()
return PaymentResult(
success=True,
payment_hash=data["payment_hash"],
preimage=data.get("preimage"),
fee_msat=data.get("fee", 0),
error=None
)
except requests.HTTPError as e:
error_msg = str(e)
try:
error_data = e.response.json()
error_msg = error_data.get("detail", str(e))
except:
pass
return PaymentResult(
success=False,
payment_hash="",
preimage=None,
fee_msat=0,
error=error_msg
)
def safe_pay(
bolt11: str,
max_amount_sats: int,
max_fee_percent: float = 1.0
) -> PaymentResult:
"""
Pay an invoice with safety checks.
Args:
bolt11: BOLT11 invoice string
max_amount_sats: Maximum amount willing to pay
max_fee_percent: Maximum fee as percentage of amount
Returns:
PaymentResult
"""
# Check balance first
wallet = get_wallet_balance()
print(f"Wallet balance: {wallet['balance_sats']} sats")
# Decode invoice
invoice = decode_invoice(bolt11)
amount = invoice["amount_sats"]
print(f"Invoice amount: {amount} sats")
print(f"Description: {invoice['description']}")
# Safety checks
if amount > max_amount_sats:
return PaymentResult(
success=False,
payment_hash="",
preimage=None,
fee_msat=0,
error=f"Amount {amount} exceeds max {max_amount_sats}"
)
if amount > wallet["balance_sats"]:
return PaymentResult(
success=False,
payment_hash="",
preimage=None,
fee_msat=0,
error=f"Insufficient balance: {wallet['balance_sats']} < {amount}"
)
# Calculate max fee
max_fee = int(amount * max_fee_percent / 100)
print(f"Max fee: {max_fee} sats ({max_fee_percent}%)")
# Execute payment
return pay_invoice(bolt11, max_fee_sats=max_fee)
# Example usage
if __name__ == "__main__":
if not LNBITS_ADMIN_KEY:
print("Set LNBITS_ADMIN_KEY environment variable")
print("WARNING: Admin key can spend funds. Keep it secret!")
exit(1)
# Example invoice (replace with real one)
test_invoice = input("Enter BOLT11 invoice: ").strip()
if not test_invoice:
# Just show balance if no invoice
wallet = get_wallet_balance()
print(f"\nWallet: {wallet['name']}")
print(f"Balance: {wallet['balance_sats']:,} sats")
exit(0)
print("\n=== Decoding Invoice ===")
try:
decoded = decode_invoice(test_invoice)
print(f"Amount: {decoded['amount_sats']} sats")
print(f"Description: {decoded['description']}")
print(f"Hash: {decoded['payment_hash'][:32]}...")
# Confirm before paying
confirm = input("\nPay this invoice? (yes/no): ").strip().lower()
if confirm != "yes":
print("Payment cancelled")
exit(0)
print("\n=== Sending Payment ===")
result = safe_pay(
bolt11=test_invoice,
max_amount_sats=100000, # Max 100k sats
max_fee_percent=1.0 # Max 1% fee
)
if result.success:
print(f"\nPayment successful!")
print(f"Preimage: {result.preimage}")
print(f"Fee: {result.fee_msat} msat")
else:
print(f"\nPayment failed: {result.error}")
except Exception as e:
print(f"Error: {e}")
Usage
# Set environment variables
export LNBITS_URL="https://your-lnbits.com"
export LNBITS_ADMIN_KEY="your-admin-key" # KEEP SECRET!
# Install and run
pip install requests
python pay_invoice.py
Example Output
=== Decoding Invoice ===
Amount: 1000 sats
Description: Coffee payment
Hash: abc123def456789...
Pay this invoice? (yes/no): yes
=== Sending Payment ===
Wallet balance: 50000 sats
Invoice amount: 1000 sats
Description: Coffee payment
Max fee: 10 sats (1.0%)
Payment successful!
Preimage: xyz789abc123...
Fee: 1200 msat
Agent Notes
Security is critical:
- Admin key = full access to funds
- Never log or expose the admin key
- Use environment variables, not hardcoded values
- Consider using a dedicated wallet with limited funds
Pre-payment checks:
- Decode invoice first to verify amount
- Check wallet balance
- Validate amount against your limits
- Set max fee to avoid routing attacks
Payment failures: Common causes are insufficient balance, expired invoice, routing failures, or the recipient being offline.