Go to Slack

Verifying requests from Slack

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?

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.

A recipe for security

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:

How to make a request signature in 4 easy steps: an overview

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-Timestamp header 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 v0.
  • With the help of HMAC SHA256 implemented in your favorite programming, hash the above basestring, using the Slack Signing Secret as the key.
  • Compare this computed signature to the X-Slack-Signature header on the request.

Step-by-step walk-through for validating a 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.

  1. Grab your Slack Signing Secret, available in the app admin panel under Basic Info. In this example, the Signing Secret is 8f742231b10e8888abcd99yyyzzz85a5. 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
  2. Extract the timestamp header (1531420618 in 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.
  3. 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 ('v0:1531420618:token=xyzz...).

    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'
  4. 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 'v0=a2114d57b48eac39b9ad189dd8316235a7b4a8d21a10bd27519666489c69b503'.

    my_signature = 'v0=' + hmac.compute_hash_sha256(
    >>> 'v0=a2114d57b48eac39b9ad189dd8316235a7b4a8d21a10bd27519666489c69b503'
  5. Compare the resulting signature to the header on the request. Note: for best practice, use an hmac compare function 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!

App management updates

For a while, you'll see two secrets side-by-side in the app management panel:

Admin page with signing secret and verification token

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.

Secret regeneration

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.

SDK support

Verifying requests is easier than microwaving a TV dinner when you use a supported SDK to build your app. These Slack SDKs currently support signing secrets:

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_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.

Verification token deprecation

We'll continue allowing apps to use verification tokens for now. However, we will deprecate them over the coming months, so switch over as soon as possible to the fresh new signing secrets.

Types of requests that support signed secrets

Known issues and gotchas

  • Use the body of the HTTP request. An HTTP request contains:
    • a request line: GET /slackapi/interactive-message-action-response.
    • 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.
  • 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.

Mutual TLS

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.

A TLS Recipe

Here's the Mutual TLS process in more detail:

  1. Configure your TLS-terminating server to request client certificates. Your server should accept client certificates issued by DigiCert SHA2 Secure Server CA, an intermediate CA under DigiCert Global Root CA. These CAs are included in many standard CA certificate bundles.

  2. 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: platform-tls-client.slack.com.
  3. 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.

  4. 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.