Ir al contenido

Desarrolladores

Webhooks

Recibí pulsos y eventos de auditoría en tu propio endpoint.

Hay dos formas de recibir empujes de Moodinary. Ambas se configuran desde el panel, en la pantalla de Integraciones de tu organización, y requieren el plan Insight (o superior).

El resumen semanal llega directo a tu canal: pegás la URL del webhook de tu servicio, elegís el destino y listo. Sin código.

Desde el panel (Integraciones → Webhooks) podés disparar un evento de prueba a cada destino sin esperar al próximo pulso real. Si no llega, revisá que tu endpoint responda 2xx en menos de 5 segundos y que el secreto configurado coincida. El secreto se muestra una sola vez al crearlo o regenerarlo: guardalo en tu servidor. No vuelve a aparecer al abrir el panel. Si lo perdiste, regeneralo y actualizá tu receptor; las firmas anteriores dejan de validar con el nuevo secreto.

Pulsos al momento de recibirse y eventos del registro de auditoría, entregados en crudo a una URL tuya, para que los proceses como quieras. Podés prender cada tipo por separado y dejar la URL vacía para desactivar.

Cada envío lleva un identificador estable y un timestamp. Para receptores nuevos, verificá X-Moodinary-Signature-V2 antes de confiar en el payload: es el HMAC-SHA256 de timestamp + "." + deliveryId + "." + bodyCrudo. El body se verifica exactamente como llegó, sin volver a serializarlo.

X-Moodinary-Event: pulse.created
X-Moodinary-Delivery-Id: <id>
X-Moodinary-Timestamp: <segundos Unix>
X-Moodinary-Signature-V2: sha256=<hex>
X-Moodinary-Signature: sha256=<hex del body crudo, contrato anterior>
import { createHmac, timingSafeEqual } from "node:crypto";
function isValid(rawBody, headers, secret, now = Date.now()) {
const timestamp = headers.get("X-Moodinary-Timestamp") ?? "";
const id = headers.get("X-Moodinary-Delivery-Id") ?? "";
const signature = headers.get("X-Moodinary-Signature-V2") ?? "";
if (!/^\d{1,12}$/.test(timestamp) || !id || id.length > 200) return false;
const age = now / 1000 - Number(timestamp);
if (age > 300 || age < -30) return false;
if (!/^sha256=[a-f0-9]{64}$/.test(signature)) return false;
const expected = "sha256=" + createHmac("sha256", secret)
.update(`${timestamp}.${id}.${rawBody}`).digest("hex");
return timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}

Después de verificar, tu receptor debe guardar el ID y encolar el trabajo en una operación atómica. Un ID ya procesado debe responder 2xx sin repetir sus efectos. Conservá los IDs al menos durante la ventana de aceptación del timestamp (por ejemplo, diez minutos). Comprobá también que el id del JSON coincida con el header. La firma permite detectar cambios; la ventana temporal y la deduplicación las implementa tu receptor. Moodinary no puede imponerlas por vos. El header anterior se conserva por compatibilidad, pero por sí solo no protege contra replay.

Solo se aceptan destinos HTTPS públicos sin credenciales en la URL. No se siguen redirects. La validación del hostname no elimina por sí sola el riesgo de DNS rebinding.

El resumen semanal congela su contenido y registra un checkpoint por canal en el workflow de producción. Un canal ya confirmado no vuelve a enviarse porque otro falle. Las alertas se marcan enviadas después de confirmar todos los canales habilitados. Incluye todas las alertas pendientes, sin el anterior tope de 50; siguen aplicando los límites de mensaje del servicio de chat. Las suscripciones canceladas conservan el resumen hasta el fin del período pagado. Una caída entre aceptación y checkpoint, o una nueva corrida manual, todavía puede producir duplicados.

Se dispara al momento de recibirse cada pulso, en paralelo con la moderación: moderationStatus es el valor pre-moderación, no el final.

{
"event": "pulse.created",
"id": "delivery_abc",
"orgId": "org_123",
"sentAt": "2026-08-19T09:12:00.500Z",
"data": {
"id": "pls_abc",
"level": 4,
"reason": "TRABAJO",
"moderationStatus": "CLEAN",
"subteamId": "sub_1",
"subteamName": "Ventas",
"createdAt": "2026-08-19T09:12:00.000Z"
}
}

Se dispara con cada entrada nueva del registro de auditoría (invitaciones, cambios de rol, exportaciones…).

{
"event": "audit.event",
"id": "delivery_def",
"orgId": "org_123",
"sentAt": "2026-08-19T09:12:00.500Z",
"data": {
"action": "member.invited",
"actorUserId": "user_abc",
"targetType": "member",
"targetId": "juan@empresa.com",
"metadata": null,
"createdAt": "2026-08-19T09:12:00.000Z"
}
}