简体   繁体   中英

Slack Interactive Messages: POST request payload has an unexpected format

I'm getting a POST request inside a Flask app from Slack. The request is sent when a user presses on an interactive message button. According to Slack docs I must extract the body of the request to verify the signature. My computed signature doesn't match the one sent by Slack, though. In fact, the body of the request comes as some encoded string. The string is actually an encoded dictionary instead of a query str parameters, as expected.

Here's the beginning of my view:

@app.route('/register', methods=['POST'])
def register_visit():
    data = request.get_data()
    signature = request.headers.get('X-Slack-Signature', None)
    timestamp = request.headers.get('X-Slack-Request-Timestamp', None)
    signing_secret = b'aaaaaaaaaaaaaaaa'


    # old message, ignore
    if round(actual_time.time() - float(timestamp)) > 60 * 5:
        return
    concatenated = ("v0:%s:%s" % (timestamp, data)).encode('utf-8')
    computed_signature = 'v0=' + hmac.new(signing_secret, msg=concatenated, digestmod=hashlib.sha256).hexdigest()
    if hmac.compare_digest(computed_signature, signature):
        ...

I've tried to format the received data to make it look like:

token=fdjkgjl&user_id=1234... but I am not aware of all of the necessary parameters that have to be present in the data.

Any ideas are highly appreciated.

The body of the message is following - after being URL decoded (note I've modified possibly sensitive data):

b'payload={"type":"interactive_message","actions": [{"name":"yes_button","type":"button","value":"236"}],"callback_id":"visit_button","team":{"id":"fffff","domain":"ffff"},"channel":{"id":"ffff","name":"directmessage"},"user":{"id":"ffffff","name":"fffft"},"action_ts":"1540403943.419120","message_ts":"1541403949.000100","attachment_id":"1","token":"8LpjBuv13J7xAjhl2lEajoBU","is_app_unfurl":false,"original_message":{"text":"Test","bot_id":"DDDDDDDDD","attachments":[{"callback_id":"visit_button","text":"Register","id":1,"color":"3AA3E3","actions":[{"id":"1","name":"yes_button","text":"Yes","type":"button","value":"236","style":""}],"fallback":"Register"}],"type":"message","subtype":"bot_message","ts":"1540413949.000100"},"response_url":"https://hooks.slack.com/actions/ffffff/ffffff/tXJjx1XInaUhrikj6oEzK08e","trigger_id":"464662548327.425084163429.dda35a299eedb940ab98dbb9386b56f0"}'

The reason you are getting the "garbled" data is that you are using request.get_data() . That method will return the raw data of a request, but not do any decoding for you.

Much more convenient is to use request.form.get('payload') , which will directly give you the JSON string of the request object. You can then convert that into a dict object with json.loads() to process it further in your app.

Note that the format you received is the correct format for interactive messages. You will not get a query string (eg "token=abc;user_id?def...") as you suggested (like for slash command requests). Interactive message request will always contain the request as JSON string in a payload form property. See here for reference.

Here is a simple working example, which will reply a greeting to the user that pressed the button. It will work directly with Slack, but I recommend using Postman to test it.

#app.py

from flask import Flask, request #import main Flask class and request object
import json

app = Flask(__name__) #create the Flask app

@app.route('/register', methods=['POST'])
def register_visit():
    slack_req = json.loads(request.form.get('payload'))
    response = '{"text": "Hi, <@' + slack_req["user"]["id"] + '>"}'
    return response, 200, {'content-type': 'application/json'}

if __name__ == '__main__':
    app.run(debug=True, port=5000) #run app in debug mode on port 5000

OK, the issue wasn't related to how Slack sends me the message. It was about misunderstanding which data comes as bytes and which data is unicode. The culprit was string formatting in my case - the line concatenated = ("v0:%s:%s" % (timestamp, data)).encode('utf-8') should have been concatenated = (b"v0:%b:%b" % (timestamp.encode("utf-8"), data)) . Data is already bytes, timestamp meanwhile is unicode. Cannot believe I've banged my head on this for hours -_-

@app.route('/register', methods=['POST'])
def register_visit():
    data = request.get_data()
    signature = request.headers.get('X-Slack-Signature', None)
    timestamp = request.headers.get('X-Slack-Request-Timestamp', None)
    signing_secret = b'aaaaaaaaaaaaaaaa'
    # old message, ignore
    if round(actual_time.time() - float(timestamp)) > 60 * 5:
        return
    concatenated = (b"v0:%b:%b" % (timestamp.encode("utf-8"), data))
    computed_signature = 'v0=' + hmac.new(signing_secret, msg=concatenated, 
    digestmod=hashlib.sha256).hexdigest()
    if hmac.compare_digest(computed_signature, signature):
        ...

This worked for me

from urllib import parse

parsed_text = parse.unquote('your bytes text here')

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM