Skip to main content
Share
Guides

Slack Bot Not Responding? Fix URL Verification, Missing Scopes and Silent Events

Slack fails quietly. The challenge handshake, the reinstall that scope changes require, the 3-second acknowledgement deadline and the retry storm it causes - each has an exact symptom and an exact fix.

Content & Engineering
Aug 4, 2026
13 min read
Updated Aug 2026Expert Reviewed
slack bot not respondingslack url verification failed challenge parameterslack missing_scope errorslack events not firingslack bot not receiving messages
TL;DR

Slack fails quietly. The challenge handshake, the reinstall that scope changes require, the 3-second acknowledgement deadline and the retry storm it causes - each has an exact symptom and an exact fix.

Key Takeaways
  • Slack's failure modes are unusually quiet.
  • A missing scope, an unverified request URL and a bot that was never invited to the channel all produce the same observable symptom: nothing happens.
  • Your logs are empty because the request never arrived.Last verified: August 2026 against the Slack Events API and OAuth scopes model.Before changing code, answer one question: is your endpoint receiving anything at all?
  • Add a log line as the very first statement in your handler, before any parsing or validation, and send a message that should trigger the bot.ObservationMeaningSectionNothing logged at allSlack is not sending - subscription, verification or invite problem1, 2, 4Request logged, no reply appearsMissing scope, or the send is failing3Reply appears several timesRetry storm from a slow handler5Works in one channel onlyBot not invited elsewhere4That single log line eliminates roughly half the possible causes in under a minute.

Why Slack bots fail silently, and where to look first

Slack's failure modes are unusually quiet. A missing scope, an unverified request URL and a bot that was never invited to the channel all produce the same observable symptom: nothing happens. Your logs are empty because the request never arrived.

Last verified: August 2026 against the Slack Events API and OAuth scopes model.

Before changing code, answer one question: is your endpoint receiving anything at all? Add a log line as the very first statement in your handler, before any parsing or validation, and send a message that should trigger the bot.

ObservationMeaningSection
Nothing logged at allSlack is not sending - subscription, verification or invite problem1, 2, 4
Request logged, no reply appearsMissing scope, or the send is failing3
Reply appears several timesRetry storm from a slow handler5
Works in one channel onlyBot not invited elsewhere4

That single log line eliminates roughly half the possible causes in under a minute.

1. URL verification: "Your URL didn't respond with the value of the challenge parameter"

Before Slack will deliver any event, it verifies you own the endpoint. It POSTs a JSON body:

{
  "token": "Jhj5dZrVaK7ZwHHjRyZWjbDl",
  "challenge": "3eZbrw1aB1m5zVFQrRvBiVAG1MMqbGqBGRTLBSFwhcSFrSKzHJ",
  "type": "url_verification"
}

You must respond with the challenge value and nothing else. This must happen before any signature verification, authentication middleware or body parsing that might reject an unfamiliar payload:

app.post('/slack/events', (req, res) => {
  if (req.body.type === 'url_verification') {
    return res.send(req.body.challenge);   // plain text, first thing
  }
  // ... normal event handling below
});

Five reasons this fails

  • Signature verification runs first. Your middleware rejects the verification request because it does not match an expected shape. Handle url_verification before any guard.
  • You returned JSON instead of the raw string. Returning {"challenge": "..."} also works if the content type is correct, but returning the plain string is more reliable.
  • A redirect sits in front. Slack does not follow redirects during verification. If your URL 301s from HTTP to HTTPS or from a bare domain to www, register the final URL directly.
  • Authentication is required. Any auth layer, IP allowlist or WAF rule in front of the endpoint blocks Slack. The events path must be publicly reachable.
  • Cold start timeout. Slack allows about 3 seconds. A serverless function with a slow cold start fails verification, then succeeds when you retry immediately because the instance is warm - which makes this look intermittent.

