Verificación de Firma HMAC
Todos los webhooks incluyen una firma HMAC-SHA256 para verificar su autenticidad. Siempre verifica la firma antes de procesar un webhook.
Cada webhook incluye estos headers:
| Header | Descripción |
|---|
X-Webhook-Signature | Firma HMAC-SHA256 del payload |
X-Webhook-Event | Tipo de evento (ej: message.received) |
X-Webhook-Timestamp | Fecha/hora ISO 8601 del envío |
X-Webhook-ID | ID único del webhook |
Cómo Funciona
- Whaapy calcula un HMAC-SHA256 del payload JSON usando tu
secret
- El resultado se envía en el header
X-Webhook-Signature
- Tú calculas el mismo HMAC con el payload recibido
- Si coinciden, el webhook es auténtico
Nunca proceses webhooks sin verificar la firma. Un atacante podría enviar webhooks falsos a tu endpoint.
Verificación en Node.js
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
// En tu endpoint Express
app.post('/webhook', express.json(), (req, res) => {
const signature = req.headers['x-webhook-signature'];
const payload = req.body;
if (!verifyWebhookSignature(payload, signature, WEBHOOK_SECRET)) {
console.error('Invalid webhook signature');
return res.status(401).json({ error: 'Invalid signature' });
}
// Webhook verificado, procesar...
console.log('Webhook received:', req.body.event);
res.json({ received: true });
});
Usa crypto.timingSafeEqual() para comparar firmas. Esto previene ataques de timing.
Verificación en Python
import hmac
import hashlib
import json
from flask import Flask, request, jsonify
app = Flask(__name__)
WEBHOOK_SECRET = "tu_secret_aqui"
def verify_webhook_signature(payload: dict, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(),
json.dumps(payload, separators=(',', ':')).encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
@app.route('/webhook', methods=['POST'])
def webhook():
signature = request.headers.get('X-Webhook-Signature')
payload = request.get_json()
if not verify_webhook_signature(payload, signature, WEBHOOK_SECRET):
return jsonify({'error': 'Invalid signature'}), 401
# Webhook verificado, procesar...
print(f"Webhook received: {payload['event']}")
return jsonify({'received': True})
Verificación en PHP
<?php
$webhookSecret = getenv('WEBHOOK_SECRET');
function verifyWebhookSignature($payload, $signature, $secret) {
$expected = hash_hmac('sha256', json_encode($payload), $secret);
return hash_equals($expected, $signature);
}
// Recibir webhook
$payload = json_decode(file_get_contents('php://input'), true);
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
if (!verifyWebhookSignature($payload, $signature, $webhookSecret)) {
http_response_code(401);
echo json_encode(['error' => 'Invalid signature']);
exit;
}
// Webhook verificado, procesar...
echo json_encode(['received' => true]);
?>
Verificación en Go
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"net/http"
)
func verifyWebhookSignature(payload []byte, signature, secret string) bool {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(payload)
expected := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(expected), []byte(signature))
}
func webhookHandler(w http.ResponseWriter, r *http.Request) {
signature := r.Header.Get("X-Webhook-Signature")
var payload map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
payloadBytes, _ := json.Marshal(payload)
if !verifyWebhookSignature(payloadBytes, signature, webhookSecret) {
http.Error(w, "Invalid signature", http.StatusUnauthorized)
return
}
// Webhook verificado, procesar...
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"received": true}`))
}
Regenerar Secret
Si crees que tu secret fue comprometido, puedes regenerarlo:
curl -X POST https://api.whaapy.com/user-webhooks/<id>/regenerate-secret \
-H "Authorization: Bearer wha_TU_API_KEY"
Después de regenerar, actualiza tu código con el nuevo secret. Los webhooks con el secret anterior fallarán la verificación.
Próximos Pasos