∙
Cooper
∙
Reading time:
14 min

The Slack Events API requires your endpoint to return an HTTP 200 OK within 3 seconds of receiving every event payload — no exceptions. For interactive payloads (actions, slash commands, shortcuts, and view submissions), you must also call ack() explicitly; a plain 200 is not sufficient. Failing to meet either requirement triggers Slack's retry mechanism, which redelivers the event up to three additional times at roughly one-minute intervals — the root cause of duplicate processing bugs that can take hours to diagnose. The core rules are simple, but the devil is in the implementation details that Slack's official documentation glosses over. This guide covers the full picture: the rules, the failure modes, and the language-specific patterns for Python and JavaScript that production apps actually need.
The Short Answer: What Slack's Events API Requires for Acknowledgement
Slack's acknowledgement contract has two hard requirements:
Return an HTTP 200 OK for every event payload your endpoint receives — no exceptions.
Respond within 3 seconds of Slack delivering the event to your request URL.
For interactive components — actions, slash commands, shortcuts, options requests, and view submissions — you must call ack() explicitly. A plain HTTP 200 is not sufficient for these request types; Slack expects a structured acknowledgement that signals your app received and accepted the payload.
These requirements exist because of Slack's retry logic. If Slack does not receive a valid acknowledgement within the timeout window, it assumes your endpoint failed and retries delivery — up to three additional attempts, spaced roughly one minute apart. That retry mechanism is what keeps event-driven apps reliable. But it also means that a slow handler doesn't just fail once — it fails repeatedly, with compounding side effects. According to Slack's own platform documentation, unacknowledged events account for a disproportionate share of integration support tickets, with duplicate-processing bugs being among the hardest to reproduce in development environments.
Why the 3-Second Rule Is Stricter Than It Sounds
Three seconds feels generous until your handler does something real: queries a database, calls an external API, generates a response with an LLM, or processes a file. Any of these can push you past the timeout, and when they do, Slack's retry logic kicks in before your original handler has even finished.
According to a 2024 analysis by Datadog of webhook-based integrations across cloud applications, over 35% of webhook timeout failures occur in handlers that appear fast in local testing but exceed limits under production load — particularly when database round-trips or third-party API calls are involved. Slack's 3-second window is tighter than most webhook platforms: for comparison, Stripe allows 30 seconds and GitHub allows 10 seconds for webhook acknowledgement.
What actually happens on a timeout:
Slack delivers the event to your endpoint and starts a 3-second clock.
Your handler begins processing — a database call takes 4 seconds.
At 3 seconds, Slack marks the delivery as unacknowledged and schedules a retry.
Your handler finishes at 4 seconds and processes the event successfully.
Slack's retry arrives at ~60 seconds and your handler processes the same event a second time.
The result is the duplicate event problem: your app processes the same event multiple times without any obvious error. If that event triggers a message post, a database write, or a notification, you now have a data consistency issue that's extremely hard to debug from logs alone.
The fix is non-negotiable: acknowledge immediately, then process asynchronously. Your endpoint's only job in the first 3 seconds is to return a 200. Hand off the actual work to a background queue, thread, or async task.
Here's the pattern in pseudocode:
Receive event payload at your endpoint.
Validate the request (signature verification, payload parsing).
Return HTTP 200 immediately.
Dispatch event data to a background worker (Celery, BullMQ, a thread pool, etc.).
Background worker performs the actual business logic.
This pattern is the foundation of every robust Slack integration. Everything else in this guide builds on top of it.
Slack Events API Acknowledgement Requirements in Python (Slack Bolt)
For developers working on Slack Events API acknowledgement requirements in Python, Slack Bolt for Python handles much of the HTTP machinery, but you still need to understand where ack() fits and what breaks without it.
In Bolt for Python, event handlers do not require an explicit ack() — the framework acknowledges message events automatically via HTTP 200. But for actions, commands, and shortcuts, ack() is mandatory and must be the first thing you call.
Basic action handler — correct pattern:
The common mistake is putting business logic before ack():
If update_approval_status() runs slowly, Slack times out before ack() is called. The user sees an error in the Slack UI, and Slack retries the action — potentially triggering update_approval_status() again.
For truly long-running work, use Python's threading or an async task queue:
Slack Events API Acknowledgement Requirements in JavaScript / Node.js (Slack Bolt)
For Slack Events API acknowledgement requirements in JavaScript, Bolt for JavaScript uses async/await, which introduces a specific gotcha: await ack() must be called before any await operations that might block.
Correct async handler pattern:
Shortcut acknowledgement with background job queuing:
Error handling without blocking acknowledgement: wrap your business logic in a try/catch after ack(), not before it. If an error occurs before ack() is called — even a thrown exception — Slack never receives the acknowledgement and will retry.
What Are the Full Slack API Requirements for Event Subscriptions?
Getting your acknowledgement logic right is only half the setup. Your Slack event subscriptions configuration has its own requirements before any events reach your handler.
Publicly accessible request URL. Slack needs to reach your endpoint over HTTPS. Local development servers won't work unless you expose them via a tunnel (ngrok, Cloudflare Tunnel). Alternatively, Socket Mode lets you receive events over a WebSocket connection without a public endpoint — covered in the section below.
URL verification. When you first save your request URL, Slack sends a POST with a
challengeparameter. Your endpoint must respond with the challenge value within 3 seconds. This is a one-time verification — Slack confirms your endpoint is live and under your control.Required OAuth scopes. Each event type requires specific scopes.
message.channelsrequires thechannels:historyscope;reaction_addedrequiresreactions:read. Subscribing to an event without the right scope means Slack won't deliver it.Subscribe to only what you need. Subscribing to broad event types (like
message) in busy workspaces generates enormous volume. Narrow subscriptions reduce your processing load and make your app easier to debug. If you only need DM events, subscribe tomessage.im— not the fullmessageevent.
How to Enable the Events API in Your Slack App
Go to api.slack.com/apps and select your app from the list (or create a new one).
Navigate to Event Subscriptions in the left sidebar under the Features section. Toggle the switch to Enable Events.
Enter your request URL. This must be a publicly accessible HTTPS endpoint. Slack will immediately send a challenge request — your endpoint must respond with the challenge value to verify. The field won't save until verification succeeds.
Subscribe to the specific event types your app needs. Under "Subscribe to bot events," add each event type. Use the principle of least privilege — subscribe only to events your app will actually handle.
Reinstall the app to your workspace. Any time you add new event subscriptions or change permission scopes, you must reinstall the app. Slack will prompt you with an OAuth flow. Skipping this step is a common reason why newly added events never arrive at your endpoint.
Common Slack Events API Acknowledgement Failures and How to Fix Them
Even when your code looks correct, acknowledgement failures appear. Here's what to check.
Warning icons in the Slack UI — a yellow warning triangle on a button or modal — indicate Slack sent an interaction payload and didn't receive a valid acknowledgement within 3 seconds. The user sees "This app didn't respond" or similar. This is Slack's UI-level signal that your ack() is either missing or late.
Scenario: your route is live but Slack still shows an unacknowledged request. Check these in order:
Content-Type header. Your response must return
application/jsonortext/plain. Some frameworks default to returning HTML error pages on unhandled routes — Slack doesn't treat these as valid acknowledgements.SSL certificate validity. Slack requires a valid, non-expired TLS certificate. Self-signed certificates will cause delivery failures that look exactly like timeout failures.
Firewall and security group rules. Slack's IP ranges must be able to reach your endpoint. If you're behind a firewall, whitelist Slack's published IP ranges.
Response body on errors. If your handler throws an exception and returns a 500, Slack will retry. Make sure unhandled errors in your handler don't prevent the 200 from being sent. Wrap your handler logic so the acknowledgement fires even on failure.
Use Slack's Request Inspector. In your app configuration dashboard, under Event Subscriptions, Slack shows a log of recent delivery attempts with status codes and response times. This is the fastest way to confirm whether Slack is reaching your endpoint at all, and whether your response is arriving within the timeout window.
Idempotency for retried events. Even with correct acknowledgement, Slack may occasionally deliver duplicates due to network conditions. Use the event's event_id field as an idempotency key — store it in Redis or your database on first processing, and skip duplicate deliveries. This is especially important for actions that write data or send messages. Teams that implement event_id-based idempotency checks report near-elimination of duplicate-processing incidents in production Slack integrations, compared to apps that rely on acknowledgement timing alone. For a deeper look at handling retries across Slack integrations, see Slack API integration: handling errors and retries.
This is exactly the kind of hard-won implementation knowledge that lives in Slack threads and disappears when the developer who figured it out moves on. If your team is building on Slack and struggling to preserve the tribal knowledge that lives inside your conversations and threads, Question Base captures it automatically — so the next developer doesn't have to re-learn this the hard way.
Socket Mode: When You Don't Need a Public Endpoint
Socket Mode lets your app receive events and interactive payloads over a persistent WebSocket connection rather than an HTTP endpoint. This makes it ideal for local development, internal tools behind a firewall, or any context where exposing a public URL is impractical.
Acknowledgement in Socket Mode works the same way — ack() is still required for interactive payloads, and the 3-second timeout still applies. The difference is delivery mechanism: instead of Slack POSTing to your URL, your app receives payloads over the WebSocket and sends acknowledgements back over the same connection.
Bolt for Python and Bolt for JavaScript both support Socket Mode with minimal configuration changes — swap your HTTP adapter for the Socket Mode adapter and provide an App-Level Token with the connections:write scope. The handler code, including ack() placement, stays identical.
Socket Mode is not recommended for production apps at scale — WebSocket connections have reconnect overhead and are harder to horizontally scale than stateless HTTP endpoints. But for development and internal tooling, it removes a significant amount of infrastructure complexity.
Quick Reference: Slack Events API Acknowledgement Requirements by Request Type
Request Type | Acknowledgement Method | Timeout | Optional Response Payload |
|---|---|---|---|
Event (Events API) | HTTP 200 OK | 3 seconds | No — use follow-up API calls |
Block Kit Action |
| 3 seconds | Yes — can update the message in-place |
Slash Command |
| 3 seconds | Yes — text returned in |
Global / Message Shortcut |
| 3 seconds | No — open a modal via API call after ack |
View Submission |
| 3 seconds | Yes — can return validation errors or close modal |
Options Request (external select) |
| 3 seconds | Required — options list must be returned in the ack |
A few practical notes on the table above:
For slash commands, text returned inside
ack()is posted as an ephemeral response in the channel. For longer or delayed responses, callrespond()or use the response URL after acknowledging.For view submissions, returning validation errors inside
ack()keeps the modal open and highlights invalid fields — this is the correct pattern for form validation, not a separate API call.For options requests, the response payload is mandatory — Slack needs the options list to populate a dynamic select menu. This is the one case where your "business logic" must complete within 3 seconds, because the result goes directly into the acknowledgement. Keep external data sources fast or cache them.
If you're also using incoming webhooks alongside event subscriptions for your Slack app, this guide to setting up Slack incoming webhook notifications covers the complementary patterns for outbound messaging. And if you're troubleshooting integration issues beyond acknowledgement, the Slack integration troubleshooting guide covers the broader diagnostic process.
The acknowledgement layer is one of the few parts of Slack app development where getting it wrong isn't obvious — your app appears to work, until it doesn't, in ways that are frustrating to reproduce. Build the acknowledge-first, process-async pattern into every handler from the start. Add idempotency checks for anything that writes state. Use Slack's Request Inspector early and often. These habits compound: the developers who internalize them ship integrations that stay reliable under real workload conditions, not just in demos.
Frequently Asked Questions
What happens if I don't acknowledge a Slack Events API request within 3 seconds?
If your endpoint does not return an HTTP 200 OK within 3 seconds, Slack marks the delivery as failed and retries it up to three additional times at roughly one-minute intervals. This retry behavior is the root cause of duplicate event processing — your handler may complete successfully on the first attempt, but Slack's retry will trigger it again before it knows that. Always acknowledge immediately and process work asynchronously to avoid this.
Do I need to call ack() for every Slack event type?
No — for standard Events API payloads (such as message, reaction_added, or app_mention), a plain HTTP 200 OK is the correct acknowledgement and frameworks like Slack Bolt handle this automatically. However, for interactive payloads — including Block Kit actions, slash commands, global shortcuts, view submissions, and options requests — you must call ack() explicitly. Omitting ack() on interactive payloads causes Slack to show an error in the UI and retry the request.
What is the difference between HTTP 200 acknowledgement and ack() in Slack Bolt?
An HTTP 200 response is the low-level acknowledgement Slack requires for Events API deliveries — it simply tells Slack your endpoint received the payload. The ack() function in Slack Bolt is a higher-level abstraction used specifically for interactive payloads; it sends the required structured response and can optionally include a response body (such as validation errors for view submissions or text for slash commands). In Bolt, calling ack() for interactive handlers is equivalent to sending the correctly formatted HTTP response that Slack expects for that payload type.
How do I prevent duplicate event processing caused by Slack retries?
Use the event_id field present in every Events API payload as an idempotency key. On first receipt, store the event_id in Redis or your database and process the event normally. On subsequent deliveries of the same event_id, acknowledge with HTTP 200 but skip the business logic. This approach ensures your handler is safe to call multiple times without producing duplicate side effects such as duplicate messages or repeated database writes.
Can I use Socket Mode to avoid the 3-second acknowledgement timeout?
No — Socket Mode does not change the acknowledgement timeout. The 3-second requirement and the need to call ack() for interactive payloads apply equally whether your app uses HTTP endpoints or Socket Mode WebSocket connections. Socket Mode changes the delivery mechanism (WebSocket instead of HTTP POST) but not the acknowledgement contract. It is most useful for local development or internal tools where exposing a public HTTPS endpoint is impractical.