8 Slack SDK packages went major on 14 July 2026: the axios-to-fetch changes that break your bot
Summary. Slack published new major versions of eight Node SDK packages on 14 July 2026, and the three that matter went into production use over the three weeks since. All of them now require Node.js 20 or later, and for five of those packages that is the only breaking change: cli-hooks v2, cli-test v4, logger v5, oauth v4 and types v3. The other three carry real work. @slack/web-api v8 replaces axios with the native Fetch API and drops four dependencies (axios, form-data, is-electron and is-stream), removing the agent, tls, requestInterceptor, adapter and attachOriginalToWebAPIRequestError options in the process. @slack/socket-mode v3 swaps the ws library for undici's WebSocket implementation and removes httpAgent. @slack/webhook v8 restructures its error classes so HTTP details are direct properties instead of living under an axios error. @slack/socket-mode v3 also pulls in undici version 7 and depends on @slack/web-api version 8 internally. If you are not behind a corporate proxy and not doing custom TLS, Slack's guidance as of 3 August 2026 is that you should need nothing beyond a version bump.
Slack has published no retirement date for the previous majors, @slack/web-api v7 and @slack/socket-mode v2, so nothing forces the move this month. That guidance is still worth testing rather than trusting. The teams that get hurt here are the ones running bots inside an enterprise network, where a proxy agent was configured once in 2022 and nobody has looked at it since.
What shipped, package by package
| Package | New major | Breaking change beyond Node 20 |
|---|---|---|
@slack/cli-hooks |
v2 | None |
@slack/cli-test |
v4 | None |
@slack/logger |
v5 | None |
@slack/oauth |
v4 | None |
@slack/types |
v3 | None |
@slack/web-api |
v8 | axios replaced by globalThis.fetch; agent, tls, requestInterceptor, adapter and attachOriginalToWebAPIRequestError removed; five deprecated methods removed |
@slack/socket-mode |
v3 |
ws replaced by undici WebSocket; httpAgent removed; error interfaces became Error subclasses; depends on @slack/web-api@^8
|
@slack/webhook |
v8 | axios replaced by globalThis.fetch; agent removed; error classes restructured |
Slack's changelog puts the theme plainly: "Most of the changes in those batch of updates fall under the theme of HTTP transport improvements, such as migrating from axios to the native Fetch API."
The Node.js floor is stated identically in the socket-mode and webhook guides: "We've dropped support for Node.js 18. Node.js 20 or later is required." If your CI matrix still has an 18 entry, that is the first thing to delete, because everything else in this migration depends on runtime APIs that only exist from Node 20 onward.
The single biggest change: agent is gone
Across @slack/web-api and @slack/webhook the agent option has been removed, and in @slack/socket-mode the equivalent httpAgent option has been removed. This is the change that silently breaks bots behind a proxy, because the code still compiles and the option is simply ignored.
Slack's web-api guide describes the new shape directly: "There is now a fetch option that replaces several transport options (agent, tls, requestInterceptor, adapter). Pass in your own fetch function to configure proxies, TLS, or whatever transport behavior you need. If you don't need any of that, the SDK uses globalThis.fetch and requires no configuration."
Here is what the old code looked like:
import { WebClient } from '@slack/web-api';
import { HttpsProxyAgent } from 'https-proxy-agent';
const agent = new HttpsProxyAgent('http://corporate.proxy:8080');
const client = new WebClient(token, {
agent,
});
Slack's preferred replacement is not a code-level agent at all. The guide notes that Node.js can read proxy environment variables natively through http.setGlobalProxyFromEnv(), and that calling it once at startup routes globalThis.fetch through your proxy without any extra packages:
import http from 'node:http';
import { WebClient } from '@slack/web-api';
http.setGlobalProxyFromEnv();
// All WebClient instances now route through HTTP_PROXY/HTTPS_PROXY automatically
const client = new WebClient(token);
The environment-variable route is even smaller. No application code changes at all:
NODE_USE_ENV_PROXY=1 HTTPS_PROXY=http://corporate.proxy:8080 node app.js
If you need per-client proxy configuration, or want proxy and TLS together, the guide points at an undici dispatcher passed through the new fetch option:
import { WebClient } from '@slack/web-api';
import { fetch, ProxyAgent } from 'undici';
const dispatcher = new ProxyAgent('http://corporate.proxy:8080');
const client = new WebClient(token, {
fetch: (url, init) => fetch(url, { ...init, dispatcher }),
});
Socket Mode handles the same problem with a top-level dispatcher option rather than a fetch wrapper, and Slack's guide is explicit that one dispatcher covers both transports: it "is used for both WebSocket connections and HTTP API calls (via the internal WebClient)".
import { SocketModeClient } from '@slack/socket-mode';
import { ProxyAgent } from 'undici';
const dispatcher = new ProxyAgent('http://corporate.proxy:8080');
const client = new SocketModeClient({
appToken: process.env.SLACK_APP_TOKEN,
dispatcher,
});
One detail from the webhook guide that will bite anyone copying these snippets: undici is not a dependency of @slack/webhook. If you take the dispatcher route you are adding a package, not enabling one you already have.
Mutual TLS moves to an undici Agent
The tls option and the TLSOptions export are both gone from @slack/web-api. Client certificates now go through a custom fetch. Before:
import { WebClient } from '@slack/web-api';
import fs from 'node:fs';
const client = new WebClient(token, {
tls: {
cert: fs.readFileSync('/path/to/client-cert.pem'),
key: fs.readFileSync('/path/to/client-key.pem'),
ca: fs.readFileSync('/path/to/ca-cert.pem'),
},
});
After:
import { WebClient } from '@slack/web-api';
import { fetch, Agent } from 'undici';
import fs from 'node:fs';
const dispatcher = new Agent({
connect: {
cert: fs.readFileSync('/path/to/client-cert.pem'),
key: fs.readFileSync('/path/to/client-key.pem'),
ca: fs.readFileSync('/path/to/ca-cert.pem'),
},
});
const client = new WebClient(token, {
fetch: (url, init) => fetch(url, { ...init, dispatcher }),
});
Note where the certificate material sits. It moves from a top-level tls object to connect inside the undici Agent. That relocation is easy to get wrong when translating an existing config, and the failure surfaces at connection time rather than at construction time.
Interceptors and adapters both become fetch
Two more removals collapse into the same replacement. requestInterceptor and the RequestInterceptor type are gone; so are adapter and AdapterConfig. In both cases the answer is to wrap or supply fetch.
Adding a custom header used to look like this:
const client = new WebClient(token, {
requestInterceptor: (config) => {
config.headers['X-Custom-Header'] = 'my-value';
console.log(`→ ${config.method?.toUpperCase()} ${config.url}`);
return config;
},
});
In v8 it looks like this:
const client = new WebClient(token, {
fetch: async (url, init) => {
const headers = { ...init?.headers, 'X-Custom-Header': 'my-value' };
console.log(`→ ${init?.method ?? 'GET'} ${url}`);
return globalThis.fetch(url, { ...init, headers });
},
});
That wrapper is also where request-level tracing goes now, which is worth planning deliberately rather than rediscovering. If your observability hooked into the axios interceptor to emit spans, that integration point no longer exists and has to be rebuilt in the fetch wrapper.
Test doubles change the same way. The old adapter returned an axios-shaped object; the new mock returns a standard Response:
import { WebClient } from '@slack/web-api';
const mockFetch = async (url: string | URL, init?: RequestInit) =>
new Response(JSON.stringify({ ok: true }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
const client = new WebClient(token, { fetch: mockFetch });
This is a straight upgrade for test suites. A Response is a standard object your other HTTP tests already know how to build, so the Slack-specific mocking helper disappears.
Error handling changed in all three packages
This is where the migration stops being mechanical.
In @slack/web-api, the attachOriginalToWebAPIRequestError option has been removed and the original error is now always attached to WebAPIRequestError. Slack's guidance is precise about both directions: if you were setting it to true, which was the default, you can safely remove it; if you were setting it to false to suppress the original error, error.original will now always be present. Anything that logs whole error objects should be checked for accidental disclosure, because a suppressed field is now populated.
In @slack/socket-mode, errors moved from interfaces to real classes. The guide states: "Errors are now proper Error subclasses instead of interfaces. This means instanceof checks work, TypeScript narrows types correctly, and error names are descriptive." Existing error.code checks keep working and the ErrorCode enum values are unchanged, but instanceof is now the recommended pattern. The trap is elsewhere: because error names are now descriptive, any logging or error-monitoring filter that matches on error.name needs updating. That is a change to your alerting, not your code, and it is exactly the kind of thing that goes unnoticed until an incident does not page anyone.
In @slack/webhook, the restructure is the most visible. Slack's guide describes it directly: in v7 both IncomingWebhookRequestError and IncomingWebhookHTTPError had an original property typed as an axios error, and you examined error.original.response for HTTP details. In v8, IncomingWebhookHTTPError no longer has original and instead exposes statusCode, statusMessage and body as direct properties, while IncomingWebhookRequestError keeps original but as a standard Error. Both extend a new SlackWebhookError base class.
Old:
try {
await webhook.send('Hello');
} catch (error) {
const httpError = error as IncomingWebhookHTTPError;
// v7: error.original was an AxiosError with response details
console.log(httpError.original.response?.status); // e.g. 404
console.log(httpError.original.response?.data); // e.g. 'channel_not_found'
}
New:
import { IncomingWebhook, IncomingWebhookHTTPError, IncomingWebhookRequestError } from '@slack/webhook';
const webhook = new IncomingWebhook(webhookUrl);
try {
await webhook.send('Hello');
} catch (error) {
if (error instanceof IncomingWebhookHTTPError) {
console.log(error.statusCode); // e.g. 404
console.log(error.body); // e.g. 'channel_not_found'
console.log(error.statusMessage); // e.g. 'Not Found'
} else if (error instanceof IncomingWebhookRequestError) {
// Network-level failure (DNS, connection refused, timeout, etc.)
console.log(error.original.message);
}
}
The channel_not_found case is the one to look at hard. Any code path that read error.original.response.data to distinguish a bad channel from a network failure now reads undefined and falls through to a generic handler. TypeScript will catch it if you were typing the error; a plain catch (error) with a cast will not.
A safe upgrade order
Slack's three guides do not publish a recommended sequence, so this is our own ordering, derived from the dependency direction the guides do state: @slack/socket-mode v3 depends on @slack/web-api@^8 internally, and its guide warns that if you pass clientOptions, the web-api breaking changes apply there too.
- Move CI and runtime to Node.js 20 or later first, and delete the Node 18 job. Nothing else can be validated until this is done.
- Bump the five no-op packages together:
cli-hooksv2,cli-testv4,loggerv5,oauthv4,typesv3. If anything fails here, it is a Node version problem, not an SDK problem, and finding that out cheaply is the point. - Upgrade
@slack/web-apito v8 on its own. Search the codebase foragent:,tls:,requestInterceptor,adapterandattachOriginalToWebAPIRequestErrorbefore you start, because those are silent removals rather than compile errors in plain JavaScript. - Upgrade
@slack/socket-modeto v3, replacinghttpAgentwithdispatcherand updating anyclientOptionsyou pass through, since those now hit web-api v8 rules. - Upgrade
@slack/webhookto v8 last, and rewrite the error handling aroundinstanceofwhile you are in there. - Fix your alerting. Any filter matching on
error.nameneeds re-checking against the new descriptive names.
| Old option or property | Package | Replacement |
|---|---|---|
agent |
web-api, webhook
|
http.setGlobalProxyFromEnv(), NODE_USE_ENV_PROXY=1, or fetch with an undici ProxyAgent
|
httpAgent |
socket-mode |
Top-level dispatcher option taking an undici Dispatcher
|
tls, TLSOptions
|
web-api |
undici Agent({ connect: { cert, key, ca } }) supplied through fetch
|
requestInterceptor, RequestInterceptor
|
web-api |
Wrap globalThis.fetch and merge into init.headers
|
adapter, AdapterConfig
|
web-api |
Custom fetch returning a standard Response
|
attachOriginalToWebAPIRequestError |
web-api |
Removed; error.original is always present |
error.original.response.status |
webhook |
error.statusCode on IncomingWebhookHTTPError
|
error.original.response.data |
webhook |
error.body on IncomingWebhookHTTPError
|
What we could not confirm, and why that matters
Two sections of Slack's documentation did not render when we retrieved these guides on 3 August 2026: the table listing the five deprecated web-api methods removed in v8, referenced to pull request 2592 in the slackapi/node-slack-sdk repository, and the table of new error class names in the socket-mode v3 guide. Both pages truncate at the first markdown table.
We are not going to guess at either list. If you depend on older web-api methods, open the migration guide and the linked pull request before you upgrade, and diff your method calls against it. If you are writing instanceof checks against socket-mode error classes, take the class names from the guide rather than from any article, including this one. That is the honest version, and it is more useful than a plausible-looking list that turns out to be wrong at 2am.
Deciding whether to do this now
Nothing here is a security patch, so there is no forced date. The arguments for moving in the next sprint are that the change is small for most codebases, that the dependency reduction is real, and that the longer you wait the more likely you are to be doing it under pressure alongside a Node upgrade you did not plan. The argument for waiting is that if your bot runs behind a proxy with mutual TLS, this is a genuine multi-day change with a testing burden, and it should be scheduled rather than squeezed in.
The engineering judgement, plainly: the risky part of this migration is not the code, it is the environments where the proxy configuration lives somewhere nobody on the current team wrote. Find that first.
If you are planning the runtime move alongside this, our Node.js release cadence and LTS upgrade guide covers the version strategy, and the Node.js security release playbook covers how to respond when the upgrade is not optional. Teams reconsidering the runtime entirely will find the trade-offs in our Bun versus Node.js backend decision, and the wider platform context sits in the 2026 web platform developer guide.
India-specific considerations
For Indian product teams and offshore engineering groups the practical wrinkle is proxy configuration rather than the SDK itself. Many enterprise clients route outbound traffic through a corporate proxy, and a bot that worked in a developer's local environment will fail in the client's network once agent stops being read. Test the upgrade inside the target network, not just in CI.
The other consideration is data handling. Slack bots frequently move employee or customer information between systems, which brings the Digital Personal Data Protection Act 2023 into scope for what your integration logs. The new pattern of always-attached error.original and direct body properties on webhook errors makes it easier to accidentally log a full response payload. Review your error logging as part of the upgrade, not after it. Teams doing this at scale usually fold it into a broader API integration and modernisation effort, and those building agent-facing tooling on top of Slack should look at MCP server development and integration.
FAQ
What is the minimum Node.js version for the new Slack SDK packages?
Node.js 20 or later, across all eight packages released on 14 July 2026. The socket-mode and webhook migration guides state it identically: support for Node.js 18 has been dropped and Node.js 20 or later is required. Upgrading the runtime and CI matrix first is the prerequisite for everything else in this migration.
Which Slack packages only need a version bump?
Five of the eight. The cli-hooks package moved to v2, cli-test to v4, logger to v5, oauth to v4 and types to v3, and for all of them Slack states that requiring Node.js 20 or later is the only breaking change. The remaining three packages carry the HTTP transport rewrite.
How do I configure a proxy now that the agent option is gone?
Slack's preferred approach is native: call http.setGlobalProxyFromEnv() once at startup, or run with NODE_USE_ENV_PROXY=1 and your HTTPS_PROXY variable set. For per-client configuration, pass a fetch function wrapping an undici ProxyAgent. Socket Mode instead takes a top-level dispatcher option that covers both WebSocket and HTTP traffic.
What replaced the tls option in web-api v8?
The tls option and the TLSOptions export were both removed. Client certificates now go through an undici Agent where cert, key and ca sit inside a connect object, and that agent is supplied to the client as a dispatcher through the new fetch option. The relocation of the certificate material is the part most likely to be mistranslated.
Does @slack/webhook still expose the original axios error?
No. In v8 IncomingWebhookHTTPError no longer has an original property and exposes statusCode, statusMessage and body directly. IncomingWebhookRequestError keeps original, but typed as a standard Error rather than an axios error. Both now extend a new SlackWebhookError base class for instanceof checks.
Do my existing error.code checks still work in socket-mode v3?
Yes. Slack states that existing error.code checks still work and that the ErrorCode enum values are unchanged, though instanceof is now the recommended pattern. The change to watch is elsewhere: error names are now descriptive, so any logging or monitoring filter matching on error.name needs updating.
Why did Slack move off axios?
The web-api guide gives the reason as dependency reduction and native platform use. Replacing axios with globalThis.fetch drops four dependencies: axios, form-data, is-electron and is-stream. Socket Mode made an equivalent move, swapping the ws library for the WebSocket implementation in undici version 7.
Which package should I upgrade first?
Move the runtime to Node.js 20 first, then bump the five no-op packages together to isolate any runtime problems cheaply. Upgrade @slack/web-api next, because @slack/socket-mode v3 depends on @slack/web-api@^8 internally and any clientOptions you pass through it are subject to the web-api rules.
How eCorpIT can help
eCorpIT is a Gurugram-based technology consultancy with senior engineering teams that build and maintain internal workflow tooling, Slack applications and API integrations for Indian and global clients. Migrations like this one are usually small in code and awkward in environment, so we start by mapping where proxy and TLS configuration actually lives before touching a package version. We are CMMI Level 5 appraised, MSME certified and ISO 27001:2022 certified, and we design integrations aligned with DPDP Act 2023 requirements for what gets logged. If a Slack bot is central to how your team works and you would rather not find out about a broken proxy in production, talk to our engineering team.
References
- Release: Multiple Node Slack SDK package updates, Slack Developer Docs, 14 July 2026
- Migrating @slack/web-api from v7 to v8, Slack Developer Docs
- Migrating the socket-mode package from v2 to v3, Slack Developer Docs
- Migrating @slack/webhook from v7 to v8, Slack Developer Docs
- Slack developer changelog, Slack Developer Docs
- Removing support for Node 18 and other upcoming breaking changes, issue 2644, slackapi/bolt-js
- Replace axios with fetch, issue 1525, slackapi/node-slack-sdk
- Issues, slackapi/node-slack-sdk, GitHub
- Socket Mode, Slack Developer Tools
- Migrating the socket-mode package to v2.x, Slack Developer Docs
- Migrating the web-api package to v7.x, Slack Developer Docs
- Migration Guide for socket-mode 2.0, slackapi/node-slack-sdk wiki
- Slack Developer Docs tools index, Slack Developer Docs
Last updated: 3 August 2026.
Top comments (0)