External health monitoring service for lnp2pBot - Receives push-based heartbeats and sends intelligent alerts when the bot experiences issues.
This service is part of the lnp2pBot Health Monitoring System described in issue #749. It provides:
- 🏥 Real-time health monitoring for lnp2pBot instances
- 🚨 Intelligent alerting with spam protection and escalation
- 📊 Status dashboard with detailed health metrics
- ⚙️ Easy deployment on Digital Ocean App Platform
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ lnp2pBot │───▶│ Monitor Service │───▶│ Telegram Alert │
│ (sends metrics) │ │ (this project) │ │ to Admins │
└─────────────────┘ └──────────────────┘ └─────────────────┘
│ │
▼ ▼
┌─────────────────┐ ┌──────────────────┐
│ UptimeRobot │ │ UptimeRobot │
│ (optional) │ │ (monitor health) │
└─────────────────┘ └──────────────────┘
- Node.js ≥ 18.0.0
- npm or yarn
- Telegram bot token (for alerts)
-
Clone the repository:
git clone https://github.com/lnp2pBot/lnp2pbot-monitor.git cd lnp2pbot-monitor -
Install dependencies:
npm install
-
Configure environment:
cp .env.example .env # Edit .env with your configuration -
Start the service:
npm start
-
Configure your bot: Add to your lnp2pBot
.env:MONITOR_HEARTBEAT_URL='http://localhost:3000' MONITOR_TOKEN='your-secret-token'
| Variable | Required | Default | Description |
|---|---|---|---|
PORT |
No | 3000 |
Server port |
TELEGRAM_BOT_TOKEN |
Yes | - | Bot token for sending alerts |
ADMIN_CHAT_ID |
Yes | - | Comma-separated list of Telegram chat IDs for alerts |
AUTH_TOKEN |
No | - | Optional authentication token |
MISSING_HEARTBEAT_THRESHOLD |
No | 6 |
Minutes before missing heartbeat alert |
CRITICAL_ALERT_THROTTLE |
No | 5 |
Minutes between critical alerts |
WARNING_ALERT_THROTTLE |
No | 30 |
Minutes between warning alerts |
LOG_LEVEL |
No | info |
Logging level (error, warn, info, debug) |
# Required
TELEGRAM_BOT_TOKEN=1234567890:ABCdefGHIjklMNOpqrsTUVwxyz
ADMIN_CHAT_ID=-1001234567890,477262720
# Optional
AUTH_TOKEN=super-secret-token-here
MISSING_HEARTBEAT_THRESHOLD=6
LOG_LEVEL=infoThe service receives and analyzes comprehensive health data from lnp2pBot:
{
"bot": "lnp2pBot",
"timestamp": 1708516800000,
"uptime": 3600,
"memory": {
"rss": 134217728,
"heapUsed": 67108864,
"heapTotal": 134217728
},
"processId": 1234,
"nodeEnv": "production",
"dbConnected": true,
"dbState": "connected",
"lightningConnected": true,
"lightningInfo": {
"alias": "my-lightning-node",
"synced_to_chain": true,
"synced_to_graph": true,
"active_channels_count": 5,
"peers_count": 10
}
}- 🚨 MongoDB disconnected
- 🚨 Lightning node disconnected
- 🚨 Bot missing heartbeats (6+ minutes)
⚠️ Lightning node not synced to chain⚠️ Lightning node not synced to graph⚠️ No active Lightning channels⚠️ High memory usage (>1GB)
Detects the worst-case failure for a P2P bot: the node paying out satoshis that never came in. When enabled, the monitor periodically lists every settled outgoing payment made by the bot's LND node and verifies each one is backed by data in the bot's database:
- Buyer payouts — matched by payment hash against
Order.payout_hash(with a fallback match on the paid invoice for older orders). The order's hold invoice (Order.hash) must exist on the node, be settled, and have received at least the amount paid out. The paid amount must equalorder.amount. - Community earnings withdrawals — matched by payment hash against
PendingPaymentrecords with acommunity_id.
Matching is done strictly by payment hash — never by time — so a payout that
settles days after the seller released the funds (e.g., a routing failure
retried with a new invoice via /setinvoice) still reconciles against the
original hold invoice.
Any settled outgoing payment that fails these checks triggers a critical
Telegram alert including the amount, destination pubkey, payment hash,
invoice, and timestamps. Unmatched payments are re-classified once after a
60-second in-pass recheck before alerting: LND can settle a payout seconds
before the bot persists the backing record, and the recheck absorbs that
race without delaying real alerts — a theft alerts at most one interval
plus 60 seconds after settling. Each payment hash is alerted only once. State
(baseline, checkpoint, alert history) is persisted in the bot's MongoDB
(monitor_reconciliation_state collection) so it survives redeploys and
ephemeral filesystems; data/reconciliation-state.json is kept as a local
fallback when the Mongo credentials are read-only.
Payments are classified page by page as they stream from LND, and pagination stops once a whole page was created more than 14 days before the baseline (the margin covers payments that sit in flight across the baseline before settling) — the first pass never loads the node's full payment history into memory.
The reconciler also watches itself: if it cannot start (MongoDB or LND unreachable) it retries every 5 minutes and alerts the admins (throttled to once per hour), and if reconciliation passes fail repeatedly while running, a critical alert is sent as well. A reconciler that cannot run must never fail silently.
To enable, set MONGO_URI (read-only Mongo user), LND_GRPC_HOST, and
LND_MACAROON_BASE64 (read-only macaroon — only ListPayments and
LookupInvoice are needed). See .env.example for all options. Check
reconciliation status at GET /api/reconciliation.
Note on database load: each reconciled payment runs
findOnelookups onorders.payout_hash,orders.buyer_invoice_paid,orders.buyer_invoice,pendingpayments.hashandpendingpayments.payment_request. Of these, onlyorders.hashis indexed by the bot today. With the default 10-minute interval only new payments are examined, so the load is minimal, but for large databases consider creating indexes on those fields in the bot's MongoDB.
Access the status dashboard at: http://your-monitor-url/
Example response:
{
"status": "✅ Healthy",
"lastSeen": "2026-02-21T14:10:00.000Z",
"uptime": "15d 3h",
"memory": "245MB",
"database": "✅ Connected",
"lightning": {
"status": "✅ Connected",
"alias": "my-lightning-node",
"channels": 5,
"synced_chain": "✅",
"synced_graph": "✅"
}
}Receives health data from lnp2pBot.
Headers:
Content-Type: application/jsonAuthorization: Bearer <token>(ifAUTH_TOKENis configured)
Request body: Health metrics JSON (see example above)
Response: 200 OK with {"status": "received"}
Status dashboard with current bot health.
Response: JSON object with health summary
Health check for this monitor service (for UptimeRobot).
Response: 200 OK with {"status": "ok", "monitoring": "active"}
Payment reconciliation status.
Response: {"enabled": false} when reconciliation is not configured,
otherwise {"enabled": true, "baselineAt": ..., "lastPaymentIndex": ..., "alertedPayments": ..., "isRunning": ..., "lastPassAt": ..., "stateStorage": ..., "lastError": ...} (isRunning is true only while
a pass is executing — passes take seconds and run every
RECONCILIATION_INTERVAL minutes; lastPassAt is when the last pass
finished; lastError is null when the last pass succeeded;
stateStorage is "mongodb" when state is persisted in the bot's database
or "file" when running on the local JSON fallback).
This service is designed for easy deployment on Digital Ocean App Platform.
-
Fork this repository to your GitHub account
-
Create a new App in Digital Ocean:
- Connect your GitHub repository
- Select "Web Service" component
- Set environment variables (see Configuration section)
-
Environment Variables in DO App Platform:
TELEGRAM_BOT_TOKEN=your_bot_token ADMIN_CHAT_ID=chat_id1,chat_id2 AUTH_TOKEN=your_secret_token -
App Specification (auto-generated):
name: lnp2pbot-monitor services: - name: web source_dir: / github: repo: your-username/lnp2pbot-monitor branch: main run_command: npm start environment_slug: node-js instance_count: 1 instance_size_slug: basic-xxs
Cost: ~$5/month for basic monitoring
# Build
docker build -t lnp2pbot-monitor .
# Run
docker run -d \
-p 3000:3000 \
-e TELEGRAM_BOT_TOKEN=your_token \
-e ADMIN_CHAT_ID=chat_id1,chat_id2 \
lnp2pbot-monitor# Deploy
git push heroku main
# Set environment variables
heroku config:set TELEGRAM_BOT_TOKEN=your_token
heroku config:set ADMIN_CHAT_ID=chat_id1,chat_id2lnp2pbot-monitor/
├── src/
│ ├── monitor.js # Core monitoring logic
│ ├── alerts.js # Alert management
│ ├── dashboard.js # Status dashboard
│ └── utils.js # Utility functions
├── config/
│ └── index.js # Configuration management
├── tests/
│ ├── monitor.test.js # Unit tests
│ └── integration.test.js # Integration tests
├── docs/
│ ├── api.md # API documentation
│ └── deployment.md # Deployment guide
├── .env.example # Example environment file
├── .gitignore
├── Dockerfile
├── package.json
├── server.js # Entry point
└── README.md
npm start # Start the service
npm run dev # Start with auto-reload (nodemon)
npm test # Run tests
npm run lint # Lint code
npm run format # Format code# Unit tests
npm test
# Integration tests (requires bot running)
npm run test:integration
# Test with curl
curl -X POST http://localhost:3000/api/heartbeat \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-token" \
-d '{"bot":"test","timestamp":1708516800000}'Monitor this service with UptimeRobot:
- Create HTTP(s) monitor in UptimeRobot
- URL:
https://your-monitor-url/health - Interval: 5 minutes
- Alert when: Status code ≠ 200
The /health endpoint provides service health:
curl https://your-monitor-url/health
# Response: {"status": "ok", "monitoring": "active"}-
Check bot configuration:
# In bot .env MONITOR_HEARTBEAT_URL=https://your-monitor-url MONITOR_TOKEN=your-secret-token -
Check bot logs for heartbeat errors
-
Verify monitor service is accessible
- Verify Telegram bot token and chat ID
- Check monitor service logs
- Test Telegram bot manually:
curl "https://api.telegram.org/bot<TOKEN>/sendMessage?chat_id=<CHAT_ID>&text=Test"
Increase memory threshold in src/monitor.js:
if (metrics.memory.rss > 2048 * 1024 * 1024) { // 2GB instead of 1GBService logs include:
[INFO] Health monitoring started - waiting for heartbeats...
[DEBUG] Heartbeat received from lnp2pBot - all systems healthy
[WARN] Lightning node not synced to chain
[ERROR] Critical: Bot missing heartbeats for 8 minutesSet LOG_LEVEL=debug for verbose logging.
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Make your changes
- Add tests for new functionality
- Ensure tests pass:
npm test - Commit:
git commit -m 'Add amazing feature' - Push:
git push origin feature/amazing-feature - Open a Pull Request
MIT License - see LICENSE file.
- lnp2pBot/bot - The main lnp2pBot Telegram bot
- lnp2pBot/mostro-watchdog - Telegram bot for Mostro dispute notifications
- Issues: GitHub Issues
- Telegram: lnp2pBot Help Group
- Email: Contact via GitHub issues
Made with ❤️ for the Bitcoin Lightning Network community
