← All Guides

Build a Telegram Signal Bot in 30 Minutes

Why Telegram for Signal Delivery?

Telegram is the platform of choice for signal delivery for good reasons: it has a powerful bot API, supports rich message formatting, allows channels with unlimited subscribers, and has no algorithmic feed that buries your content. When you send a signal, every subscriber sees it immediately.

Compared to Discord (complex, gaming-oriented), email (slow, spam-filtered), or Twitter (algorithm-dependent), Telegram gives you the most direct line to your audience. This guide walks you through building a complete signal bot from scratch in about 30 minutes.

Step 1: Create Your Bot via BotFather

Every Telegram bot starts with BotFather, Telegram's official bot-creation tool:

  1. Open Telegram and search for @BotFather
  2. Send the command /newbot
  3. Enter a display name for your bot (e.g., "Crypto Signal Pro")
  4. Enter a username for your bot (must end in "bot", e.g., "CryptoSignalProBot")
  5. BotFather will respond with your bot token — save this securely. It looks like: 7123456789:AAF1kD3..._xG4qwY

Configure Bot Settings

While still in BotFather, configure these settings:

Step 2: Set Up the Node.js Project

Create your project directory and install dependencies:

mkdir signal-bot && cd signal-bot
npm init -y
npm install node-telegram-bot-api node-cron better-sqlite3 dotenv

Create a .env file for your configuration:

BOT_TOKEN=your-bot-token-here
ADMIN_CHAT_ID=your-personal-chat-id

Project Structure

signal-bot/
  .env
  bot.js          # Main bot logic
  data.js         # Data source connection
  scheduler.js    # Signal scheduling
  db.js           # Subscriber database
  package.json

Step 3: Build the Bot Core

Create bot.js with the basic subscriber management:

import TelegramBot from 'node-telegram-bot-api';
import Database from 'better-sqlite3';
import 'dotenv/config';

const bot = new TelegramBot(process.env.BOT_TOKEN, { polling: true });
const db = new Database('subscribers.db');

// Create subscriber table
db.exec(`
  CREATE TABLE IF NOT EXISTS subscribers (
    chat_id INTEGER PRIMARY KEY,
    username TEXT,
    subscribed_at TEXT DEFAULT (datetime('now')),
    active INTEGER DEFAULT 1
  )
`);

// /start command - subscribe
bot.onText(/\/start/, (msg) => {
  const chatId = msg.chat.id;
  const username = msg.from.username || 'unknown';

  db.prepare(
    'INSERT OR REPLACE INTO subscribers (chat_id, username, active) VALUES (?, ?, 1)'
  ).run(chatId, username);

  bot.sendMessage(chatId,
    'Welcome! You are now subscribed to signals.\n' +
    'You will receive automated alerts as they are generated.\n\n' +
    'Commands:\n/stop - Unsubscribe\n/status - Check subscription'
  );
});

// /stop command - unsubscribe
bot.onText(/\/stop/, (msg) => {
  db.prepare('UPDATE subscribers SET active = 0 WHERE chat_id = ?')
    .run(msg.chat.id);
  bot.sendMessage(msg.chat.id, 'You have been unsubscribed. Use /start to resubscribe.');
});

// /status command
bot.onText(/\/status/, (msg) => {
  const sub = db.prepare('SELECT * FROM subscribers WHERE chat_id = ?')
    .get(msg.chat.id);
  if (sub && sub.active) {
    bot.sendMessage(msg.chat.id, 'Active subscriber since ' + sub.subscribed_at);
  } else {
    bot.sendMessage(msg.chat.id, 'Not subscribed. Use /start to subscribe.');
  }
});

// Broadcast function - send signal to all active subscribers
export function broadcastSignal(message) {
  const subs = db.prepare('SELECT chat_id FROM subscribers WHERE active = 1').all();
  let sent = 0;
  for (const sub of subs) {
    try {
      bot.sendMessage(sub.chat_id, message, { parse_mode: 'HTML' });
      sent++;
    } catch (err) {
      console.error('Failed to send to ' + sub.chat_id, err.message);
    }
  }
  return sent;
}

export default bot;

Step 4: Connect to Your Data Source

The bot needs data to generate signals. This depends on your niche, but here is the general pattern using a data.js module:

