← All guides

Verify Meta’s X-Hub-Signature-256 in Node.js (correctly)

2026-10-09

Every webhook Meta sends carries X-Hub-Signature-256: sha256=<hex> — an HMAC-SHA256 of the raw request body keyed with your app secret. Verify it or anyone who finds your URL can inject fake leads.

The code

import { createHmac, timingSafeEqual } from 'node:crypto'; function verify(rawBody, header, appSecret) { if (!header?.startsWith('sha256=')) return false; const expected = createHmac('sha256', appSecret).update(rawBody).digest('hex'); const a = Buffer.from(header.slice(7), 'hex'); const b = Buffer.from(expected, 'hex'); return a.length === b.length && timingSafeEqual(a, b); }

The three classic mistakes

1. Parsed body. If you feed JSON.stringify(req.body) to the HMAC it will almost never match — key order, unicode escaping and whitespace differ. Keep the raw bytes (in Express: express.raw() or the verify hook; capture before any JSON middleware). 2. String comparison. === leaks timing; use timingSafeEqual after checking lengths. 3. Wrong secret. The HMAC key is the app secret (App Dashboard → Settings → Basic), not the verify token — the verify token is only for the one-time hub.challenge GET handshake.

Also worth knowing

Respond 200 before doing slow work; Meta deactivates endpoints that keep failing. And if you forward events onward, downstream tools verifying "Meta's" signature need the body re-signed — the original header is only valid for the original bytes.

Relayo verifies every Meta signature on the raw body, and re-signs each forwarded copy so your tools’ own verification keeps passing.

Get early access to Relayo