Prefer to chat? Query the API with natural language using our AI Skill →
Give your AI tool live Cito endpoint context before you write integration code.
Public docs assistant
Tell Cito API what you are building.
Get endpoint recommendations, exact requests, code snippets, and a free-key CTA without digging through every page.
Building a Discord Bot
Real-time match notifications and player lookups
In this guide, we'll build a Discord bot that monitors live Fortnite matches and posts updates to a channel. The bot will also respond to slash commands for player lookups.
What you'll learn:
- • Setting up a Discord bot with discord.js
- • Polling Cito API for live matches
- • Using webhooks for real-time updates
- • Creating slash commands for player lookups
Prerequisites
- Node.js 18+ installed
- A Discord account and server
- A Cito API key (get one free)
Step 1: Project Setup
mkdir fortnite-bot && cd fortnite-bot
npm init -y
npm install discord.js dotenvCreate a .env file with your credentials:
.env
DISCORD_TOKEN=your_discord_bot_token
CITO_API_KEY=cito_live_your_key_here
CHANNEL_ID=your_channel_idStep 2: Basic Bot Structure
index.js
require('dotenv').config();
const { Client, GatewayIntentBits, EmbedBuilder } = require('discord.js');
const client = new Client({
intents: [GatewayIntentBits.Guilds]
});
async function citoGet(path) {
const response = await fetch(`https://api.citoapi.com/api/v1${path}`, {
headers: { 'x-api-key': process.env.CITO_API_KEY }
});
if (!response.ok) throw new Error(`Cito API error: ${response.status}`);
return response.json();
}
client.once('ready', () => {
console.log(`Logged in as ${client.user.tag}`);
startMatchMonitor();
});
client.login(process.env.DISCORD_TOKEN);Step 3: Match Monitor (Polling)
Poll for live matches every 30 seconds and post updates:
const seenMatches = new Set();
async function startMatchMonitor() {
const channel = client.channels.cache.get(process.env.CHANNEL_ID);
setInterval(async () => {
try {
const data = await citoGet('/cod/matches/live');
const matches = data.matches || data.data || [];
for (const match of matches) {
if (!seenMatches.has(match.id)) {
seenMatches.add(match.id);
const embed = new EmbedBuilder()
.setColor(0x00E5CC)
.setTitle(`LIVE: ${match.tournament || 'Call of Duty League'}`)
.setDescription(match.status || 'Live match')
.addFields(
{ name: 'Status', value: match.status || 'live', inline: true },
{ name: 'Match ID', value: match.id, inline: true }
)
.setTimestamp();
await channel.send({ embeds: [embed] });
}
}
} catch (error) {
console.error('Error fetching matches:', error);
}
}, 30000); // 30 seconds
}Step 4: Using Webhooks (Recommended)
Instead of polling, use webhooks for instant notifications:
server.js
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());
app.post('/webhook', (req, res) => {
// Verify signature
const signature = req.headers['x-cito-signature'];
const expectedSig = crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(JSON.stringify(req.body))
.digest('hex');
if (signature !== expectedSig) {
return res.status(401).send('Invalid signature');
}
const { event, data } = req.body;
const channel = client.channels.cache.get(process.env.CHANNEL_ID);
if (event === 'match.started') {
const embed = new EmbedBuilder()
.setColor(0x00E5CC)
.setTitle(`Match Started: ${data.tournament}`)
.setDescription(`${data.players_count} players`)
.setTimestamp();
channel.send({ embeds: [embed] });
}
res.sendStatus(200);
});
app.listen(3000, () => console.log('Webhook server running'));Step 5: Slash Commands
Add player lookup commands:
const { SlashCommandBuilder } = require('discord.js');
const commands = [
new SlashCommandBuilder()
.setName('player')
.setDescription('Look up a Fortnite player')
.addStringOption(option =>
option.setName('username')
.setDescription('Player username')
.setRequired(true)
)
];
// Register commands
client.once('ready', async () => {
await client.application.commands.set(commands);
});
// Handle interactions
client.on('interactionCreate', async (interaction) => {
if (!interaction.isChatInputCommand()) return;
if (interaction.commandName === 'player') {
await interaction.deferReply();
const username = interaction.options.getString('username');
try {
const { data: player } = await cito.fortnite.players.get(username);
const embed = new EmbedBuilder()
.setColor(0x00E5CC)
.setTitle(player.username)
.addFields(
{ name: 'Wins', value: player.stats.career.wins.toString(), inline: true },
{ name: 'K/D', value: player.stats.career.kd_ratio.toFixed(2), inline: true },
{ name: 'Earnings', value: `$${player.stats.competitive.earnings.toLocaleString()}`, inline: true }
);
await interaction.editReply({ embeds: [embed] });
} catch (error) {
await interaction.editReply(`Player "${username}" not found.`);
}
}
});