// Example: Fetch crypto price data
export async function fetchSignalData() {
  const response = await fetch(
    'https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,ethereum&vs_currencies=usd&include_24hr_change=true'
  );
  const data = await response.json();
  return data;
}

// Example: Generate a signal from the data
export function generateSignal(data) {
  const btcChange = data.bitcoin.usd_24h_change;
  const ethChange = data.ethereum.usd_24h_change;

  // Simple momentum signal
  if (btcChange > 5) {
    return {
      type: 'BULLISH',
      asset: 'BTC',
      message: 'BTC up ' + btcChange.toFixed(1) + '% in 24h. Strong momentum.'
    };
  }
  if (btcChange < -5) {
    return {
      type: 'BEARISH',
      asset: 'BTC',
      message: 'BTC down ' + btcChange.toFixed(1) + '% in 24h. Risk off.'
    };
  }
  return null; // No signal
}

Replace this with your actual data source: market data APIs, web scrapers, database queries, or AI analysis pipelines. The key is that fetchSignalData() returns raw data and generateSignal() turns it into an actionable message.

Step 5: Schedule Signal Delivery

Use node-cron to run your signal generation on a schedule:

import cron from 'node-cron';
import { fetchSignalData, generateSignal } from './data.js';
import { broadcastSignal } from './bot.js';

// Run every hour
cron.schedule('0 * * * *', async () => {
  console.log('Checking for signals...');

  const data = await fetchSignalData();
  const signal = generateSignal(data);

  if (signal) {
    const message =
      '<b>' + signal.type + ' SIGNAL: ' + signal.asset + '</b>\n\n' +
      signal.message + '\n\n' +
      '<i>Generated at ' + new Date().toISOString() + '</i>';

    const sent = broadcastSignal(message);
    console.log('Signal sent to ' + sent + ' subscribers');
  } else {
    console.log('No signal generated this cycle');
  }
});

// Daily summary at 8 AM UTC
cron.schedule('0 8 * * *', async () => {
  const data = await fetchSignalData();
  const summary =
    '<b>Daily Market Summary</b>\n\n' +
    'BTC: $' + data.bitcoin.usd.toLocaleString() +
    ' (' + data.bitcoin.usd_24h_change.toFixed(1) + '%)\n' +
    'ETH: $' + data.ethereum.usd.toLocaleString() +
    ' (' + data.ethereum.usd_24h_change.toFixed(1) + '%)';

  broadcastSignal(summary);
});

Step 6: Add Subscriber Management

For a production bot, you need admin controls. Add these commands that only respond to your admin chat ID:

// Admin: get subscriber count
bot.onText(/\/stats/, (msg) => {
  if (String(msg.chat.id) !== process.env.ADMIN_CHAT_ID) return;

  const total = db.prepare('SELECT COUNT(*) as c FROM subscribers').get().c;
  const active = db.prepare('SELECT COUNT(*) as c FROM subscribers WHERE active = 1').get().c;

  bot.sendMessage(msg.chat.id,
    'Subscribers: ' + active + ' active / ' + total + ' total'
  );
});

// Admin: manually broadcast a message
bot.onText(/\/broadcast (.+)/, (msg, match) => {
  if (String(msg.chat.id) !== process.env.ADMIN_CHAT_ID) return;

  const sent = broadcastSignal(match[1]);
  bot.sendMessage(msg.chat.id, 'Broadcast sent to ' + sent + ' subscribers');
});

Deploying Your Bot

Your bot needs to run 24/7 to respond to subscriber commands and send scheduled signals. The simplest deployment:

  1. Spin up a $6/month DigitalOcean droplet (Ubuntu 22.04)
  2. Clone your project and install dependencies
  3. Create a systemd service:
[Unit]
Description=Telegram Signal Bot
After=network.target

[Service]
Type=simple
User=root
WorkingDirectory=/opt/signal-bot
ExecStart=/usr/bin/node bot.js
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target

Enable and start: sudo systemctl enable signal-bot && sudo systemctl start signal-bot

Monetization Options

Once you have subscribers receiving value from your signals, here are the monetization paths:

The best signal bots focus on one niche and deliver consistent, actionable value. Do not try to cover everything. A bot that sends 2-3 high-quality signals per day will retain subscribers better than one that sends 20 mediocre alerts.

Recommended Tools & Platforms

Tags

telegrambotsignalsnode.jsautomationsubscribers