During local development, use a tunnel such as ngrok, and remember the URL changes every restart on the free tier - you must re-verify each time. If that becomes tedious, Socket Mode removes the public endpoint requirement entirely and is the better development path.

2. Scopes: the reinstall step everyone forgets

Slack's most common runtime error:

{"ok": false, "error": "missing_scope", "needed": "chat:write", "provided": "channels:read,users:read"}

Helpfully, the response names exactly what you need. The trap is what comes next.

Adding a scope in the app configuration does nothing until the app is reinstalled to the workspace. The token you already hold was issued with the old scope set and does not gain new permissions retroactively. Change scopes, then go to OAuth & Permissions -> Reinstall to Workspace, and use the newly issued token. Skipping the reinstall is the single most common Slack integration mistake.

The scopes a conversational bot actually needs

ScopeGrants
chat:writeSend messages to channels the bot is in
app_mentions:readReceive @-mentions of the bot
im:history, im:writeRead and send direct messages
channels:historyRead messages in public channels the bot is in
groups:historyRead messages in private channels - separate from the above
users:readResolve user IDs to names
chat:write.publicPost to public channels without being invited

Two distinctions matter. Public and private channel history are separate scopes, so a bot that reads public channels fine and sees nothing in private ones needs groups:history. And bot tokens (xoxb-) differ from user tokens (xoxp-): most bot actions need the bot token, and using the wrong one produces confusing permission errors.

Try it yourself
Build your first chatbot free
Free plan, no credit card required. Live on your site in about 10 minutes.
Start building free

3. Events not firing at all

If your endpoint verified successfully but no events arrive, work these in order.

You subscribed to no events

Verifying the URL and subscribing to events are separate steps. Under Event Subscriptions -> Subscribe to bot events, add the specific events you need - most commonly message.channels, message.im, message.groups and app_mention. An empty subscription list means Slack has nothing to send.

You subscribed to the wrong message event

Message events are split by conversation type, and each is a distinct subscription:

  • message.channels - public channels
  • message.groups - private channels
  • message.im - direct messages
  • message.mpim - group direct messages

A bot that responds in public channels and ignores DMs is subscribed to message.channels but not message.im. This is the Slack equivalent of the intent split in other platforms.

The bot is not in the channel

Subscribing to message.channels does not deliver messages from channels the bot has not joined. Invite it with /invite @yourbot in each channel where it should work. A bot that works in one channel and not another has almost always just not been invited.

Attempting to post to a channel it is not in returns:

{"ok": false, "error": "not_in_channel"}

The chat:write.public scope allows posting to public channels without an invite, but it does not grant the ability to read them - you still need the bot in the channel to receive messages.

Your app is on the wrong delivery mode

Socket Mode and the Events API are mutually exclusive. If Socket Mode is enabled, Slack pushes over a WebSocket and ignores your request URL entirely - so a perfectly correct HTTP endpoint receives nothing. Teams that enable Socket Mode for local development and forget to disable it before deploying hit this exactly once, and it is baffling until you know.

4. The 3-second rule and the retry storm

Slack requires an HTTP 200 within 3 seconds of delivering an event. Miss it and Slack retries - up to three times, at increasing intervals. Your handler runs again each time, and the user receives the same reply three or four times.

This is the cause of nearly every "my Slack bot sends duplicates" report, and it becomes certain the moment you add an AI model call to the handler.

Acknowledge first, then work:

app.post('/slack/events', async (req, res) => {
  res.sendStatus(200);              // acknowledge immediately, always
  await handleEvent(req.body);      // then take as long as you need
});

For slash commands and interactive components you can also respond immediately with a placeholder and update it later using the response_url, which stays valid for 30 minutes and accepts up to five follow-up messages.

Detect retries rather than reprocessing them

Slack marks retried deliveries with headers you should check:

X-Slack-Retry-Num: 1
X-Slack-Retry-Reason: http_timeout

