First, separate the two symptoms - they have nothing in common
"Bot offline" and "bot online but silent" are completely different failures. Fixing one will never fix the other, and most wasted debugging time comes from treating them as the same problem.
Last verified: August 2026 against the Discord Developer Portal and Gateway API. Error codes quoted are the literal values Discord returns.
| Symptom | What it proves | Look at |
|---|---|---|
| Grey / offline in member list | No gateway connection exists | Token, process, close codes 4004 and 4014 |
| Green / online, ignores everything | Connected, but not receiving or not permitted | Message Content intent, channel permissions |
| Online, works in one channel only | Connected and receiving; permission overwrite blocking | Channel-level permission overwrites |
| Slash commands missing from menu | Commands not registered, or still propagating | Global vs guild registration, applications.commands scope |
If your bot is green and ignoring messages, skip straight to the next section. That is the single most common Discord bot problem, and it has one cause.
1. The Message Content intent - the cause of most silent bots
Since Discord's 2022 privacy changes, message content is a privileged intent. Without it, your bot still receives every MESSAGE_CREATE event, but the content field arrives as an empty string. Your command matching compares "" against "!help", finds no match, and does nothing - with no error, no exception and no log line.
That silence is what makes this so hard to diagnose. The bot is connected, events are flowing, your handler is running. It just cannot see what anyone typed.
The fix requires two changes, and people usually only make one
Step 1 - enable it in the portal. Go to the Discord Developer Portal, open your application, select Bot in the sidebar, scroll to Privileged Gateway Intents, and toggle on Message Content Intent. Save.
Step 2 - request it in your code. The portal toggle grants permission; your gateway connection must still ask for it. In discord.js:
const { Client, GatewayIntentBits } = require('discord.js');
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent, // the one people forget
],
});In discord.py:
import discord
intents = discord.Intents.default()
intents.message_content = True # the one people forget
client = discord.Client(intents=intents)Step 3 - restart the process. Intents are negotiated when the gateway connection is established. Changing the portal toggle while the bot is running has no effect until it reconnects. A surprising number of "I enabled it and it still doesn't work" reports are just a bot that was never restarted.
Three privileged intents, and what each unlocks
| Intent | Needed for | Symptom without it |
|---|---|---|
| Message Content | Reading what users type | Prefix commands silently never match |
| Server Members | Join/leave events, full member lists | Welcome messages never fire; role automation breaks |
| Presence | Online status and activity tracking | Status-based features see everyone as offline |
The 100-server wall
Once a bot reaches 100 servers it must be verified to continue using privileged intents. If you are approaching that number, apply early - verification is a manual review and takes time. Bots that hit 100 servers unverified lose privileged intents and go silent for their entire user base at once, which is a memorable way to discover this rule.
Or sidestep it: use slash commands
Slash command interactions deliver their arguments directly in the interaction payload, so they work without the Message Content intent at all. If your bot only needs explicit commands rather than reading ambient conversation, migrating from prefix commands to slash commands removes this entire problem - and removes the 100-server verification requirement along with it.
2. Gateway error 4014: disallowed intents
If the bot never comes online and the connection drops immediately, check for this close code:
Gateway closed: 4014 - Disallowed intent(s)This is the exact inverse of the previous section: your code is requesting a privileged intent that the portal has not granted. Discord refuses the connection outright rather than downgrading it.
Two fixes, depending on what you actually need:
- Enable the intent in the Developer Portal (Bot -> Privileged Gateway Intents), or
- Remove it from your intents list in code if the bot does not genuinely require it
The frequent trigger is copying a starter template that requests Intents.all() or every GatewayIntentBits value. That works on a fresh unverified bot only if you have toggled all three privileges on. Request the minimum set your features need - it connects more reliably and delays the verification requirement.
The neighbouring close codes
| Code | Meaning | Fix |
|---|---|---|
| 4004 | Authentication failed | Token is wrong, revoked, or has whitespace. Regenerate and redeploy. |
| 4013 | Invalid intent(s) | The intent bitfield contains an undefined value. Usually a hand-computed number. |
| 4014 | Disallowed intent(s) | Requesting a privileged intent not enabled in the portal. |
| 4008 | Rate limited | Too many gateway payloads. Usually a reconnect loop. |
Note the difference between 4013 and 4014: 4013 means the value is not a real intent, 4014 means it is real but you are not allowed it. If you are constructing the bitfield by hand, stop - use the library's named constants.
3. Token invalid, and the Git commit that killed it
Close code 4004 or an immediate Improper token has been passed means the token is bad. Beyond the usual copy-paste errors, one cause is worth calling out because it produces a bot that worked yesterday and does not today:
Discord automatically invalidates tokens it finds in public repositories. Their scanner monitors GitHub and other public sources. Commit your token, and it stops working - often within minutes, with no notification beyond an email to the application owner. If your bot died suddenly and nothing changed on your side, check whether the token reached a public repo, a Pastebin, a screenshot in a support thread, or a Discord message in a public channel.
The remedy is the same either way: regenerate the token in the Developer Portal, move it into an environment variable or secrets manager, and add the file that held it to .gitignore. Regenerating immediately invalidates the previous token, so redeploy everywhere at once or you will have half your instances failing.
Other reliable token failures:
- Client secret used instead of bot token. They are different strings on adjacent pages of the portal. The client secret is for OAuth flows and will never authenticate a gateway connection.
- The
Botprefix. The Authorization header needsBot <token>with a space. Most libraries add this for you - if you are calling the REST API directly with fetch or curl, you must add it yourself. - Trailing newline from a secrets file. Trim the value before use.
4. Missing Permissions (50013) and Missing Access (50001)
Your bot is online, receiving events, and its replies never appear. The API is almost certainly returning:
{"message": "Missing Permissions", "code": 50013}
{"message": "Missing Access", "code": 50001}Discord permissions resolve through several layers, and a bot can hold a permission at server level while being denied it in the exact channel where you are testing:
@everyoneserver-wide permissions- Permissions from each role the bot holds
- Channel-level overwrites - these override the above and are where the problem usually lives
- Category overwrites, inherited by channels that sync with the category
A single deny on a channel overwrite beats any allow granted higher up. Denies win.
Check it the fast way
Server Settings -> Roles -> select your bot's role -> the permission list shows server-wide grants. Then right-click the specific channel -> Edit Channel -> Permissions, and look for the bot's role or the bot itself with a red X.
The permissions a conversational bot needs in each channel it works in:
- View Channel - without it the bot cannot see the channel exists
- Send Messages - the obvious one
- Read Message History - required to reply to a specific message
- Embed Links - rich embeds are silently dropped without it
- Attach Files - only if you send images or documents
- Use External Emojis - if your replies use emojis from other servers
The role hierarchy rule
A bot can only manage roles below its own highest role in the list. A moderation bot that cannot assign a role, kick a member or add a nickname is usually positioned too low in Server Settings -> Roles. Drag the bot's role above the roles it needs to manage. This produces the specific symptom of a bot that moderates most members successfully and fails on the ones with elevated roles.
Threads and forums are separate
Sending in a thread requires Send Messages in Threads, which is a distinct permission from Send Messages. A bot that works in a normal channel and fails in a thread has hit exactly this. Archived threads must also be unarchived before a message can post to them.
5. Slash commands that never appear
Registered commands that do not show in the client have three usual causes, in this order.
The applications.commands scope was missing from the invite
This is the big one, and it is invisible after the fact. If you invited the bot with a URL that requested only the bot scope, the server never authorised it to register slash commands - and no amount of re-registering fixes it. Your invite URL needs both:
https://discord.com/api/oauth2/authorize?client_id=YOUR_ID&permissions=YOUR_PERMS&scope=bot%20applications.commandsThe fix is to re-invite the bot using a URL that includes applications.commands. You do not need to kick it first - visiting the corrected URL and re-authorising adds the missing scope to the existing installation.
Global commands take up to an hour
Global commands propagate across Discord's infrastructure and can take up to one hour to appear. Guild-scoped commands register instantly. During development, always register to a single test guild - otherwise you will spend an afternoon convinced your registration code is broken when it worked the first time.
// Instant - use during development
await rest.put(Routes.applicationGuildCommands(clientId, testGuildId), { body: commands });
// Up to 1 hour - use for production release
await rest.put(Routes.applicationCommands(clientId), { body: commands });Command permissions are restricting visibility
Server admins can restrict which roles and channels a command appears in, via Server Settings -> Integrations -> your bot. A command that is invisible to regular members but visible to you as an admin has been restricted here, not broken in code.
The 3-second interaction deadline
A separate and very common failure: you must acknowledge an interaction within 3 seconds or Discord shows "The application did not respond" and the interaction token becomes invalid. Any bot that calls an AI model, queries a database or hits an external API will exceed this.
Defer immediately, then follow up:
await interaction.deferReply(); // buys you up to 15 minutes
const answer = await callSlowThing(); // now take your time
await interaction.editReply(answer);This single pattern accounts for most "my AI bot says it did not respond but the logs show it worked" reports.
6. DMs, mentions and the events you are not subscribed to
Guild messages and direct messages arrive through different intents. A bot subscribed to GuildMessages but not DirectMessages works perfectly in servers and ignores every DM:
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.DirectMessages, // separate subscription
GatewayIntentBits.MessageContent,
]Two more DM-specific rules catch people out:
- Users can block DMs from server members in their privacy settings. Sending to someone with this enabled returns
Cannot send messages to this user(code 50007). There is no way to detect this in advance and no workaround - it is a user preference. - In discord.js, DM channels may need partials enabled to receive events for channels that were not cached at startup. Without
Partials.Channel, DMs from users the bot has not interacted with in that session can be missed.
Mention matching that fails on nicknames
If your bot responds to being @-mentioned, do not string-match the mention text. When a user has a server nickname, the mention renders differently, and matching on <@123456> will miss <@!123456>. Use the library's mention helper - message.mentions.has(client.user) in discord.js - which handles both forms.
The infinite loop guard
Always ignore messages from bots, including your own, as the first line of your handler:
if (message.author.bot) return;Without it, a bot that replies to messages will reply to its own reply, forever, until Discord rate limits it. Two bots in the same channel without this guard will do it to each other and generate thousands of messages in seconds.
7. Rate limits, reconnect loops and sharding
Discord returns 429 with a retry_after value in seconds. Well-maintained libraries queue and retry automatically, so if you are seeing raw 429s you are probably calling the REST API directly.
Two limits matter more than the general one:
- Global: around 50 requests per second across your whole application.
- Per route: tighter and specific. Message sends in a single channel are limited to roughly 5 per 5 seconds, which is easy to hit with a bot that splits replies into several bubbles.
Exceeding limits repeatedly can trigger a Cloudflare ban at the IP level, which returns HTML rather than JSON and lasts an hour or more. If your bot suddenly gets unparseable responses from the API, this is why - and it usually follows a reconnect loop.
Identify limits and the crash loop
Gateway identifies are limited to roughly 1,000 per day. A bot crash-looping on startup - throwing an exception after connecting, being restarted by the supervisor, connecting again - burns through that budget and gets locked out of connecting entirely. If a bot has been failing all day and now cannot connect at all even after the bug is fixed, wait for the daily reset.
Sharding at 2,500 guilds
Bots in 2,500 or more guilds must shard. Discord refuses the connection otherwise. Most libraries provide a sharding manager that handles this, but it is a deliberate step you have to take - it does not happen automatically as you grow.
The diagnostic order that finds it fastest
| # | Question | If no |
|---|---|---|
| 1 | Is the bot green in the member list? | Token (4004) or intents (4014). Read the gateway close code. |
| 2 | Does console.log in your message handler print anything? | Wrong intent for the event source - guild vs DM. |
| 3 | Is message.content non-empty when it prints? | Message Content intent. Portal toggle + code + restart. |
| 4 | Does a manual send from that handler succeed? | Permissions - 50013 or 50001. Check channel overwrites. |
| 5 | Do slash commands appear in the menu? | Missing applications.commands scope, or global propagation delay. |
| 6 | Do interactions respond within 3 seconds? | Call deferReply() first, then editReply(). |
| 7 | Are you logging full API error bodies? | Discord's error codes are precise. Do not discard them. |
Step 3 is the one to reach for first when the bot is online. Printing message.content and seeing an empty string is the definitive confirmation of the intents problem, and it takes ten seconds to check.
When to stop maintaining the plumbing
Everything above is transport-layer work: intent negotiation, gateway close codes, permission resolution, interaction deadlines, shard counts, identify budgets. None of it is your product. It is the toll for operating the Discord API directly, and it is the same toll for every bot on the platform.
That is a fine trade for a community bot you enjoy building. It is a worse one when Discord is a support or sales channel for a business, and considerably worse when Discord is one of several channels you are expected to keep alive - because each platform has an entirely different version of this same list.
Conferbot's Discord integration handles connection, intents, permissions and interaction deadlines for you: you connect the bot and build the conversation in a visual flow editor rather than in gateway code. The same flow runs on Telegram, WhatsApp, Slack and a website widget, with buttons and media adapting to each platform automatically.
Start free with Conferbot - the free plan includes 600 conversations a month and needs no credit card, so you can have the same flow running on a second channel this afternoon.
If you are debugging today, work the table above - if the bot is online and silent, it is the Message Content intent far more often than anything else. If you are on your third channel and writing the same plumbing a third time, that is the signal to stop.
Was this article helpful?
Build and deploy in 10 minutes. No coding needed.
Discord Bot Not Responding? The Intents Problem and 8 Other Causes FAQ
Everything you need to know about chatbots for discord bot not responding? the intents problem and 8 other causes.
About the Author
The Conferbot team writes about building, deploying, and improving AI chatbots.
View all articles