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 registered to another app or guild | Global vs guild registration, application ID |
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. If instead you have a literal error code in hand, the Discord error-code directory maps every JSON error and gateway close code to its fix.
Check the invite as well: if the bot is online but cannot post or moderate, paste its invite link into the Discord permissions calculator to see exactly which permissions it was added with. The Message Content intent is a separate switch in the Developer Portal, not a permission.
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 two growth walls: 100 servers and 10,000 users
There are two separate reviews, and since June 10, 2026 they are no longer the same process. Verification is about growth: unverified bots are capped at 100 servers, and the API refuses further joins with error 30001. Privileged intent review is about data: once your app can reach 10,000 users across its servers, you must apply for each privileged intent and reapply once a year. Older guidance tied intents to 100 servers; that rule was replaced. Apply early either way - both are manual reviews. Reaching that line does not switch anything off on the spot: you get a notification in the Developer Portal and 90 days to apply, and the bot keeps working and joining servers while the submission is under review. Miss the window and the privileged intents are removed, which is when a bot goes silent for its entire user base at once.
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 privileged-intent review at 10,000 users along with it.
Our Message Content intent guide covers the exceptions, the code for discord.js and discord.py, and the June 2026 switch to a 10,000-user review threshold.
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. The gateway 4014 reference page has the library-specific fixes.
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 keeps you clear of privileged-intent review. Verification is a separate process, triggered at 100 servers.
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 | More than 120 gateway payloads in 60 seconds on one connection. Usually a presence or request 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. Discord is a GitHub secret scanning partner, so a bot token pushed to a public GitHub repository is reported to Discord and reset, often within minutes, and the owner is notified. If your bot died suddenly and nothing changed on your side, check whether the token reached a public repo first. Treat any other public exposure the same way, whether or not anything scanned it: a token pasted into a forum post or a support thread is compromised.
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.
If you need a fresh token, here is how to get and safely store a Discord bot token.
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. Channels that sync with their category inherit the category's overwrites.
Inside a channel, overwrites apply in a fixed order: the @everyone overwrite (deny, then allow), then all role overwrites (every role deny, then every role allow), then member-specific overwrites (deny, then allow). Two consequences matter for bots. If one of the bot's roles is denied a permission in the channel and another of its roles is allowed it, the allow wins, whatever the role order. And a member-specific overwrite on the bot itself beats every role overwrite. Administrator bypasses overwrites entirely. The 50013 reference page walks the overwrite layers in order. 50001 is the channel-visibility version of the same problem and has its own page at Missing Access.
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.
If the bot was invited with the wrong permissions, rebuild the link using our Discord bot invite link guide.
5. Slash commands that never appear
Registered commands that do not show in the client have three usual causes, in this order.
The commands were never registered, or registered somewhere else
Today the applications.commands scope is included by default with the bot scope, so a current invite link already lets the app add commands. What usually goes wrong is registration itself: the registration script never ran in production, it ran against a different application ID than the bot's token belongs to, or it registered to a test guild that is not the server you are looking at. Fetch what Discord actually holds with GET /applications/{application.id}/commands (global) or GET /applications/{application.id}/guilds/{guild.id}/commands (guild) and compare it with what you expect.
Very old installations are the one case where re-inviting helps. A bot added years ago with a link that requested only bot, before the scope was bundled, can be re-authorised from a current invite link without kicking it first:
https://discord.com/oauth2/authorize?client_id=YOUR_ID&permissions=YOUR_PERMS&scope=bot%20applications.commandsGlobal vs guild commands
Guild commands update instantly and only exist in the guild you registered them to, which is why Discord recommends them for testing. Global commands are available in every guild that adds your app (and in DMs with the bot when the user shares a server with it), and they have built-in read-repair: if a user's client still holds an old version, Discord rejects that use and reloads the command. During development, register to a single test guild, and switch to global registration when the commands are ready for public use.
// Instant - use during development
await rest.put(Routes.applicationGuildCommands(clientId, testGuildId), { body: commands });
// Global - 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. For the other causes of that message, including buttons, modals, a stray Interactions Endpoint URL and serverless cold starts, see our dedicated guide to fixing "The application did not respond".
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. Current clients send <@123456>, but the legacy nickname form <@!123456> still turns up in older messages, so an exact string match can miss one or the other. 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 (the full set of REST, gateway and interaction ceilings is on the Discord limits page):
- Global: around 50 requests per second across your whole application.
- Per route: tighter and specific, and subject to change. Read the
X-RateLimit-RemainingandX-RateLimit-Reset-Afterheaders rather than hard-coding numbers; a bot that splits one reply into several message bubbles hits per-channel send limits quickly.
Separately, an IP that makes too many invalid requests (401, 403 or 429 responses, currently 10,000 per 10 minutes) is temporarily restricted from the API. This is the Cloudflare ban, and it returns HTML rather than JSON. 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 1,000 per 24 hours, across all shards (resumes do not count). A bot crash-looping on startup - throwing an exception after connecting, being restarted by the supervisor, connecting again - burns through that budget fast. Hitting the limit is not a lockout you can wait out: Discord terminates every active session, resets the bot token and emails the application owner. If a bot has been crash-looping and now fails with an invalid token (close code 4004), fix the crash, copy the new token from the Developer Portal into your secret store and redeploy.
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.
8. HTTP interactions: the other way a bot can go silent
Everything above assumes your bot holds a persistent WebSocket connection to the gateway, which is how discord.js and discord.py work by default. Discord also supports a second, gateway-free transport for slash commands: an Interactions Endpoint URL, configured under General Information in the Developer Portal. Instead of pushing interaction payloads down a socket, Discord POSTs each one to your HTTPS endpoint - the same shape as a webhook. Bots built on serverless functions often use this so they never need a long-running process.
This path fails differently, and more silently, than a gateway disconnect.
The PING/PONG handshake
The moment you save an Interactions Endpoint URL, Discord sends a synthetic PING payload (type: 1) to it and expects a 200 response containing a PONG (also type: 1) within a few seconds. Fail that check and Discord refuses to save the URL at all - there is no partial state where the field is set but broken, so if your endpoint field is empty, this handshake is what rejected it.
Signature verification, not a bearer token
Every request Discord sends to an interactions endpoint carries X-Signature-Ed25519 and X-Signature-Timestamp headers. You must verify them - by concatenating the timestamp with the raw request body and checking the Ed25519 signature against your application's public key - before parsing the JSON. Skipping this, or verifying against the parsed body instead of the raw bytes, produces a rejected handshake that looks identical to a network problem. Discord deliberately sends malformed test requests to confirm you are actually checking the signature rather than always returning 200.
| Transport | How updates arrive | Typical failure |
|---|---|---|
| Gateway (default) | Persistent WebSocket, all event types | Close codes 4004/4013/4014, missing intents |
| HTTP interactions endpoint | Discord POSTs to your URL, interactions only | Failed PING/PONG, bad signature verification, endpoint not public |
Because this endpoint only ever carries interactions - never raw MESSAGE_CREATE events - a bot built entirely on it can never read prefix commands, regardless of intents. If you switched to this transport and prefix commands stopped working while slash commands kept working, that is expected, not a regression. It is also the same 3-second acknowledgement deadline as the gateway path: return a DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE response type immediately if the real answer needs an API call or model inference.
If the endpoint fails Discord's validation and you are not sure whether the fault is the Ed25519 check, the PING handler or the network in front of it, our webhook debugging guide has a platform-neutral order for isolating which of the three it is.
Running Discord as a support or sales channel, not just a community server
Most of this guide describes problems that show up whether the bot is a hobby project or a revenue channel. But the two use it very differently. A community bot can absorb an afternoon of gateway debugging as a side quest; a business relying on Discord for support, onboarding or sales cannot, because every hour spent on intents and close codes is an hour not spent on the conversation itself.
Discord shows up as a real support and community surface across a specific set of industries more than others: education, where servers double as course cohorts and study groups; media and creator businesses, where a server is the primary fan community; tech companies, where Discord has become the default place developer communities live; and nonprofits, who use it as a low-cost volunteer and donor hub. If community moderation and growth - rather than transport debugging - is the actual problem you are solving, our guide to Discord bot community management is the more relevant next read.
What changes when you stop hand-rolling the transport
Conferbot's Discord integration is available on every plan, including Free, and it takes most of this guide off your plate: the gateway connection, reconnects, rate-limit retries and reply delivery are handled by the platform, so a flow change is a visual edit rather than a redeploy. Two steps stay with you: you still turn on the Message Content intent in the Developer Portal and give the bot permission to read and send in the channels it should answer. On Pro and up, conversations can also branch out to real systems through Conferbot's native integrations - Zapier, Slack, Mailchimp, Stripe, SalesForce, HubSpot, Google Sheets, Airtable and Google Calendar - and on any paid plan through webhooks or the API if you need something the built-in integrations do not cover. There is no bring-your-own retrieval pipeline and no canned-response library bolted on; a flow is a flow, and it runs the same way on Discord as it does everywhere else it deploys.
The invite URL that grants a bot the applications.commands scope is itself an OAuth 2.0 authorization request - the same protocol Slack, Google and most SaaS integrations use for delegated access, which is worth knowing if you are debugging why a scope change did not take effect: like Slack, some scope changes require the authorization to be redone, not just re-saved.
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? | Commands not registered for this app or guild, or restricted by an admin. |
| 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, rate limiting, 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 - our guides to Telegram bots not responding and Slack bots not responding exist because those platforms make you pay an entirely different version of the same tax, and our piece on API rate limiting covers the pacing problem in more depth than any single-platform guide can.
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. If your visitors also reach you through a website, the same class of debugging shows up there too; see our guide to a chat widget not showing on your website when that is the channel giving you trouble.
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 7 Other Causes FAQ
Everything you need to know about chatbots for discord bot not responding? the intents problem and 7 other causes.
About the Author
The Conferbot team writes about building, deploying, and improving AI chatbots.
View all articlesRelated Articles
From the reference shelf
Fact-checked reference pages and free tools for the platform this article covers.