Een Discord-verificatiebot is een kleine maar essentiële beveiligingslaag die voorkomt dat elk nieuw lid je kanalen bereikt voordat het bewijst een echt persoon te zijn. Wie de poort passeert met één klik of een korte captchacode wordt toegelaten; botaccounts, raidscripts en spamgolven blijven buiten. In deze gids bouwen we vanaf nul een verificatiesysteem met discord.js, beginnend met een knop en versterkt met een captcha. Je zet het rollen- en rechtenmodel op, automatische roltoewijzing en de beveiligingspraktijken die in de echte wereld standhouden, stap voor stap.
Waarom je een verificatiesysteem nodig hebt
Een open server is een plek waar iedereen die de uitnodigingslink bemachtigt zo naar binnen kan lopen en gaan typen. Dat nodigt twee soorten aanvallen uit:
- Bot-/self-botgolven: geautomatiseerde accounts accepteren binnen minuten honderden uitnodigingen en dumpen advertenties of schadelijke links.
- Raids: gecoördineerde groepen komen tegelijk binnen, overspoelen kanalen, vallen leden lastig en maken de server onbruikbaar.
Verificatie plaatst die wrijving precies waar ze hoort: een paar seconden voor een mens, maar een vervelende hindernis voor automatisering. Een captcha toevoegen geeft je nog een extra beschermingsniveau tegen geavanceerde bots die simpele "klik op de knop"-stromen nabootsen.
Het rechten- en rollenmodel opzetten
Voordat je code schrijft, moet de serverstructuur kloppen. De logica is eenvoudig: een ongeverifieerd lid mag niets zien, en toegang moet pas opengaan na verificatie. Er zijn twee gangbare patronen:
- Openzetten met een Verified-rol: verberg alle kanalen voor de
@everyone-rol zodat alleen één#verificatie-kanaal zichtbaar blijft. Maak een rol "Verified" aan en geef die Kanaal bekijken op de echte kanalen. Wanneer een lid zich verifieert, voegt de bot deze rol toe en gaat de server open. - Beperken met een Unverified-rol: geef iedereen die binnenkomt automatisch een "Unverified"-rol, weiger die overal toegang en verwijder de rol bij verificatie.
De eerste methode is meestal schoner, omdat de standaardstaat "gesloten" is, wat het risico verkleint dat een kanaal per ongeluk open blijft. Wat je ook kiest, onthoud twee regels: de bot moet de rechten Rollen beheren hebben, en de rol van de bot moet boven de rol staan die hij toekent. Anders weigert Discord de rol toe te voegen.
Automatisch handelen als een lid binnenkomt
Om nieuwe leden op te vangen luisteren we naar het GuildMemberAdd-event. Om dit event te laten afgaan moet je de Server Members Intent van de bot inschakelen, zowel in de developer portal als in de clientopties.
const { Client, GatewayIntentBits, Events } = require('discord.js');
const client = new Client({
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers],
});
client.on(Events.GuildMemberAdd, async (member) => {
// Gebruik je het Unverified-patroon, ken de rol dan toe bij binnenkomst:
await member.roles.add(process.env.UNVERIFIED_ROLE_ID);
});
client.login(process.env.TOKEN);
Bij het Verified-patroon is deze stap niet nodig; het lid ziet standaard al niets, behalve het verificatiekanaal.
Discord-verificatiebot: verifiëren met een knop
De eenvoudigste en snelste methode is verificatie via een knop. Eerst stuur je een vast paneelbericht naar het verificatiekanaal:
const { ButtonBuilder, ButtonStyle, ActionRowBuilder } = require('discord.js');
const row = new ActionRowBuilder().addComponents(
new ButtonBuilder()
.setCustomId('verify')
.setLabel('Mijn account verifiëren')
.setEmoji('✅')
.setStyle(ButtonStyle.Success),
);
await channel.send({
content: 'Klik op de knop hieronder om toegang tot de server te krijgen.',
components: [row],
});
Het veld customId is hier cruciaal: het is de unieke identifier waarmee je de klik herkent. Laten we nu luisteren naar klikken op deze knop. We sturen antwoorden privé met flags: MessageFlags.Ephemeral zodat het kanaal niet volloopt met "je bent geverifieerd"-berichten:
const { Events, MessageFlags } = require('discord.js');
client.on(Events.InteractionCreate, async (interaction) => {
if (!interaction.isButton() || interaction.customId !== 'verify') return;
const role = interaction.guild.roles.cache.get(process.env.VERIFIED_ROLE_ID);
if (interaction.member.roles.cache.has(role.id)) {
return interaction.reply({
content: 'Je bent al geverifieerd.',
flags: MessageFlags.Ephemeral,
});
}
await interaction.member.roles.add(role);
await interaction.reply({
content: 'Geverifieerd — welkom!',
flags: MessageFlags.Ephemeral,
});
});
Voor de meeste kleine communities is dit voldoende. Maar wil je nog een stap verder gaan tegen bots die de knop programmatisch aanklikken, dan komt de captcha in beeld.
Geavanceerde verificatie met een captcha
In de captchastroom tonen we het lid bij het klikken op de knop een willekeurige code en vragen we die in te tikken in een modaalformulier. Een eenvoudige Map volstaat om de code in het geheugen te bewaren (op grote servers heeft Redis de voorkeur). Open eerst het modaal bij de knopdruk:
const {
Events,
ModalBuilder,
TextInputBuilder,
TextInputStyle,
ActionRowBuilder,
} = require('discord.js');
const pending = new Map();
client.on(Events.InteractionCreate, async (interaction) => {
if (!interaction.isButton() || interaction.customId !== 'verify') return;
const code = Math.random().toString(36).slice(2, 8).toUpperCase();
pending.set(interaction.user.id, code);
const input = new TextInputBuilder()
.setCustomId('captcha_input')
.setLabel(`Tik deze code exact in: ${code}`)
.setStyle(TextInputStyle.Short)
.setRequired(true);
const modal = new ModalBuilder()
.setCustomId('captcha_modal')
.setTitle('Verificatie')
.addComponents(new ActionRowBuilder().addComponents(input));
await interaction.showModal(modal);
});
Wanneer het lid het formulier verstuurt, komt er een ModalSubmit-interactie binnen. We vergelijken de ingevoerde waarde met de verwachte code en kennen de rol toe als ze overeenkomen:
client.on(Events.InteractionCreate, async (interaction) => {
if (!interaction.isModalSubmit() || interaction.customId !== 'captcha_modal') return;
const expected = pending.get(interaction.user.id);
const given = interaction.fields
.getTextInputValue('captcha_input')
.trim()
.toUpperCase();
if (!expected || given !== expected) {
return interaction.reply({
content: 'Onjuiste code. Klik opnieuw op de knop.',
flags: MessageFlags.Ephemeral,
});
}
pending.delete(interaction.user.id);
const role = interaction.guild.roles.cache.get(process.env.VERIFIED_ROLE_ID);
await interaction.member.roles.add(role);
await interaction.reply({
content: 'Geverifieerd — welkom! 🎉',
flags: MessageFlags.Ephemeral,
});
});
Wil je de code in een afbeelding stoppen in plaats van platte tekst, dan kun je met een bibliotheek als canvas een visual genereren en die via DM of een ephemeral bericht sturen in plaats van het modaal; de logica blijft hetzelfde, je maakt de code alleen lastiger leesbaar voor machines.
Beveiligingstips en best practices
- Stel een time-out in: wis
pending-vermeldingen na een paar minuten metsetTimeoutzodat verouderde codes zich niet in het geheugen opstapelen. - Pas rate limiting toe: voorkom dat dezelfde gebruiker snel te veel pogingen doet; kick wie blijft falen automatisch.
- Houd logs bij: schrijf geslaagde en mislukte verificaties via webhook naar een logkanaal; tijdens een raid zien wie erdoor kwam is onbetaalbaar.
- Accountleeftijdfilter: kick zeer recent aangemaakte accounts (bijv. jonger dan 7 dagen) automatisch vóór verificatie om botgolven al bij de deur te stoppen.
- Bescherm de token: zet de bottoken nooit hardcoded in de code; gebruik
process.envmet een.env-bestand en voeg het toe aan.gitignore.
Veelgestelde vragen
Welke rechten heeft een verificatiebot nodig?
De bot heeft minimaal het recht Rollen beheren nodig, en de rol van de bot moet boven de "Verified"-rol staan die hij toekent. Om binnenkomsten van leden op te vangen moet ook de Server Members Intent ingeschakeld zijn.
Captcha of knop — wat is beter?
Voor kleine, rustige servers volstaat een knop met één klik meestal. Op grote servers of servers die vaak geraid worden, biedt een captcha duidelijke extra bescherming door bots die het paneel automatisch aanklikken eruit te filteren. Beide combineren is de sterkste oplossing.
Moet ik de code in een database opslaan in plaats van een Map?
Voor kleine bots met één proces is een Map snel en voldoende. Openstaande codes worden gewist als de bot herstart, wat geen probleem is. Gebruik je sharding of heb je persistentie nodig, kies dan een gedeelde opslag zoals Redis.
Een betrouwbaar verificatiesysteem nodig voor je server? Ik kan een Discord-bot op maat voor jouw community ontwerpen en bouwen, met knop, captcha, anti-raid en logging. Neem contact op en laten we het over je project hebben.