Webhooks
MB Suite te avisa con un POST firmado cuando algo pasa en tu organización: cómo suscribirte, verificar la firma y recibir los reintentos.
Un webhook es una suscripción: eliges los eventos y una URL https:// pública, y cada vez que uno ocurre MB Suite manda un POST con el evento firmado. Se crean y administran en Configuración → API y MCP → Webhooks (owners y admins). Por la API y el MCP se leen las suscripciones, el catálogo de eventos y las entregas.
Crear una suscripción
- 1Crear webhook: la URL de tu servidor, los eventos, el formato (Completo o Solo IDs) y, en un workspace, si escucha también a sus Cuentas.
- 2En Avanzado, hasta 5 encabezados fijos si tu receptor exige un token propio (Make, Monday). Sus valores se guardan cifrados y no se vuelven a mostrar.
- 3Copia el secreto de firma (
whsec_…): se muestra una sola vez. Se puede rotar; durante 24 horas se firma con el nuevo y con el anterior. - 4Enviar evento de prueba manda un
webhook.testfirmado, aunque la suscripción esté apagada.
Cada organización tiene un tope de suscripciones activas según su plan, y nunca más de 20. El destino tiene que ser un servidor público: las direcciones privadas, internas o de MB Suite se rechazan, y se vuelven a revisar antes de cada envío.
Qué recibe tu servidor
Un POST con Content-Type: application/json, un cuerpo de hasta 256 KB y estos encabezados:
| Encabezado | Valor |
|---|---|
MB-Signature | t=<unix>,v1=<hex> (ver abajo). |
MB-Event-Id | El id del evento (evt_…). |
MB-Event-Type | El tipo, por ejemplo crm.deal.stage_changed. |
MB-Webhook-Id | La suscripción. |
MB-Delivery-Id | La entrega (un reenvío manual crea otra). |
MB-Api-Version | La versión con la que se armó el cuerpo. |
Idempotency-Key | El id del evento: igual en todos los reintentos. |
User-Agent | MB-Suite-Webhooks/1.0 (+https://developers.mb-suite.com/webhooks) |
{
"id": "evt_4f9c1a7e2b6d0c3a8e5f1b9d2c",
"type": "crm.deal.stage_changed",
"created_at": "2025-11-01T12:26:40.000Z",
"api_version": "2026-11-01",
"org_id": "ORG_ID",
"data": { "…": "…" }
}- Completo (
snapshot, recomendado):datatrae los registros enteros, en la versión de la suscripción. - Solo IDs (
thin): solo ids y valores simples; tu sistema consulta el resto por la API. - Un evento que no entra en 256 KB se manda como Solo IDs, con
"truncated": trueendata.
La lista completa de eventos, con un ejemplo de cada uno, está en Eventos. Cada operación de la referencia dice qué eventos puede emitir; lo que un evento todavía no cubre lo dice su descripción.
Verificar la firma
MB-Signature es t=<unix>,v1=<hex>, con v1 = HMAC-SHA256(secreto, "<t>.<cuerpo>") sobre el cuerpo exacto, sin volver a serializarlo. Durante una rotación llegan dos v1=: vale cualquiera. Rechaza un t de más de 5 minutos (replay) y compara en tiempo constante.
import crypto from "node:crypto";
import express from "express";
function verifyMbSignature(header, rawBody, secret, toleranceSec = 300) {
if (!header) return false;
const parts = header.split(",").map((p) => p.trim().split("="));
const t = Number(parts.find(([k]) => k === "t")?.[1]);
const signatures = parts.filter(([k]) => k === "v1").map(([, v]) => v);
if (!t || signatures.length === 0) return false;
if (Math.abs(Math.floor(Date.now() / 1000) - t) > toleranceSec) return false; // replay
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return signatures.some((sig) => sig.length === expected.length && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected)));
}
const app = express();
// El cuerpo crudo: express.raw, NO express.json (re-serializar cambia la firma).
app.post("/mb-suite/webhook", express.raw({ type: "application/json" }), (req, res) => {
if (!verifyMbSignature(req.get("MB-Signature"), req.body, process.env.MB_WEBHOOK_SECRET)) {
return res.sendStatus(400);
}
const event = JSON.parse(req.body);
// event = { id, type, created_at, api_version, org_id, data }
// Idempotencia: `event.id` (también viene como `Idempotency-Key`) se repite si reintentamos.
switch (event.type) {
case "crm.lead.created":
// event.data.person, event.data.prospect | event.data.deal, event.data.source
break;
case "crm.deal.stage_changed":
// event.data.deal, event.data.stage, event.data.previous.stage
break;
}
res.sendStatus(200); // cualquier 2xx = entregado; otra cosa o más de 10 s = reintento
});Responder y reintentos
- Responde 2xx en menos de 10 segundos y deja el trabajo pesado para una cola. Cualquier otra respuesta, una redirección (3xx) o el silencio cuentan como falla.
- Reintentos: al instante, a los 5 minutos, a los 30, a las 2 horas, a las 5, a las 10 y después cada 12 horas: 11 intentos en unas 72 horas.
- Un reintento trae el mismo
idde evento: úsalo para no procesar dos veces. - Después de 24 horas de fallas seguidas, los owners y admins reciben un aviso por correo; a las 72 horas la suscripción se apaga sola. Al reactivarla puedes reenviar lo que falló en las 72 horas anteriores, y cada entrega se puede reenviar a mano desde Ver entregas.
- Eventos y entregas se guardan 30 días.
Recetas: Make, Odoo y Monday
Make
- 1En Make, crea un escenario que arranque con Webhooks → Custom webhook y copia su URL (
https://hook.us1.make.com/…). - 2En MB Suite, Crear webhook con esa URL y los eventos que quieras (por ejemplo
crm.lead.createdycrm.deal.stage_changed). - 3En Make, Re-determine data structure; en MB Suite, Enviar evento de prueba (o un cambio real) para que Make aprenda la forma del JSON.
- 4Make no verifica HMAC: agrega un encabezado fijo (
X-Make-Token: <un valor largo>) y compáralo en un Filter del escenario.
Odoo
Odoo 17 o posterior tiene reglas de automatización con el disparador On webhook: cada regla da una URL propia (https://<tu-odoo>/web/hook/<uuid>) y una acción Ejecutar código Python lee payload (el evento entero). Como esa acción no ve el cuerpo crudo, protégela con un encabezado fijo y guarda payload["id"] para no crear el lead dos veces en un reintento. En versiones anteriores, pon Make en el medio con Odoo → Create a Record.
Monday
Monday no recibe webhooks de otros sistemas: lo más simple es Make en el medio (Custom webhook → Monday → Create an Item), mapeando data.person.full_name, data.deal.title, data.stage.name o data.source.name. Para no duplicar ítems en un reintento, filtra por el id del evento o busca el ítem por data.deal.id antes de crearlo.