Multi-bot orchestration platform for Telegram, Twitch, YouTube, Twitter, Discord, Slack, Bluesky and crypto trading.
Site: https://ssrjkk.github.io/bothive/
by ssrjkk — run a fleet of social bots with shared infrastructure: one API, one queue layer, one dashboard, one script engine.
Live site: ssrjkk.github.io/bothive — screenshots, architecture and the dashboard preview.
- API reference · Script engine · Webhooks
- Deployment · Kubernetes · Security model · Security policy
- Backup & restore · Capacity planning · Troubleshooting
- SLOs & alerting · Distributed tracing · Local AI · Load testing
- Architecture decisions · Runbooks
The public site is the rack console below — captured from ssrjkk.github.io/bothive, 1440×900 and 390×844.
| Desktop | Mobile |
|---|---|
![]() |
![]() |
The rest of the page, section by section:
| Chaos & resilience | Script sandbox |
|---|---|
![]() |
![]() |
| Admin console | Prometheus & API |
|---|---|
![]() |
![]() |
The admin dashboard ships with light and dark themes.
| Page | Light | Dark |
|---|---|---|
| Sign in | ![]() |
![]() |
| Dashboard | ![]() |
![]() |
| Bots | ![]() |
![]() |
| Bot editor | ![]() |
![]() |
| Accounts | ![]() |
![]() |
| Users | ![]() |
![]() |
| Scripts | ![]() |
![]() |
| Queues | ![]() |
![]() |
| Webhooks | ![]() |
![]() |
| Events | ![]() |
![]() |
| Logs | ![]() |
![]() |
| Settings | ![]() |
![]() |
BotHive lets you register accounts and bots for eight platforms, start/stop them from one place, automate them with sandboxed scripts, react to events through webhooks, and observe everything on a single dashboard with Prometheus metrics.
- 8 platform adapters — Telegram (long-polling or webhook), Twitch (IRC + Helix), YouTube (LiveChat), Twitter (v2 API), Discord, Slack, Bluesky, crypto/Binance (opt-in)
- Crypto trading worker (opt-in, off by default) — a fifth adapter that runs Binance strategies against a generated EVM wallet. It is not started by
docker compose up; see Crypto trading - Queue-driven control plane — every connect / disconnect / action is a BullMQ job, so control is reliable and restart-safe
- Script engine — attach event-driven or interval scripts to any bot: 20 triggers (
message,follow,subscribe,donation,comment,interval,raid,host,price,signal,trade,reaction,member_join,team_join, and the six Bluesky events) and 26ctx/apiactions — the full table with the payload of each trigger is in docs/scripts.md - Webhook sink — push events to your own endpoints with HMAC signatures and SSRF protection
- RBAC —
admin/viewerroles resolved from the database on every request (not from a stale JWT claim); admins can create/delete users and change roles in the dashboard - Secrets at rest — account tokens are encrypted with AES-256-GCM; encryption keys are validated at startup
- Proxy pool — manage HTTP/SOCKS5 outbound proxies per-bot; the leader worker rotates healthy proxies with priority weighting, failure cooldown and health decay/boost, and
POST /api/proxies/:id/testruns a reachability probe - Backup & restore — one-click JSON export/import with an atomic transaction and encrypted credential round-trips
- Observability —
/metricsfor Prometheus, queue depth per platform, per-bot status and log stream, live worker health per platform, and a dark/light theme
┌─────────────────────┐ ┌──────────────────────────────┐
│ Dashboard (nginx) │ │ API (Fastify + Prisma) │
│ React + antd │──/api──▶ auth · bots · accounts · │
│ │ │ scripts · webhooks · logs · │
└─────────────────────┘ │ stats · queues · backup │
└───────┬──────────────┬───────┘
│ enqueue │ CRUD + config
▼ ▼
┌───────────────┐ ┌──────────────┐
│ Redis │ │ PostgreSQL │
│ BullMQ queues│ │ (Prisma ORM) │
│ memory store │ └──────────────┘
└───────┬───────┘
▼ consume
┌───────────────┬─────────────┼───────────────┬───────────────┬───────────────┬───────────────┬───────────────┐
▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼
workers-telegram workers-twitch workers-youtube workers-twitter workers-discord workers-slack workers-bluesky workers-crypto
(one process per platform — a crash never takes down the others; crypto is opt-in)
Monorepo layout
| Package | Role |
|---|---|
packages/core |
Domain logic: CQRS commands, validation, credential cipher, rate limiters, webhook signing, script config safety, VM-friendly event bus |
packages/api |
Fastify HTTP API, JWT auth + RBAC, BullMQ enqueuing, Prisma schema/migrations, Prometheus metrics |
packages/workers |
BullMQ consumers + platform adapters (grammy, tmi.js, twurple, googleapis, twitter-api-v2), script engine, webhook dispatcher, and an opt-in crypto trading worker |
packages/dashboard |
React + antd admin panel (lazy-loaded pages, admin-gated routes) |
# 1. prepare secrets (copy the example and fill in ENCRYPTION_KEY / PASSWORD_PEPPER / JWT_SECRET)
cp .env.example .env
# 2. start the whole stack — postgres, redis, api, per-platform workers, dashboard, prometheus, grafana
docker compose up -d --build- Dashboard: http://localhost:80
- API: http://localhost:3000
- Prometheus: http://localhost:9090 · Grafana: http://localhost:3001 (pre-configured Prometheus datasource + BotHive — API overview dashboard; default login
admin/admin, override viaGF_ADMIN_PASSWORD) - First admin user: run
npx prisma db seedfrom the repo root (it usesDATABASE_URL, which already points at the compose Postgres onlocalhost:5433). It creates[email protected]/admin123(change it!). The seed runs throughtsx, which is a dev dependency — it cannot run inside the api image (no dev deps, no npm).
One worker process runs per platform (
workers-telegram,workers-twitch, …). Scale any of them independently:docker compose up -d --scale workers-telegram=3
npm install
docker compose up -d postgres redis # just the infra
npm run dev # api + workers + dashboard (workspaces)Run checks:
npm run check # lint + build + typecheck + tests — one command
npm run build # TypeScript across all workspaces
npm run lint
npm run typecheck # typechecks sources AND tests (build skips __tests__)
npm run coverage # vitest with coverage thresholds (enforced in CI)
npm test # vitestKey environment variables (see .env.example for the full list with comments):
| Variable | Required | Purpose |
|---|---|---|
DATABASE_URL |
yes | PostgreSQL connection string |
REDIS_URL |
yes | Redis for BullMQ queues, bot memory and pub/sub |
REDIS_PASSWORD |
Redis auth password (also usable in the URL) | |
REDIS_SENTINELS |
host:port[,host:port,...] — switch all Redis connections to Sentinel (HA/failover); REDIS_URL is then ignored |
|
REDIS_SENTINEL_NAME |
Sentinel master name (default mymaster when sentinels are set) |
|
REDIS_TLS |
true enables TLS for cloud-managed Redis |
|
REDIS_DB |
Numeric logical Redis DB index for all connections | |
ENCRYPTION_KEY |
yes | 32-byte hex key for AES-256-GCM credential encryption |
JWT_SECRET |
yes | Session signing secret |
PASSWORD_PEPPER |
yes | Pepper mixed into scrypt password hashes |
API_PORT / API_HOST |
API listen address | |
LOG_RETENTION_DAYS |
Automatic log cleanup window (default 30) |
|
EVENT_RETENTION_DAYS |
Automatic event-store cleanup window (default 30) |
|
DLQ_RETENTION_DAYS |
Automatic dead-letter job cleanup window (default 30) |
|
WORKER_CONCURRENCY |
BullMQ jobs processed concurrently per worker (default 10) |
|
INTERVAL_POLL_MS |
Interval-script polling frequency (default 30000) |
|
ALLOW_PRIVATE_WEBHOOK_URLS |
no | Never enable in production (SSRF) |
WEBHOOK_DNS_CHECK |
Resolve webhook hostnames and block private IPs |
Rotating
ENCRYPTION_KEYmakes previously stored credentials undecryptable — keep it stable.
Scripts are attached to a bot and fire on platform events or a timer. They run inside a hardened Node vm sandbox: fetch is SSRF-guarded on every redirect hop, the host realm cannot leak functions, return values are sanitized, and infinite loops are killed by a timeout. A per-script maxExecutionMs (100–600 000 ms; unset = no global limit) caps the whole action chain against a wall-clock deadline — the chain aborts between steps once it's exceeded.
Triggers: message · follow · subscribe · donation · comment · interval · raid · host · price · signal · trade · reaction · member_join · team_join · like · repost · mention · quote · reply · notification
Actions exposed to scripts: sendMessage, sendPhoto, deleteMessage, say, timeout, tweet, reply, react, post, like, repost, follow, addReaction, updateMessage, openModal, log, fetch, remember(key, value, ttl), recall(key), forget(key).
Crypto actions (only execute when a crypto worker is running — see Crypto trading): getPrice(symbol), getCandles(symbol, interval?, limit?), getBalance(asset), getWallet(), marketBuy(symbol, amountUsdt), marketSell(symbol, quantity). Order placement respects the bot's maxOrderValueUsdt and maxDailyOrderValueUsdt caps; see docs/scripts.md.
Safety checks run at save time too — the API rejects scripts with catastrophic regex filters, sandbox-escaping custom code, or disallowed webhook URLs (also enforced on backup import).
Bots can push events to your endpoints. Webhooks support per-bot or global (botId: null) targets, event filtering, HMAC signing (X-BotHive-Signature) and delivery telemetry (status, error, last delivered, delivery count). Private/loopback URLs are blocked by default to prevent SSRF, and an optional DNS check blocks hostnames that resolve to private ranges.
A fifth worker adapter trades on Binance using per-bot strategies. It is off by default — docker compose up starts only the four social-platform workers. Enable it explicitly:
docker compose --profile crypto up -d workers-cryptoBots on the crypto platform are created like any other, and the API generates a dedicated EVM wallet for each one (private key encrypted at rest; only the address is returned).
Safety defaults
tradeModedefaults todry, so no live order is placed until a bot explicitly setslive.maxOrderValueUsdtcaps a single order (default 100 USDT; override the default withCRYPTO_MAX_ORDER_USDT).maxDailyOrderValueUsdtcaps rolling daily BUY spend — the default of 0 disables the cap entirely, so set it before going live.
Without the worker running, crypto bots and their scripts do nothing. Nothing surfaces an error, because no consumer is reading those jobs — the bot simply never acts. Trading alerts in prometheus/rules/bothive.yml only evaluate while the worker is up.
Workers stay polite when platforms are unhappy, instead of hammering them:
- Per-bot circuit breaker (
packages/core/src/resilience/circuit-breaker.ts): after 5 consecutive connect failures the connection circuit opens and reconnects stop; one probe is let through per 60s cooldown, and a single successful connect closes it again. Reconcile/auto-start also skips bots whose circuit is open. - Adaptive backoff (
packages/core/src/resilience/adaptive-backoff.ts): reconnect delays are exponential with jitter (no more fixed[5s, 15s, 30s, 60s, 120s]table) and scale with the bot's recent failure rate, capped at 5 minutes — so a fleet never reconnects in lock-step and a failing bot backs off hard. - Health score (
packages/core/src/resilience/health-score.ts): every connect and action outcome feeds a 1-hour sliding window that yields a 0-100 score per bot. Workers publish these to Redis and the API's/metricsexposes them asbothive_bot_health_score{bot_id="...",status="..."}, plusbothive_bot_uptime_seconds,bothive_bot_actions_total{result="success|failure"},bothive_bot_reconnect_attempts_totalandbothive_bot_script_executions_total. - Per-bot rate limits: set
rateLimitPerMinutein a bot's config to enforce a separate outbound budget for that bot (in addition to the global per-window limit). Limits are enforced via Redis, so they hold across a scaled fleet. - Rate limit budgeting (
packages/core/src/rate-limit/budget.ts): per-action-type budgets give every endpoint its own window/cap, so atweetburst can never starvesendMessage(and vice versa). Configure viaRATE_LIMIT_BUDGETS(JSON env) or a bot'sconfig.rateLimitBudgets, withdefault+ per-actionoverrides. - Idempotency keys: every event carries an
eventIdderived from the platform's natural id (message id, update id, tweet id) when one exists. Workers claim it in a Redis dedup window before fan-out, so provider webhook redelivery or a worker failover replay can never double-fire scripts/webhooks/analytics. - Versioned contracts (
packages/core/src/contracts/): every emitted event is stamped with its contract version (v), and webhook envelopes carry the envelope version. The event store persists it, replay re-runs under the original version, and an unsupported (newer) contract is rejected instead of mis-parsed when a platform API changes. - Dead-letter queues: jobs that exhaust their retry budget are moved into per-platform DLQs (
<queue>-dlq, including webhook deliveries) with the payload and failure reason preserved. Admins can inspect and replay them viaGET /api/queues/dead-letter/POST /api/queues/dead-letter/:platform/:id/replay/POST /api/queues/dead-letter/replay-all. - Event store & replay: every platform event is persisted (batched, idempotent by
eventId) and browsable viaGET /api/events.POST /api/events/:id/replayre-runs a stored event through the platform worker — scripts, webhooks and AI exactly as on the original emission — so a lost delivery or a script bug can be replayed after the fact. - Proxy pool (
packages/core/src/proxy/proxy-pool.ts): the leader worker reloads proxies from the DB every reconcile cycle and injects a healthy one (proxy/proxyType) into each connect. Selection is weighted by priority with round-robin rotation, a failed proxy enters a 30s cooldown, and every connect outcome feeds its health score (bothive_proxy_health_score{proxy_id,type,priority}).
- Indexes (
packages/api/prisma/migrations/): hot query paths are indexed — accounts by platform, bots by(platform, status)and byaccountId, scripts by(botId, trigger)andenabled, webhooks bybotId, logs by(botId, createdAt),(botId, level)andcreatedAt, event records by(botId, createdAt),(platform, type)andcreatedAt(unique oneventId). - Bounded pagination: list endpoints cap results via
parsePage(100 per page, 1000 max for most endpoints, skip capped at 100 000; the log stream caps at 500 and the CSV export at 50 000) so deep paging can't grind the DB. - Filterable bot list:
GET /api/botsaccepts?platform=,?status=and?q=(name substring, case-insensitive) — all index-friendly and validated. - Connection pooling: each service's Prisma pool is bounded via
DATABASE_URL?...&connection_limit=10(seedocker-compose.ymland.env.example) so a scaled worker fleet cannot exhaust Postgres connections.
- Prometheus metrics (
GET /metrics): HTTP counters/histograms (rate, latency, response size per route), queue depths per platform/state (bothive_queue_jobs,bothive_worker_queue_depth), dead-letter backlog per queue (bothive_queue_dlq_jobs), event-store size and replay counter (bothive_event_store_total,bothive_event_replays_total), per-bot health/uptime/action/reconnect/script-execution metrics, worker liveness and concurrency (bothive_worker_up,bothive_worker_concurrency_current), proxy health scores, Prisma row counts and Node runtime gauges. Protected byMETRICS_TOKEN, JWT, orMETRICS_OPEN=true. - Readiness (
GET /health/ready) probes both Postgres and Redis (503 when either is unavailable) — it is safe to use as a load-balancer/K8s readiness probe. - Alerting (
prometheus/rules/bothive.yml): 22 rules — API unreachable/high error rate/slow p95, all-workers-down, workers down, queue backlog/high-wait/stuck failed jobs, dead-letter backlog, event-replay spikes, unhealthy bots/proxies, script failure spikes, worker heap growth/reconnect thrashing/sandbox worker leaks, crypto high error rate/no fills/volume spike, plus SLO burn-rate/latency pages.
Warning: Alertmanager notifies nobody by default. Out of the box every rule except
severity="page"goes to a null receiver, and pages land in the bundledwebhook-receiver, which just appends todata/webhook-capture.jsonl. The rules are evaluated and visible in the Prometheus/Alertmanager UI, but no human is paged until you pointbothive-webhookat a real endpoint (PagerDuty/Slack/email) — see the header ofalertmanager.yml. Checkcurl -s localhost:9093/api/v2/alertsafter a deploy to confirm alerting works end to end.
- Credentials are encrypted at rest (AES-256-GCM) and never returned by the API — including webhook HMAC secrets (
enc:-prefixed, legacy plaintext keeps working on read) and crypto wallet private keys (bot responses only ever expose the wallet address; a config save that omits the key preserves the stored one instead of destroying it). - Sessions use httpOnly cookies + short-lived JWTs; roles are re-read from the database per request (fail-closed: unknown role ⇒
viewer). WebSocket log streams re-check the user too. - Login is rate-limited; passwords are hashed with scrypt plus a pepper.
- RBAC: only
admincan manage scripts, queues, webhooks, settings, backups and bulk operations;viewergets read-only access. - The API and dashboard emit security headers on every response, including HSTS; auth responses that carry the token in the body set
Cache-Control: no-store. - Webhook delivery and script
fetchare SSRF-hardened; bulk operation errors are masked instead of echoing raw exceptions.
GET /health, /health/ready, /metrics · GET /api/health/workers · /api/docs (Swagger UI) · /ws/logs (live log stream)
POST /api/auth/register, /api/auth/login, /api/auth/logout
GET /api/auth/me · PATCH /api/auth/password
GET/POST /api/auth/users · PATCH /api/auth/users/:id/role · DELETE /api/auth/users/:id
GET /api/bots · POST /api/bots · GET/PATCH/DELETE /api/bots/:id
POST /api/bots/:id/start · /stop · /restart · /action · GET/DELETE /api/bots/:id/memory[/:key]
GET /api/accounts · POST /api/accounts · GET/PATCH/DELETE /api/accounts/:id
GET /api/scripts/patterns · POST /api/scripts/generate · CRUD /api/scripts
POST /api/scripts/:id/test · /clone
GET /api/webhooks · CRUD /api/webhooks · POST /api/webhooks/:id/test
GET /api/queues · /api/queues/failed · /api/queues/dead-letter
POST /api/queues/dead-letter/:platform/:id/replay · /api/queues/dead-letter/replay-all
GET /api/events · /api/events/:id · POST /api/events/:id/replay · DELETE /api/events/:id
GET /api/logs · /api/logs/export · /api/stats
GET/POST /api/proxies · GET/PATCH/DELETE /api/proxies/:id · POST /api/proxies/:id/test
GET /api/backup/export · POST /api/backup/import · POST /api/bulk/bots · /api/bulk/scripts
Vitest across all workspaces — 700+ tests covering domain rules, RBAC, sandbox isolation, webhook SSRF guards, backup round-trips, leader election, circuit breakers, rate limiting, proxy rotation/health, Redis connection options, API behaviour, and unit-level worker chaos (crash/requeue with a mocked queue). npm test is the source of truth for the exact count; it is not mirrored here because it changes with every PR.
Coverage thresholds (statements/lines 50%, branches/functions 45%) are enforced in CI — see vitest.config.ts. They are a floor rather than a target.
.env.example is kept in step with the code by packages/core/src/__tests__/env-docs.test.ts, which fails if the source reads a process.env.* variable the example file never mentions.
npm testBeyond unit tests, .github/workflows/chaos.yml runs compose-level chaos / E2E against the real Docker stack (chaos/): Postgres and Redis outages, worker hang detection (SIGSTOP) and worker crash recovery (SIGKILL), asserting /health/ready flips to 503 and the bothive_worker_up metric reflects the fault, then recovering to 200 / 1.
-
CI (
.github/workflows/ci.yml) runs on every push/PR: ESLint, full build, typecheck of sources and tests, the whole test suite with coverage on Node 22 and 26,docker composevalidation, and a Docker build of every image target (api / workers / dashboard). Onmainthe images are pushed to Docker Hub as:latestand:<sha>; PRs build them locally so a broken Dockerfile is caught before merge. -
Chaos / E2E (
.github/workflows/chaos.yml) starts the real compose stack and faults it — Postgres/Redis outages, worker hang and crash — then checks recovery via/health/readyand worker metrics. -
Releases (
.github/workflows/release.yml) — push a semver tag and the images are published as:latest,:<tag>and:<sha>, plus a draft GitHub release with a changelog:git tag v1.2.3 && git push origin v1.2.3 -
Dependabot (
.github/dependabot.yml) keeps npm, Docker and GitHub Actions dependencies up to date weekly.
Secrets required for image publishing: DOCKERHUB_USERNAME, DOCKERHUB_TOKEN (configured in the repo settings).
These are deliberate open items rather than bugs — listed so nobody assumes the capability is live.
- Crypto trading is opt-in and off by default. The worker exists and is tested, but
docker compose updoes not start it (see Crypto trading). Nothing surfaces an error for a crypto bot whose worker is stopped — the jobs are simply never consumed. - Encryption key rotation with versioning.
ENCRYPTION_KEYSsupports multiple key versions (e.g.,v1:old-key,v2:new-key), allowing in-place rotation viaPOST /api/backup/rotate-encryption. LegacyENCRYPTION_KEY(single key) is still supported for backward compatibility. See docs/security.md for the rotation procedure.
Sitnikov Sergey Alekseevich
Full-stack Engineer · Saint Petersburg
GitHub · Telegram · [email protected]
MIT — see LICENSE. Contributions are welcome: read CONTRIBUTING and the Code of Conduct first.
git clone https://github.com/ssrjkk/bothive.git
cd bothive
npm installnpm startSSRJKK — GitHub · Telegram · Projects hub · Bot catalogue





























