{"record":{"id":"9c280f0e6fb0ee3e","repo":"sickn33/agentic-awesome-skills","slug":"invalid-webhook-signature","errorCode":null,"errorMessage":"Invalid webhook signature","messagePattern":"Invalid webhook signature","errorType":"http","errorClass":null,"httpStatus":401,"severity":"critical","filePath":"skills/whatsapp-cloud-api/assets/boilerplate/nodejs/src/webhook-handler.ts","lineNumber":52,"sourceCode":"\n    const expectedSignature =\n      'sha256=' +\n      crypto.createHmac('sha256', appSecret).update(rawBody).digest('hex');\n\n    if (!SIGNATURE_RE.test(signature)) {\n      console.warn('Invalid webhook signature format');\n      res.sendStatus(401);\n      return;\n    }\n\n    const signatureBuffer = Buffer.from(signature, 'utf8');\n    const expectedSignatureBuffer = Buffer.from(expectedSignature, 'utf8');\n    const isValid =\n      signatureBuffer.length === expectedSignatureBuffer.length &&\n      crypto.timingSafeEqual(signatureBuffer, expectedSignatureBuffer);\n\n    if (!isValid) {\n      console.warn('Invalid webhook signature');\n      res.sendStatus(401);\n      return;\n    }\n\n    next();\n  };\n}\n\n/**\n * Middleware para capturar o raw body antes do JSON parse.\n * Necessario para validacao HMAC.\n */\nexport function rawBodyMiddleware(req: Request, _res: Response, buf: Buffer): void {\n  (req as any).rawBody = buf;\n}\n\n/**\n * Handler de verificacao do webhook (GET).","sourceCodeStart":34,"sourceCodeEnd":70,"githubUrl":"https://github.com/sickn33/agentic-awesome-skills/blob/58d857988fcfac6986206bca2b2fe223aa437e4b/skills/whatsapp-cloud-api/assets/boilerplate/nodejs/src/webhook-handler.ts#L34-L70","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before — body parser consumes the stream, rawBody is undefined\napp.use(express.json());\n\n// after — capture raw bytes for HMAC verification\napp.use(express.json({\n  verify: (req, res, buf) => { (req as any).rawBody = buf; }\n}));","handlingStrategy":"validation","validationCode":"app.use(express.json({ verify: (req, _res, buf) => { (req as any).rawBody = buf; } }));\nif (!Buffer.isBuffer((req as any).rawBody)) throw new Error('raw body missing; HMAC cannot be verified');","typeGuard":"function hasRawBody(req: Request): req is Request & { rawBody: Buffer } {\n  return Buffer.isBuffer((req as any).rawBody);\n}","tryCatchPattern":null,"preventionTips":["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"],"tags":["webhook","whatsapp","express","hmac","timing-safe-equal","secret-mismatch","raw-body"],"backgroundTag":"webhook-signature-verification-failed","analyzedSha":"58d857988fcfac6986206bca2b2fe223aa437e4b","analyzedAt":"2026-08-26T11:55:59.350Z","schemaVersion":2},"datasetVersion":"2026-08-26T14:46:13.012Z"}