The first time you wire up a Slack app, Slack asks a question that quietly decides your whole deployment story: how should it deliver events to your backend? You get two answers. Either you expose a public HTTPS endpoint that Slack POSTs to, or you turn on slack socket mode and your app opens a persistent WebSocket back to Slack. They feel interchangeable when you start, because the events and scopes are identical. They are not interchangeable when you go to production, and the team that picked Socket Mode for a service that needed to autoscale finds that out the hard way.
I have built this both ways across a handful of client deployments, from a single-workspace internal bot to an Enterprise Grid org-level install. This is the decision laid out the way I wish someone had laid it out for me.
How Slack actually reaches your backend
When a user types a slash command, clicks a button, or mentions your bot, Slack needs to hand that event to your code. There are exactly two transport models.
HTTP means your app exposes one or more public HTTPS endpoints. Slack POSTs JSON to them on every event, and your app has to return a 200 within three seconds. A typical config registers a handful of request URLs:
- Event Subscriptions request URL, for example
/slack/events - Interactivity request URL, often folded into the same
/slack/events - Slash Commands request URL
- OAuth redirect URL, for example
/slack/oauth/callback
The first time you save a request URL, Slack runs a handshake. It POSTs {"type":"url_verification","challenge":"..."} and your endpoint has to echo the challenge value back within three seconds, or Slack refuses to save the URL. After that, every request carries an X-Slack-Signature and X-Slack-Request-Timestamp header, and you verify it with HMAC-SHA256 against your signing secret. Skip that verification and an attacker can spoof events at your bot.
Socket Mode flips the direction. Your app creates an app-level token (xapp-...) with the connections:write scope, calls apps.connections.open to get a WebSocket URL, and connects outbound. Slack pushes events down that socket and your app acks on the same connection. No public port, no TLS certificate of your own, no signature verification, because the connection is already authenticated by the app token. Locally you just run node app.js and you are receiving events. No tunnel required.
When Socket Mode is the right call
Socket Mode wins on setup speed and on network simplicity. You can stand it up in about five minutes and you never touch public infrastructure. That makes it the obvious choice in three situations.
The first is local development. You do not want to run ngrok or a Cloudflare Tunnel just to test a slash command on your laptop. Socket Mode lets you iterate against a real dev workspace with zero tunneling.
The second is demos and hackathons, where the whole point is to have something working in an afternoon and nobody is going to load-test it.
The third is the one people forget: an internal tool behind a corporate firewall that genuinely cannot accept inbound connections from Slack. I ran into this with a Vietnamese commercial bank whose compliance posture made an inbound public endpoint a non-starter. Their network team would not open the firewall to Slack's IP ranges, full stop. Socket Mode's outbound-only connection sidesteps that entirely, because the app dials out rather than waiting to be dialed.
A minimal Bolt for JavaScript app in Socket Mode looks like this:
const { App } = require('@slack/bolt');
const app = new App({
token: process.env.SLACK_BOT_TOKEN, // xoxb-...
appToken: process.env.SLACK_APP_TOKEN, // xapp-...
socketMode: true,
});
app.command('/campaign', async ({ ack, body, client }) => {
await ack();
// open a modal, write to a DB, whatever the command needs
});
(async () => {
await app.start();
console.log('bot running in socket mode');
})();
Notice there are no request URLs anywhere. When you enable Socket Mode, the slash command and interactivity config pages stop asking for URLs at all.
Where Socket Mode falls apart at scale
Everything that makes Socket Mode pleasant for dev makes it wrong for most production deployments.
It needs a process that stays alive to hold the connection, which rules out serverless functions. People try to run Socket Mode on Lambda and it bites them: a Lambda has a 15-minute ceiling, the socket dies when the function times out, and events that land during the gap are simply missed. Socket Mode wants an EC2 instance or a long-running container, never a function.
It does not scale horizontally in any clean way. Slack caps you at 10 sockets per app, so you cannot fan out to 100 replicas. Worse, if you run three replicas, each opens its own socket and Slack load-balances events across them, so unless every handler is idempotent you get duplicate writes, double-posted messages, and races where one replica acts while the others ack into the void. The pragmatic rule is that Socket Mode runs as a single replica.
And it is not allowed on the Slack Marketplace. Distributed apps that go through Slack's app review have to use HTTP. Socket Mode does not pass review.
HTTP is what you ship
For production, HTTP is the default and usually the right one. It maps onto the stateless model your infra team already knows: Lambda behind API Gateway, or containers on Kubernetes with a horizontal pod autoscaler, or Cloud Run scaling from zero. No long-lived connection to babysit, easy load balancing, clean blue-green deploys.
The pattern I keep landing on with clients is Socket Mode for local dev, then a flip to HTTP for production on the same codebase. An e-commerce operations team I worked with runs their ops bot on Lambda plus API Gateway and pushes around 50k messages a day through it without thinking about connection limits. A large engineering org runs theirs on containers because they need a persistent DB connection pool and an on-call cron, but it is still HTTP under the hood. A retail bank's marketing approval bot sits on Cloud Run autoscaling 0 to 10. Across every production deployment I have shipped, none of them use Socket Mode in production. Socket Mode stays a dev convenience.
The flip itself is mechanical. Turn off Socket Mode in the app config, fill in the Event Subscriptions, Interactivity, and Slash Command request URLs, switch the Bolt receiver from socketMode: true to an ExpressReceiver, deploy, and let Slack run the challenge handshake against the live URL.
The one production pitfall worth repeating: do not disable signature verification on an HTTP endpoint. Bolt's ExpressReceiver verifies X-Slack-Signature for you, and an unverified endpoint is an open door for someone to post fake events that make your bot spam channels or create junk.
The four components underneath both models
The transport is only half the picture. Every app, HTTP or Socket, is built from the same four pieces.
The bot token (xoxb-...) is the workhorse. It is issued to your app's bot user when the app is installed, and it backs almost every action: chat.postMessage, conversations.create, file uploads, opening modals. Its powers come from the Bot Token Scopes you declare, where chat:write unlocks the posting methods, users:read unlocks user lookups, and so on. Store it in a secret manager, never in git.
The user token (xoxp-...) runs as a real person rather than the bot. You only need it for two reasons: an action that must show up in a specific user's audit trail, or a method that simply does not exist for bots. Search is the classic example, since search.messages requires a user token, which is why that same Vietnamese commercial bank uses a user token so a compliance officer can run audit exports. Most apps never need one. User tokens are also more dangerous, because they can read DMs and post as the person, so scope them minimally and keep them in a separate column from bot tokens in your database.
Event subscriptions are how Slack notifies you that something happened, whether that is app_mention, message.channels, member_joined_channel, or app_home_opened. You ack within three seconds; if the real work takes longer, ack first and spawn a background job. Slack retries on a slow ack (roughly at 1, 5, and 15 minutes), so you must dedupe on event_id or you will process the same event twice and create duplicate records.
Interactivity is the inbound side of UI: a button click, a select change, a modal submit. Slack POSTs a payload typed as block_actions, view_submission, and so on, and again you have three seconds to ack. The detail that trips people up is the trigger_id. It is a short-lived token, alive for three seconds, and it is your only ticket to open a modal via views.open. If you burn those three seconds on a DB query first, the trigger expires and the modal never opens. The correct order is: ack, call views.open immediately, then do the heavy work.
Here is how they compose in a real approval flow. A button labeled "Approve Marketing" is posted with the bot token. When a user clicks it, interactivity delivers a block_actions payload carrying the action_id and a value like campaign_C-2026-0042. The handler acks empty, checks the clicker against a manager user group, writes the approval to the DB, and calls chat.update to disable the button and show who approved. No user token anywhere, because Slack already records who clicked inside the message metadata.
Takeaway
The HTTP-versus-Socket-Mode choice is really a question about your network and your scale, not about features. Use Socket Mode when you are developing locally, demoing, or running a low-traffic internal tool that cannot expose a public endpoint, and accept that it stays single-replica and off-serverless. Use HTTP for anything that has to autoscale, anything on serverless, and anything headed for the Marketplace, and verify request signatures without exception. Underneath either transport, lean on the bot token by default, reserve user tokens for audit and search, dedupe events on event_id, and spend your trigger_id on views.open before you do anything else. Get those calls right early and you save yourself the refactor that comes from discovering the constraints in production.
If your team is weighing this decision on a real Slack integration, or you have a Socket Mode prototype that needs to grow into something production-grade, we have shipped these architectures end to end and can help you skip the painful detours. Tell us about your setup at /contact, or see how we approach platform work on our /service page.








