Event Signature Validation

How to validate the `X-Honeybee-Signature` header with common language examples.

All the examples here can be found on here on Github.

Ruby

require 'cgi'
require 'base64'
require 'openssl'
require 'json'

request = {
    url: "https://webhook.site/bafb9675-e60a-4db3-ab3c-377061a12ed5",
    method: "POST",
    body: {
        "event_id": "5d62dca6-1ce3-4807-afc6-8cfaa47577c5",
        "event_type": "RX_RECEIVED",
        "patient_id": "v3y4od",
        "medication_requests": [
            {
                "id": 82,
                "prescription_id": 82,
                "active": true,
                "patient_id": "v3y4od",
                "prescriber_name": "Dr. Jane Foster",
                "receive_date": "2023-05-10T16:16:58.469+00:00",
                "drug_name": "ATORVASTATIN 10MG TABLET",
                "ndc": "5976201551",
                "sig_text": "Take 1 tablet daily",
                "written_qty": 90,
                "days_supply": nil,
                "refills_left": 2,
                "expire_date": "2023-06-12T14:15:22Z",
                "drug_schedule": 4
            }
        ]
    }
}

CLIENT_SECRET = "[CLIENT_SECRET_HERE]"
HASHED_CLIENT_SECRET = Digest::SHA256.hexdigest(CLIENT_SECRET)

base = CGI.escape(request[:method] + request[:url] + (request[:body].to_json))
digest = Base64.encode64("#{OpenSSL::HMAC.digest('sha1', HASHED_CLIENT_SECRET, base)}\n")

puts digest

Python

"""Example illustrating how to validate webhook event signature in Python"""
import hashlib
import hmac
import json
from base64 import b64encode
import urllib.parse

request = {
    "url": "https://webhook.site/bafb9675-e60a-4db3-ab3c-377061a12ed5",
    "method": "POST",
    "body": {
        "event_id": "5d62dca6-1ce3-4807-afc6-8cfaa47577c5",
        "event_type": "RX_RECEIVED",
        "patient_id": "v3y4od",
        "medication_requests": [
            {
                "id": 82,
                "prescription_id": 82,
                "active": True,
                "patient_id": "v3y4od",
                "prescriber_name": "Dr. Jane Foster",
                "receive_date": "2023-05-10T16:16:58.469+00:00",
                "drug_name": "ATORVASTATIN 10MG TABLET",
                "ndc": "5976201551",
                "sig_text": "Take 1 tablet daily",
                "written_qty": 90,
                "days_supply": None,
                "refills_left": 2,
                "expire_date": "2023-06-12T14:15:22Z",
                "drug_schedule": 4,
            }
        ],
    },
}

CLIENT_SECRET = "[CLIENT_SECRET_HERE]"
HASHED_CLIENT_SECRET = hashlib.sha256(CLIENT_SECRET.encode("utf-8")).hexdigest()
request_str = json.dumps(request["body"], separators=(",", ":"))

base = urllib.parse.quote_plus(
    request["method"] + request["url"] + request_str, safe=""
)
digest = hmac.digest(
    HASHED_CLIENT_SECRET.encode("utf-8"), base.encode("utf-8"), hashlib.sha1
)

print(b64encode(digest + b"\n").decode())

Node.js

const crypto = require('crypto');
const querystring = require('querystring');

const request = {
  url: "https://webhook.site/bafb9675-e60a-4db3-ab3c-377061a12ed5",
  method: "POST",
  body: {
    "event_id": "5d62dca6-1ce3-4807-afc6-8cfaa47577c5",
    "event_type": "RX_RECEIVED",
    "patient_id": "v3y4od",
    "medication_requests": [
      {
        "id": 82,
        "prescription_id": 82,
        "active": true,
        "patient_id": "v3y4od",
        "prescriber_name": "Dr. Jane Foster",
        "receive_date": "2023-05-10T16:16:58.469+00:00",
        "drug_name": "ATORVASTATIN 10MG TABLET",
        "ndc": "5976201551",
        "sig_text": "Take 1 tablet daily",
        "written_qty": 90,
        "days_supply": null,
        "refills_left": 2,
        "expire_date": "2023-06-12T14:15:22Z",
        "drug_schedule": 4,
      }
    ],
  },
};

const CLIENT_SECRET = "[CLIENT_SECRET_HERE]";
const HASHED_CLIENT_SECRET = crypto.createHash('sha256').update(CLIENT_SECRET).digest('hex');
const requestStr = JSON.stringify(request.body);

const base = querystring.escape(request.method + request.url + requestStr).replace(/%20/g, '+');
const hmac = crypto.createHmac('sha1', HASHED_CLIENT_SECRET);
hmac.update(base);
const digest = Buffer.from(hmac.digest('binary') + '\n', 'binary').toString('base64')

console.log(digest);

Go

package main

import (
	"crypto/hmac"
	"crypto/sha1"
	"crypto/sha256"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"net/url"
)

type RawRequest struct {
	Url    string
	Method string
	Body   string
}

type RequestBody struct {
	EventID            string              `json:"event_id"`
	EventType          string              `json:"event_type"`
	PatientID          string              `json:"patient_id"`
	MedicationRequests []MedicationRequest `json:"medication_requests"`
}

