Webhook Signature authentication issues

I am trying to verify the x-sendbird-signature in a webhook request using python. I have followed the documentation as well as this post but nothing I have tried is generating the correct signature.

Here is my code

    sig = request.headers.get("X-Sendbird-Signature")

    api_key = str(django_settings.SENDBIRD_API_TOKEN).encode("utf8")

    # NOTE: being verbose here so I can mess around with the data being passed to hmac
    data = r"{}".format(request.data) # NOTE: this is pulled from the referenced forum ticket link above
    new_data = str(data).encode("utf8")

    signature_to_compare = hmac.new(
        key=api_key, msg=new_data, digestmod=hashlib.sha256,
    ).hexdigest()

    return signature_to_compare == sig

data is being printed out as

{'category': 'open_channel:message_send', 'silent': False, 'custom_type': '', 'mention_type': 'users', 'mentioned_users': [], 'app_id': '767EBC26-B17E-4E59-9CF3-2D6045F48C62', 'sender_ip_addr': '54.156.5.93', 'type': 'ADMM', 'payload': {'created_at': 1606160253790, 'message': '{"type": "ASSIGNMENT_UPDATED", "assignment": {"id": 392910, "assignedTicket": 884513, "agent": {"id": 488, "displayName": "Ammar Mian", "role": "AGENT", "status": "ACTIVE", "connection": "ONLINE", "email": "ammar.mian08@gmail.com", "photoThumbnailUrl": "", "tier": "INTERMEDIATE", "phoneNumber": null}, "assignedAt": "2020-11-19T14:35:49.686687Z", "responsedAt": null, "endedAt": null, "status": "NOT_RESPONSED", "unreadCount": 57, "responseTime": null}}', 'data': '', 'message_id': 6615908978, 'custom_type': ''}, 'channel': {'data': '', 'channel_url': 'ticket_info_58574dd9_ba06_4985_8b45_57c3977335ce', 'name': 'SENDBIRD_DESK_OPEN_CHANNEL', 'custom_type': 'SENDBIRD_DESK_CHANNEL_CUSTOM_TYPE'}, 'sdk': 'API'}

and the new_data variable returns

b'{\'category\': \'open_channel:message_send\', \'silent\': False, \'custom_type\': \'\', \'mention_type\': \'users\', \'mentioned_users\': [], \'app_id\': \'767EBC26-B17E-4E59-9CF3-2D6045F48C62\', \'sender_ip_addr\': \'54.156.5.93\', \'type\': \'ADMM\', \'payload\': {\'created_at\': 1606160253790, \'message\': \'{"type": "ASSIGNMENT_UPDATED", "assignment": {"id": 392910, "assignedTicket": 884513, "agent": {"id": 488, "displayName": "Ammar Mian", "role": "AGENT", "status": "ACTIVE", "connection": "ONLINE", "email": "ammar.mian08@gmail.com", "photoThumbnailUrl": "", "tier": "INTERMEDIATE", "phoneNumber": null}, "assignedAt": "2020-11-19T14:35:49.686687Z", "responsedAt": null, "endedAt": null, "status": "NOT_RESPONSED", "unreadCount": 57, "responseTime": null}}\', \'data\': \'\', \'message_id\': 6615908978, \'custom_type\': \'\'}, \'channel\': {\'data\': \'\', \'channel_url\': \'ticket_info_58574dd9_ba06_4985_8b45_57c3977335ce\', \'name\': \'SENDBIRD_DESK_OPEN_CHANNEL\', \'custom_type\': \'SENDBIRD_DESK_CHANNEL_CUSTOM_TYPE\'}, \'sdk\': \'API\'}'

This is my current way of trying it, though I have replicated the exact code recommended in the webhook documentation for python to no avail. I have tried every API key available to me, though I know that the master key is the one you are requiring.

Any help you can offer will be appreciated

Hi @bredelman, please try to bytes instead of string. It should work.
For example: Instead of api_key = str(django_settings.SENDBIRD_API_TOKEN).encode(“utf8”) and new_data = str(data).encode(“utf8”),
api_key = bytes(str(django_settings.SENDBIRD_API_TOKEN).encode(“utf8”))
new_data = bytes(str(data).encode(“utf8”))

Sorry I had already tried that and just tried again but it doesn’t work.

     api_key = bytes(str(django_settings.SENDBIRD_API_TOKEN).encode("utf8"))
    data = bytes(str(request.data).encode("utf8"))
    new_data = str(data).encode("utf8")
    signature_to_compare = hmac.new(
        key=api_key, msg=new_data, digestmod=hashlib.sha256,
    ).hexdigest()
    sig = request.headers.get("X-Sendbird-Signature")
    return signature_to_compare == sig

If it’s helpful this is my raw request

