useLoyalty
Integrations

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

ActivityDefault PointsConfigurable
Message1 pointPer message
Reaction1 pointPer reaction
Voice1 point/minuteMinimum 5 minutes
Thread Create10 pointsPer thread
Event Attend50 pointsPer event
Server Boost500 pointsOne-time
Member Join25 pointsOne-time
Account Link100 pointsOne-time

Setup

1. Create Discord Bot

  1. Go to Discord Developer Portal
  2. Click New Application
  3. Go to BotAdd Bot
  4. Enable required intents:
    • Server Members Intent
    • Message Content Intent
    • Presence Intent
  5. Copy the Bot Token

2. Connect to useLoyalty

  1. Navigate to SettingsIntegrationsDiscord
  2. Enter your Bot Token
  3. Enter your Server (Guild) ID
  4. 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.commands

Required permissions:

  • Read Messages/View Channels
  • Send Messages
  • Read Message History
  • Add Reactions
  • Connect (voice)
  • Use Voice Activity

4. Configure Points Settings

SettingDefaultDescription
Enable MessagesYesAward points for messages
Points per Message1Points per message
Message Cooldown60sTime between earning
Max Message Points/Day100Daily cap
Enable VoiceYesAward points for voice
Points per Voice Minute1Points per minute
Min Voice Minutes5Minimum session length
Max Voice Points/Day200Daily 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

  1. Member clicks "Link Discord" in widget or dashboard
  2. Redirect to Discord OAuth
  3. User authorizes
  4. Callback exchanges code for tokens
  5. Link created in database
  6. Bonus points awarded

API Endpoints

GET /api/discord/auth

Initiates OAuth flow. Returns redirect URL.

GET /api/discord/auth/callback

Handles 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:

ActivityDefault Cooldown
Message60 seconds
Reaction30 seconds
Thread60 minutes

Daily Limits

Cap points per activity type:

ActivityDefault Limit
Messages100 points/day
Reactions50 points/day
Voice200 points/day

API Reference

Track Event

POST /api/v1/discord/events

Headers:

X-Discord-Guild-Id: 123456789
X-Discord-Signature: hmac_signature
X-Discord-Timestamp: 1704067200

Body:

{
  "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

TypeRequired Fields
MESSAGEchannelId, messageId
REACTIONchannelId, messageId, emoji
VOICE_SESSIONchannelId, voiceMinutes
THREAD_CREATEchannelId, threadId
EVENT_ATTENDeventId
SERVER_BOOST-
MEMBER_JOINinviterId (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

  1. Verify bot has required permissions
  2. Check channel isn't excluded
  3. Verify cooldown has passed
  4. Check daily limit not reached
  5. Ensure account is linked (if required)

Webhook Errors

GET /api/discord/events/logs

View recent webhook deliveries and errors.

Voice Sessions Not Tracking

  1. Ensure bot has Voice Activity intent
  2. Check minimum session length (default 5 min)
  3. 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 points

4. Run Multiplier Events

Boost engagement during events:

"Community Day" - 2x Discord points

On this page