Skip to main content
RapidDev - Software Development Agency
API AutomationsDiscordAPI Key

How to Automate Discord Welcome Messages using the API

Automate Discord welcome messages by listening to the GUILD_MEMBER_ADD Gateway event (requires GUILD_MEMBERS privileged intent enabled in both Developer Portal and code) and posting an embed via POST /channels/{channel.id}/messages. This is Gateway-only — there is no REST polling fallback. The bot must maintain an active WebSocket connection. Rate limit: 5 messages per 5 seconds per channel.

Need help automating? Talk to an expert
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner7 min read15-30 minutesDiscordMay 2026RapidDev Engineering Team
TL;DR

Automate Discord welcome messages by listening to the GUILD_MEMBER_ADD Gateway event (requires GUILD_MEMBERS privileged intent enabled in both Developer Portal and code) and posting an embed via POST /channels/{channel.id}/messages. This is Gateway-only — there is no REST polling fallback. The bot must maintain an active WebSocket connection. Rate limit: 5 messages per 5 seconds per channel.

API Quick Reference

Auth

Bot Token

Rate limit

5 messages / 5 seconds per channel

Format

JSON

SDK

Available

Understanding the Discord API

Discord's architecture separates real-time events (Gateway WebSocket) from actions (REST API). Welcome messages are driven entirely by the Gateway: when a user joins, Discord fires a GUILD_MEMBER_ADD event to all bots connected to that guild via WebSocket. There is no REST endpoint to poll for new joins — the Gateway connection is mandatory.

The GUILD_MEMBERS privileged intent is required to receive GUILD_MEMBER_ADD. Without it, the Gateway silently drops the event (close code 4014 if you explicitly request the intent without enabling it in the portal). For bots in fewer than 100 guilds, toggle it on in the Developer Portal. For bots in 100+ guilds, Discord requires verification plus staff review.

Once the event arrives, sending the welcome message is a standard REST call: POST /channels/{channel.id}/messages with an embed payload. The channel ID is your designated welcome channel, which you configure in your bot's settings. Official docs: https://discord.com/developers/docs/events/gateway-events#guild-member-add

Base URLhttps://discord.com/api/v10

Setting Up Discord API Authentication

Bot tokens are static credentials that do not expire until reset. You must also enable the GUILD_MEMBERS privileged intent in two places: the Developer Portal toggle AND your code's intent bitmask. Mismatching these causes Gateway close code 4014 — the bot connects but receives no member events.

  1. 1Go to https://discord.com/developers/applications and click New Application.
  2. 2Open the Bot tab. Click Reset Token — copy it immediately (shown once).
  3. 3Under Privileged Gateway Intents, toggle on Server Members Intent.
  4. 4Open OAuth2 > URL Generator. Select scopes: bot and applications.commands.
  5. 5Under Bot Permissions, check: Send Messages, Embed Links, View Channels.
  6. 6Copy the generated URL and invite the bot to your test server.
  7. 7Create or identify your welcome channel and copy its ID (right-click channel > Copy Channel ID).
  8. 8Store DISCORD_BOT_TOKEN and DISCORD_WELCOME_CHANNEL_ID in environment variables.
auth.py
1import os
2import discord
3
4# discord.py v2.7.1 requires Python 3.8+
5# Install: pip install discord.py
6
7BOT_TOKEN = os.environ["DISCORD_BOT_TOKEN"]
8WELCOME_CHANNEL_ID = int(os.environ["DISCORD_WELCOME_CHANNEL_ID"])
9
10# Enable GUILD_MEMBERS intent in code must match Developer Portal setting
11intents = discord.Intents.default()
12intents.members = True # Enables GUILD_MEMBER_ADD events
13
14client = discord.Client(intents=intents)
15
16@client.event
17async def on_ready():
18 print(f"Bot connected as {client.user}")
19
20client.run(BOT_TOKEN)