{'category': 'open_channel:message_send', 'silent': False, 'custom_type': '', 'mention_type': 'users', 'mentioned_users': [], 'app_id': '767EBC26-B17E-4E59-9CF3-2D6045F48C62', 'sender_ip_addr': '54.156.5.93', 'type': 'ADMM', 'payload': {'created_at': 1606229304667, 'message': '{"type": "ASSIGNMENT_UPDATED", "assignment": {"id": 392912, "assignedTicket": 884513, "agent": {"id": 491, "displayName": "Brian Edelman", "role": "AGENT", "status": "ACTIVE", "connection": "OFFLINE", "email": "bd.edelman@gmail.com", "photoThumbnailUrl": "", "tier": "INTERMEDIATE", "phoneNumber": null}, "assignedAt": "2020-11-23T20:58:29.040149Z", "responsedAt": "2020-11-23T20:58:51.838921Z", "endedAt": null, "status": "RESPONSED", "unreadCount": 0, "responseTime": 22}}', 'data': '', 'message_id': 6616349242, 'custom_type': ''}, 'channel': {'data': '', 'channel_url': 'ticket_info_58574dd9_ba06_4985_8b45_57c3977335ce', 'name': 'SENDBIRD_DESK_OPEN_CHANNEL', 'custom_type': 'SENDBIRD_DESK_CHANNEL_CUSTOM_TYPE'}, 'sdk': 'API'}

I have a suspicion that maybe Django Rest Framework or django is doing something to the payload that makes this method not work correctly? Just a suspicion though, no evidence yet.

Sorry if I make you confuse. I missed the part data = r"{}".format(request.data) on your previous code.
I think that you can use the request.data itself. Because it is bytes. No need to convert it to string and convert back to bytes and convert to a string again. Could you try the following, please?
data=request.data
signature_to_compare = hmac.new(
key=api_key, msg=data, digestmod=hashlib.sha256,
).hexdigest()
If it does not work again, could you please write direct message?

Sorry I was away from this task for a while/my account was in limbo. I finally got this working, but now I have another issue, related.

Here is working code.

    api_key = bytes(str(django_settings.SENDBIRD_API_TOKEN).encode("utf8"))
    data = bytes(request.body)
    signature_to_compare = hmac.new(
        key=api_key, msg=data, digestmod=hashlib.sha256,
    ).hexdigest()
    if is_desk:
        sig = request.headers.get("x-hub-signature")
    else:
        sig = request.headers.get("X-Sendbird-Signature")
    return signature_to_compare == sig

The key was using request.body.

Anyway, you can see I am checking for the type of webhook we are validating against (desk or chat). The documentation for desk API says
assert signature_to_compare == 'x_hub_signature' # Check if the value of the 'x-hub-signature' request header is the same as the comparison value you've created.
which I believe is wrong entirely from the way it is written, but that aside there are no headers being sent from a status change to my webhook from the sendbird desk API.

Here are my headers for a successful non desk request:
{'Host': '18fab870a280.ngrok.io', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'User-Agent': 'SendBird', 'X-Signature': 'f1d9758302eb38bef05104a44998b4d7167edf1aa37eb30e2cdf399cddf39f38', 'Content-Type': 'application/json', 'Unicode-Escaped': 'true', 'X-Sendbird-Signature': 'f1d9758302eb38bef05104a44998b4d7167edf1aa37eb30e2cdf399cddf39f38', 'Content-Length': '551', 'X-Forwarded-Proto': 'https', 'X-Forwarded-For': '18.214.175.125'}

and here are my headers for a desk request that is not successful:
{'Host': '18fab870a280.ngrok.io', 'User-Agent': 'python-requests/2.22.0', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Content-Type': 'application/json', 'Content-Length': '2624', 'X-Forwarded-Proto': 'https', 'X-Forwarded-For': '54.146.122.252'}

So we’re almost there! But it looks like sendbird desk isn’t sending what it says it will.

Hi @bredelman, could you share with me your APP_ID by message, please? I will check it.

@bredelman I would suggest removing your message and DMing him the APP_ID.

Yeah I couldn’t find a spot to actually do that. Any suggestions where to find that?

If you click on Sardorbek’s user, in the upper right hand corner is a message button.

Gotcha. This is likely due to you being a newer member to the forums. Discourse requires you to be a certain level before it allows you to send messages. I went ahead and sent your APP_ID to him internally. Sorry about the confusion.

1 Like

Hi @bredelman. We checked your app. Seems that you didn’t set secret key information for your desk webhooks. Desk webhook is a bit different from chat webhook. You need to set the secret key for the desk webhook. Could you change the secret key? (Settings > Desk > Webhooks > secret key form). Then please use the secret key while comparing signatures.

Ah ok thanks for checking on that. That resolves the issue. For what it’s worth that field is not required though it seems as though it should be given the issues I had. Also it looks as though it is filled with a value with the black circles (like for a password field), even if it is not.

1 Like