Slack signs its requests using a secret that's unique to your app.
With the help of signing secrets, your app can more confidently verify whether requests from us are authentic. The signing process is the cooler, fresher sibling of verification tokens.
- What are signed secrets?
- A recipe for security
- App management updates
- Secret regeneration
- SDK support
- What about verification tokens?
- Types of requests that use signed secrets
- Known issues and gotchas
- Mutual TLS
Slack creates a unique string for your app and shares it with you. Verify requests from Slack with confidence by verifying signatures using your signing secret.
On each HTTP request that Slack sends, we add an
X-Slack-Signature HTTP header. The signature is created by combining the signing secret with the body of the request we're sending using a standard HMAC-SHA256 keyed hash.
The resulting signature is unique to each request and doesn't directly contain any secret information. That keeps your app secure, preventing bad actors from causing mischief.
Signing secrets replace the old verification tokens. Good news: the new signature is used exactly the same way as the deprecated verification token. It's just computed more securely.
Some SDKs perform signature verification automatically, accessible via an easy drop-in replacement of your signing secret for your old verification token. See the SDK support section for more detail.
Request signing follows this pattern:
- Your app receives a request from Slack.
- Your app computes a signature based on the request.
- You make sure the signature you've computed matches the signature on the request.
Slack uses HTTP requests to notify your app that something has happened. If you're subscribed to the Events API, your app might receive a request when a reacji has been added to a message. If you're the proud owner of a slash command, your app'll be notified when someone uses your command. You might even be notified that your app has been given new resources and permissions.
When Slack sends your app a request, your app must check to make sure it's authentic. You do this by computing a signature. The recipe for the signature is as simple as a cookbook recipe... and not one of those fancy cookbooks recipes, either. This one's as simple as good old mac-and-cheese:
Ingredients required: one (1) HTTP request from Slack, one (1) signing secret, one (1) programming language of your choice.
Here's an overview of the process to validate a signed request from Slack:
- Retrieve the
X-Slack-Request-Timestampheader on the HTTP request, and the body of the request.
- Concatenate the version number, the timestamp, and the body of the request to form a basestring. Use a colon as the delimiter between the three elements. For example,
v0:123456789:command=/weather&text=94070. The version number right now is always
- With the help of HMAC SHA256 implemented in your favorite programming language, hash the above basestring, using the Slack
Signing Secretas the key.
- Compare this computed signature to the
X-Slack-Signatureheader on the request.
Note: This pseudocode example walks through validating a request. Nearly all modern programming languages can perform equivalent calculations with HMAC SHA256. Here's the library for Node, for example.
Grab your Slack Signing Secret, available in the app admin panel under Basic Info. In this example, the Signing Secret is
8f742231b10e8888abcd99yyyzzz85a5. (Hint: you don't need to convert the Signing Secret to binary. Treat it as a standard UTF-8 string.) While you're at it, extract the raw request body from the request.
Make sure that this request body contains no headers and is not deserialized in any way. Use only the raw request payload.
slack_signing_secret = 'MY_SLACK_SIGNING_SECRET' // Set this as an environment variable >>> 8f742231b10e8888abcd99yyyzzz85a5 request_body = request.body() >>> token=xyzz0WbapA4vBCDEFasx0q6G&team_id=T1DC2JH3J&team_domain=testteamnow&channel_id=G8PSS9T3V&channel_name=foobar&user_id=U2CERLKJA&user_name=roadrunner&command=%2Fwebhook-collect&text=&response_url=https%3A%2F%2Fhooks.slack.com%2Fcommands%2FT1DC2JH3J%2F397700885554%2F96rGlfmibIGlgcZRskXaIFfN&trigger_id=398738663015.47445629121.803a0bc887a14d10d2c447fce8b6703c
Extract the timestamp header (
1531420618in this example) from the request. The signature depends on the timestamp to protect against replay attacks. While you're extracting the timestamp, check to make sure that the request occurred recently. In this example, we verify that the timestamp does not differ from local time by more than five minutes.
timestamp = request.headers['X-Slack-Request-Timestamp'] >>> 1531420618 if absolute_value(time.time() - timestamp) > 60 * 5: # The request timestamp is more than five minutes from local time. # It could be a replay attack, so let's ignore it. return
Concatenate the version number (
v0), the timestamp (
1531420618), and the request body (
token=xyzz...) together, using a colon (
:) as a delimiter. In this example, the resulting basestring is (
sig_basestring = 'v0:' + timestamp + ':' + request_body >>> 'v0:1531420618:token=xyzz0WbapA4vBCDEFasx0q6G&team_id=T1DC2JH3J&team_domain=testteamnow&channel_id=G8PSS9T3V&channel_name=foobar&user_id=U2CERLKJA&user_name=roadrunner&command=%2Fwebhook-collect&text=&response_url=https%3A%2F%2Fhooks.slack.com%2Fcommands%2FT1DC2JH3J%2F397700885554%2F96rGlfmibIGlgcZRskXaIFfN&trigger_id=398738663015.47445629121.803a0bc887a14d10d2c447fce8b6703c'
Then hash the resulting string, using the signing secret as a key, and taking the hex digest of the hash. In this example, we compute a hex digest of
a2114d57b48eac39b9ad189dd8316235a7b4a8d21a10bd27519666489c69b503. The full signature is formed by prefixing the hex digest with
v0=, to make
my_signature = 'v0=' + hmac.compute_hash_sha256( slack_signing_secret, sig_basestring ).hexdigest() >>> 'v0=a2114d57b48eac39b9ad189dd8316235a7b4a8d21a10bd27519666489c69b503'
Compare the resulting signature to the header on the request. Note: for best practice, use an hmac
comparefunction instead of directly comparing the signatures for equality.
slack_signature = request.headers['X-Slack-Signature'] >>> 'v0=a2114d57b48eac39b9ad189dd8316235a7b4a8d21a10bd27519666489c69b503' if hmac.compare(my_signature, slack_signature): # hooray, the request came from Slack! deal_with_request(request)
For a while, you'll see two secrets side-by-side in the app management panel:
One secret is the new Signing Secret, and one is the deprecated verification token. We strongly recommend you only use the Signing Secret from now on.
If your secret leaks, no use crying over it like spilled milk. Regenerate your secret using the "Regenerate" button next to the Signing Secret in the admin panel.
Verifying requests is easier than microwaving a TV dinner when you use a supported SDK to build your app. These Slack SDKs, among others, currently support signing secrets:
- Node Interactive Messages SDK
- Node Events API SDK
- Python Events API SDK
- Slack SDK for Java (including Bolt)
In each package, using a signed secret is no different than using the old verification token. Simply set your Signing Secret as an environment variable:
export SLACK_SIGNING_SECRET=abc123. Then, initialize the package with the secret. For example, a Node app that uses Interactive Messages would create a message listener using the following single line of code:
// Create the adapter using the app's signing secret, read from env const interactions = createMessageAdapter(process.env.SLACK_SIGNING_SECRET);
A Node app that uses the Events API would initialize a listener for events using the following code:
const slackEvents = createSlackEventAdapter(process.env.SLACK_SIGNING_SECRET);
A Python app that uses the Events API would initialize a listener for events using the following code:
SLACK_SIGNING_SECRET = os.environ["SLACK_SIGNING_SECRET"] slack_events = SlackEventAdapter(SLACK_SIGNING_SECRET, "/slack/events")
You can also check out the open-source BotKit to see an implementation of Slack signature verification in the wild.
We'll continue allowing apps to use verification tokens for now. However, we will retire them completely in coming months. We strongly recommend switching to request signing as soon as possible.
- Slash commands
- Events API
- Interactive messages
- Message buttons
- Message menus
- Legacy outgoing webhooks
- Use the body of the HTTP request. An HTTP request contains:
- a request line:
- a series of headers, like
Content-type: application/json; charset=utf-8.
- a blank line.
- a message body, like
userID=1234&someField=5678. A Slack request message body is always UTF-8 encoded. Use only this message body, combined with the timestamp header, as the basestring of your signature.
- a request line:
- Use the raw request body, before it has been deserialized from JSON or other forms. For example, in Python's Flask, use request.get_data() before accessing any other methods on the request in order to get the raw request payload, without performing JSON deserialization.
Requests from Slack also support authentication through Mutual TLS.
Mutual TLS has the same goal as request signing: it allows your app to verify that an outgoing HTTP request is authentic. Mutual TLS differs from request signing in where and how it occurs. Instead of verifying request signatures in your application code, you configure your TLS-terminating server to authenticate client certificates from Slack.
Here's the Mutual TLS process in more detail:
Configure your TLS-terminating server to request client certificates. Your TLS-terminating server should be configured to accept client certificates under the DigiCert Global Root CA certificate. Slack's client certificate may not be signed directly by the root CA certificate, but requests from Slack will include any intermediate CA certificates necessary for verification.
Extract either of the following fields in the certificate.
Subject Alternative Name:
DNS:platform-tls-client.slack.com. By RFC 6125, this is the recommended field to extract.
Subject Common Name:
Inject the extracted domain into a header, and forward the request to your application server. Here's an example header you might add to the request:
X-Client-Certificate-SAN: platform-tls-client.slack.com. Whatever you choose to call your header, check to make sure this header hasn't already been added to the request. Your upstream application server must know that the header was added by your TLS-terminating server as part of the Mutual TLS process.
In your application server, check the header and verify that its value matches the expected domain,
platform-tls-client.slack.com. Do not accept header values with any other domain name, including other subdomains of slack.com.