Signature Verification
Outbound webhooks delivered by HookBridge include a signature you can verify before processing payloads.
Signature Headers
Section titled “Signature Headers”HookBridge includes:
X-Webhook-SignatureX-Webhook-Timestamp
Signature format:
X-Webhook-Signature: sha256=<hex>The signature is computed over:
timestamp + "." + raw_request_bodyGet Your Signing Secret
Section titled “Get Your Signing Secret”Each outbound endpoint has its own signing secret.
curl https://api.hookbridge.io/v1/endpoints/YOUR_ENDPOINT_ID \ -H "Authorization: Bearer YOUR_API_KEY"Store this secret securely. Rotate it if needed with:
curl -X POST https://api.hookbridge.io/v1/endpoints/YOUR_ENDPOINT_ID/rotate-secret \ -H "Authorization: Bearer YOUR_API_KEY"Verify the Signature
Section titled “Verify the Signature”Node.js
Section titled “Node.js”import crypto from 'node:crypto';
function verifyWebhookSignature(rawBody, signatureHeader, timestamp, secret) { if (!signatureHeader || !timestamp || !secret) return false;
const signedPayload = `${timestamp}.${rawBody}`; const expected = `sha256=${crypto .createHmac('sha256', secret) .update(signedPayload, 'utf8') .digest('hex')}`;
return crypto.timingSafeEqual( Buffer.from(signatureHeader), Buffer.from(expected) );}Python
Section titled “Python”import hashlibimport hmac
def verify_webhook_signature(raw_body, signature_header, timestamp, secret): signed_payload = f"{timestamp}.{raw_body}" expected = "sha256=" + hmac.new( secret.encode("utf-8"), signed_payload.encode("utf-8"), hashlib.sha256, ).hexdigest() return hmac.compare_digest(signature_header, expected)import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "fmt")
func verifyWebhookSignature(rawBody, signatureHeader, timestamp, secret string) bool { signedPayload := timestamp + "." + rawBody mac := hmac.New(sha256.New, []byte(secret)) mac.Write([]byte(signedPayload)) expected := "sha256=" + hex.EncodeToString(mac.Sum(nil)) return hmac.Equal([]byte(signatureHeader), []byte(expected))}require "openssl"
def verify_webhook_signature(raw_body, signature_header, timestamp, secret) signed_payload = "#{timestamp}.#{raw_body}" expected = "sha256=" + OpenSSL::HMAC.hexdigest("sha256", secret, signed_payload) return false unless signature_header.bytesize == expected.bytesize signature_header.bytes.zip(expected.bytes).reduce(0) { |acc, (a, b)| acc | (a ^ b) }.zero?endfunction verifyWebhookSignature( string $rawBody, string $signatureHeader, string $timestamp, string $secret): bool { $signedPayload = $timestamp . '.' . $rawBody; $expected = 'sha256=' . hash_hmac('sha256', $signedPayload, $secret); return hash_equals($expected, $signatureHeader);}Security Recommendations
Section titled “Security Recommendations”- Verify with the raw request body, not parsed JSON.
- Reject requests with missing signature or timestamp headers.
- Enforce a timestamp tolerance window to reduce replay risk.
- Use constant-time comparison.
- Rotate signing secrets during incident response or key hygiene cycles.
Personalize Examples
Enter your credentials to populate code examples throughout the docs.