type MedicationRequest struct {
	ID             int         `json:"id"`
	PrescriptionID int         `json:"prescription_id"`
	Active         bool        `json:"active"`
	PatientID      string      `json:"patient_id"`
	PrescriberName string      `json:"prescriber_name"`
	ReceiveDate    string      `json:"receive_date"`
	DrugName       string      `json:"drug_name"`
	NDC            string      `json:"ndc"`
	SigText        string      `json:"sig_text"`
	WrittenQty     int         `json:"written_qty"`
	DaysSupply     interface{} `json:"days_supply"`
	RefillsLeft    int         `json:"refills_left"`
	ExpireDate     string      `json:"expire_date"`
	DrugSchedule   int         `json:"drug_schedule"`
}

func main() {
	request := RawRequest{
		Url:    "https://webhook.site/bafb9675-e60a-4db3-ab3c-377061a12ed5",
		Method: "POST",
		Body: `{
			"event_id": "5d62dca6-1ce3-4807-afc6-8cfaa47577c5",
			"event_type": "RX_RECEIVED",
			"patient_id": "v3y4od",
			"medication_requests": [
				{
					"id": 82,
					"prescription_id": 82,
					"active": true,
					"patient_id": "v3y4od",
					"prescriber_name": "Dr. Jane Foster",
					"receive_date": "2023-05-10T16:16:58.469+00:00",
					"drug_name": "ATORVASTATIN 10MG TABLET",
					"ndc": "5976201551",
					"sig_text": "Take 1 tablet daily",
					"written_qty": 90,
					"days_supply": null,
					"refills_left": 2,
					"expire_date": "2023-06-12T14:15:22Z",
					"drug_schedule": 4
				}
			]
		}`,
	}

	clientSecret := "[CLIENT_SECRET_HERE]"
	hashedClientSecret := sha256.Sum256([]byte(clientSecret))
	hashedClientSecretStr := hex.EncodeToString(hashedClientSecret[:])

	flattenedBody, err := flattenJson(request.Body)
	if err != nil {
		panic(err)
	}

	base := url.QueryEscape(request.Method + request.Url + flattenedBody)
	hash := hmac.New(sha1.New, []byte(hashedClientSecretStr))
	hash.Write([]byte(base))
	digest := hash.Sum(nil)

	fmt.Println(base64.StdEncoding.EncodeToString(append(digest, '\n')))
}

func flattenJson(jsonStr string) (string, error) {
	var data RequestBody
	if err := json.Unmarshal([]byte(jsonStr), &data); err != nil {
		return "", err
	}

	body, err := json.Marshal(data)
	if err != nil {
		return "", err
	}

	return string(body), nil
}

Java

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.InvalidKeyException;
import java.util.Base64;
import java.util.Map;

public class Main {
    public static void main(String[] args)
            throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeyException,
            ArrayStoreException, NullPointerException {
        Map<String, String> request = Map.of(
                "url", "https://webhook.site/bafb9675-e60a-4db3-ab3c-377061a12ed5",
                "method", "POST",
                "body",
                "{\"event_id\":\"5d62dca6-1ce3-4807-afc6-8cfaa47577c5\",\"event_type\":\"RX_RECEIVED\",\"patient_id\":\"v3y4od\",\"medication_requests\":[{\"id\":82,\"prescription_id\":82,\"active\":true,\"patient_id\":\"v3y4od\",\"prescriber_name\":\"Dr. Jane Foster\",\"receive_date\":\"2023-05-10T16:16:58.469+00:00\",\"drug_name\":\"ATORVASTATIN 10MG TABLET\",\"ndc\":\"5976201551\",\"sig_text\":\"Take 1 tablet daily\",\"written_qty\":90,\"days_supply\":null,\"refills_left\":2,\"expire_date\":\"2023-06-12T14:15:22Z\",\"drug_schedule\":4}]}");

        String CLIENT_SECRET = "[CLIENT_SECRET_HERE]";

        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        byte[] hash = digest.digest(CLIENT_SECRET.getBytes(StandardCharsets.UTF_8));
        String HASHED_CLIENT_SECRET = bytesToHex(hash);

        String encodedUrl = URLEncoder.encode(request.get("url"), StandardCharsets.UTF_8.toString());
        String encodedMethod = URLEncoder.encode(request.get("method"), StandardCharsets.UTF_8.toString());
        String encodedBody = URLEncoder.encode(request.get("body"), StandardCharsets.UTF_8.toString());

        String base = encodedMethod + encodedUrl + encodedBody;

        Mac sha1_HMAC = Mac.getInstance("HmacSHA1");
        SecretKeySpec secret_key = new SecretKeySpec(HASHED_CLIENT_SECRET.getBytes(), "HmacSHA1");
        sha1_HMAC.init(secret_key);

        byte[] hashBase = sha1_HMAC.doFinal(base.getBytes());
        byte[] hashBaseWithNewline = new byte[hashBase.length + 1];
        System.arraycopy(hashBase, 0, hashBaseWithNewline, 0, hashBase.length);
        hashBaseWithNewline[hashBase.length] = '\n';

        String base64HashBase = Base64.getEncoder().encodeToString(hashBaseWithNewline);

        System.out.println(base64HashBase);
    }

    private static String bytesToHex(byte[] hash) {
        StringBuilder hexString = new StringBuilder(2 * hash.length);
        for (byte b : hash) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) {
                hexString.append('0');
            }
            hexString.append(hex);
        }
        return hexString.toString();
    }
}