Seguridad y firma de payloads
Cada payload de SocialHook se firma con HMAC-SHA256 usando tu clave secreta. Debes verificar esta firma en tu servidor antes de procesar cualquier mensaje — esto te protege contra payloads falsificados.
Cómo funciona la firma
Cuando SocialHook entrega un payload a tu URL de webhook, calcula un hash HMAC-SHA256 sobre el cuerpo crudo de la solicitud usando tu clave secreta. Este hash se envía en la cabecera HTTP X-SocialHook-Signature en el formato sha256=<hex_digest>.
En tu servidor, calcula el mismo hash usando la misma clave secreta y el mismo cuerpo crudo — y compara los dos valores. Si coinciden, el payload es auténtico.
Nunca omitas la verificación de firma. Sin ella, cualquiera que conozca tu URL de webhook puede enviar payloads falsos a tu servidor.
Verificación — Node.js
Node.js / Express
const crypto = require('crypto')
function verifySocialHookSignature(req, secret) {
const header = req.headers['x-socialhook-signature']
if (!header) return false
const hash = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(req.rawBody) // must be raw Buffer
.digest('hex')
// Use timingSafeEqual to prevent timing attacks
return crypto.timingSafeEqual(
Buffer.from(hash),
Buffer.from(header)
)
}
// In your Express middleware:
app.use(express.json({
verify: (req, _, buf) => { req.rawBody = buf }
}))
app.post('/webhook', (req, res) => {
if (!verifySocialHookSignature(req, process.env.SOCIALHOOK_SECRET)) {
return res.status(401).send('Invalid signature')
}
// Safe to process
res.sendStatus(200)
})Verificación — Python
Python / Flask
import hmac
import hashlib
from flask import Flask, request, abort
import os
app = Flask(__name__)
def verify_signature(payload_body, signature_header, secret):
if not signature_header:
return False
expected = 'sha256=' + hmac.new(
secret.encode(),
payload_body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature_header)
@app.route('/webhook', methods=['POST'])
def webhook():
sig = request.headers.get('X-SocialHook-Signature')
if not verify_signature(request.data, sig, os.environ['SOCIALHOOK_SECRET']):
abort(401)
# Safe to process request.json
return '', 200Verificación — PHP
PHP
<?php
function verifySocialHookSignature($rawBody, $signatureHeader, $secret): bool {
if (empty($signatureHeader)) return false;
$expected = 'sha256=' . hash_hmac('sha256', $rawBody, $secret);
return hash_equals($expected, $signatureHeader);
}
$rawBody = file_get_contents('php://input');
$sig = $_SERVER['HTTP_X_SOCIALHOOK_SIGNATURE'] ?? '';
if (!verifySocialHookSignature($rawBody, $sig, getenv('SOCIALHOOK_SECRET'))) {
http_response_code(401);
exit('Invalid signature');
}
$payload = json_decode($rawBody, true);
// Safe to process $payload
http_response_code(200);Buenas prácticas de seguridad
Usa siempre timingSafeEqual — una comparación simple de cadenas es vulnerable a ataques de temporización. Usa
crypto.timingSafeEqual() (Node), hmac.compare_digest() (Python) o hash_equals() (PHP).Firma el cuerpo crudo, no el JSON parseado — calcula el HMAC sobre los bytes del cuerpo crudo de la solicitud antes de cualquier parseo de JSON. Distintos serializadores de JSON pueden producir secuencias de bytes diferentes.
Guarda tu clave secreta como variable de entorno — nunca la incrustes en el código fuente ni la subas a un repositorio.
Devuelve 200 antes de procesar — SocialHook espera hasta 10 segundos por tu respuesta. Devuelve 200 inmediatamente y procesa el payload de forma asíncrona.
Usa solo HTTPS — SocialHook solo entrega a endpoints HTTPS. Los endpoints HTTP planos se rechazan al configurarlos.
Implementa idempotencia — En casos raros, SocialHook puede entregar el mismo evento dos veces. Guarda los valores de
delivery_id y descarta los duplicados.Rota tu clave secreta periódicamente — Genera una nueva clave secreta desde la configuración de tu panel y actualiza la variable de entorno de tu servidor.
¿Encontraste una vulnerabilidad de seguridad? Por favor repórtala de forma responsable a security@socialhook.io. No la divulgues públicamente antes de que tengamos la oportunidad de resolverla. Respondemos en menos de 72 horas.