How to properly verify the signature in webhooks?

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • wlinna
    Junior Member
    • Sep 2024
    • 2

    How to properly verify the signature in webhooks?

    I'm trying to verify the authenticity of webhooks with the help of this guide:


    According to the guide, the signature is computed like this:
    $signature = base64_encode($webhookId . ':' . hash_hmac('sha256', $payload, $secretKey, true));​

    Our server is NodeJS based though, so I've rewritten this in TypeScript. I've tried various alternatives but I can't get the signatures match. I've made sure that I'm using the webhookId and secretKey found under Administration > Webhooks. I am using the payload verbatim without first converting it to

    Has someone been able to verify the signature? Or is there an easier way to verify the authenticity of the event?

    Here are the variations I've tried to generate a signature similar to what PHP would generate. All of them fail.

    ```
    export function generateSignature(webhookId: string, payload: string, secretKey: string): string {
    const hash = crypto.createHmac('sha256', secretKey).update(payload).digest('binary');
    const signature = Buffer.from(`${webhookId}:${hash}`).toString('base 64');
    return signature;
    }​
    ```
    ```
    export function generateSignature(webhookId: string, payload: string, secretKey: string): string {
    const hash = crypto.createHmac('sha256', secretKey).update(payload).digest('hex');
    const signature = Buffer.from(`${webhookId}:${hash}`).toString('base 64');
    return signature;
    }​
    ```

    This is what ChatGPT generated

    ```
    function generateSignature(webhookId: string, payload: string, secretKey: string): string {
    const hash = crypto.createHmac('sha256', secretKey).update(payload).digest();
    const signature = Buffer.from(`${webhookId}:${hash.toString('base64' )}`).toString('base64');
    return signature;
    }

    ​```
  • yuri
    Member
    • Mar 2014
    • 8443

    #2
    Here's where the signature header is generated: https://github.com/espocrm/espocrm/b...ender.php#L128

    To make sure that original values are the same you check against.
    If you find EspoCRM good, we would greatly appreciate if you could give the project a star on GitHub. We believe our work truly deserves more recognition. Thanks.

    Comment

    • wlinna
      Junior Member
      • Sep 2024
      • 2

      #3
      Thank you very much for a quick reply. I figured the solution on my own though (a good night's sleep does wonders!)

      The key is to keep the hash as binary and do the concatenation with buffers directly.
      Here is the code that works if anyone else stumbles on this

      Code:
      export function generateSignature(webhookId: string, payload: string, secretKey: string): string {
          const hash = crypto.createHmac('sha256', secretKey).update(payload).digest();
          const signature = Buffer.concat([
              Buffer.from(webhookId + ':'),
              hash
          ]).toString('base64');
          return signature;
      }
      ​

      Comment

      • yuri
        Member
        • Mar 2014
        • 8443

        #4
        Fixed in v9.0.

        The X-Signature header will still be available until v11.0. The new Signature header is constructed without binary parts.

        Code:
        $signature = base64_encode($webhookId . ':' . hash_hmac('sha256', $payload, $secretKey));
        If you find EspoCRM good, we would greatly appreciate if you could give the project a star on GitHub. We believe our work truly deserves more recognition. Thanks.

        Comment

        Working...