Security notes

  • Store DISCORD_BOT_TOKEN in environment variables — never commit it to version control.
  • Enable only the privileged intents you actually need — Server Members for GUILD_MEMBER_ADD, nothing else for this use case.
  • Validate that the welcome channel ID exists and the bot has Send Messages and View Channel permissions before going live.
  • If the token is exposed, immediately click Reset Token in the Developer Portal.
  • Use a test server during development to avoid spamming your production server's welcome channel.

Key endpoints

POST/channels/{channel.id}/messages

Sends a message to the welcome channel. Include content for a text ping and embeds for a formatted welcome card. This is the only REST endpoint needed for welcome message automation.

ParameterTypeRequiredDescription
contentstringoptionalText content — mention the user here to trigger a notification ping
embedsarrayoptionalUp to 10 embed objects. Use one embed for the welcome card with title, description, color, and thumbnail.

Request

json
1{"content":"<@123456789>","embeds":[{"title":"Welcome to the Server!","description":"We're glad you're here, <@123456789>! Read the rules in #rules and introduce yourself in #introductions.","color":5814783,"thumbnail":{"url":"https://cdn.discordapp.com/avatars/123456789/avatar_hash.png"},"footer":{"text":"Member #42"}}]}

Response

json
1{"id":"998877665544","type":0,"content":"<@123456789>","channel_id":"111222333444","author":{"id":"bot_id","username":"WelcomeBot","bot":true},"embeds":[{"title":"Welcome to the Server!","description":"We're glad you're here!","color":5814783}],"timestamp":"2026-05-07T10:00:00.000Z"}
GET/guilds/{guild.id}/members

Fetches a list of guild members — use this to get the total member count for displaying 'Member #N' in the welcome message. Requires GUILD_MEMBERS privileged intent.

ParameterTypeRequiredDescription
guild.idstringrequiredThe snowflake ID of the guild
limitnumberoptionalMax members to return (1-1000, default 1)

Response

json
1[{"user":{"id":"123456789","username":"johndoe","avatar":"abc123"},"roles":["role_id"],"joined_at":"2026-05-07T10:00:00.000Z","deaf":false,"mute":false}]
GET/guilds/{guild.id}?with_counts=true

Returns guild info including approximate_member_count and approximate_presence_count — use this for member count in welcome messages without needing to fetch all members.

ParameterTypeRequiredDescription
with_countsbooleanoptionalSet to true to include approximate_member_count in the response

Response

json
1{"id":"555555555555","name":"My Server","approximate_member_count":1247,"approximate_presence_count":423}

Step-by-step automation

1

Enable GUILD_MEMBERS Intent in Both Portal and Code

Why: Mismatching the intent — enabled in portal but not code, or vice versa — causes either silent event drops or close code 4014.

In the Discord Developer Portal, go to your application > Bot > Privileged Gateway Intents and toggle on Server Members Intent. In your code, set intents.members = True (discord.py) or add GatewayIntentBits.GuildMembers to your intents bitmask (discord.js). Both must match or GUILD_MEMBER_ADD events will never arrive.

request.sh
1# Verify GUILD_MEMBER_ADD arrives by checking bot flags
2curl -s -H "Authorization: Bot $DISCORD_BOT_TOKEN" \
3 "https://discord.com/api/v10/users/@me" | python3 -m json.tool

Pro tip: Add a startup log message: print('Intents.members enabled:', client.intents.members) to confirm the intent is active before going to production.

Expected result: The on_member_join (Python) or guildMemberAdd (discord.js) event fires when a user joins the server. If it never fires, the intent is misconfigured.

2

Build the Welcome Embed

Why: A formatted embed is significantly more engaging than plain text and allows you to include the user's avatar, server rules link, and member number.

Construct an embed object with the new member's username, avatar URL (from the member.user.avatar field), and any server-specific information. The embed color, thumbnail, and footer fields are all optional but make the welcome message feel polished. Keep the description under 4096 characters.

