Verify WhatsApp Webhook Signatures
WhatsApp Cloud API webhooks — like all Meta webhooks — are signed with HMAC-SHA256 over the raw body using your app secret, sent as sha256=… in X-Hub-Signature-256. Paste the raw body, the app secret and the header value.
Everything runs in your browser — the payload and secret never leave this page. Want to verify a different provider? See the webhook signature verifier hub or the generic HMAC generator.
How WhatsApp signs webhooks
- Take the raw request body exactly as delivered — before any JSON parsing or re-serialising.
- Compute HMAC-SHA256 with your Meta app secret (App Dashboard → App settings → Basic) as the key, hex-encoded.
- Prefix it with
sha256=and compare toX-Hub-Signature-256with a constant-time check. Reject the request on mismatch.
Verify WhatsApp signatures in code
const crypto = require('crypto');
// express.raw() keeps the exact bytes Meta signed
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const expected = 'sha256=' + crypto
.createHmac('sha256', process.env.META_APP_SECRET)
.update(req.body) // Buffer, not a re-stringified object
.digest('hex');
const received = req.get('x-hub-signature-256') || '';
const ok = received.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received));
if (!ok) return res.sendStatus(401);
res.sendStatus(200); // ack fast, process async
handle(JSON.parse(req.body));
});import hmac, hashlib, os
from flask import request, abort
@app.post("/webhook")
def whatsapp_webhook():
raw = request.get_data() # raw bytes, before request.json
expected = "sha256=" + hmac.new(
os.environ["META_APP_SECRET"].encode(), raw, hashlib.sha256
).hexdigest()
received = request.headers.get("X-Hub-Signature-256", "")
if not hmac.compare_digest(expected, received):
abort(401)
return "", 200Frequently asked questions
Which secret signs WhatsApp webhooks?
Your Meta app secret, from App Dashboard → App settings → Basic. Not the verify token (that is only checked in the GET handshake) and not the WhatsApp access token.
Why does my computed signature never match?
Almost always because the body was parsed and re-serialised before hashing — whitespace, key order or character escaping change and the bytes no longer match. Hash the raw body (express.raw(), request.get_data()). The second cause is using the wrong app's secret when several Meta apps share one endpoint.
Is the GET verification request signed?
No. The hub.mode / hub.verify_token / hub.challenge handshake carries no signature — you authenticate it by comparing the verify token. Only event POSTs carry X-Hub-Signature-256.
Do Messenger and Instagram webhooks use the same scheme?
Yes. All Meta Graph API webhooks — WhatsApp, Messenger, Instagram, Pages — sign with X-Hub-Signature-256 using the app secret, so this verifier works for all of them.
Verify other providers
Receiving WhatsApp webhooks on a server behind a firewall or on localhost? Webhook Relay can forward them to your internal service and even verify or transform them before delivery.
