Web API
The @slack/web-api
package contains a simple, convenient, and configurable HTTP client for making requests to Slack's
Web API. Use it in your app to call any of the over 130
methods, and let it handle formatting, queuing, retrying, pagination, and more.
Installation
$ npm install @slack/web-api
Prerequisites
To use this package, you must have a Slack access token. Access tokens are issued to Slack applications. Each application installation, that is, the unique combination of an application and a specific Slack workspace it is installed to, will generate a workspace-specific access token for the application.
We recommend you read our documentation on Basic app setup. This article contains the steps you must follow to get your access token:
- Create a new Slack application
- Set permissions your app will request
- Install the app to a workspace
- Finally, get your access token
You can also read the Getting Started guide which guides
you through creating an app, retrieving an access token, and using this @slack/web-api
package to post a message.
Initialize the client
The package exports a WebClient
class. You must initialize it with an access
token so that you don't have to provide the token each time you call a method. A
token usually begins with xoxb
or xoxp
.
const { WebClient } = require('@slack/web-api');
// Read a token from the environment variables
const token = process.env.SLACK_TOKEN;
// Initialize
const web = new WebClient(token);
Initializing without a token
Alternatively, you can create a client without a token, and use it with multiple workspaces as long as you supply a
token
when you call a method.
const { WebClient } = require('@slack/web-api');
// Initialize a single instance for the whole app
const web = new WebClient();
// Find a token in storage (database) before making an API method call
(async () => {
// Some fictitious database
const token = await db.findTokenByTeam(teamId, enterpriseId)
// Call the method
const result = web.auth.test({ token });
})();
Call a method
The client instance has a named method for each of the public methods in the Web API. The most popular one is
called chat.postMessage
, and its used to send a message to a conversation. For every method, you pass arguments as
properties of an options object. This helps with the readablility of your code since every argument has a name. All
named methods return a Promise
which resolves with the response data, or rejects with an error.
// Given some known conversation ID (representing a public channel, private channel, DM or group DM)
const conversationId = '...';
(async () => {
// Post a message to the channel, and await the result.
// Find more arguments and details of the response: https://api.slack.com/methods/chat.postMessage
const result = await web.chat.postMessage({
text: 'Hello world!',
channel: conversationId,
});
// The result contains an identifier for the message, `ts`.
console.log(`Successfully send message ${result.ts} in conversation ${conversationId}`);
})();
Hint: If you're using an editor that supports TypeScript, even if you're not using TypeScript to write your code, you'll get hints for all the arguments each method supports. This helps you save time by reducing the number of times you need to pop out to a webpage to check the reference. There's more information about using TypeScript with this package in the documentation website.
Note: Use the Block Kit Builder for a playground where you can prototype your message's look and feel.
Using a dynamic method name
If you want to provide the method name as a string, so that you can decide which method to call dynamically, or to call
a method that might not be available in your version of the client, use the WebClient.apiCall(methodName, [options])
method. The API method call above can also be written as follows:
const conversationId = '...';
(async () => {
// Using apiCall() allows the app to call any method and to do it programmatically
const response = await web.apiCall('chat.postMessage', {
text: 'Hello world!',
channel: conversationId,
});
})();
Handle errors
Errors can happen for many reasons: maybe the token doesn't have the proper scopes to
call a method, maybe its been revoked by a user, or maybe you just used a bad argument. In these cases, the returned
Promise
will reject with an Error
. You should catch the error and use the information it contains to decide how your
app can proceed.
Each error contains a code
property, which you can check against the ErrorCode
export to understand the kind of
error you're dealing with. For example, when Slack responds to your app with an error, that is an
ErrorCode.PlatformError
. These types of errors provide Slack's response body as the data
property.
// Import ErrorCode from the package
const { WebClient, ErrorCode } = require('@slack/web-api');
(async () => {
try {
// This method call should fail because we're giving it a bogus user ID to lookup.
const response = await web.users.info({ user: '...' });
} catch (error) {
// Check the code property, and when its a PlatformError, log the whole response.
if (error.code === ErrorCode.PlatformError) {
console.log(error.data);
} else {
// Some other error, oh no!
console.log('Well, that was unexpected.');
}
}
})();
More error types
There are a few more types of errors that you might encounter, each with one of these code
s:
-
ErrorCode.RequestError
: A request could not be sent. A common reason for this is that your network connection is not available, orapi.slack.com
could not be reached. This error has anoriginal
property with more details. -
ErrorCode.RateLimitedError
: The Web API cannot fulfill the API method call because your app has made too many requests too quickly. This error has aretryAfter
property with the number of seconds you should wait before trying again. See the documentation on rate limit handling to understand how the client will automatically deal with these problems for you. -
ErrorCode.HTTPError
: The HTTP response contained an unfamiliar status code. The Web API only responds with200
(yes, even for errors) or429
(rate limiting). If you receive this error, its likely due to a problem with a proxy, a custom TLS configuration, or a custom API URL. This error has thestatusCode
,statusMessage
,headers
, andbody
properties containing more details.
Pagination
Many of the Web API's methods return
lists of objects, and are known to be cursor-paginated. The result of calling these methods will contain a part of
the list, or a page, and also provide you with information on how to continue to the next page on a subsequent API call.
Instead of calling many times manually, the WebClient
can manage getting each page, allowing you to determine when to
stop, and help you process the results.
The process of retrieving multiple pages from Slack's API can be described as asynchronous iteration, which means
you're processing items in a collection, but getting each item is an asynchronous operation. Fortunately, JavaScript
has this concept built in, and in newer versions of the language there's syntax to make it even simpler:
for await...of
.
(async () => {
let result;
// Async iteration is similar to a simple for loop.
// Use only the first two parameters to get an async iterator.
for await (const page of web.paginate('something.list', { name: 'value' })) {
// You can inspect each page, find your result, and stop the loop with a `break` statement
if (containsTheThing(page.something)) {
result = page.something.thing;
break;
}
}
})();
The for await...of
syntax is available in Node v10.0.0 and above. If you're using an older version of Node, see
functional iteration below.
Using functional iteration
The .paginate()
method can accept up to two additional parameters. The third parameter, stopFn
, is a function that
is called once for each page of the result, and should return true
when the app no longer needs to get another page.
The fourth parameter is reducerFn
, which is a function that gets called once for each page of the result, but can
be used to aggregate a result. The value it returns is used to call it the next time as the accumulator
. The first
time it gets called, the accumulator
is undefined.
(async () => {
// The first two parameters are the method name and the options object.
const done = await web.paginate('something.list', { name: 'value' },
// The third is a function that receives each page and should return true when the next page isn't needed.
(page) => { /* ... */ },
// The fourth is a reducer function, similar to the callback parameter of Array.prototype.reduce().
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
// The accumulator is initialized to undefined.
(accumulator, page, index) => { /* ... */ },
);
})();
The returned value is a Promise
, but what it resolves to depends on whether or not you include the fourth (optional)
parameter. If you don't include it, the resolved value is always undefined
. In this case, its used for control flow
purposes (resuming the rest of your program), and the function in the third parameter is used to capture a result. If
you do include the fourth parameter, then the resolved value is the value of the accumulator
. This is a familiar
pattern for people that use functional programming.
Opening modals
Modals can be created by calling the views.open
method. The method requires you to pass a valid view payload in addition to a trigger_id
, which can be obtained when a user invokes your app using a slash command, clicking a button, or using another interactive action.
const { WebClient } = require('@slack/web-api');
// trigger_ids can be obtained when a user invokes your app.
// Find more information on triggers: https://api.slack.com/docs/triggers
const trigger = 'VALID_TRIGGER_ID';
(async () => {
// Open a modal.
// Find more arguments and details of the response: https://api.slack.com/methods/views.open
const result = await web.views.open({
trigger_id: trigger,
view: {
type: 'modal',
callback_id: 'view_identifier',
title: {
type: 'plain_text',
text: 'Modal title'
},
submit: {
type: 'plain_text',
text: 'Submit'
},
blocks: [
{
type: 'input',
label: {
type: 'plain_text',
text: 'Input label'
},
element: {
type: 'plain_text_input',
action_id: 'value_indentifier'
}
}
]
}
});
// The result contains an identifier for the root view, view.id
console.log(`Successfully opened root view ${result.view.id}`);
})();
Dynamically updating a modal
After the modal is opened, you can update it dynamically by calling views.update
with the view ID returned in the views.open
result.
const { WebClient } = require('@slack/web-api');
// The view ID returned in a views.open call
const vid = 'YOUR_VIEW_ID';
(async () => {
// Update a modal
// Find more arguments and details of the response: https://api.slack.com/methods/views.update
const result = await web.views.update({
view_id: vid
view: {
type: 'modal',
callback_id: 'view_identifier',
title: {
type: 'plain_text',
text: 'Modal title'
},
blocks: [
{
type: 'section',
text: {
type: 'plain_text',
text: 'An updated modal, indeed'
}
}
]
}
});
})();
Logging
The WebClient
will log interesting information to the console by default. You can use the logLevel
to decide how
much information, or how interesting the information needs to be, in order for it to be output. There are a few possible
log levels, which you can find in the LogLevel
export. By default, the value is set to LogLevel.INFO
. While you're
in development, its sometimes helpful to set this to the most verbose: LogLevel.DEBUG
.
// Import LogLevel from the package
const { WebClient, LogLevel } = require('@slack/web-api');
// Log level is one of the options you can set in the constructor
const web = new WebClient(token, {
logLevel: LogLevel.DEBUG,
});
All the log levels, in order of most to least information are: DEBUG
, INFO
, WARN
, and ERROR
.
Sending log output somewhere besides the console
You can also choose to have logs sent to a custom logger using the logger
option. A custom logger needs to implement
specific methods (known as the Logger
interface):
Method | Parameters | Return type |
---|---|---|
setLevel() | level: LogLevel | void |
setName() | name: string | void |
debug() | ...msgs: any[] | void |
info() | ...msgs: any[] | void |
warn() | ...msgs: any[] | void |
error() | ...msgs: any[] | void |
A very simple custom logger might ignore the name and level, and write all messages to a file.
const { createWriteStream } = require('fs');
const logWritable = createWriteStream('/var/my_log_file'); // Not shown: close this stream
const web = new WebClient(token, {
// Creating a logger as a literal object. It's more likely that you'd create a class.
logger: {
debug(...msgs): { logWritable.write('debug: ' + JSON.stringify(msgs)); },
info(...msgs): { logWritable.write('info: ' + JSON.stringify(msgs)); },
warn(...msgs): { logWritable.write('warn: ' + JSON.stringify(msgs)); },
error(...msgs): { logWritable.write('error: ' + JSON.stringify(msgs)); },
setLevel(): { },
setName(): { },
},
});
Automatic retries
In production systems, you want your app to be resilient to short hiccups and temporary outages. Solving for this
problem usually involves building a queuing system that handles retrying failed tasks. The WebClient
comes with this
queuing system out of the box, and its on by default! The client will retry a failed API method call up to 10 times,
spaced out over about 30 minutes. If the request doesn't succeed in that time, then the returned Promise
will reject.
You can observe each of the retries in your logs by setting the log level to DEBUG. Try running the
following code with your network disconnected, and then re-connect after you see a couple of log messages:
const { WebClient, LogLevel } = require('@slack/web-api');
const web = new WebClient('bogus token');
(async () => {
await web.auth.test();
console.log('Done!');
})();
Shortly after re-connecting your network, you should see the Done!
message. Did you notice the program doesn't use a
valid token? The client is doing something clever and helpful here. It knows the difference between an error such as not
being able to reach api.slack.com
and an error in the response from Slack about an invalid token. The former is
something that can be resolved with a retry, so it was retried. The invalid token error means that the call isn't going
to succeed until your app does something differently, so it stops attempting retries.
You might not think 10 retries in 30 minutes is a good policy for your app. No problem, you can set the retryConfig
to
one that works better for you. The retryPolicies
export contains a few well known options, and you can always write
your own.
const { WebClient, retryPolicies } = require('@slack/web-api');
const web = new WebClient(token, {
retryConfig: retryPolicies.fiveRetriesInFiveMinutes,
});
Here are some other values that you might want to use for retryConfig
:
retryConfig | Description |
---|---|
retryPolicies.tenRetriesInAboutThirtyMinutes | (default) |
retryPolicies.fiveRetriesInFiveMinutes | Five attempts in five minutes |
retryPolicies.rapidRetryPolicy | Used to keep tests running fast |
{ retries: 0 } | No retries (other options) |
Note: If an API call results in a rate limit being exceeded, you might still notice the client automatically
retrying the API call. If you'd like to opt out of that behavior, set the rejectRateLimitedCalls
option to true
.
Upload a file
Blobs of binary data, sometimes structured, sometimes called a "file", can be useful to upload and share to a channel.
Such files can be uploaded and shared to a channel with the nifty filesUploadV2
API:
const { WebClient } = require('@slack/web-api');
const token = process.env.SLACK_TOKEN;
const web = new WebClient(token);
const result = await web.filesUploadV2({
channel_id: 'C0123456789',
initial_comment: 'Here is the new company logo!',
file: './path/to/logo.png',
filename: 'logo.png',
});
console.log('File uploaded:', result.files);
The file
parameter accepts either the path to a file, a Buffer, or a ReadStream. Details on handling different forms
of binary data follow soon!
The channel_id
and initial_comment
aren't required, but the file either won't be shared to a channel or it won't be
posted with a message if these aren't included.
In a successful response, the result.files
contains an array of shared files.
These files are "private" and available to just the token
holder if no channel_id
is included in the request, and
are marked "public" when shared to a provided channel_id
.
Multiple files can also be uploaded at once, without needing to write a loop, using the files_uploads
parameter. This
accepts similar attributes as a single file:
alt_text
: A description of images for a screen-reader.content
: The file contents as a string. If omitted,file
must be provided.file
: The file path or data to upload. If omitted,content
must be provided.filename
: The name of the file.filetype
: A file type identifier. Reference.snippet_type
: Syntax type of the snippet being uploaded. E.g.python
.title
: The title of the file.
const result = await web.filesUploadV2({
channel_id: 'C0123456789',
thread_ts: '1223313423434.131321',
initial_comment: 'Here are the new company assets!',
file_uploads: [
{
file: './path/to/logo.png',
filename: 'logo.png',
},
{
file: './path/to/logo-sm.png',
filename: 'logo-sm.png',
},
],
});
Notice a thread_ts
even snuck into that last example just for those threaded uploaded needs.
Handling binary data:
Several methods, filesUploadV2
, files.upload (deprecated)
, and users.setPhoto
, allow you to upload a file. In
Node, there are a few ways you might be dealing with files, or more generally, binary data. When you have the whole file
in memory (like when you've just generated or processed an image), then in Node you'd have a
Buffer
that contains that binary data. Or, when you are reading the file from
disk or a network (like when you have a path to file name), then you'd typically have a
ReadStream
. The client can handle both of these binary data types
for you, and it looks like any other API call.
The following example shows how you can use filesUploadV2
to upload a file that is read from disk (as a ReadStream
).
const { createReadStream } = require('fs');
const { WebClient } = require('@slack/web-api');
const token = process.env.SLACK_TOKEN;
const web = new WebClient(token);
// A file name is required for the upload
const filename = 'test_file.csv';
(async () => {
const result = await web.filesUploadV2({
filename,
// You can use a ReadStream or a Buffer for the file option
// This file is located in the current directory (`process.pwd()`), so the relative path resolves
file: createReadStream(`./${fileName}`),
});
console.log('File uploaded:', result.files);
})();
In the example above, you could also use a Buffer
object as the value for the file
property of the options object.
Migrating from the files.upload
API method:
Prior to @slack/web-api v6.8.0, files.upload
was the recommended way to upload files. Release @slack/web-api v6.8.0
introduced the now recommended filesUploadV2
above.
At that time we were receiving many reports on the performance issue of the prior files.upload
API. So, to cope with
the problem, our Platform team decided to unlock a new way to upload files to Slack via public APIs.
To utilize this new approach, developers need to implement the following steps on their code side:
- Call WebClient#files.getUploadURLExternal() method to receive a URL to use for each file
- Perform an HTTP POST request to the URL you received in step 1 for each file
- Call WebClient#files.completeUploadExternal() method with the pairs of file ID and title to complete the whole process, plus share the files in a channel
- If you would like the full metadata of the files, call WebClient#files.info() method for each file
We understand that writing the above code requires many lines of code. Also, existing WebClient#files.upload() users have to take a certain amount of time for migration. To mitigate the pain, we've added a wrapper method named WebClient#filesUploadV2().
Also, in addition to the performance improvements, another good news is that 3rd party apps can now upload multiple files at a time!
Changes needed to migrate from files.upload
to filesUploadV2
are detailed after this example of a files.upload
call:
const { WebClient } = require('@slack/web-api');
const token = process.env.SLACK_TOKEN;
const web = new WebClient(token);
const result = await web.files.upload({
file: './path/to/logo.png',
filename: 'logo.png',
channels: 'C0123456789',
initial_comment: 'Here is the new company logo!',
});
console.log('File uploaded:', result.file);
To update that snippet to filesUploadV2
, the following changes are needed:
- Replace
web.files.upload
withweb.filesUploadV2
- Swap
channels
forchannel_id
and ensure only one channel is provided - Change
result.file
toresult.files
const { WebClient } = require('@slack/web-api');
const token = process.env.SLACK_TOKEN;
const web = new WebClient(token);
const result = await web.filesUploadV2({
file: './path/to/logo.png',
filename: 'logo.png',
channel_id: 'C0123456789',
initial_comment: 'Here is the new company logo!',
});
console.log('File uploaded:', result.files);
Please note
the planned sunset date of
2025-03-11 for the files.upload
method if you're using it at this time. Migrating when possible is recommended and
filesUploadV2
offers advantages that we're excited to share!
Proxy requests with a custom agent
The client allows you to customize the HTTP
Agent
used to create the connection to Slack.
Using this option is the best way to make all requests from your app through a proxy, which is a common requirement in
many corporate settings.
In order to create an Agent
from some proxy information (such as a host, port, username, and password), you can use
one of many npm packages. We recommend https-proxy-agent
. Start
by installing this package and saving it to your package.json
.
$ npm install https-proxy-agent
Import the HttpsProxyAgent
class, and create an instance that can be used as the agent
option of the WebClient
.
const { WebClient } = require('@slack/web-api');
const HttpsProxyAgent = require('https-proxy-agent');
const token = process.env.SLACK_TOKEN;
// One of the ways you can configure HttpsProxyAgent is using a simple string.
// See: https://github.com/TooTallNate/node-https-proxy-agent for more options
const proxy = new HttpsProxyAgent(process.env.http_proxy || 'http://168.63.76.32:3128');
const web = new WebClient(token, { agent: proxy });
// All API method calls will now go through the proxy
Modify outgoing requests with a request interceptor
The client allows you to customize a request
interceptor
to modify outgoing requests.
Using this option allows you to modify outgoing requests to conform to the requirements of a proxy, which is a common requirement in many corporate settings.
For example you may want to wrap the original request information within a POST request:
const { WebClient } = require('@slack/web-api');
const token = process.env.SLACK_TOKEN;
const webClient = new WebClient(token, {
requestInterceptor: (config) => {
config.headers['Content-Type'] = 'application/json';
config.data = {
method: config.method,
base_url: config.baseURL,
path: config.url,
body: config.data ?? {},
query: config.params ?? {},
headers: structuredClone(config.headers),
test: 'static-body-value',
};
return config;
}
});
Using a pre-configured http client to handle outgoing requests
The client allows you to specify an
adapter
to handle outgoing requests.
Using this option allows you to use a pre-configured http client, which is a common requirement in many corporate settings.
For example you may want to use an HTTP client which is already configured with logging capabilities, desired timeouts, etc.
const { WebClient } = require('@slack/web-api');
const { CustomHttpClient } = require('@company/http-client')
const token = process.env.SLACK_TOKEN;
const customClient = CustomHttpClient();
const webClient = new WebClient(token, {
adapter: (config: RequestConfig) => {
return customClient.request(config);
}
});
Rate limits
When your app calls API methods too frequently, Slack will politely ask (by returning an error) the app to slow down,
and also let your app know how many seconds later it should try again. This is called rate limiting and the
WebClient
handles it for your app with grace. The client will understand these rate limiting errors, wait the
appropriate amount of time, and then retry the request without any changes in your code. The Promise
returned only
resolves when Slack has given your app a real response.
It's a good idea to know when you're bumping up against these limits, so that
you might be able to change the behavior of your app to hit them less often. Your users would surely appreciate getting
things done without the delay. Each time a rate limit related error occurs, the WebClient
instance emits an event:
WebClientEvent.RATE_LIMITED
. We recommend that you use the event to inform users when something might take longer than
expected, or just log it for later.
const { WebClient, WebClientEvent } = require('@slack/web-api');
const token = process.env.SLACK_TOKEN;
const web = new WebClient(token);
web.on(WebClientEvent.RATE_LIMITED, (numSeconds, { url }) => {
console.log(`A rate-limiting error occurred while calling ${url} and the app is going to retry in ${numSeconds} seconds.`);
});
You might not want to the WebClient
to handle rate limits in this way. Perhaps the operation was time sensitive, and
it won't be useful by the time Slack is ready for another request. Or, you have a more sophisticated approach. In these
cases, you can set the rejectRateLimitedCalls
option on the client to true
. Once you set this option, method calls
can fail with rate limiting related errors. These errors have a code
property set to ErrorCode.RateLimitedError
. See
error handling for more details.
const { WebClient } = require('@slack/web-api');
const token = process.env.SLACK_TOKEN;
// Setting the rejectRateLimitedCalls option to true turns off automatic retries
const web = new WebClient(token, { rejectRateLimitedCalls: true });
Request concurrency
Each of the API method calls the client starts are happening concurrently, or at the same time. If your app tries
to perform a lot of method calls, let's say 100 of them, at the same time, each one of them would be competing for the
same network resources (such as bandwidth). By competing, they might negatively affect the performance of all the rest,
and therefore negatively affect the performance of your app. This is one of the reasons why the WebClient
limits the
concurrency of requests by default to one hundred, which means it keeps track of how many requests are waiting, and only
starts an additional request when one of them completes. The exact number of requests the client allows at the same time
can be set using the maxRequestConcurrency
option.
const { WebClient } = require('@slack/web-api');
const token = process.env.SLACK_TOKEN;
// Limit the number of concurrent requests to 5
const web = new WebClient(token, { maxRequestConcurrency: 5 });
The lower you set the maxRequestConcurrency
, the less parallelism you'll have in your app. Imagine setting the
concurrency to 1
. Each of the method calls would have to wait for the previous method call to complete before it can
even be started. This could slow down your app significantly. So its best not to set this number too low.
Another reason, besides competing for resources, that you might limit the request concurrency is to minimize the amount of state in your app. Each request that hasn't completed is in some ways a piece of state that hasn't yet been stored anywhere except the memory of your program. In the scenario where you had 100 method calls waiting, and your program unexpectedly crashes, you've lost information about 100 different things going on in the app. But by limiting the concurrency to a smaller number, you can minimize this risk. So its best not to set this number too high.
Custom TLS configuration
Each connection to Slack starts with a handshake that allows your app to trust that it is actually Slack you are
connecting to. The system for establishing this trust is called TLS. In order for TLS to work, the host running your app
keeps a list of trusted certificate authorities, that it can use to verify a signature Slack produces. You don't
usually see this list, its usually a part of the operating system you're running on. In very special cases, like certain
testing techniques, you might want to send a request to another party that doesn't have a valid TLS signature that your
certificate authority would verify. In these cases, you can provide alternative TLS settings, in order to change how the
operating system might determine whether the signature is valid. You can use the tls
option to describe the settings
you want (these settings are the most common and useful from the standard Node
API).
const { WebClient } = require('@slack/web-api');
const { readFileSync } = require('fs');
const token = process.env.SLACK_TOKEN;
// Load a custom certificate authority from a file in the current directory
const ca = readFileSync('./ca.crt');
// Initialize a client with the custom certificate authority
const web = new WebClient(token, { tls: { ca } });
tls property | Description |
---|---|
ca | Optionally override the trusted CA certificates. Any string or Buffer can contain multiple PEM CAs concatenated together. |
key | Private keys in PEM format. PEM allows the option of private keys being encrypted. Encrypted keys will be decrypted with passphrase . |
cert | Cert chains in PEM format. One cert chain should be provided per private key. |
pfx | PFX or PKCS12 encoded private key and certificate chain. pfx is an alternative to providing key and cert individually. PFX is usually encrypted, if it is, passphrase will be used to decrypt it. |
passphrase | Shared passphrase used for a single private key and/or a PFX. |
Custom API URL
The URLs for method calls to Slack's Web API always begin with https://slack.com/api/
. In very special cases, such as
certain testing techniques, you might want to send these requests to a different URL. The slackApiUrl
option allows
you to replace this prefix with another.
const { WebClient } = require('@slack/web-api');
const token = process.env.SLACK_TOKEN;
const options = {};
// In a testing environment, configure the client to send requests to a mock server
if (process.env.NODE_ENV === 'test') {
options.slackApiUrl = 'http://localhost:8888/api/';
}
// Initialize a client using the configuration
const web = new WebClient(token, options);
Exchange an OAuth grant for a token
There's one method in the Slack Web API that doesn't requires a token, because its the method that gets a token! This
method is called oauth.v2.access
. It's used as part of the OAuth
2.0 process that users initiate when installing your app into a workspace. In the
last step of this process, your app has received an authorization grant called code
which it needs to exchange for
an access token (token
). You can use an instance of the WebClient
that has no token to easily complete this
exchange.
const { WebClient } = require('@slack/web-api');
// App credentials found in the Basic Information section of the app configuration
const clientId = process.env.SLACK_CLIENT_ID;
const clientSecret = process.env.SLACK_CLIENT_SECRET;
// Not shown: received an authorization grant called `code`.
(async () => {
// Create a client instance just to make this single call, and use it for the exchange
const result = await (new WebClient()).oauth.v2.access({
client_id: clientId,
client_secret: clientSecret,
code
});
// It's now a good idea to save the access token to your database
// Some fictitious database
await db.createAppInstallation(result.team_id, result.enterprise_id, result.access_token, result.bot);
})();
Note: If you're looking for a more complete solution that handles more of the OAuth process for your app, take a look at the @aoberoi/passport-slack Passport Strategy.
Sign in with Slack via OpenID Connect
Sign in With Slack via OpenID Connect gives users the ability to sign into your service using their Slack profile.
The @slack/web-api
package supports the following API methods which you can use to implement Sign in With Slack:
Here's a fully-functioning sample application implementation for your perusal!
To read more about how Sign in with Slack works, and to access helpful resources like a Sign in With Slack button generator and other design assets, check out: Authentication: Sign in with Slack documentation page.