Verifying webhook signatures
How to validate the X-Funkel-Signature header so you trust what you receive.
Every webhook from Funkel carries an HMAC-SHA256 signature so your endpoint can prove the request came from us and wasn’t modified in flight. Verifying the signature is non-negotiable for anything that takes action on the payload.
Where the signature lives
- Header name:
X-LeadPilot-Signature - Format:
sha256=<hex> - What it signs: the raw HTTP request body, byte-for-byte
Find your signing secret
Open Settings → Webhooks, click your subscription, and copy the Signing secret. The secret is shown once when the webhook is first created, store it somewhere safe. Treat it like a password; rotate it if it ever leaks.
Verify in Node.js
import crypto from 'node:crypto';
export function verify(req, secret) {
const header = req.headers['x-leadpilot-signature'] || '';
const expected =
'sha256=' +
crypto.createHmac('sha256', secret)
.update(req.rawBody) // Buffer or string of the exact request body
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(header),
Buffer.from(expected),
);
}Verify in Go
func verify(body []byte, header, secret string) bool {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(body)
expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(header), []byte(expected))
}Verify in Python
import hmac, hashlib
def verify(body: bytes, header: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(
secret.encode(), body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(header, expected)Common mistakes
- Signing the parsed JSON, not the raw body. Reformatting changes whitespace and breaks the signature. Most frameworks parse the body before you can hash it, capture the raw bytes first.
- Using
===or==to compare. Always use a constant-time comparison (timingSafeEqual,hmac.Equal,compare_digest) to avoid leaking secret bits via timing. - Hard-coding “Funkel” into the header check. The header is currently
X-LeadPilot-Signature. Match on that name verbatim.
Test before you trust
On any webhook subscription, click Send test event in Settings → Webhooks. Funkel posts a synthetic payload signed with your real secret. Run your verifier against it; if it passes, you’re ready for live events.