SocialHook
Docs/Seguridad y firma

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 '', 200

Verificació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.