Verifying Slack requests in Python

If you’re building a Slack app in Python, you’ll need to verify that the inbound callbacks you’re receiving are genuine.

This page in the Slack API docs explains the process in pseudocode: Verifying requests from Slack

Below is a Python function, based on that pseudocode, which will simply return True if the request is valid, and False if not. This has been tested using a Flask request object, but should be easily adaptable to any other web framework.

I’ve declared the secrets in code here for ease of the example, but you shouldn’t do this in real life. You should put your secrets somewhere outside of the code (e.g. environment variables) and read them from config when needed.

import hmac
import hashlib
import time
def verify_request(request):
    SIGNING_SECRET = "your_slack_signing_secret"
    # Convert your signing secret to bytes
    slack_signing_secret = bytes(SIGNING_SECRET, "utf-8")
    request_body = request.get_data().decode()
    slack_request_timestamp = request.headers["X-Slack-Request-Timestamp"]
    slack_signature = request.headers["X-Slack-Signature"]
    # Check that the request is no more than 60 seconds old
    if (int(time.time()) - int(slack_request_timestamp)) > 60:
        print("Verification failed. Request is out of date.")
        return False
    # Create a basestring by concatenating the version, the request  
      timestamp, and the request body
    basestring = f"v0:{slack_request_timestamp}:{request_body}".encode("utf-8")
    # Hash the basestring using your signing secret, take the hex digest, and prefix with the version number
    my_signature = (
        "v0=" + hmac.new(slack_signing_secret, basestring, hashlib.sha256).hexdigest()
    )
    # Compare the resulting signature with the signature on the request to verify the request
    if hmac.compare_digest(my_signature, slack_signature):
        return True
    else:
        print("Verification failed. Signature invalid.")
        return False

This could also be implemented as a Python decorator (if I get a chance I’ll update this post with an example of that too).

The “datetime.datetime not JSON serializable” thing

I hit this every time I start a new project involving APIs and data stored in PostgreSQL; and every time I spend ages googling to try and remember how to fix it.

The issue is that psycopg2 returns datetime.datetime types from database timestamp fields, which then can’t be converted when calling jsonify() on the returned dictionary.

There are actually quite a few ways to solve this and I’ve tended to use whichever one seems to hurt my head the least at the time…

Previously I’ve used the following method:

class DateEncoder(JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime.date):
return obj.isoformat()
return JSONEncoder.default(self, obj)

But this time I discovered it can actually be as simple as:

json_friendly_date = meany_datetime_type.isoformat()

.isoformat() returns you a nice ISO8601 complaint string which will then jsonify() nicely

>> datetime.datetime(2015, 11, 9, 20, 30, 00).isoformat()
"2015-11-09T20:30:00"