🇪🇸 Español 🇬🇧 English 🇧🇷 Português

Verificar la firma HMAC de los webhooks salientes (lado receptor)

Cómo validar en tu propio servidor que un webhook saliente de Omnifox es auténtico, usando la firma HMAC-SHA256.

Jul 11, 2026

Cuando configurás un Webhook saliente en Omnifox (Configuración > Webhooks), cada evento (contact.created, message.delivered, conversation.assigned, etc.) se envía por HTTP POST a la URL que definiste. Para que tu servidor pueda confirmar que ese POST realmente vino de Omnifox y no de un tercero, cada entrega incluye una firma HMAC-SHA256.

Requisitos

  • Un Webhook creado en Configuración > Webhooks, con URL HTTPS (obligatorio).
  • El signing_secret (con prefijo whsec_) que Omnifox te muestra una sola vez al crear el webhook o al rotarlo (POST /api/v1/webhooks/{webhook}/rotate-secret). Guardalo de inmediato en un lugar seguro: no se puede volver a consultar.

Encabezados que recibís en cada entrega

Encabezado Contenido
X-Omnifox-Event Nombre del evento, ej. message.delivered
X-Omnifox-Delivery-Id ID único de esa entrega (útil para deduplicar reintentos)
X-Omnifox-Timestamp Timestamp Unix (segundos) en que se firmó el payload
X-Omnifox-Signature sha256=<hex> — la firma HMAC
X-Omnifox-Attempt Número de intento (1 = primer intento)

Cómo se calcula la firma

firma = HMAC_SHA256( "{timestamp}.{cuerpo_json_exacto}" , signing_secret )

El timestamp es el mismo valor del encabezado X-Omnifox-Timestamp, unido con un punto (.) al cuerpo JSON tal cual llegó (sin reformatear ni reordenar claves).

Ejemplo: verificación en Node.js

const crypto = require('crypto');

function verifyOmnifoxWebhook(rawBody, headers, signingSecret) {
  const signatureHeader = headers['x-omnifox-signature'] || '';
  const timestamp = headers['x-omnifox-timestamp'] || '';
  const [, providedHex] = signatureHeader.split('=');

  const expectedHex = crypto
    .createHmac('sha256', signingSecret)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex');

  const valid = providedHex &&
    crypto.timingSafeEqual(Buffer.from(expectedHex), Buffer.from(providedHex));

  return valid;
}

Usá siempre el cuerpo crudo (raw body, string sin parsear) para calcular la firma; si tu framework ya parseó el JSON, tenés que capturar el raw body antes.

Consejos

  • Rechazá (HTTP 401) cualquier entrega cuya firma no coincida, antes de procesar el payload.
  • Agregá una validación opcional de "replay": si X-Omnifox-Timestamp tiene más de unos minutos de antigüedad, rechazá la entrega.
  • Usá X-Omnifox-Delivery-Id para deduplicar: Omnifox reintenta entregas con backoff exponencial si tu endpoint responde con error o timeout, así que podés recibir el mismo evento más de una vez.

Solución de problemas

  • La firma nunca coincide: lo más común es comparar contra el JSON re-serializado por tu framework en vez del cuerpo crudo exacto que llegó.
  • No llegan los encabezados de firma: si el webhook se creó antes de rotar/generar signing_secret, algunas suscripciones antiguas quedan sin firmar (legado); rotá el secreto desde Configuración > Webhooks para activarla.
  • Perdiste el signing_secret: no hay forma de recuperarlo; rotalo con POST /api/v1/webhooks/{webhook}/rotate-secret y actualizá tu verificación con el nuevo valor.
¿Te fue útil?

Artículos relacionados