If X-Slack-Retry-Num is present, acknowledge and return without reprocessing. Combine that with deduplication on event_id and duplicate replies disappear entirely - even if a genuine timeout occurs.

Calculate your chatbot ROI
See exactly how much a chatbot saves your business. Free calculator, no signup required.
Try Calculator

5. Infinite loops, thread targeting and ID mistakes

The bot replying to itself

Bot messages generate message events too. Without a guard, your bot replies to its own reply indefinitely until Slack rate limits it. Check for the bot marker as the first line of the handler:

if (event.bot_id || event.subtype === 'bot_message') return;

Two unguarded bots in one channel will do this to each other and produce thousands of messages in seconds.

Message subtypes you probably want to ignore

Edits, deletions, joins and file shares all arrive as message events with a subtype. A bot that responds to message_changed will reply again every time someone edits a message. Handle only the subtypes you intend to.

Replying in the wrong place

To reply inside a thread, pass thread_ts - the ts of the parent message. Omit it and your reply lands in the channel, out of context. Conversely, passing thread_ts when you meant to post to the channel buries the message in a thread nobody is watching.

channel_not_found

Usually a channel name used where an ID was required. Slack channel IDs look like C01ABCDEF; using #general works in some endpoints and not others. Store IDs rather than names - names change, IDs do not. The same error appears for private channels the bot has not been invited to, since it genuinely cannot see them.

Rate limits

Slack rate limits per method in tiers, and chat.postMessage is generally around one message per second per channel with short bursts tolerated. Exceeding it returns HTTP 429 with a Retry-After header in seconds - honour it exactly rather than retrying immediately.

The diagnostic checklist

#CheckIf it fails
1Request URL shows VerifiedHandle url_verification before any middleware
2Socket Mode is OFF in productionIt overrides your request URL completely
3Bot events are subscribed, not just the URL verifiedAdd message.im, message.channels, app_mention
4App reinstalled since the last scope changeOld tokens never gain new scopes
5Bot invited to the channel/invite @yourbot
6Handler returns 200 within 3 secondsAcknowledge first, process after
7Bot messages ignoredGuard on bot_id to prevent loops
8Full API error bodies loggedSlack names the missing scope for you

Row 8 deserves emphasis. Slack's missing_scope response includes both needed and provided, which is as close to a self-diagnosing error as any platform offers. Code that logs only a status code discards the answer.

6. Buttons, modals and slash commands that do nothing

Interactive components fail differently from events, and they fail more visibly - the user sees a spinner or an error in the Slack client rather than silence.

The Interactivity request URL is separate

This is the most common cause and the least obvious. Event Subscriptions and Interactivity & Shortcuts have their own separate request URLs. Verifying your events endpoint does nothing for button clicks. If your bot posts a message with buttons and nothing happens when they are pressed, open Interactivity & Shortcuts, turn interactivity on, and set a request URL there too - it can be the same endpoint, but it must be configured.

Interaction payloads are form-encoded, not JSON

Slack sends interactive payloads as application/x-www-form-urlencoded with a single payload field containing JSON as a string. An endpoint that only parses JSON receives nothing usable:

app.post('/slack/interactive', (req, res) => {
  res.sendStatus(200);                          // acknowledge first
  const payload = JSON.parse(req.body.payload); // note: not req.body
  handleInteraction(payload);
});

A handler that works perfectly for events and throws on every button click is almost always missing this. Make sure your body parser handles URL-encoded bodies on that route.

trigger_id expires in 3 seconds

Opening a modal requires a trigger_id from the interaction payload, and it is valid for 3 seconds only. You cannot defer, queue, or await an AI call before calling views.open - the trigger will be dead. Open the modal immediately with a loading state, then update it with views.update once the slow work finishes.

The error you get is expired_trigger_id, and it appears intermittently under load, which makes it look like a flaky network problem rather than a design constraint.

Slash commands have their own URL too

