lnbits/misc-aio/lnbits-websocket-guide.md
2025-10-14 09:49:08 +02:00

12 KiB

LNbits WebSocket Implementation Guide

Overview

LNbits provides real-time WebSocket connections for monitoring wallet status, payment confirmations, and transaction updates. This guide covers how to implement and use these WebSocket connections in your applications.

WebSocket Endpoints

1. Payment Monitoring WebSocket

  • URL: ws://localhost:5006/api/v1/ws/{wallet_inkey}
  • HTTPS: wss://your-domain.com/api/v1/ws/{wallet_inkey}
  • Purpose: Real-time payment notifications and wallet updates

2. Generic WebSocket Communication

  • URL: ws://localhost:5006/api/v1/ws/{item_id}
  • Purpose: Custom real-time communication channels

Client-Side Implementation

JavaScript/Browser Implementation

Basic WebSocket Connection

// Construct WebSocket URL
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
const websocketUrl = `${protocol}//${window.location.host}/api/v1/ws`

// Connect to payment monitoring
const ws = new WebSocket(`${websocketUrl}/${wallet.inkey}`)

// Handle incoming messages
ws.onmessage = (event) => {
  const data = JSON.parse(event.data)
  console.log('Received:', data)
  
  if (data.payment) {
    handlePaymentReceived(data.payment)
  }
}

// Handle connection events
ws.onopen = () => {
  console.log('WebSocket connected')
}

ws.onclose = () => {
  console.log('WebSocket disconnected')
}

ws.onerror = (error) => {
  console.error('WebSocket error:', error)
}

Using LNbits Built-in Event System

// Using the built-in LNbits event system
LNbits.events.onInvoicePaid(wallet, (data) => {
  if (data.payment) {
    console.log('Payment confirmed:', data.payment)
    
    // Update UI
    updateWalletBalance(data.payment.amount)
    showPaymentNotification(data.payment)
  }
})

Vue.js Implementation Example

// Vue component method
initWebSocket() {
  const protocol = location.protocol === 'http:' ? 'ws://' : 'wss://'
  const wsUrl = `${protocol}${document.domain}:${location.port}/api/v1/ws/${this.wallet.inkey}`
  
  this.ws = new WebSocket(wsUrl)
  
  this.ws.addEventListener('message', async ({ data }) => {
    const response = JSON.parse(data.toString())
    
    if (response.payment) {
      // Handle payment update
      await this.handlePaymentUpdate(response.payment)
    }
  })
  
  this.ws.addEventListener('open', () => {
    this.connectionStatus = 'connected'
  })
  
  this.ws.addEventListener('close', () => {
    this.connectionStatus = 'disconnected'
    // Implement reconnection logic
    setTimeout(() => this.initWebSocket(), 5000)
  })
}

Python Client Implementation

import asyncio
import websockets
import json

async def listen_to_wallet(wallet_inkey, base_url="ws://localhost:5006"):
    uri = f"{base_url}/api/v1/ws/{wallet_inkey}"
    
    try:
        async with websockets.connect(uri) as websocket:
            print(f"Connected to WebSocket: {uri}")
            
            async for message in websocket:
                data = json.loads(message)
                
                if 'payment' in data:
                    payment = data['payment']
                    print(f"Payment received: {payment['amount']} sat")
                    print(f"Payment hash: {payment['payment_hash']}")
                    
                    # Process payment
                    await handle_payment_received(payment)
                    
    except websockets.exceptions.ConnectionClosed:
        print("WebSocket connection closed")
    except Exception as e:
        print(f"WebSocket error: {e}")

async def handle_payment_received(payment):
    """Process incoming payment"""
    # Update database
    # Send notifications  
    # Update application state
    pass

# Run the WebSocket listener
if __name__ == "__main__":
    wallet_inkey = "your_wallet_inkey_here"
    asyncio.run(listen_to_wallet(wallet_inkey))

Node.js Client Implementation

const WebSocket = require('ws')

class LNbitsWebSocketClient {
  constructor(walletInkey, baseUrl = 'ws://localhost:5006') {
    this.walletInkey = walletInkey
    this.baseUrl = baseUrl
    this.ws = null
    this.reconnectInterval = 5000
  }
  
  connect() {
    const url = `${this.baseUrl}/api/v1/ws/${this.walletInkey}`
    this.ws = new WebSocket(url)
    
    this.ws.on('open', () => {
      console.log(`Connected to LNbits WebSocket: ${url}`)
    })
    
    this.ws.on('message', (data) => {
      try {
        const message = JSON.parse(data.toString())
        this.handleMessage(message)
      } catch (error) {
        console.error('Error parsing WebSocket message:', error)
      }
    })
    
    this.ws.on('close', () => {
      console.log('WebSocket connection closed. Reconnecting...')
      setTimeout(() => this.connect(), this.reconnectInterval)
    })
    
    this.ws.on('error', (error) => {
      console.error('WebSocket error:', error)
    })
  }
  
  handleMessage(message) {
    if (message.payment) {
      console.log('Payment received:', message.payment)
      this.onPaymentReceived(message.payment)
    }
  }
  
  onPaymentReceived(payment) {
    // Override this method to handle payments
    console.log(`Received ${payment.amount} sat`)
  }
  
  disconnect() {
    if (this.ws) {
      this.ws.close()
    }
  }
}

// Usage
const client = new LNbitsWebSocketClient('your_wallet_inkey_here')
client.onPaymentReceived = (payment) => {
  // Custom payment handling
  console.log(`Processing payment: ${payment.payment_hash}`)
}
client.connect()

Server-Side Implementation (LNbits Extensions)

Sending WebSocket Updates

from lnbits.core.services import websocket_manager

