Webhook Delivery Verification

Introduction

Each organization webhook has a unique secret. MedChat uses this base 64 encoded string to compute HMAC signatures on webhook payloads. Clients should use this signature to authenticate the request and validate that its contents have not been tampered with.

Computing Request Signatures

MedChat HMAC signatures incorporate four request properties in the signed payload.

  1. Request method
* Upper case
* Example: POST
  1. Request relative path and query
* Case is maintained from webhook definition 
* Example: /webhook?foo=bar
  1. Request date
    • UNIX timestamp in seconds
    • Computed from the request’s Date header
    • Example: 1605888000
  2. Request body
* MD5 hashed
* Example: N3qBVCHS42o41ff4Q/FIeg==

These four properties are concatenated, each on a new line using the /n character. The resulting string to sign should contain four lines.

❗️

The request body hash is NOT followed by a newline character.

POST
/webhook?foo=bar
1605888000
N3qBVCHS42o41ff4Q/FIeg==

Once the string to sign is complete, the webhook's secret is used as the SHA256 key to generate the final signature. Every webhook delivery will include an x-medchat-signature-sha256 header with the computed hash.

Example Implementation

The following is a C# implementation of the signature computation routine:

private static string ComputeSignature(string requestMethod, string requestPathAndQuery, 
  DateTimeOffset requestDate, string requestBody, string hmacKey)
{
    var stringToSign = new StringBuilder();
    stringToSign.Append($"{requestMethod.ToUpper()}\n"); // e.g. POST\n
    stringToSign.Append($"{requestPathAndQuery}\n"); // e.g. /webhook?foo=bar\n
    stringToSign.Append($"{requestDate.ToUnixTimeSeconds()}\n"); // e.g. 1605888000\n

    var requestBodyBytes = Encoding.UTF8.GetBytes(requestBody);

    using (var md5 = MD5.Create())
    {
        var md5Bytes = md5.ComputeHash(requestBodyBytes);
        var md5Hash = Convert.ToBase64String(md5Bytes);
        stringToSign.Append($"{md5Hash}"); // e.g. N3qBVCHS42o41ff4Q/FIeg==
    }

    var hmacKeyBytes = Encoding.UTF8.GetBytes(hmacKey);
    using (var hmacSha256 = new HMACSHA256(hmacKeyBytes))
    {
        var stringToSignBytes = Encoding.UTF8.GetBytes(stringToSign.ToString());
        var signatureBytes = hmacSha256.ComputeHash(stringToSignBytes);
        var signatureHash = Convert.ToBase64String(signatureBytes);
        return signatureHash; // e.g. JLfji1ARdL/lXs7npq+DnpiPXfBRXUfUu6+CPpPEPoM=
    }
}

Given the following inputs:

requestMethod: POST
requestPathAndQuery: /webhook?foo=bar
requestDate: Fri, 20 Nov 2020 16:00:00 GMT
requestBody: {"Type":"ChatArchived","Timestamp":"2020-11-20T16:00:00.0000000Z","OrgId":"39da3946-82e5-5612-0958-cbc25f0e076d","ChatId":"ce2c7c16-0f35-42a9-a7d3-8fca82ecd6c9"}
hmacKey: ogMmn6cb5vXh0P9IdptVNtceLcw=

The resulting MD5 hash of the request body is:

N3qBVCHS42o41ff4Q/FIeg==

The final string to sign is:

POST\n/webhook?foo=bar\n1605888000\nN3qBVCHS42o41ff4Q/FIeg==

The computed signature is:

JLfji1ARdL/lXs7npq+DnpiPXfBRXUfUu6+CPpPEPoM=