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).
Notificaciones (Slack, Discord, Teams)
Sección titulada «Notificaciones (Slack, Discord, Teams)»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.
Probar tu configuración
Sección titulada «Probar tu configuración»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.
Webhooks de datos (tu endpoint)
Sección titulada «Webhooks de datos (tu endpoint)»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.
Verificación
Sección titulada «Verificación»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.createdX-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.
pulse.created
Sección titulada «pulse.created»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" }}audit.event
Sección titulada «audit.event»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" }}