Event Source Validation

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

X-Honeybee-Secret Validation

For webhooks sent by Honeybee, the Honeybee Secret is a self-defined secret that can be added in the "Developers" section of the Partner Dashboard under API & Webhook Settings. The value added will be sent in the X-Honeybee-Secret header.

X-Honeybee-Signature Validation

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

For webhooks sent by Honeybee, the Honeybee Signature is a cryptographic signature generated from the SHA1 digest of a string containing the request method, request URL, and the request body, using the SHA256 hashed client secret of the event signing credentials created on your partner account.

For API responses from Honeybee, the Honeybee Signature is a cryptographic signature generated from the SHA1 digest of a string containing the response method, response URL, and the response body, using the SHA256 hashed client secret of the credentials associated with the access token used in the API request.

Below, you may find implementations for generating the X-Honeybee-Signature header that you can use to compare against received header values.

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 (NO LONGER SUPPORTED)

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();
    }
}