Back to Blog
Guide

Are Slack Incoming Webhooks Deprecated? We Tested Legacy and App Webhooks

App incoming webhooks are current. The old 'Incoming WebHooks' custom integration carries a deprecation notice but could still be added on 2 October 2026. We added both, sent channel and username overrides, and posted 60 messages at once to look for a 429.

Slack Green Team
October 2, 2026
October 2, 2026
3 min read
Share:
slack api
developers
webhooks

No. Incoming webhooks that belong to a Slack app are the current way to post into a channel from outside Slack, and nothing marks them as deprecated. What Slack does call deprecated is the old "Incoming WebHooks" custom integration, the one you add from the Marketplace page instead of creating an app. On 2 October 2026 we could still add it to our Free-plan test workspace, and it still honored the channel, username and icon_emoji fields that app webhooks ignore. If you use the legacy kind, plan to move to an app webhook, and expect those three fields to stop working when you do.

What Slack shows for the legacy integration

The Marketplace page for Incoming WebHooks opens with this notice:

Slack Marketplace page for Incoming WebHooks with the notice:

"Will be deprecated and possibly removed in the future" has no date. The Add to Slack button still worked: we picked a channel, clicked "Add Incoming WebHooks integration", and got a hooks.slack.com/services/... URL. An app webhook URL has the same shape, so the URL alone does not tell you which kind you have. The settings page does: a legacy one lives under Custom Integrations at <workspace>.slack.com/services/B..., and an app webhook lives in the app's settings at api.slack.com/apps.

A new legacy webhook posted nowhere until we saved its channel

Our first plain post to the new legacy URL failed:

HTTP 404
channel_not_found

We had picked #w1pm-hooks on the add page, but the integration's settings showed "Post to Channel" empty:

Legacy Incoming WebHooks settings: Post to Channel is an empty dropdown; the Webhook URL is hidden in this image; Customize Name says incoming-webhook

The fix: pick the channel in "Post to Channel" on that settings page and press "Save Settings". The same post then returned 200 ok. A writer on our team saw the same channel_not_found on a fresh legacy webhook on 1 October, so check this field before you blame your payload. Posts that named a channel in the JSON worked even before the fix, because they did not need the default.

Never appear "away" on Slack again

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

Overrides: honored by legacy, ignored by app webhooks

We sent the same JSON to both URLs:

{"text": "...", "channel": "#w1pm-public-out", "username": "Deploy Bot", "icon_emoji": ":rocket:"}

Both answered 200 ok. Only the legacy one did what the JSON asked:

Top: in #w1pm-public-out, a message from
Field in the JSONLegacy Incoming WebHooksApp incoming webhook
channel: "#other-channel"posted in #other-channelignored, posted in the channel picked at install
channel: "<user ID>"posted in that user's Slackbot DM, as "incoming-webhook"ignored, posted in the install channel
usernameusedignored, the app's name
icon_emojiusedignored, the app's icon
HTTP answer200 ok200 ok, with no warning that fields were dropped

This is what breaks most migrations. A script that posts to several channels through one legacy URL will send everything to one channel after the switch, and Slack will not return an error. The replacement is chat.postMessage with a bot token: it takes any channel the bot is in, and username plus icon_emoji with the chat:write.customize scope. Our bot name and icon test has the details. If you only need one channel, an app webhook is enough; how to create a Slack webhook has the four steps, and our webhook curl example has the request.

Rate limit: 120 posts, no 429

Slack documents about one message per second for incoming webhooks. We sent faster than that to see what happens:

TestResult
App webhook, 30 posts in 10.9 s, one after another30 x 200 ok, no 429
App webhook, 60 posts from 20 parallel workers60 x 200 ok, no 429, last answer after 11.3 s
Legacy webhook, 60 posts from 20 parallel workers60 x 200 ok, no 429, last answer after 20.3 s

Our 1 October run sent 290 posts to an app webhook in about 15 seconds, also with no 429. Two things showed up instead:

  • • The answers came back in pairs about 0.3 s apart, so some requests waited 10 to 20 seconds for their 200. Set a client timeout well above that, or a slow ok looks like a failure and you post the message twice.
  • • Parallel posts arrived out of order. The channel showed "parallel 56, 57, 58, 59, 60, 8, 15, 48, 13". If order matters, send one at a time.

This is the loop we ran for the 30-in-10-seconds test (url comes from an environment variable):

import os, time, requests

url = os.environ["SLACK_WEBHOOK_URL"]
t0 = time.time()
for i in range(30):
    while time.time() < t0 + i / 3:
        time.sleep(0.01)
    r = requests.post(url, json={"text": f"burst {i + 1}/30"}, timeout=15)
    print(i + 1, round(time.time() - t0, 2), r.status_code, r.text, r.headers.get("retry-after"))

The last line it printed was 30 10.88 200 ok None. When a webhook does get limited, our rate limit tests found the body says rate_limited and Retry-After is 1 second, so retry after that.

FAQ

Can I still add the legacy Incoming WebHooks integration?

On 2 October 2026, yes, on our Free-plan workspace. The page also showed a "With Slack Pro, you can install unlimited apps" banner above the notice.

Does an app webhook let me mention people?

Yes, with the user ID in the text, such as <@U0123ABCD>; names alone do not ping anyone. See how to mention a user in a Slack webhook.

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

assistant.threads.setStatus vs agents.sessions.setStatus: Both Tested in Slack

We called assistant.threads.setStatus and agents.sessions.setStatus on a test app before and after turning on the agent feature, clicked the stop button, and timed how long the status line stays. Every response, the stop event, and what Slack showed.

Slack Green Team
Guide

Slack Slash Command Payload: Every Field, Responses, and response_url Limits

We caught the payload a slash command sends, answered it as ephemeral, in_channel, plain text and empty, posted to its response_url until it failed, and typed the command inside a thread. Every result is from a test app on 2 October 2026.

Slack Green Team
Guide

Slackbot MCP Client: We Connected a 17-Line MCP Server and Slackbot Called It

We wrote a one-tool MCP server, added it to a Slack app with the mcp_servers manifest field, switched it on in Slackbot and asked for the time. Every request Slackbot sent to the server, the permission prompt, and what Slackbot said when the server was down.

Slack Green Team