Slack URL Verification Challenge: What Passes and What Fails, Tested
Slack verifies an Events API Request URL by POSTing a url_verification challenge. We ran a 16-line Flask handler behind a tunnel and recorded the request, the Verified tick, and the exact error Slack shows for a wrong body, an HTTP error and a slow reply.
On this page
When you enter a Request URL under Event Subscriptions, Slack sends one POST with "type": "url_verification" and a random challenge string. Your endpoint passes if it answers with HTTP 200 and that same string, within about 3 seconds. We tested this on 1 October 2026 with a Flask handler behind a cloudflared tunnel, and broke it on purpose to record each error Slack shows.
The request Slack sends
Our handler logged this request when we typed the URL. We removed the value of the token field, which is the app's legacy verification token.
{
"headers": {
"User-Agent": "Slackbot 1.0 (+https://api.slack.com/robots)",
"Content-Type": "application/json",
"X-Slack-Request-Timestamp": "1790838390",
"X-Slack-Signature": "v0=fef48515cc1868f141a0abb8c000fc2ec77bc559827e6dbac34750c1483c52b6"
},
"body": {
"token": "(removed)",
"challenge": "T83O7x5KH4DJE8aGMpULU3RfgqtrYvPE9Ax8O1f478WRk7lTnEFF",
"type": "url_verification"
}
}
The headers carry the request signature, so you can check that the request came from Slack before you answer it. Verification passes without that check, but your real event handler should have it.
A handler that passes
This is the whole app we ran with uv run --with flask python verify.py:
from flask import Flask, jsonify, request
app = Flask(__name__)
@app.post("/slack/events")
def slack_events():
body = request.get_json()
if body.get("type") == "url_verification":
return jsonify(challenge=body["challenge"])
print("event:", body["event"]["type"], flush=True)
return "", 200
if __name__ == "__main__":
app.run(port=8791)
Slack marked the URL as Verified, and the next message event reached the same handler. The Flask log:
127.0.0.1 - - [01/Oct/2026 16:23:59] "POST /slack/events?t=min HTTP/1.1" 200 -
event: message
Answering with the bare challenge string as text/plain also passed. The JSON form is the one Slack documents, so use it unless your framework makes it awkward.
Each failure and the message Slack shows
We changed one thing at a time and typed a new URL each time. These are the red banner texts, copied from the page:
| What our endpoint did | Banner text in Slack |
|---|---|
HTTP 200 with {"challenge": "not-the-challenge"} | Your request URL didn't respond with the correct challenge value. Update your URL to receive a new request and value. |
| HTTP 500 | Your request URL responded with an HTTP error. Update your URL to receive a new request and challenge value. |
HTTP 404 (wrong path, /events in place of /slack/events) | Your request URL responded with an HTTP error. Update your URL to receive a new request and challenge value. |
| Correct body after a 4 s sleep | Hmm, our verification request timed out before we received a response. Try again? |
Under the field, the label read "Your URL didn't respond with the value of the challenge parameter." in all four cases, so read the banner at the top to learn which failure you have. For a wrong body, Slack also opens a debug panel. It shows "error": "challenge_failed" and an empty body, even though our handler had sent a JSON body:
Never appear "away" on Slack again
Cloud-based. No downloads. Works 24/7 even when your laptop is off.
How long you have to answer
We added a sleep before the correct answer and typed a fresh URL for each value:
| Sleep before answering | Result |
|---|---|
| 2.5 s | Verified (twice) |
| 2.7 s | Verified |
| 2.9 s | Timed out (twice) |
| 3.2 s | Timed out |
| 4.0 s | Timed out |
The limit we measured sits between 2.7 and 2.9 seconds of handler time. Our requests went through a cloudflared quick tunnel to a laptop in Seoul, which adds its own delay, so give yourself less than 2 seconds in production. Do the challenge reply before any database call or slow import. Real events have the same time limit, and Slack resends them when you miss it; see Events API retries.
When Slack does not send the challenge
Two cases surprised us:
- • Retyping the URL that was already verified showed Verified at once, and our handler received no request. To force a new check, change the URL. We added a query string such as
?t=2. - • An app created with
apps.manifest.create, or changed withapps.manifest.update, accepts arequest_urlwith no challenge at all. The API returned{"ok": true, "app_id": "A0C5WFY5P7C", "permissions_updated": false}while our endpoint was set to return a wrong challenge. The settings page then showed this:
Events still arrived at that URL. A message posted right after the update reached our handler while the page said the URL did not respond. Pressing Retry sent a real challenge and turned the field to Verified. If you deploy apps from a manifest, press Retry once, or open the page and check the label, so you know the endpoint works.
In n8n's Slack Trigger, the Test URL passes only while the node is listening, and the Production URL only after the workflow is published; our n8n test has each case.
If you do not want a public URL at all, Socket Mode in Python receives the same events over a WebSocket and has no verification step.
FAQ
Does Slack send url_verification again later?
In our session Slack sent it only when the URL changed or when we pressed Retry. Events after that came as normal event_callback requests.
Can I verify a URL on localhost?
No. Slack has to reach the URL from the internet. A tunnel such as cloudflared or ngrok gives your local port a public HTTPS address; we used cloudflared tunnel --url http://localhost:8791.
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
How to See All Your DMs in Slack, Including the Hidden Ones
The DMs tab lists your direct messages with people. App DMs, Slackbot and DMs with deleted accounts sit elsewhere. We compared the tab with the full list the Slack API returned.
Slack Images Not Showing? 5 Causes We Reproduced
We uploaded images to a Slack test channel and then switched each image setting off, ran /collapse, and posted from a bot that was not in the channel. Five causes, each reproduced.
How to Message Yourself on Slack (and What It Notifies)
Your own name under Direct messages opens a private DM with yourself. We sent it a note, a file, a scheduled message and a reminder, and checked which ones raised a badge.