Each slash command is registered individually under Slash Commands, each with its own request URL. Commands also require the app to be reinstalled after being added. And a command name already taken by another app in that workspace will silently route to the other app - if your /help command triggers something unexpected, a competing app owns it.

Block Kit validation failures

Malformed Block Kit JSON returns invalid_blocks with little detail. Common causes are exceeding 50 blocks in a message, a text field over 3,000 characters, more than 5 buttons in an actions block, or a missing required field on an element. Validate structures in Slack's Block Kit Builder before shipping - it catches these instantly, and the API does not.

7. Workspace installs, org-wide apps and admin approval

A bot that works in your development workspace and cannot be installed - or installs and sees nothing - in a customer's workspace is usually hitting an administrative control rather than a technical one.

App approval is on by default in many organisations

Workspace admins can require approval before any app is installed. Your OAuth flow completes, the user sees "request sent to your admin", and nothing works until someone approves it. There is no technical workaround - the install genuinely has not happened. If you distribute a Slack app, your onboarding needs to account for this delay explicitly rather than assuming installation is instant.

Org-wide installs behave differently from workspace installs

In Slack Enterprise Grid, an app can be installed to a single workspace or org-wide across many. Org-wide installs return a different token context, and code that assumes one team_id per token breaks - events arrive with varying team identifiers and API calls need the right context. If your app is only ever used in one workspace this never surfaces; the first Enterprise Grid customer surfaces it immediately.

Token storage for multi-workspace apps

A distributed app receives a distinct bot token per installation. Hardcoding a single token from your own workspace - which works throughout development - fails the moment a second workspace installs. Store tokens keyed by team_id (and enterprise_id for Grid) and look up the right one per incoming event.

Token rotation

Apps with token rotation enabled receive tokens that expire and must be refreshed with a refresh token. If your app worked for hours and then began returning token_expired, rotation is enabled and you are not refreshing. Either implement the refresh flow or disable rotation if your security posture allows.

A note on Slack Connect channels

Shared channels with external organisations impose extra restrictions. Bots may not see all messages in a Slack Connect channel, and some API methods behave differently. If your bot works in internal channels and misses messages in a shared one, this is why - and it is a platform constraint rather than a configuration you can change.

When the plumbing stops being worth it

Everything above is transport work: challenge handshakes, OAuth scope lifecycles, event subscription matrices, acknowledgement deadlines, retry deduplication, rate pacing. It is the same list for every Slack app ever built, and none of it is your product.

That is a reasonable investment for an internal tool. It is a poor one when Slack is one of several channels you are expected to keep alive, because each platform has a completely different version of the same list - and our guides to Telegram bots not responding and Discord bots not responding exist precisely because those lists share nothing with this one.

Conferbot's Slack integration handles installation, scopes, event delivery and acknowledgement timing for you. You build the conversation once in a visual flow editor and the same flow runs on Microsoft Teams, Telegram, WhatsApp, Discord and a website widget, with formatting adapted per platform.

Start free with Conferbot - 600 conversations a month, no credit card. If you are debugging today, run the checklist above first; if you are about to write this plumbing for a third platform, that is the signal to stop.

Share this article:

Was this article helpful?

Ready to build your chatbot?

Join the businesses. Deploy on website, WhatsApp, and 11 more channels in minutes. Free forever plan available.

No credit cardNo coding13+ channels
Start Building Free

Get chatbot insights delivered weekly

Join 5,000+ professionals getting actionable AI chatbot strategies, industry benchmarks, and product updates.

🎯Automate this with a free chatbot

Build and deploy in 10 minutes. No coding needed.

FAQ

Slack Bot Not Responding? Fix URL Verification, Missing Scopes and Silent Events FAQ

Everything you need to know about chatbots for slack bot not responding? fix url verification, missing scopes and silent events.

🔍
Popular:

