Back to Blog
Guide

Slack dispatch_failed: 7 Ways We Reproduced It, and the 3-Second Fix

We pointed a slash command at a local server and broke it on purpose: HTTP 500, 401 and 302, a wrong path, a dead tunnel, a stopped server and Socket Mode with no process. Each one gave dispatch_failed. A slow reply gave operation_timeout instead.

Slack Green Team
October 2, 2026
October 2, 2026
4 min read
Share:
slack api
developers
slash commands

dispatch_failed means Slack sent your slash command to the app and did not get a usable reply: the Request URL returned an error status, could not be reached, or the app runs in Socket Mode and no process was connected. We reproduced seven causes on 2 October 2026 with a test app in our own workspace, and each one returned dispatch_failed. A reply that arrives but takes longer than about 3 seconds gives a different error, operation_timeout. The fix for both is the same: return HTTP 200 at once and send the real answer to response_url.

What Slack shows for each failure

Slack web hides the error code for most failures. For an HTTP 500, it posted this, visible only to the person who typed the command, and left the command in the message box:

Slack web: an

The error code is in the response to chat.command, the call the Slack client makes when you press Enter. We made the same call from the browser session and got:

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

A timeout looks different. Slack names the error in the message:

Slack web:

So if you see the "did not respond" text, look for a failed request. If you see operation_timeout, your handler is working but slow.

The causes we reproduced

Our handler was a small Flask app on localhost:8851, reached through a cloudflared quick tunnel. We changed one thing at a time and ran the command:

What we brokechat.command responseTime to the errorWhat our server saw
Handler returns HTTP 500dispatch_failed0.6 sthe request, then our 500
Handler returns HTTP 401 (what a failed signature check sends)dispatch_failed0.8 sthe request, then our 401
Handler returns HTTP 302 (a redirect, such as http to https)dispatch_failed0.5 sthe request; Slack did not follow it
Request URL path wrong (/wrong/path)dispatch_failed0.6 sa 404 for /wrong/path
Local server stopped, tunnel still up (the tunnel answered 502)dispatch_failed0.6 snothing
Tunnel stopped, so the hostname no longer resolveddispatch_failed0.3 snothing
Socket Mode turned on, no Socket Mode process runningdispatch_failed0.3 snothing
Handler sleeps 2.5 s, then repliesok3.2 sthe request, reply after 2.5 s
Handler sleeps 2.8 s, 3.0 s or 4 s, then repliesoperation_timeout3.4 sthe request; the reply was thrown away

Three things the table shows:

  • • Slack sent each command once. Our log has one request per attempt, with no retries, so a failed command is lost unless the user runs it again.
  • • The time budget is shorter than 3 seconds of your own code. Through the tunnel, a 2.5 s handler passed and a 2.8 s handler failed. Network time counts too.
  • • Socket Mode and a dead URL give the same code. If your app uses Socket Mode, dispatch_failed usually means the process that holds the WebSocket is not running; see our Socket Mode in Python test.
  • For the network causes, open the Request URL from the command's settings in a browser or with curl. If the tunnel or host is down, you will see it there before you look at your code. A free cloudflared or ngrok URL changes every time it restarts, and the slash command keeps pointing at the old one.

Never appear "away" on Slack again

Cloud-based. No downloads. Works 24/7 even when your laptop is off.

The fix: answer first, work later

Return an empty HTTP 200 within the time budget, then post the result to the response_url that came with the command. This is the branch of our test server that did it:

import threading, time
import requests
from flask import Flask, request, make_response

app = Flask(__name__)

def later(url, delay, text):
    time.sleep(delay)                      # the slow work goes here
    r = requests.post(url, json={"response_type": "ephemeral", "text": text}, timeout=10)
    print("response_url:", r.status_code, r.text)

@app.post("/slack/commands")
def commands():
    form = request.form.to_dict()
    threading.Thread(target=later, daemon=True,
                     args=(form["response_url"], 5, f"finished the slow work for '{form.get('text')}'")).start()
    return make_response("", 200)          # the acknowledgement Slack waits for

With a 5-second job, chat.command returned {"ok": true} in 0.6 s. Five seconds later our server printed:

response_url: 200 ok

and Slack showed "finished the slow work for 'raw ack later'" to the user. For a button click, the response_url took 5 posts and expired after 30 minutes in our interactivity tests; plan for the same limits here. In Bolt for Python the same pattern is ack() first, then respond(...).

Two more checks when the handler is fast and you still get dispatch_failed:

  • • The handler must answer the exact path in the Request URL. Our wrong-path test reached the server and still failed, because Flask returned 404.
  • • Any status outside 2xx failed in our tests: 500, 404, 401 and even a 302 redirect. A 204 with no body passed. If your signature check rejects the request with a 401, the user sees the same "did not respond" message, so log the status your server returns, not only the requests it receives.
  • FAQ

    Does Slack retry a failed slash command?

    No. In our tests each failed command reached the server at most once. Events API deliveries are different: Slack retries those three times; see our retry timings.

    What does the user see while the slow work runs?

    Nothing. With the empty 200, chat.command returned {"ok": true}, the command left the message box, and no message appeared until our server posted to response_url 5 seconds later. If the work takes longer than a second or two, post a short "working on it" reply first, or return it as the body of the 200.

    Always Active

    Stop Jiggling Your Mouse.

    Join hundreds of remote workers who never worry about their Slack status. Set it up once, stay green forever.

    Related Articles

    Guide

    Slack API Pagination: next_cursor, the Last Page and invalid_cursor, Tested

    Slack paginates list methods with a cursor: pass response_metadata.next_cursor back as cursor until it is empty. We paged 27 real messages on 2 October 2026 and broke the cursor five ways to see which ones fail.

    Slack Green Team
    Guide

    Slack RTM API Deprecated: What rtm.connect Returns for a New App, and the Socket Mode Fix

    A Slack app created today cannot use the RTM API. We called rtm.connect and rtm.start with every token a new app gets on 2 October 2026, tried to request the rtm:stream scope, and ran the same bot over Socket Mode.

    Slack Green Team
    Guide

    Slack Bot Icon and Name Per Message: icon_emoji, icon_url and username, Tested

    icon_emoji, icon_url and username only work with the chat:write.customize scope, and Slack ignores them silently without it. We tested every case on 2 October 2026, plus webhooks and the app icon upload limits.

    Slack Green Team