Skip to content

Signature Verification

Outbound webhooks delivered by HookBridge include a signature you can verify before processing payloads.

HookBridge includes:

  • X-Webhook-Signature
  • X-Webhook-Timestamp

Signature format:

X-Webhook-Signature: sha256=<hex>

The signature is computed over:

timestamp + "." + raw_request_body

Each outbound endpoint has its own signing secret.

Terminal window
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:

Terminal window
curl -X POST https://api.hookbridge.io/v1/endpoints/YOUR_ENDPOINT_ID/rotate-secret \
-H "Authorization: Bearer YOUR_API_KEY"
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)
);
}
import hashlib
import 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?
end
function 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);
}
  • 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.