How it works
- DroneBundle computes an HMAC-SHA256 hash of the raw JSON request body using your webhook’s secret
- The hash is sent in the
x-dronebundle-signatureheader in the formatsha256=<hex_digest> - Your server computes the same hash and compares the two values
Always use the raw request body for verification, not a parsed-and-re-serialized version. Re-serializing JSON can change key order or whitespace, which will produce a different hash.
Code examples
import { createHmac, timingSafeEqual } from 'node:crypto';
function verifyWebhookSignature(body, secret, signatureHeader) {
const expected = createHmac('sha256', secret)
.update(body)
.digest('hex');
const received = signatureHeader.replace('sha256=', '');
return timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(received, 'hex')
);
}
import hmac
import hashlib
def verify_webhook_signature(body: bytes, secret: str, signature_header: str) -> bool:
expected = hmac.new(
key=secret.encode(),
msg=body,
digestmod=hashlib.sha256
).hexdigest()
received = signature_header.replace("sha256=", "")
return hmac.compare_digest(expected, received)
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public static boolean verifyWebhookSignature(String body, String secret, String signatureHeader) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA256"));
byte[] hash = mac.doFinal(body.getBytes("UTF-8"));
StringBuilder expected = new StringBuilder();
for (byte b : hash) {
expected.append(String.format("%02x", b));
}
String received = signatureHeader.replace("sha256=", "");
return expected.toString().equals(received);
}
using System.Security.Cryptography;
using System.Text;
public static bool VerifyWebhookSignature(string body, string secret, string signatureHeader)
{
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
byte[] hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(body));
string expected = BitConverter.ToString(hash).Replace("-", "").ToLower();
string received = signatureHeader.Replace("sha256=", "");
return expected == received;
}
function verifyWebhookSignature(string $body, string $secret, string $signatureHeader): bool {
$expected = hash_hmac('sha256', $body, $secret);
$received = str_replace('sha256=', '', $signatureHeader);
return hash_equals($expected, $received);
}
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"strings"
)
func verifyWebhookSignature(body []byte, secret string, signatureHeader string) bool {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(body)
expected := hex.EncodeToString(mac.Sum(nil))
received := strings.TrimPrefix(signatureHeader, "sha256=")
return hmac.Equal([]byte(expected), []byte(received))
}
use hmac::{Hmac, Mac};
use sha2::Sha256;
type HmacSha256 = Hmac<Sha256>;
fn verify_webhook_signature(body: &[u8], secret: &str, signature_header: &str) -> bool {
let mut mac = HmacSha256::new_from_slice(secret.as_bytes())
.expect("HMAC can take key of any size");
mac.update(body);
let expected = hex::encode(mac.finalize().into_bytes());
let received = signature_header
.strip_prefix("sha256=")
.unwrap_or(signature_header);
expected == received
}