Saltar al contenido principal

Webhooks (Salientes)

Los webhooks permiten que SalesOS llame a tu sistema cuando algo ocurre (un lead es ofertado, un ranking se publica, un mensaje se despacha). Nos das una URL HTTPS y un secreto; hacemos POST de un payload JSON firmado cada vez que dispara un evento suscrito.
Esta página cubre webhooks salientes (SalesOS → tu endpoint). Para enviar datos hacia SalesOS, consulta Actividades y API Keys.

Configurar un webhook en el Dashboard

Ve a Integraciones → Webhooks → Nuevo Webhook y completa el formulario.
1

Básico

  • Nombre (obligatorio) — ej.: Notificar CRM.
  • Clave — un identificador estable (ej.: notify_crm).
  • Descripción — opcional.
2

Eventos (disparadores)

Elige una Categoría y selecciona los eventos que disparan este webhook — esta es tu suscripción (ver el catálogo). Déjalo vacío para un webhook que solo disparas manualmente (botón Probar) o desde un workflow.
3

Destino

  • MétodoPOST (por defecto), PUT o PATCH.
  • Timeout (segundos) — cuánto esperamos tu 2xx (por defecto 30).
  • URL (obligatorio) — tu endpoint. Debe ser https:// y público (hosts internos/loopback están bloqueados).
  • Autenticación — elige HMAC para firmar cada entrega y, en Configuración de Autenticación (JSON), indica tu secreto:
    { "secret": "whsec_tu_secreto" }
    
    (Otros esquemas disponibles: Bearer, API Key, Basic, OAuth2.)
4

Avanzado

  • Headers Personalizados (JSON) — headers extra enviados en cada entrega.
  • Plantilla del Payload (JSON con variables {{ }}) — modela el data del envelope a partir del contexto del evento, ej.: { "id": "{{event.id}}", "type": "{{event.type}}", "to": "{{event.to}}" }.
Los valores de la plantilla se interpolan en JSON. Usa campos planos y escalares. Inyectar un objeto anidado vía "{{event.data}}" puede no renderizar limpio hoy — revisa siempre el Preview / envía una Prueba antes.
5

Preview, probar y guardar

El formulario muestra un Preview en vivo (headers + cuerpo). Haz clic en Guardar webhook y usa Probar Webhook para enviar una entrega de ejemplo y confirmar que tu endpoint recibe — y verifica — el payload.

El envelope

En la ruta de evento, cada entrega es un único objeto JSON:
{
  "id": "evt_2KWPBgLlAfxdpx2AI54pPJ85f4W",
  "type": "ranking.weekly.published",
  "version": "1",
  "ts": "2026-06-03T12:00:00.000Z",
  "tenant": "9b1f0c2e-1a2b-4c3d-8e4f-5a6b7c8d9e0f",
  "data": { /* específico del evento — ver catálogo */ }
}
CampoDescripción
idId único del evento, estable entre reintentos → tu clave de idempotencia.
typeTipo de evento (ver catálogo).
versionVersión del schema de data. Evoluciona de forma aditiva.
tsCuándo ocurrió el evento (ISO 8601, UTC).
tenantTenant de origen (uuid).
dataPayload específico del evento.

Headers

HeaderDescripción
X-SalesOS-EventTipo de evento — enruta sin leer el cuerpo.
X-SalesOS-Event-IdIgual a id; estable entre reintentos (idempotencia).
X-SalesOS-Delivery-IdId del intento (cambia en cada reintento; para depurar).
X-SalesOS-TimestampUnix en segundos — parte de la firma (anti-replay).
X-SalesOS-TenantId del tenant de origen.
X-SalesOS-Signaturesha256=<hmac> — ver Verificar.

Verificar la firma

Verifica siempre la firma antes de confiar en una entrega. Sin ella, cualquiera que descubra tu URL podría falsificar eventos.
En la ruta de evento, la firma cubre {id}.{timestamp}.{rawBody} (estilo Standard Webhooks), por lo que autentica el payload y el timestamp (anti-replay). Recalcula el HMAC con tu secreto, compara en tiempo constante y rechaza si el timestamp está a más de 300s.
import crypto from "node:crypto";

// raw = el cuerpo crudo exacto de la petición (NO hagas JSON.parse antes)
export function verify(req, secret) {
  const id  = req.headers["x-salesos-event-id"];
  const ts  = req.headers["x-salesos-timestamp"];
  const sig = req.headers["x-salesos-signature"]; // "sha256=<hex>"
  const raw = req.rawBody;

  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; // ventana de replay

  const expected =
    "sha256=" + crypto.createHmac("sha256", secret).update(`${id}.${ts}.${raw}`).digest("hex");

  const a = Buffer.from(sig), b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
Entregas de prueba (el botón “Probar Webhook”) y los disparadores legados de workflow usan hoy un esquema más simple: el cuerpo es la plantilla renderizada cruda (no el envelope) y el header de firma es X-Signature: <prefijo><hmac(cuerpo)>sin el prefijo id.timestamp., por lo que no tiene anti-replay. Prefiere la ruta de evento de arriba en producción.

Catálogo de eventos

typeCuándo disparadata
lead.offer_createdUn lead es ofertado a un corredorpayload del lead/oportunidad
lead.acceptedUn corredor acepta un leadpayload del lead/oportunidad
lead.offer_expiredUna oferta expira sin aceptaciónpayload del lead/oportunidad
mission.completedUna misión se completapayload de la misión
ranking.weekly.publishedRanking semanal publicado (programado)leaderboard (filas por email)
ranking.consolidated.publishedRanking consolidado publicadoleaderboard (filas por email)
message.dispatchMensaje despachado a un usuario/grupo{ to, m }
notification.push.dispatchPush despachado a un usuario/grupo{ to, n }
Ejemplo de data para ranking.weekly.published:
{
  "p": { "type": "weekly", "label": "Semana 23/2026", "start": "2026-05-25", "end": "2026-05-31" },
  "org_unit": null,
  "totals": { "users": 843, "points": 1820400, "missions": 5120 },
  "rows": [
    { "rank": 1, "delta": 2, "email": "maria@loja.com", "points": 4820, "missions": 37, "org_unit": "ou_12" }
  ]
}
Los destinatarios se identifican por email — nombres e ids no se envían; enriquece de tu lado si lo necesitas.

Confiabilidad

Responde rápido. Devuelve un estado 2xx rápidamente (antes de procesamiento pesado) para confirmar la recepción.
Tu respuestaQué hace SalesOS
2xxMarca la entrega como completada. Sin reintento.
4xx (400, 401, 422…)Tratado como rechazo permanente. Sin reintento (excepto 429, que respeta el backoff).
5xx / timeout / error de conexiónReentrega con backoff exponencial (2^n minutos, hasta 5 intentos), luego dead-letter.
Idempotencia. El mismo evento puede entregarse más de una vez (reintentos). Deduplica por X-SalesOS-Event-Id — se mantiene igual entre reintentos.
Las entregas fallidas aparecen en el panel Entregas (Dashboard → Integraciones → Webhooks), donde puedes inspeccionar estado, intentos y el último error, y reenviar desde la cola de dead-letter.