Webhook Signature Verification
All webhook requests from Meta include an HMAC-SHA256 signature. CubeConnect verifies this signature automatically, but if you're building custom integrations, here's how it works.
How It Works
- Meta computes an HMAC-SHA256 hash of the request body using your app secret
- The hash is included in the
X-Hub-Signature-256header - The receiver recomputes the hash and compares it to verify authenticity
Header Format
http
X-Hub-Signature-256: sha256=a1b2c3d4e5f6...The header value starts with sha256= followed by the hex-encoded HMAC digest.
Verification Examples
php
function verifySignature(string $payload, string $signature, string $appSecret): bool
{
$expected = hash_hmac('sha256', $payload, $appSecret);
$received = str_replace('sha256=', '', $signature);
return hash_equals($expected, $received);
}
// Usage
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_HUB_SIGNATURE_256'] ?? '';
$appSecret = env('WHATSAPP_APP_SECRET');
if (!verifySignature($payload, $signature, $appSecret)) {
http_response_code(401);
exit('Invalid signature');
}javascript
const crypto = require('crypto')
function verifySignature(payload, signature, appSecret) {
const expected = crypto
.createHmac('sha256', appSecret)
.update(payload)
.digest('hex')
const received = signature.replace('sha256=', '')
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(received)
)
}
// Usage in Express
app.post('/webhook', (req, res) => {
const signature = req.headers['x-hub-signature-256'] || ''
const payload = JSON.stringify(req.body)
if (!verifySignature(payload, signature, process.env.WHATSAPP_APP_SECRET)) {
return res.status(401).send('Invalid signature')
}
// Process webhook...
res.sendStatus(200)
})python
import hmac
import hashlib
def verify_signature(payload: bytes, signature: str, app_secret: str) -> bool:
expected = hmac.new(
app_secret.encode(),
payload,
hashlib.sha256
).hexdigest()
received = signature.replace('sha256=', '')
return hmac.compare_digest(expected, received)
# Usage in Flask
@app.route('/webhook', methods=['POST'])
def webhook():
signature = request.headers.get('X-Hub-Signature-256', '')
payload = request.get_data()
if not verify_signature(payload, signature, os.environ['WHATSAPP_APP_SECRET']):
return 'Invalid signature', 401
# Process webhook...
return '', 200Important Notes
- Always use time-safe comparison (
hash_equalsin PHP,timingSafeEqualin Node.js,compare_digestin Python) to prevent timing attacks - Use the raw request body for signature computation, not a parsed/re-serialized version
- The app secret is configured per WhatsApp account and can be found in your Meta App settings
- CubeConnect handles signature verification automatically for webhooks sent to the platform