GoZap Analytics

API v1

Webhooks

Lo que te avisamos sin que preguntes, para que no tengas que sondear el padrón. El evento que los justifica no es la comodidad: es la baja.

Si una persona revoca su consentimiento con nosotros y tú no te enteras, sigue en tu base sin querer estarlo. Eso no es un retraso: es un incumplimiento, tuyo y nuestro. Si vas a escuchar un solo evento, que sea fan.baja.

Eventos

EventoCuándoQué hacer
fan.alta
nombre y correo
Nació una credencial nueva. Da de alta a la persona en tu base, si la necesitas ahí.
fan.baja
nombre y correo
Se dio de baja, su correo rebotó o marcó el envío como spam. Deja de contactarla. Es el evento que justifica todo esto: si no te enteras, esa persona sigue en tu base sin querer estarlo.
puntos.cambio El saldo de puntos de un fan cambió, venga de donde venga. Refresca el saldo que muestres en tu terminal.
codigo.asignado Un código de tu inventario quedó en manos de una persona. Descuéntalo de lo que consideras disponible.

Los marcados con nombre y correo son una transferencia de datos personales: el dueño del programa tiene que nombrarte en su aviso de privacidad antes de activarlos.

Qué llega

Entrega
POST https://tu-backend/hooks/gz
X-GZ-Signature: t=1787423400,v1=5d0e04155e4b77636d87f5e06aa7df1d…
X-GZ-Evento: fan.baja
X-GZ-Entrega: 4821

{
  "evento": "fan.baja",
  "ocurrio": "2026-08-21T18:30:00.000Z",
  "datos": {
    "correo": "fan@ejemplo.mx",
    "motivo": "baja"
  }
}

X-GZ-Entrega es el id de la entrega, y el mismo evento reintentado repite ese id. Úsalo para no procesar dos veces.

Verificar la firma

HMAC-SHA256 sobre {t}.{cuerpo_crudo} con tu secreto (whsec_…), comparado contra v1.

El timestamp va dentro de lo firmado a propósito. Si solo firmáramos el cuerpo, quien capture una entrega podría reproducirla mañana con la misma firma válida. Rechaza lo que traiga un t viejo — cinco minutos es un margen razonable.

Usa el cuerpo tal como llegó, sin deserializar y volver a serializar: un espacio de diferencia cambia el HMAC.

Node · Express
const crypto = require('crypto');

app.post('/hooks/gz', express.raw({ type: 'application/json' }), (req, res) => {
  const [, t, v1] = /t=(\d+),v1=([0-9a-f]+)/.exec(req.get('X-GZ-Signature')) || [];
  // El cuerpo CRUDO, sin re-serializar: un espacio cambia el HMAC.
  const esperado = crypto.createHmac('sha256', process.env.GZ_WEBHOOK_SECRET)
    .update(`${t}.${req.body}`).digest('hex');

  if (!v1 || !crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(esperado))) return res.sendStatus(401);
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return res.sendStatus(401); // anti-replay

  const evento = JSON.parse(req.body);
  // Responde rápido y procesa aparte: cualquier 2xx nos basta.
  res.sendStatus(200);
  encolar(evento, req.get('X-GZ-Entrega'));
});
C# · ASP.NET
[HttpPost("/hooks/gz")]
public async Task<IActionResult> Recibir()
{
    using var sr = new StreamReader(Request.Body);
    var cuerpo = await sr.ReadToEndAsync();          // crudo, sin deserializar

    var m = Regex.Match(Request.Headers["X-GZ-Signature"], @"t=(\d+),v1=([0-9a-f]+)");
    if (!m.Success) return Unauthorized();

    using var h = new HMACSHA256(Encoding.UTF8.GetBytes(_secreto));
    var esperado = Convert.ToHexString(
        h.ComputeHash(Encoding.UTF8.GetBytes($"{m.Groups[1].Value}.{cuerpo}"))).ToLower();

    if (!CryptographicOperations.FixedTimeEquals(
            Encoding.UTF8.GetBytes(esperado), Encoding.UTF8.GetBytes(m.Groups[2].Value)))
        return Unauthorized();

    var edad = Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - long.Parse(m.Groups[1].Value));
    if (edad > 300) return Unauthorized();           // anti-replay

    Encolar(cuerpo, Request.Headers["X-GZ-Entrega"]);
    return Ok();
}

Entrega y reintentos

  • Encolamos y entregamos desde un proceso aparte: tu latencia nunca frena a un invitado del portal. Puedes tardar sin romper nada de nuestro lado.
  • Cualquier 2xx cuenta como entregado. No leemos tu respuesta.
  • Hasta 6 intentos con espera creciente — 1 min, 5 min, 30 min, 2 h y 6 h: unas nueve horas en total.
  • Después queda marcado como fallido a la vista del dueño del programa, que puede reencolarlo con un botón cuando arregles tu endpoint. Nada se pierde en silencio.
  • Solo HTTPS. La firma protege de manipulación, no de lectura, y el cuerpo lleva correos.

Cómo se activa

Lo configura el dueño del programa en su panel: tu URL, los eventos que quieres, y el secreto —que se muestra una sola vez—. Pídeselo con esos tres datos a la mano.