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.
Avoiding "gotchas" for validating the webhook signature:
Our current implementation for generating a signature will encode a payload following the Common Gateway Interface (CGI) specification as defined in IETF RFC 3875. The encodeURIComponent function in javascript will encode a payload following the Uniform Resource Identifier (URI) syntax specification as defined in IETF RFC 3986.
Because of differences in encoding specifications, and because javascript clients do not retain decimal places on whole numbers, the X-Honeybee-Signature
validation methodology is no longer supported for javascript clients. For all other clients, this methodology will continue to be supported.
As an alternative, javascript clients may implement the X-Honeybee-Secret
validation methodology.
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();
}
}