Discord 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.
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.
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.
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
# 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.
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.
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
// 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.