Lightning Beginner 6 min read
BOLT11 Invoices
Lightning invoice format and parsing. Payment requests, amount encoding, expiry, and route hints.
bolt11 invoice payment-request bech32
BOLT11 Invoices
A BOLT11 invoice is a payment request—a string that encodes everything needed to make a Lightning payment. It’s the standard way to request payment on Lightning.
Why Invoices Matter for Agents
| Benefit | Description |
|---|---|
| Self-contained | All payment info in one string |
| Verifiable | Cryptographically signed |
| Expiring | Automatic invalidation |
| Parseable | Machine-readable format |
Invoice Structure
lnbc10u1pjnrfzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypq...
│ │ │ │
│ │ │ └─ Data (bech32)
│ │ └─ Timestamp
│ └─ Amount (10 microsatoshis)
└─ Network (mainnet)
Network Prefixes
| Prefix | Network |
|---|---|
lnbc | Bitcoin mainnet |
lntb | Bitcoin testnet |
lnbcrt | Bitcoin regtest |
lnsb | Bitcoin signet |
Amount Encoding
| Suffix | Multiplier | Example |
|---|---|---|
| (none) | 1 BTC | lnbc1... = 1 BTC |
m | 0.001 BTC | lnbc1m... = 1 mBTC |
u | 0.000001 BTC | lnbc1u... = 1 μBTC |
n | 0.000000001 BTC | lnbc1n... = 1 nBTC |
p | 0.000000000001 BTC | lnbc1p... = 1 pBTC |
Common amounts:
lnbc10u= 10 μBTC = 1,000 satslnbc1m= 1 mBTC = 100,000 satslnbc100n= 100 nBTC = 10 sats
Invoice Fields
Required Fields
| Field | Tag | Description |
|---|---|---|
| Payment hash | p | 32-byte hash of preimage |
| Timestamp | - | Unix timestamp of creation |
| Signature | - | Node’s signature |
Optional Fields
| Field | Tag | Description |
|---|---|---|
| Description | d | Human-readable purpose |
| Description hash | h | SHA256 of long description |
| Expiry | x | Seconds until expiration (default: 3600) |
| Min CLTV | c | Minimum CLTV delta (default: 18) |
| Fallback address | f | On-chain address if payment fails |
| Route hints | r | Private channel routing info |
| Features | 9 | Feature bit vector |
| Payee pubkey | n | Destination node (if not in signature recovery) |
Parsing an Invoice
Using Python
import bolt11
# Parse invoice string
invoice = bolt11.decode(
"lnbc10u1pj9nrfzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqf..."
)
print(f"Network: {invoice.currency}")
print(f"Amount: {invoice.amount_msat} msat")
print(f"Payment hash: {invoice.payment_hash}")
print(f"Description: {invoice.description}")
print(f"Expiry: {invoice.expiry} seconds")
print(f"Destination: {invoice.payee}")
Using JavaScript
import { decode } from 'bolt11';
const invoice = decode(
'lnbc10u1pj9nrfzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqf...'
);
console.log(`Amount: ${invoice.satoshis} sats`);
console.log(`Description: ${invoice.tags.find(t => t.tagName === 'description')?.data}`);
console.log(`Payment hash: ${invoice.tags.find(t => t.tagName === 'payment_hash')?.data}`);
Using LND CLI
lncli decodepayreq lnbc10u1pj9nrfzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqf...
Output:
{
"destination": "03abc...",
"payment_hash": "0001...",
"num_satoshis": "1000",
"timestamp": "1706745600",
"expiry": "3600",
"description": "Coffee payment",
"cltv_expiry": "40"
}
Creating an Invoice
Via LNbits API
curl -X POST https://legend.lnbits.com/api/v1/payments \
-H "X-Api-Key: $INVOICE_KEY" \
-H "Content-Type: application/json" \
-d '{
"out": false,
"amount": 1000,
"memo": "Payment for service",
"expiry": 3600
}'
Response:
{
"payment_hash": "abc123...",
"payment_request": "lnbc10u1pj9nrfz..."
}
Via LND
invoice = lnd.add_invoice(
value=1000, # satoshis
memo="Payment for service",
expiry=3600 # seconds
)
print(f"Invoice: {invoice.payment_request}")
print(f"Payment hash: {invoice.r_hash.hex()}")
Invoice States
┌──────────┐
│ Open │ ─── payment received ───→ ┌──────────┐
└──────────┘ │ Paid │
│ └──────────┘
│
└─── time > expiry ──────────────→ ┌──────────┐
│ Expired │
└──────────┘
Checking Invoice Status
# Via LNbits
response = requests.get(
f"https://legend.lnbits.com/api/v1/payments/{payment_hash}",
headers={"X-Api-Key": INVOICE_KEY}
)
status = response.json()
print(f"Paid: {status['paid']}")
Route Hints
Private channels aren’t in the gossip network. Route hints tell senders how to reach the recipient:
Invoice with route hints:
Route 1:
- Pubkey: 03abc...
- Short channel ID: 123x456x0
- Fee base: 1000 msat
- Fee rate: 100 ppm
- CLTV delta: 40
Adding Route Hints
# LND: Invoice for private channel
invoice = lnd.add_invoice(
value=1000,
memo="Private channel payment",
private=True # Includes route hints automatically
)
Amount-less Invoices
Some invoices don’t specify an amount—the payer decides:
lnbc1pj9nrfzpp5... # No amount suffix
Use cases:
- Donations
- Tips
- Variable pricing
Security note: Amount-less invoices should verify payment amounts application-side.
Expiry Best Practices
| Use Case | Expiry | Reason |
|---|---|---|
| Point of sale | 60-300s | Immediate payment expected |
| E-commerce | 3600s (1 hour) | Checkout flow |
| Subscriptions | 86400s (1 day) | User convenience |
| Donations | 604800s (1 week) | Maximum flexibility |
Default: 3600 seconds (1 hour)
Invoice Features (Bits)
Feature bits indicate supported capabilities:
| Bit | Feature | Required? |
|---|---|---|
| 8/9 | var_onion_optin | Variable onion |
| 14/15 | payment_secret | Payment secret |
| 16/17 | basic_mpp | Multi-path payments |
Error Handling
Common Issues
| Error | Cause | Solution |
|---|---|---|
| Invoice expired | Past expiry time | Request new invoice |
| Unknown payment hash | Already paid or invalid | Check payment status |
| Amount mismatch | Sent wrong amount | Use exact invoice amount |
| Route not found | No path to destination | Request route hints |
Validating Before Payment
def validate_invoice(invoice_str: str, expected_amount: int) -> bool:
invoice = bolt11.decode(invoice_str)
# Check expiry
if invoice.timestamp + invoice.expiry < time.time():
raise ValueError("Invoice expired")
# Check amount (if specified)
if invoice.amount_msat and invoice.amount_msat != expected_amount * 1000:
raise ValueError("Amount mismatch")
# Check network
if invoice.currency != "bc": # mainnet
raise ValueError("Wrong network")
return True
BOLT12 Offers (Preview)
BOLT12 is an upcoming improvement:
| Feature | BOLT11 | BOLT12 |
|---|---|---|
| Reusable | No | Yes |
| Payer privacy | Limited | Blinded paths |
| Amount negotiation | No | Yes |
| Subscriptions | No | Built-in |
BOLT12 is still in draft status but supported by some nodes (CLN).
Related Topics
- HTLCs - What happens after paying
- Routing - How payments find paths
- BOLT-11 - Full specification
- API: LNbits - Creating invoices via API
Machine-Readable Summary
{
"topic": "bolt11-invoices",
"key_concepts": [
"payment-request",
"bech32-encoding",
"expiry",
"route-hints"
],
"fields": {
"required": ["payment_hash", "timestamp", "signature"],
"optional": ["description", "expiry", "fallback", "route_hints"]
},
"prefixes": {
"mainnet": "lnbc",
"testnet": "lntb",
"regtest": "lnbcrt"
}
}