Outbound Signature Verification
Why You Would Use This
Section titled “Why You Would Use This”Signature verification lets your receiver confirm that a webhook request actually came from HookBridge and was not modified in transit.
Use it to:
- block spoofed webhook requests
- prevent payload tampering from being accepted
- enforce a timestamp window to reduce replay risk
How It Works
Section titled “How It Works”Each outbound delivery includes:
X-Webhook-SignatureX-Webhook-Timestamp
Signature format (single key):
X-Webhook-Signature: sha256=<hex>During signing key rotation, both keys produce signatures in the same header:
X-Webhook-Signature: sha256=<hex1>,sha256=<hex2>Your receiver should split on , and accept the request if any signature is valid.
Verification input:
timestamp + "." + raw_request_bodyImportant: use the raw request body bytes exactly as received.
Console Workflow
Section titled “Console Workflow”- Open Endpoints and select your outbound endpoint.
- Copy the endpoint signing secret.
- In your receiver service, verify
X-Webhook-Signaturefor every request. - Reject invalid signatures with
401. - If a secret is exposed, rotate it in endpoint settings and update your receiver.
API Workflow
Section titled “API Workflow”API reference:
1) Retrieve endpoint details
Section titled “1) Retrieve endpoint details”curl https://api.hookbridge.io/v1/endpoints/YOUR_ENDPOINT_ID \ -H "Authorization: Bearer YOUR_API_KEY"2) Rotate key when needed
Section titled “2) Rotate key when needed”See Signing Key Rotation for the full zero-downtime rotation workflow.
# Add new keycurl -X POST https://api.hookbridge.io/v1/endpoints/YOUR_ENDPOINT_ID/signing-keys \ -H "Authorization: Bearer YOUR_API_KEY"
# After updating your receiver, delete the old keycurl -X DELETE https://api.hookbridge.io/v1/endpoints/YOUR_ENDPOINT_ID/signing-keys/OLD_KEY_ID \ -H "Authorization: Bearer YOUR_API_KEY"Verification Examples
Section titled “Verification Examples”Use this to send a signed test request to your receiver endpoint and validate your verification logic.
TIMESTAMP=$(date +%s)PAYLOAD='{"event":"order.created","order_id":"ord_123"}'SIGNING_SECRET='YOUR_SIGNING_SECRET'
SIGNATURE_HEX=$(printf '%s.%s' "$TIMESTAMP" "$PAYLOAD" \ | openssl dgst -sha256 -hmac "$SIGNING_SECRET" -hex \ | sed 's/^.* //')
curl -X POST https://your-receiver.example/webhooks \ -H "Content-Type: application/json" \ -H "X-Webhook-Timestamp: $TIMESTAMP" \ -H "X-Webhook-Signature: sha256=$SIGNATURE_HEX" \ -d "$PAYLOAD"import crypto from 'node:crypto';
function verifyWebhookSignature(rawBody, signatureHeader, timestamp, secret) { 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 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")
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);}Receiver Checklist
Section titled “Receiver Checklist”- Read raw body bytes (not parsed/re-serialized JSON).
- Validate timestamp freshness (for example 5 minutes).
- Compare signatures with constant-time comparison.
- Return non-2xx for invalid signatures.
- Monitor signature failures for possible secret/config issues.
Personalize Examples
Enter your credentials to populate code examples throughout the docs.