Home / Developers / Discord Bot
🤖

Discord Bot

JavaScript / Node.js Discord.js Bot

Overview

Build a Discord bot that rewards community members for voting on topgservers. The bot registers a /checkvote slash command, listens for webhooks to grant roles or points in real time, and can post vote notifications to a channel. Perfect for game server communities that use Discord as their hub.

Prerequisites

  • A Discord bot application — create one at discord.com/developers
  • Node.js 18 or newer
  • discord.js v14 installed (npm install discord.js)
  • A topgservers API key — generate one in My Servers → API
  • Your webhook secret (optional, for real-time rewards)
  • Bot permissions: Manage Roles, Send Messages, Use Slash Commands

Installation

1 Set up the project

Create a new Node.js project for your Discord bot with the required dependencies.

Directory structure
topg-vote-bot/
├── index.js
├── commands/
│   └── checkvote.js
├── webhook.js
├── .env
└── package.json

2 Install dependencies

Install discord.js for the bot and express for the webhook listener.

Terminal
npm init -y
npm install discord.js express dotenv

3 Create the bot entry point

Set up the Discord client, register the slash command, and start the webhook server.

index.js
require('dotenv').config();
const { Client, GatewayIntentBits, SlashCommandBuilder, REST, Routes }
    = require('discord.js');
const { startWebhookServer } = require('./webhook');

const client = new Client({
    intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers]
});

const TOPG_API_KEY = process.env.TOPG_API_KEY;
const VOTER_ROLE_ID = process.env.VOTER_ROLE_ID;

client.once('ready', async () => {
    console.log('Bot ready as ' + client.user.tag);

    // Register slash command
    const command = new SlashCommandBuilder()
        .setName('checkvote')
        .setDescription('Check if you voted on topgservers today');

    const rest = new REST().setToken(process.env.DISCORD_TOKEN);
    await rest.put(
        Routes.applicationCommands(client.user.id),
        { body: [command.toJSON()] }
    );

    // Start webhook listener
    startWebhookServer(client);
});

client.on('interactionCreate', async (interaction) => {
    if (!interaction.isChatInputCommand()) return;
    if (interaction.commandName !== 'checkvote') return;

    await interaction.deferReply({ ephemeral: true });

    try {
        const res = await fetch(
            'https://topgservers.net/api/v1/vote-check'
            + '?username=' + interaction.user.id,
            { headers: { 'Authorization': 'Bearer ' + TOPG_API_KEY } }
        );
        const data = await res.json();

        if (data.voted) {
            // Grant voter role
            const member = interaction.member;
            if (member && VOTER_ROLE_ID) {
                await member.roles.add(VOTER_ROLE_ID);
            }

            await interaction.editReply(
                'Thanks for voting! You have been granted the Voter role.'
            );
        } else {
            await interaction.editReply(
                'No vote found today. Visit topgservers.net to vote!'
            );
        }
    } catch (err) {
        console.error('Vote check error:', err);
        await interaction.editReply('Vote check failed. Try again later.');
    }
});

client.login(process.env.DISCORD_TOKEN);

Configuration

.env
# Discord Bot
DISCORD_TOKEN=your_bot_token_here
VOTER_ROLE_ID=123456789012345678
VOTE_LOG_CHANNEL_ID=123456789012345678

# topgservers
TOPG_API_KEY=tgs_your_api_key_here
TOPG_WEBHOOK_SECRET=whsec_your_secret_here
WEBHOOK_PORT=8090

Vote Check

Call the /api/v1/vote-check endpoint to determine if a player has voted today.

Vote check (fetch)
const res = await fetch(
    'https://topgservers.net/api/v1/vote-check?username=' + discordUserId,
    { headers: { 'Authorization': 'Bearer ' + TOPG_API_KEY } }
);
const data = await res.json();

if (data.voted) {
    // User voted today — grant reward
}

Webhook Receiver

Verify the X-TopG-Signature header using HMAC-SHA256 to ensure webhook payloads are authentic.

webhook.js
const express = require('express');
const crypto = require('crypto');

const WEBHOOK_SECRET = process.env.TOPG_WEBHOOK_SECRET;
const VOTER_ROLE_ID = process.env.VOTER_ROLE_ID;
const LOG_CHANNEL_ID = process.env.VOTE_LOG_CHANNEL_ID;

function startWebhookServer(discordClient) {
    const app = express();
    app.use(express.raw({ type: 'application/json' }));

    app.post('/webhook', (req, res) => {
        const body = req.body.toString();
        const sigHeader = req.headers['x-topg-signature'] || '';

        if (!verifySignature(body, sigHeader)) {
            return res.status(401).send('Invalid signature');
        }

        const data = JSON.parse(body);
        const discordId = data.username; // Discord user ID

        // Grant voter role
        const guild = discordClient.guilds.cache.first();
        if (guild && discordId) {
            guild.members.fetch(discordId)
                .then(member => {
                    if (VOTER_ROLE_ID) {
                        member.roles.add(VOTER_ROLE_ID);
                    }
                    member.send('Thanks for voting on topgservers! '
                        + 'You received the Voter role.')
                        .catch(() => {});
                })
                .catch(err => console.error('Member fetch error:', err));
        }

        // Post to vote log channel
        if (LOG_CHANNEL_ID) {
            const channel = discordClient.channels.cache.get(LOG_CHANNEL_ID);
            if (channel) {
                channel.send(
                    '<@' + discordId + '> just voted on topgservers!'
                );
            }
        }

        res.status(200).send('OK');
    });

    const port = process.env.WEBHOOK_PORT || 8090;
    app.listen(port, () => {
        console.log('Webhook server listening on port ' + port);
    });
}

function verifySignature(body, sigHeader) {
    const parts = Object.fromEntries(
        sigHeader.split(',')
            .map(p => p.split('='))
            .filter(p => p.length === 2)
    );

    const timestamp = parts.t || '';
    const received = parts.v1 || '';

    const expected = crypto
        .createHmac('sha256', WEBHOOK_SECRET)
        .update(timestamp + '.' + body)
        .digest('hex');

    return crypto.timingSafeEqual(
        Buffer.from(expected),
        Buffer.from(received)
    );
}

module.exports = { startWebhookServer };

Reward Examples

Reward examples (Discord.js)
// Grant a role
await member.roles.add(VOTER_ROLE_ID);

// Send a DM
await member.send('Thanks for voting! You earned 50 points.');

// Post in a channel
const channel = client.channels.cache.get(LOG_CHANNEL_ID);
await channel.send({
    embeds: [{
        title: 'New Vote!',
        description: '<@' + member.id + '> voted on topgservers!',
        color: 0x5865F2,
        timestamp: new Date().toISOString()
    }]
});

// Add points (if using a points/economy bot database)
// db.run('UPDATE users SET points = points + 50 WHERE discord_id = ?',
//     [member.id]);

// Remove voter role after 24h
setTimeout(() => {
    member.roles.remove(VOTER_ROLE_ID).catch(() => {});
}, 24 * 60 * 60 * 1000);

Notes & Tips

Use Discord user IDs (snowflakes) as the username parameter when registering your server on topgservers. Players should link their Discord account so votes can be mapped. For the webhook, ensure your server is reachable from the internet (use a reverse proxy like nginx or Cloudflare Tunnel). The bot requires the Manage Roles permission and the voter role must be below the bot's role in the hierarchy.