Slack POSTs a url_verification payload containing a challenge string, and your endpoint must echo that value back within about 3 seconds. The usual cause is signature-verification or authentication middleware running first and rejecting the request - handle url_verification as the very first statement in your handler. Slack also does not follow redirects during verification, so register the final URL rather than one that 301s.

The error response names exactly what you need in its needed field. Add that scope under OAuth & Permissions, then reinstall the app to the workspace and use the newly issued token. Adding a scope without reinstalling changes nothing, because the token you already hold was issued with the old scope set - this is the most common Slack integration mistake.

Check four things in order: that you actually subscribed to bot events rather than only verifying the URL; that you subscribed to the right message event, since public channels, private channels and DMs are separate subscriptions; that the bot has been invited to the channel with /invite @yourbot; and that Socket Mode is off, because it overrides your request URL entirely.

Message events are split by conversation type and each is a distinct subscription. You need message.im for direct messages alongside message.channels for public channels, plus the im:history and im:write scopes. Subscribing to message.channels alone gives you exactly this symptom.

Your handler is not returning HTTP 200 within Slack's 3-second window, so Slack retries up to three times and your code processes the event each time. Acknowledge immediately with res.sendStatus(200) and do the work afterwards. Also check the X-Slack-Retry-Num header and skip reprocessing when it is present, and deduplicate on event_id.

The bot is trying to post to a channel it has not joined. Invite it with /invite @yourbot in that channel. The chat:write.public scope allows posting to public channels without an invite, but it does not let the bot read them - you still need it in the channel to receive messages.

Socket Mode delivers events over a WebSocket your app opens outbound, requiring no public endpoint, which makes it ideal for local development and firewalled environments. The Events API POSTs to a public HTTPS request URL. They are mutually exclusive - with Socket Mode enabled, Slack ignores your request URL completely, which catches out teams who enable it for development and forget to disable it before deploying.

At minimum chat:write to send messages and app_mentions:read to receive mentions. Add im:history and im:write for direct messages, channels:history for public channel messages, groups:history for private channels (a separate scope), users:read to resolve names, and chat:write.public to post to public channels without an invite.

Bot messages generate message events too. Add `if (event.bot_id || event.subtype === 'bot_message') return;` as the first line of your handler. Without it the bot replies to its own reply indefinitely until Slack rate limits it, and two unguarded bots in one channel will generate thousands of messages in seconds.

Usually a channel name used where an ID was required - Slack channel IDs look like C01ABCDEF, and #general does not work in every endpoint. Store IDs rather than names, since names change and IDs do not. The same error also appears for private channels the bot has not been invited to, because it genuinely cannot see them.

Three seconds to return HTTP 200. For slash commands and interactive components you can respond immediately with a placeholder and then update it using the response_url, which remains valid for 30 minutes and accepts up to five follow-up messages. Any handler that calls an AI model will exceed 3 seconds, so acknowledge first as a matter of course.

Slack rate limits per method in tiers, and chat.postMessage is generally around one message per second per channel with short bursts tolerated. Exceeding it returns HTTP 429 with a Retry-After header in seconds, which you should honour exactly rather than retrying immediately.

About the Author

Content & Engineering

The Conferbot team writes about building, deploying, and improving AI chatbots.

View all articles
Skip the blank canvas
Start from one of 250+ free chatbot templates for lead generation, support, e-commerce, and 20+ industries - customize and launch in minutes.
Browse free templates

Related Articles

Omnichannel Platform

One Chatbot,
Every Channel

Your chatbot works seamlessly across WhatsApp, Messenger, Slack, and 6 more platforms. Build once, deploy everywhere.

View All Channels
Conferbot
online
Hi! How can I help you today?
I need pricing info
Conferbot
Active now
Welcome! What are you looking for?
Book a demo
Sure! Pick a time slot:
#support
Conferbot
New ticket from Sarah: "Can't access dashboard"
Auto-resolved. Password reset link sent.