request.sh
1# Send a welcome embed directly
2curl -X POST \
3 -H "Authorization: Bot $DISCORD_BOT_TOKEN" \
4 -H "Content-Type: application/json" \
5 -d "{
6 \"content\": \"<@$USER_ID>\",
7 \"embeds\": [{
8 \"title\": \"Welcome to the server!\",
9 \"description\": \"We're happy you're here, <@$USER_ID>! Check out #rules and introduce yourself.\",
10 \"color\": 5814783,
11 \"thumbnail\": {\"url\": \"$AVATAR_URL\"},
12 \"footer\": {\"text\": \"Member #1247\"}
13 }]
14 }" \
15 "https://discord.com/api/v10/channels/$WELCOME_CHANNEL_ID/messages"

Pro tip: Use Discord's embed preview tool (discohook.org) to prototype your embed layout before coding it — it shows exactly what members will see.

Expected result: An embed object ready to be passed to the message POST endpoint — formatted with the user's avatar, a description, and footer.

3

Send the Welcome Message on Member Join

Why: This is the core action — posting the embed to the welcome channel when the GUILD_MEMBER_ADD event fires.

In your GUILD_MEMBER_ADD handler, call POST /channels/{welcome_channel_id}/messages with the embed payload. In discord.py or discord.js, use the channel.send() method which handles the REST call internally. In raw REST, POST the message directly. Mention the user in the content field (not just the embed description) to trigger a notification ping.

request.sh
1# This step is triggered by a Gateway event no cURL equivalent
2# For testing, manually send a welcome message:
3curl -X POST \
4 -H "Authorization: Bot $DISCORD_BOT_TOKEN" \
5 -H "Content-Type: application/json" \
6 -d '{"content":"<@USER_ID> Welcome!","embeds":[{"title":"Welcome!","color":5765632}]}' \
7 "https://discord.com/api/v10/channels/$WELCOME_CHANNEL_ID/messages"

Pro tip: During large join raids (e.g., after a Twitter shoutout), GUILD_MEMBER_ADD events queue up. Add a small random delay (50-500ms) before posting to avoid sending 50 messages/second to the welcome channel and hitting the per-channel rate limit.

Expected result: When a user joins the server, the welcome message appears in the designated channel within milliseconds of the join event.

4

Handle Join Raids with Rate Limit Awareness

Why: Discord's 5 messages per 5 seconds per channel limit means a raid with 100 joins in 10 seconds will back up your welcome message queue.

Implement an asyncio queue (Python) or a Promise queue (Node.js) that buffers GUILD_MEMBER_ADD events and processes them at a controlled rate. Track the channel rate limit bucket by reading X-RateLimit-Remaining on raw REST calls. For SDK-based bots, discord.js and discord.py handle some rate limiting automatically, but their queues don't throttle application-level spam.

request.sh
1# Check rate limit headers after posting
2curl -v -X POST \
3 -H "Authorization: Bot $DISCORD_BOT_TOKEN" \
4 -H "Content-Type: application/json" \
5 -d '{"content":"Test"}' \
6 "https://discord.com/api/v10/channels/$WELCOME_CHANNEL_ID/messages" \
7 2>&1 | grep -i 'x-ratelimit'

Pro tip: During an obvious join raid (50+ joins in 60 seconds), consider skipping individual welcome messages and posting a single message like 'Welcome to our 50 newest members!' to reduce noise.

Expected result: Welcome messages are sent one per second regardless of join rate, preventing channel rate limit exhaustion during raids.

Complete working code

A complete discord.py bot that listens for GUILD_MEMBER_ADD events, builds a formatted welcome embed with the user's avatar and member count, and posts it to a designated welcome channel with rate-limit-safe queuing. Runs continuously as a process.