async def notify_wallet_update(wallet_inkey: str, payment_data: dict):
    """Send payment update to connected WebSocket clients"""
    message = {
        "payment": payment_data,
        "timestamp": int(time.time())
    }
    
    await websocket_manager.send(wallet_inkey, json.dumps(message))

# Example usage in payment processing
async def process_payment_confirmation(payment_hash: str):
    payment = await get_payment(payment_hash)
    
    if payment.wallet:
        await notify_wallet_update(payment.wallet, {
            "payment_hash": payment.payment_hash,
            "amount": payment.amount,
            "memo": payment.memo,
            "status": "confirmed"
        })

HTTP Endpoints for WebSocket Updates

# Send data via GET request
@router.get("/notify/{wallet_inkey}/{message}")
async def notify_wallet_get(wallet_inkey: str, message: str):
    await websocket_manager.send(wallet_inkey, message)
    return {"sent": True, "message": message}

# Send data via POST request  
@router.post("/notify/{wallet_inkey}")
async def notify_wallet_post(wallet_inkey: str, data: str):
    await websocket_manager.send(wallet_inkey, data)
    return {"sent": True, "data": data}

Message Format

Payment Notification Message

{
  "payment": {
    "payment_hash": "abc123...",
    "amount": 1000,
    "memo": "Test payment",
    "status": "confirmed",
    "timestamp": 1640995200,
    "fee": 1,
    "wallet_id": "wallet_uuid"
  }
}

Custom Message Format

{
  "type": "balance_update",
  "wallet_id": "wallet_uuid", 
  "balance": 50000,
  "timestamp": 1640995200
}

Best Practices

1. Connection Management

  • Implement automatic reconnection logic
  • Handle connection timeouts gracefully
  • Use exponential backoff for reconnection attempts

2. Error Handling

class WebSocketManager {
  constructor(walletInkey) {
    this.walletInkey = walletInkey
    this.maxReconnectAttempts = 10
    this.reconnectAttempts = 0
    this.reconnectDelay = 1000
  }
  
  connect() {
    try {
      this.ws = new WebSocket(this.getWebSocketUrl())
      this.setupEventHandlers()
    } catch (error) {
      this.handleConnectionError(error)
    }
  }
  
  handleConnectionError(error) {
    console.error('WebSocket connection error:', error)
    
    if (this.reconnectAttempts < this.maxReconnectAttempts) {
      this.reconnectAttempts++
      const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1)
      
      setTimeout(() => {
        console.log(`Reconnection attempt ${this.reconnectAttempts}`)
        this.connect()
      }, delay)
    } else {
      console.error('Max reconnection attempts reached')
    }
  }
}

3. Message Validation

function validatePaymentMessage(data) {
  if (!data.payment) return false
  
  const payment = data.payment
  return (
    typeof payment.payment_hash === 'string' &&
    typeof payment.amount === 'number' &&
    payment.amount > 0 &&
    ['pending', 'confirmed', 'failed'].includes(payment.status)
  )
}

4. Security Considerations

  • Use HTTPS/WSS in production
  • Validate wallet permissions before connecting
  • Implement rate limiting for WebSocket connections
  • Never expose admin keys through WebSocket messages

Testing WebSocket Connections

Using wscat (Command Line Tool)

# Install wscat
npm install -g wscat

# Connect to WebSocket
wscat -c ws://localhost:5006/api/v1/ws/your_wallet_inkey

# Test with SSL
wscat -c wss://your-domain.com/api/v1/ws/your_wallet_inkey

Browser Console Testing

// Open browser console and run:
const ws = new WebSocket('ws://localhost:5006/api/v1/ws/your_wallet_inkey')
ws.onmessage = (e) => console.log('Received:', JSON.parse(e.data))
ws.onopen = () => console.log('Connected')
ws.onclose = () => console.log('Disconnected')

Sending Test Messages

# Using curl to trigger WebSocket message
curl "http://localhost:5006/api/v1/ws/your_wallet_inkey/test_message"

# Using POST
curl -X POST "http://localhost:5006/api/v1/ws/your_wallet_inkey" \
  -H "Content-Type: application/json" \
  -d '"test message data"'

Troubleshooting

Common Issues

  1. Connection Refused

    • Verify LNbits server is running on correct port
    • Check firewall settings
    • Ensure WebSocket endpoint is enabled
  2. Authentication Errors

    • Verify wallet inkey is correct
    • Check wallet permissions
    • Ensure wallet exists and is active
  3. Message Not Received

    • Check WebSocket connection status
    • Verify message format
    • Test with browser dev tools
  4. Frequent Disconnections

    • Implement proper reconnection logic
    • Check network stability
    • Monitor server logs for errors

Debug Logging

// Enable verbose WebSocket logging
const ws = new WebSocket(wsUrl)
ws.addEventListener('open', (event) => {
  console.log('WebSocket opened:', event)
})
ws.addEventListener('close', (event) => {
  console.log('WebSocket closed:', event.code, event.reason)
})
ws.addEventListener('error', (event) => {
  console.error('WebSocket error:', event)
})

Production Deployment

Nginx Configuration

location /api/v1/ws/ {
    proxy_pass http://localhost:5006;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_read_timeout 86400;
}

SSL/TLS Configuration

server {
    listen 443 ssl http2;
    server_name your-domain.com;
    
    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;
    
    location /api/v1/ws/ {
        proxy_pass http://localhost:5006;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        # ... other headers
    }
}

Conclusion

LNbits WebSocket implementation provides a robust foundation for real-time wallet monitoring and payment processing. By following this guide, you can implement reliable WebSocket connections that enhance user experience with instant payment notifications and live wallet updates.

Remember to implement proper error handling, reconnection logic, and security measures when deploying to production environments.