waiaconnect

Documentación / Esencial

Verificar la firma

X-Connect-Signature-256: sha256=HMACSHA256(secret, "<X-Connect-Timestamp>.<cuerpo crudo>"). Calculá el HMAC sobre el timestamp + "." + los bytes crudos del request (re-serializar el JSON cambia el hash) y comparalo en tiempo constante. El timestamp va DENTRO del HMAC → un payload capturado no se puede reenviar; rechazá entregas de más de 5 minutos. El secret (whsec) se muestra UNA vez al crear el endpoint.

# Verify X-Connect-Signature-256 on the server that RECEIVES the webhook.
# The header is: sha256=HMAC_SHA256(secret, "<X-Connect-Timestamp>.<raw body>")
# Recompute it over the timestamp + "." + the EXACT raw request body and compare.
# (Shell alone can't compare in constant time — use one of the snippets below.)
echo -n "${TIMESTAMP}.${RAW_BODY}" | openssl dgst -sha256 -hmac "whsec_YOUR_ENDPOINT_SECRET"
import crypto from "crypto";
const SECRET = "whsec_YOUR_ENDPOINT_SECRET";

function verify(req) {
  const ts = req.header("X-Connect-Timestamp");
  const sig = req.header("X-Connect-Signature-256") || "";
  // Reject anything older than 5 minutes (anti-replay).
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
  const expected = "sha256=" + crypto
    .createHmac("sha256", SECRET)
    .update(ts + "." + req.rawBody)   // req.rawBody = the EXACT bytes received
    .digest("hex");
  const a = Buffer.from(sig), b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
<?php
$SECRET = "whsec_YOUR_ENDPOINT_SECRET";

function verify(string $rawBody, array $headers): bool {
  $ts  = $headers["X-Connect-Timestamp"] ?? "";
  $sig = $headers["X-Connect-Signature-256"] ?? "";
  if (abs(time() - (int)$ts) > 300) return false;        // anti-replay: 5 min
  $expected = "sha256=" . hash_hmac("sha256", $ts . "." . $rawBody, $GLOBALS["SECRET"]);
  return hash_equals($expected, $sig);                    // constant-time compare
}
import hmac, hashlib, time

SECRET = b"whsec_YOUR_ENDPOINT_SECRET"

def verify(raw_body: bytes, headers) -> bool:
    ts  = headers.get("X-Connect-Timestamp", "")
    sig = headers.get("X-Connect-Signature-256", "")
    if abs(time.time() - int(ts)) > 300:      # anti-replay: 5 min
        return False
    mac = hmac.new(SECRET, (ts + ".").encode() + raw_body, hashlib.sha256)
    expected = "sha256=" + mac.hexdigest()
    return hmac.compare_digest(expected, sig)  # constant-time compare

Es el punto donde más gente se traba y donde muchos terminan no validando nada (un agujero de seguridad). Este código está completo y correcto en los 4 lenguajes.

El secret real sale de tu endpoint en Webhooks (se muestra una sola vez; podés rotarlo).