automate_discord_welcome.py
1import os, asyncio, logging
2from collections import deque
3import discord
4
5logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
6
7BOT_TOKEN = os.environ["DISCORD_BOT_TOKEN"]
8WELCOME_CHANNEL_ID = int(os.environ["DISCORD_WELCOME_CHANNEL_ID"])
9
10intents = discord.Intents.default()
11intents.members = True # Required for GUILD_MEMBER_ADD
12
13client = discord.Client(intents=intents)
14welcome_queue = deque()
15
16def build_embed(member: discord.Member) -> discord.Embed:
17 embed = discord.Embed(
18 title=f"Welcome to {member.guild.name}!",
19 description=(
20 f"{member.mention}, we're glad you joined!\n\n"
21 f"Read the rules and get started."
22 ),
23 color=discord.Color.green()
24 )
25 embed.set_thumbnail(url=member.display_avatar.url)
26 embed.set_footer(text=f"Member #{member.guild.member_count}")
27 embed.timestamp = discord.utils.utcnow()
28 return embed
29
30async def queue_processor():
31 await client.wait_until_ready()
32 channel = client.get_channel(WELCOME_CHANNEL_ID)
33 if channel is None:
34 logging.error(f"Welcome channel {WELCOME_CHANNEL_ID} not found")
35 return
36 logging.info(f"Welcome queue processor started, posting to #{channel.name}")
37 while not client.is_closed():
38 if welcome_queue:
39 member = welcome_queue.popleft()
40 try:
41 await channel.send(content=member.mention, embed=build_embed(member))
42 logging.info(f"Welcomed {member.name} (guild: {member.guild.name})")
43 except discord.HTTPException as e:
44 logging.error(f"Failed to welcome {member.name}: {e}")
45 await asyncio.sleep(1.0) # Max 1 welcome/second stays under rate limit
46
47@client.event
48async def on_ready():
49 logging.info(f"Connected as {client.user}")
50 client.loop.create_task(queue_processor())
51
52@client.event
53async def on_member_join(member: discord.Member):
54 logging.info(f"{member.name} joined — queued welcome")
55 welcome_queue.append(member)
56
57client.run(BOT_TOKEN)
58

Error handling

4014 (Gateway close code)Disallowed intents specified — you requested an intent you have not been granted
Cause

The GUILD_MEMBERS privileged intent is enabled in code but not toggled on in the Developer Portal, or the bot needs verification (100+ guilds) but hasn't been verified.

Fix

Go to https://discord.com/developers/applications > Bot tab > Privileged Gateway Intents and enable Server Members Intent. For bots in 100+ guilds, complete the verification process first.

Retry strategy

Do not retry — fix the portal configuration. The Gateway will reject the connection until resolved.

429{"message": "You are being rate limited.", "retry_after": 1.0}
Cause

The welcome channel received more than 5 messages in 5 seconds — typically caused by a join raid without message queuing.

Fix

Implement a message queue that processes one welcome per second. The SDK (discord.py/discord.js) will queue messages automatically, but you should still add application-level throttling for large join spikes.

Retry strategy

Discord SDKs handle this automatically with exponential backoff. For raw REST, sleep for retry_after + 0.2s before retrying.

403{"code": 50013, "message": "Missing Permissions"}
Cause

The bot does not have Send Messages or Embed Links permission in the welcome channel.

Fix

Check the welcome channel's permission overrides. Ensure the bot's role has Send Messages and Embed Links. If the channel inherits from a category, check category-level permissions too.

Retry strategy

Do not retry — fix channel permissions.

404{"code": 10003, "message": "Unknown Channel"}
Cause

The DISCORD_WELCOME_CHANNEL_ID environment variable points to a channel that doesn't exist or has been deleted.

Fix

Re-copy the channel ID (right-click channel > Copy Channel ID in Discord). Verify the ID in your environment variables. Add a startup check: fetch the channel at on_ready and log an error if it returns null.

Retry strategy

Do not retry — fix the channel ID configuration.

Rate Limits for Discord API

