Heartbeat plugin
Introduction
Section titled “Introduction”@warden/heartbeat adds health monitoring to your Warden bot by sending periodic pings to an external status service. If the pings stop, the service knows something is wrong and can alert you. It’s a simple, effective way to know your bot is actually running — because finding out your bot is down when users need it most is not the experience you want.
The plugin works with popular uptime monitoring services like Uptime Kuma, Betterstack (formerly Better Uptime), and Healthchecks.io. If your service accepts an HTTP ping, it works with this plugin.
Installation
Section titled “Installation”pnpm add @warden/heartbeatThe peer dependencies are:
| Package | Description |
|---|---|
@warden/core | Warden framework (you already have this) |
No additional drivers or adapters needed — the plugin makes plain HTTP requests.
Register HeartbeatServiceProvider in your bootstrap file:
import {Bot} from '@warden/core';import {HeartbeatServiceProvider} from '@warden/heartbeat';
new Bot() .discover(d => d .interactions('./src/{commands,buttons,modals,menus}/**/*.ts') .events('./src/events/**/*.ts')) .intents(i => i.defaults()) .partials(p => p.defaults()) .plugins([HeartbeatServiceProvider]) .start();Then configure the heartbeat URL and interval:
import {env} from '@warden/core';
export default { url: env('HEARTBEAT_URL'), interval: env.number('HEARTBEAT_INTERVAL', 60),};And set the environment variables:
HEARTBEAT_URL=https://uptime.example.com/api/push/your-monitor-idHEARTBEAT_INTERVAL=60That’s the entire setup. The plugin starts pinging during boot() and stops cleanly during shutdown().
How it works
Section titled “How it works”The heartbeat is straightforward: at a fixed interval, the plugin sends an HTTP GET request to your configured URL. If the request succeeds, your monitoring service records that the bot is alive. If the request stops arriving (because the bot crashed, the server went down, or the process was killed), the monitoring service notices the gap and triggers an alert.
Bot running → ping → ping → ping → ping → ...Bot crashes → ping → ping → (silence)Monitoring service → ⚠ alert triggeredThe interval is configurable, but 60 seconds is a sensible default. Most monitoring services expect pings within a grace period (typically 2-3x your interval), so a 60-second interval gives you a 2-3 minute detection window.
Supported services
Section titled “Supported services”The heartbeat plugin works with any service that accepts an HTTP ping at a URL. Here are the most popular ones and how to set them up:
Uptime Kuma is a self-hosted monitoring tool. Create a new monitor of type “Push” and use the provided URL:
HEARTBEAT_URL=https://your-kuma-instance.com/api/push/abc123?status=up&msg=OKBetterstack (formerly Better Uptime) is a hosted monitoring platform. Create a heartbeat monitor and use the provided URL:
HEARTBEAT_URL=https://uptime.betterstack.com/api/v1/heartbeat/your-heartbeat-idHealthchecks.io is a cron job and heartbeat monitoring service. Create a new check and use the ping URL:
HEARTBEAT_URL=https://hc-ping.com/your-check-uuidIf you’re using a different service, or even a custom endpoint, just set the URL. The plugin sends a plain GET request with no body or special headers:
HEARTBEAT_URL=https://your-custom-endpoint.com/health/modbotCustom health checks
Section titled “Custom health checks”By default, the heartbeat plugin just pings the URL on a timer. But sometimes you want to verify that the bot is actually healthy before reporting that it’s alive. Maybe the database connection dropped, or Redis is unreachable, or the Discord gateway is disconnected.
Other plugins (and your own code) can register health checks that are evaluated before each ping. If any health check fails, the ping is skipped — which causes your monitoring service to trigger an alert:
import {Bot, Plugin} from '@warden/core';import {HeartbeatService} from '@warden/heartbeat';
export class DatabaseHealthPlugin implements Plugin { register(bot: Bot): void { // ... }
boot(bot: Bot): void { const heartbeat = bot.resolve(HeartbeatService); const db = bot.resolve(DrizzleService);
heartbeat.addCheck('database', async () => { // Run a lightweight query to verify connectivity await db.query.execute('SELECT 1'); return true; }); }}You can register as many health checks as you like. Each check is a named async function that returns true for healthy or false (or throws) for unhealthy. All checks must pass for the ping to be sent:
// In your bootstrap or a plugin's boot() methodconst heartbeat = bot.resolve(HeartbeatService);
heartbeat.addCheck('database', async () => { await db.query.execute('SELECT 1'); return true;});
heartbeat.addCheck('cache', async () => { await cache.set('health:check', 'ok', 5); return await cache.get('health:check') === 'ok';});
heartbeat.addCheck('discord', () => { return bot.client.ws.status === 0; // 0 = READY});If a health check throws an error, the plugin catches it and treats it as a failure. The error is logged so you can investigate, but the bot keeps running — it just stops reporting as healthy until the issue resolves.
Configuration
Section titled “Configuration”The heartbeat plugin reads from the heartbeat config namespace:
import {env} from '@warden/core';
export default { url: env('HEARTBEAT_URL'), interval: env.number('HEARTBEAT_INTERVAL', 60), // seconds between pings};| Option | Type | Default | Description |
|---|---|---|---|
url | string | — | The URL to ping. If not set, the plugin is disabled. |
interval | number | 60 | Seconds between pings. |
Production tips
Section titled “Production tips”Set your monitoring grace period
Section titled “Set your monitoring grace period”Most monitoring services let you configure a “grace period” — how long to wait after a missed ping before alerting. Set this to 2-3x your heartbeat interval. With a 60-second interval, a 2-3 minute grace period prevents false alarms from brief network blips.
Use health checks in production
Section titled “Use health checks in production”In development, a simple timer-based ping is fine. In production, always register health checks for your critical dependencies. A bot that’s running but can’t reach the database isn’t really “up” from a user’s perspective:
// A good production setupheartbeat.addCheck('database', async () => { await db.query.execute('SELECT 1'); return true;});
heartbeat.addCheck('discord', () => { return bot.client.ws.status === 0;});Don’t forget the heartbeat URL in production deploys
Section titled “Don’t forget the heartbeat URL in production deploys”A common gotcha: you set everything up locally, deploy to production, and forget to set HEARTBEAT_URL in the production environment. The plugin silently does nothing (by design), and you think you have monitoring when you don’t. Add it to your deployment checklist or use env.required() if you want a hard failure:
// src/config/heartbeat.ts — strict versionimport {env} from '@warden/core';
export default { url: env.required('HEARTBEAT_URL'), // bot won't start without this interval: env.number('HEARTBEAT_INTERVAL', 60),};