Discord Integration
Reward Discord community activity with loyalty points
Connect your Discord server to award points for community engagement. Members earn points for messages, voice activity, event attendance, and more.
Features
- Message Points: Reward chat participation
- Voice Activity: Points for time in voice channels
- Event Attendance: Bonus for scheduled events
- Server Boosts: Reward Nitro boosters
- Account Linking: Connect Discord to loyalty account
- Role Multipliers: Bonus earning for VIP roles
- Channel Filtering: Include/exclude specific channels
Activity Types
| Activity | Default Points | Configurable |
|---|---|---|
| Message | 1 point | Per message |
| Reaction | 1 point | Per reaction |
| Voice | 1 point/minute | Minimum 5 minutes |
| Thread Create | 10 points | Per thread |
| Event Attend | 50 points | Per event |
| Server Boost | 500 points | One-time |
| Member Join | 25 points | One-time |
| Account Link | 100 points | One-time |
Setup
1. Create Discord Bot
- Go to Discord Developer Portal
- Click New Application
- Go to Bot → Add Bot
- Enable required intents:
- Server Members Intent
- Message Content Intent
- Presence Intent
- Copy the Bot Token
2. Connect to useLoyalty
- Navigate to Settings → Integrations → Discord
- Enter your Bot Token
- Enter your Server (Guild) ID
- Click Connect
3. Invite Bot to Server
Generate invite URL with required permissions:
https://discord.com/api/oauth2/authorize?client_id=YOUR_CLIENT_ID&permissions=1099511627776&scope=bot%20applications.commandsRequired permissions:
- Read Messages/View Channels
- Send Messages
- Read Message History
- Add Reactions
- Connect (voice)
- Use Voice Activity
4. Configure Points Settings
| Setting | Default | Description |
|---|---|---|
| Enable Messages | Yes | Award points for messages |
| Points per Message | 1 | Points per message |
| Message Cooldown | 60s | Time between earning |
| Max Message Points/Day | 100 | Daily cap |
| Enable Voice | Yes | Award points for voice |
| Points per Voice Minute | 1 | Points per minute |
| Min Voice Minutes | 5 | Minimum session length |
| Max Voice Points/Day | 200 | Daily cap |
Bot Implementation
Using the SDK
import { GamificationClient, VoiceSessionTracker } from '@useloyalty/discord-sdk';
const gamification = new GamificationClient({
apiUrl: process.env.USELOYALTY_API_URL,
guildId: process.env.DISCORD_GUILD_ID,
webhookSecret: process.env.USELOYALTY_WEBHOOK_SECRET
});
const voiceTracker = new VoiceSessionTracker(gamification);Message Tracking
client.on('messageCreate', async (message) => {
// Ignore bots
if (message.author.bot) return;
// Track message
await gamification.trackMessage(
message.author.id,
message.author.username,
message.channel.id,
message.id
);
});Voice Activity
client.on('voiceStateUpdate', async (oldState, newState) => {
await voiceTracker.handleVoiceStateUpdate(oldState, newState);
});The voice tracker automatically:
- Records session start when joining voice
- Calculates duration when leaving
- Sends points request if minimum met
Reactions
client.on('messageReactionAdd', async (reaction, user) => {
if (user.bot) return;
await gamification.trackReaction(
user.id,
user.username,
reaction.message.channel.id,
reaction.message.id,
reaction.emoji.name
);
});Event Attendance
client.on('guildScheduledEventUserAdd', async (event, user) => {
await gamification.trackEventAttendance(
user.id,
user.username,
event.id
);
});Server Boosts
client.on('guildMemberUpdate', async (oldMember, newMember) => {
// Check if user started boosting
if (!oldMember.premiumSince && newMember.premiumSince) {
await gamification.trackServerBoost(
newMember.id,
newMember.user.username
);
}
});Member Join
client.on('guildMemberAdd', async (member) => {
await gamification.trackMemberJoin(
member.id,
member.user.username
);
});Full Bot Example
import { Client, GatewayIntentBits } from 'discord.js';
import { GamificationClient, VoiceSessionTracker } from '@useloyalty/discord-sdk';
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent,
GatewayIntentBits.GuildMessageReactions,
GatewayIntentBits.GuildVoiceStates,
GatewayIntentBits.GuildMembers,
GatewayIntentBits.GuildScheduledEvents
]
});
const gamification = new GamificationClient({
apiUrl: process.env.USELOYALTY_API_URL,
guildId: process.env.DISCORD_GUILD_ID,
webhookSecret: process.env.USELOYALTY_WEBHOOK_SECRET
});
const voiceTracker = new VoiceSessionTracker(gamification);
// Message points
client.on('messageCreate', async (message) => {
if (message.author.bot) return;
try {
const result = await gamification.trackMessage(
message.author.id,
message.author.username,
message.channel.id,
message.id
);
if (result.pointsAwarded > 0) {
console.log(`${message.author.username} earned ${result.pointsAwarded} points`);
}
} catch (error) {
console.error('Failed to track message:', error);
}
});
// Voice tracking
client.on('voiceStateUpdate', async (oldState, newState) => {
await voiceTracker.handleVoiceStateUpdate(oldState, newState);
});
// Reactions
client.on('messageReactionAdd', async (reaction, user) => {
if (user.bot) return;
await gamification.trackReaction(
user.id,
user.username,
reaction.message.channel.id,
reaction.message.id,
reaction.emoji.name
);
});
// Server boost
client.on('guildMemberUpdate', async (oldMember, newMember) => {
if (!oldMember.premiumSince && newMember.premiumSince) {
await gamification.trackServerBoost(newMember.id, newMember.user.username);
}
});
// Graceful shutdown
process.on('SIGINT', async () => {
console.log('Shutting down...');
await voiceTracker.cleanup(); // Send pending voice sessions
client.destroy();
process.exit(0);
});
client.login(process.env.DISCORD_BOT_TOKEN);Account Linking
Allow members to link their Discord account to their loyalty account:
OAuth Flow
- Member clicks "Link Discord" in widget or dashboard
- Redirect to Discord OAuth
- User authorizes
- Callback exchanges code for tokens
- Link created in database
- Bonus points awarded
API Endpoints
GET /api/discord/authInitiates OAuth flow. Returns redirect URL.
GET /api/discord/auth/callbackHandles OAuth callback and creates link.
Member Lookup
Once linked, Discord activity automatically credits the correct member:
// Event processing looks up member by Discord ID
const linkedAccount = await db.discordLinkedAccount.findUnique({
where: {
discordGuildId_discordUserId: {
discordGuildId: guildId,
discordUserId: event.discordUserId
}
}
});
if (linkedAccount) {
// Award points to linked member
await awardPoints(linkedAccount.memberId, points);
}Channel Configuration
Include Only Specific Channels
{
"includedChannels": ["123456789", "987654321"]
}Only messages in listed channels earn points.
Exclude Channels
{
"excludedChannels": ["111111111", "222222222"]
}Messages in listed channels don't earn points.
Priority
If both are set, includedChannels takes priority (whitelist mode).
Role Multipliers
Configure bonus earning for specific roles:
{
"bonusRoles": {
"VIP_ROLE_ID": 1.5,
"PREMIUM_ROLE_ID": 2.0
}
}Members with these roles earn multiplied points:
- VIP: 1 message = 1.5 points
- Premium: 1 message = 2 points
If member has multiple bonus roles, the highest multiplier applies.
Cooldowns and Limits
Cooldowns
Prevent spam by requiring time between earnings:
| Activity | Default Cooldown |
|---|---|
| Message | 60 seconds |
| Reaction | 30 seconds |
| Thread | 60 minutes |
Daily Limits
Cap points per activity type:
| Activity | Default Limit |
|---|---|
| Messages | 100 points/day |
| Reactions | 50 points/day |
| Voice | 200 points/day |
API Reference
Track Event
POST /api/v1/discord/eventsHeaders:
X-Discord-Guild-Id: 123456789
X-Discord-Signature: hmac_signature
X-Discord-Timestamp: 1704067200Body:
{
"eventId": "unique_event_id",
"eventType": "MESSAGE",
"discordUserId": "user_123",
"discordUsername": "JohnDoe",
"channelId": "channel_456",
"messageId": "message_789"
}Response:
{
"success": true,
"pointsAwarded": 1,
"multiplier": 1,
"cooldownActive": false,
"dailyLimitReached": false
}Event Types
| Type | Required Fields |
|---|---|
MESSAGE | channelId, messageId |
REACTION | channelId, messageId, emoji |
VOICE_SESSION | channelId, voiceMinutes |
THREAD_CREATE | channelId, threadId |
EVENT_ATTEND | eventId |
SERVER_BOOST | - |
MEMBER_JOIN | inviterId (optional) |
ACCOUNT_LINK | - |
Webhook Security
All events must be signed:
function signWebhook(body: string, secret: string): string {
return crypto
.createHmac('sha256', secret)
.update(body)
.digest('hex');
}
// Send request
const signature = signWebhook(JSON.stringify(payload), webhookSecret);
fetch(apiUrl, {
method: 'POST',
headers: {
'X-Discord-Guild-Id': guildId,
'X-Discord-Signature': signature,
'X-Discord-Timestamp': Date.now().toString()
},
body: JSON.stringify(payload)
});Troubleshooting
Points Not Awarded
- Verify bot has required permissions
- Check channel isn't excluded
- Verify cooldown has passed
- Check daily limit not reached
- Ensure account is linked (if required)
Webhook Errors
GET /api/discord/events/logsView recent webhook deliveries and errors.
Voice Sessions Not Tracking
- Ensure bot has Voice Activity intent
- Check minimum session length (default 5 min)
- Verify graceful shutdown sends pending sessions
Best Practices
1. Set Reasonable Limits
Prevent point farming:
{
"maxMessagePointsDay": 100,
"messageCooldownSecs": 60
}2. Use Channel Exclusions
Exclude off-topic or bot channels:
{
"excludedChannels": ["bot-commands", "memes"]
}3. Reward Quality Over Quantity
Higher points for valuable activities:
Message: 1 point
Voice (per min): 1 point
Event Attend: 50 points
Server Boost: 500 points4. Run Multiplier Events
Boost engagement during events:
"Community Day" - 2x Discord points