ScopeLimitWindow
Per channel (messages)5 messagesper 5 seconds
Global (per bot)50 requestsper second
Gateway events120 eventsper 60 seconds (Gateway send limit)
retry-handler.ts
1import asyncio
2from collections import deque
3
4class WelcomeQueue:
5 def __init__(self, rate=1.0):
6 self.queue = deque()
7 self.rate = rate # messages per second
8
9 async def add(self, member):
10 self.queue.append(member)
11
12 async def process(self, channel, send_func):
13 while True:
14 if self.queue:
15 member = self.queue.popleft()
16 try:
17 await send_func(channel, member)
18 except Exception as e:
19 print(f'Error: {e}')
20 await asyncio.sleep(1.0 / self.rate)
  • Process welcome messages through a queue at 1/second to stay well under the 5/5s per-channel limit.
  • Cache the welcome channel object at startup — calling client.get_channel() on every event is free (in-memory cache), but fetching from the API on every join is wasteful.
  • Monitor join rate: if joins exceed 60/minute for 3+ minutes, switch to a batched welcome ('Welcome to our 20 newest members: @user1, @user2...') to reduce message volume.
  • Store the welcome channel ID in config, not hardcoded — makes it easy to switch channels without redeploying.
  • Test with a rate limit simulator: join the server from 10 alt accounts in quick succession to verify your queue handles the load correctly.

Security checklist

  • Store DISCORD_BOT_TOKEN in environment variables — never in source code.
  • Enable only the GUILD_MEMBERS privileged intent — not Message Content or Presence, unless your bot specifically needs them.
  • Validate the welcome channel ID at startup and log an error if it resolves to null — prevents silent failures.
  • Sanitize any user-provided content in the welcome message — do not include the username directly in a way that could render as a mention of a role or @everyone.
  • Implement raid detection: if more than 20 members join in 60 seconds, log an alert and optionally pause welcome messages during bot-raid events.
  • Use a test server during development to avoid polluting the production welcome channel with test events.
  • Set up process monitoring (systemd, PM2, or a container) to auto-restart the bot if it crashes — GUILD_MEMBER_ADD events are lost during downtime.

Automation use cases

DM Welcome Message

beginner

Send a private DM to the new member with onboarding instructions and a server guide link, in addition to or instead of the public welcome channel message.

Auto-Role on Join

beginner

Combine welcome message with automatic role assignment — assign a default Member role immediately on GUILD_MEMBER_ADD.

Membership Tier Welcome

advanced

Check if the new member's email matches a Patreon or Stripe subscriber list and assign a tier-appropriate role and welcome message.

Multi-Language Welcome

intermediate

Detect the new member's locale from their user data and send a welcome message in their language if supported.

No-code alternatives

Don't want to write code? These platforms can automate the same workflows visually.

Zapier

Free tier available

Zapier's Discord integration cannot listen for member join events directly — it relies on triggers from other apps, making it unsuitable for pure join-based welcome messages.

Pros
  • + Easy setup for cross-app triggers
  • + No code required
Cons
  • - Cannot listen to Discord Gateway events
  • - Not suitable for join-based triggers
  • - Requires a separate trigger source

Make (formerly Integromat)

Free tier available (1,000 operations/month)

Make's Discord module has similar limitations to Zapier — it can post messages to Discord but cannot receive Gateway events like GUILD_MEMBER_ADD without a custom webhook bridge.

Pros
  • + Can post formatted messages to Discord
  • + Visual workflow builder
Cons
  • - No native GUILD_MEMBER_ADD trigger
  • - Requires custom webhook setup to receive join events
  • - Added latency vs. direct bot

n8n

Free self-hosted; Cloud from €20/month

n8n can receive Discord webhooks and has a Discord trigger node, but GUILD_MEMBER_ADD specifically requires the bot to maintain a WebSocket connection — n8n's Discord integration covers webhook-based events better than Gateway events.

Pros
  • + Self-hostable
  • + More flexible than Zapier/Make
  • + HTTP webhook support
Cons
  • - GUILD_MEMBER_ADD is Gateway-only — requires custom bot bridge
  • - Gateway events not natively supported
  • - More complex setup

Best practices

  • Enable GUILD_MEMBERS privileged intent in both the Developer Portal AND your code — mismatch causes silent failure or close code 4014.
  • Always check that the welcome channel exists (not null) before trying to send — channel IDs can become invalid if the channel is deleted.
  • Queue welcome messages and process at 1/second to handle join raids without hitting the 5 messages/5 seconds per-channel limit.
  • Include the user's mention (not just username) in the content field outside the embed — this triggers the mobile push notification that welcome messages in embeds alone do not send.
  • Cache the welcome channel object at startup using client.get_channel() rather than fetching it from the API on every join event.
  • Use member.display_avatar.url (discord.py) or member.user.displayAvatarURL() (discord.js) for the thumbnail — these handle the default avatar fallback when the user has no custom avatar.
  • Test by joining your test server from a second account — the actual join event is the only reliable way to verify the setup works end-to-end.

