sickn33/agentic-awesome-skills · critical
Invalid webhook signature
Error message
Invalid webhook signature
What it means
validateHMAC reached the final check: the header is well-formed, but after the length comparison and crypto.timingSafeEqual it does not equal the HMAC computed over (req as any).rawBody with the configured app secret. Either the secret differs from the one Meta used to sign, or the rawBody buffer being verified is not the exact byte sequence Meta signed.
Source
Thrown at skills/whatsapp-cloud-api/assets/boilerplate/nodejs/src/webhook-handler.ts:52
const expectedSignature =
'sha256=' +
crypto.createHmac('sha256', appSecret).update(rawBody).digest('hex');
if (!SIGNATURE_RE.test(signature)) {
console.warn('Invalid webhook signature format');
res.sendStatus(401);
return;
}
const signatureBuffer = Buffer.from(signature, 'utf8');
const expectedSignatureBuffer = Buffer.from(expectedSignature, 'utf8');
const isValid =
signatureBuffer.length === expectedSignatureBuffer.length &&
crypto.timingSafeEqual(signatureBuffer, expectedSignatureBuffer);
if (!isValid) {
console.warn('Invalid webhook signature');
res.sendStatus(401);
return;
}
next();
};
}
/**
* Middleware para capturar o raw body antes do JSON parse.
* Necessario para validacao HMAC.
*/
export function rawBodyMiddleware(req: Request, _res: Response, buf: Buffer): void {
(req as any).rawBody = buf;
}
/**
* Handler de verificacao do webhook (GET).View on GitHub (pinned to 58d857988f)
Solutions
- Confirm the process's app secret equals the Meta App Secret exactly
- Capture the raw body: app.use(express.json({ verify: (req, res, buf) => { (req as any).rawBody = buf; } })) and mount validateHMAC after it
- After any secret rotation, redeploy every instance so verification uses the new value
Example fix
// before — body parser consumes the stream, rawBody is undefined
app.use(express.json());
// after — capture raw bytes for HMAC verification
app.use(express.json({
verify: (req, res, buf) => { (req as any).rawBody = buf; }
})); Defensive patterns
Strategy: validation
Validate before calling
app.use(express.json({ verify: (req, _res, buf) => { (req as any).rawBody = buf; } }));
if (!Buffer.isBuffer((req as any).rawBody)) throw new Error('raw body missing; HMAC cannot be verified'); Type guard
function hasRawBody(req: Request): req is Request & { rawBody: Buffer } {
return Buffer.isBuffer((req as any).rawBody);
} Prevention
- Always configure express.json's verify callback to stash rawBody before validateHMAC runs
- Rotate the app secret in Meta and the server env in the same deployment window
When it happens
Trigger: App secret in the Node env differs from the Meta app's App Secret; (req as any).rawBody missing or reconstructed differently because express.json() consumed the stream without a verify callback; a proxy rewriting the body (recompression, re-serialization); signature computed over parsed JSON rather than the raw bytes.
Common situations: App secret rotated in the Meta console but not redeployed; express.json() mounted without capturing rawBody so the field is undefined or stale; different secrets across environments; whitespace-pasted secrets.
Related errors
AI-assisted analysis of sickn33/agentic-awesome-skills@58d857988f (2026-08-26).
Data as JSON: /api/errors/9c280f0e6fb0ee3e.
Report an issue: GitHub.