Verify signatures
Each webhook request is signed with HMAC-SHA256 using your account's shared secret.
Check the signature on every request. It's the only way to know a request comes from Bigblue.
How the signature is made
X-Bigblue-Hmac-SHA256 = base64(HMAC-SHA256(raw_body, shared_secret))- Algorithm
- HMAC-SHA256
- Key
- Your shared secretOne 30-character secret per account, used for all webhooks. Find it in the Bigblue app.
- Message
- The raw request bodyThe exact bytes, before parsing.
- Encoding
- Standard Base64With padding. Not URL-safe Base64.
Use the raw body
Compute the HMAC on the body exactly as received. If you parse the JSON and
serialize it again, the bytes change and the signature won't match. In Express, use
express.raw() instead of express.json(). In Next.js, read await req.text()
before parsing.
Code
import crypto from 'node:crypto';
export function verify(rawBody: Buffer | string, header: string | null): boolean {
if (!header) return false;
const expected = crypto
.createHmac('sha256', process.env.BIGBLUE_WEBHOOK_SECRET!)
.update(rawBody)
.digest('base64');
const a = Buffer.from(header);
const b = Buffer.from(expected);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}Compare signatures with a constant-time function, as in these examples, not with
==.
The shared secret
- There is one secret per account, used for all webhooks.
- Copy it from the Bigblue app before you register your first webhook. The verification request is already signed with it.
- It's different from your API key. The API key authenticates your requests to Bigblue. The secret authenticates Bigblue's requests to you.
- When you rotate the secret, old signatures stop working immediately. Rotate at a quiet time. Requests that fail during the change are retried.
Store the secret in a secret manager.
Invalid signatures
Return 401. Bigblue retries the request, so real events that failed during a
secret rotation are delivered again. Log rejected requests with the event type and
time.