Ask AI to help

Copy one of these prompts to get a personalized, working implementation.

ChatGPT / Claude Prompt

I'm building a Discord welcome message bot using discord.py v2.7.1. I've enabled the GUILD_MEMBERS privileged intent in both the Developer Portal and my code (intents.members = True). Help me: 1) handle the on_member_join event, 2) build a formatted embed with the member's avatar, username, and member count, 3) send it to a designated welcome channel, 4) implement a queue to process at max 1 welcome/second to avoid hitting the 5/5s rate limit during join raids, and 5) handle 403 Missing Permissions errors gracefully with a helpful log message.

Lovable / V0 Prompt

Build a web dashboard for managing a Discord welcome message bot. It should have: a live preview of the welcome embed (showing title, description, color, thumbnail template), a form to configure the welcome channel ID and message template with variables like {username} and {member_count}, a test button that sends a preview to the welcome channel, a join analytics chart showing member growth over time, and a recent joins log showing the last 50 members with their join timestamps.

Frequently asked questions

Is the Discord API free to use for welcome bots?

Yes. The Discord REST API is completely free with no per-request pricing. The only costs are your server hosting. Privileged intents (including GUILD_MEMBERS) are also free — they require a portal toggle and verification at 100+ guilds, but there is no fee.

What happens if I hit the rate limit posting welcome messages?

You get HTTP 429 with retry_after in seconds. Discord's official libraries (discord.py, discord.js) handle this automatically with exponential backoff. However, during large join raids you may see welcome messages delayed by several seconds as the queue builds up. Implement a 1/second processing queue to prevent this.

Why does GUILD_MEMBER_ADD never fire on my bot?

Two causes: (1) the Server Members Intent is not enabled in the Developer Portal under your app's Bot tab, or (2) you enabled it in the portal but forgot intents.members = True in discord.py or GatewayIntentBits.GuildMembers in discord.js. Both portal and code must match. Also verify close code 4014 is not appearing in your bot's logs.

Can I send a welcome DM instead of posting to a channel?

Yes. In discord.py, use await member.send(embed=embed) instead of channel.send(). In discord.js, use member.user.send({ embeds: [embed] }). Note that some users have DMs disabled from non-friend users — wrap the send in a try/catch and fall back to the channel message if the DM fails.

Does my bot need to stay online 24/7 for welcome messages to work?

Yes. GUILD_MEMBER_ADD is a Gateway event delivered only to bots with an active WebSocket connection. If your bot is offline when a member joins, you will never receive that event — there is no replay or REST polling alternative. Host your bot on a always-on service (VPS, Railway, Fly.io, etc.).

Can RapidDev help build a custom Discord welcome bot?

Yes. RapidDev has built 600+ apps including Discord bots with welcome flows, role assignment, and CRM integrations. Visit rapidevelopers.com for a free consultation.

How do I include the server's total member count in the welcome message?

In discord.py, access member.guild.member_count — this is available directly from the GUILD_MEMBER_ADD event payload without an extra API call. In discord.js, use member.guild.memberCount. Note: this reflects the count after the new member joined, so the first member sees #1.

Can I trigger a welcome message from a REST API call without a running bot?

Partially. You can POST a message to the welcome channel via REST with just a bot token — no Gateway connection needed. But you won't know when to call it unless you have another system notifying you of joins. The Gateway (WebSocket) is the only reliable way to receive real-time join events.

RapidDev

Need this automated?

Our team has built 600+ apps with API automations. We can build this for you.

Book a free consultation

Skip the coding — we'll build it for you

Our experts have built 600+ API automations. From prototype to production in days, not weeks.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.