Webhooks salientes
Los webhooks envían eventos de Manisma a un endpoint HTTPS público de tu organización.
Configurar el endpoint
Abre como Admin Administración → Conexiones, crea un endpoint y elige nombre, URL y eventos. El signing secret se muestra una vez. Guárdalo de forma segura. En una rotación, el nuevo secret se aplica desde el momento de guardar.
La URL debe:
- usar HTTPS;
- ser alcanzable públicamente mediante DNS;
- usar el puerto 443 u 8443;
- no contener usuario/contraseña en la URL;
- no apuntar a localhost, IP privada, link-local o metadatos de la nube.
Utiliza Test para enviar un ping antes de activar eventos reales.
Eventos
| Evento | Disparador |
|---|---|
ping |
Prueba manual del endpoint. |
client.created |
Cliente creado. |
client.updated |
Cliente modificado. |
job.created |
Orden de trabajo creada. |
job.planned |
Estado de la orden de trabajo cambiado a planificada. |
job.completed |
Estado de la orden de trabajo cambiado a completada. |
job.cancelled |
Estado de la orden de trabajo cambiado a cancelada. |
approval.approved |
El cliente ha aprobado una propuesta. |
approval.declined |
El cliente ha rechazado una propuesta. |
workorder.completed |
Datos de lista de trabajo listos para facturar al completarse. |
Una modificación en otro campo de una orden de trabajo ya completada no vuelve a enviar job.completed; debe cambiar el propio estado.
Envelope y headers
{
"event": "job.completed",
"version": 1,
"tenant_id": "tenant-uuid",
"occurred_at": "2026-07-13T09:15:00.000Z",
"data": {}
}
Headers:
X-Manisma-Event: nombre del evento;X-Manisma-Delivery: id estable de esta entrega lógica;X-Manisma-Signature:sha256=<hex HMAC-SHA256>sobre el cuerpo en bruto exacto.
Verifica el HMAC antes de parsear el JSON y utiliza una comparación timing-safe:
import { createHmac, timingSafeEqual } from 'node:crypto'
export function verify(rawBody, header, secret) {
if (!header?.startsWith('sha256=')) return false
const expected = createHmac('sha256', secret).update(rawBody).digest('hex')
const supplied = header.slice(7)
const a = Buffer.from(expected, 'hex')
const b = Buffer.from(supplied, 'hex')
return a.length === b.length && timingSafeEqual(a, b)
}
Payloads
Los eventos de cliente contienen id, customer_number, name, client_type, email y phone. Las notas internas del cliente no se incluyen.
Los eventos de orden de trabajo contienen id/número de orden, título, estado, prioridad, planificación y objetos compactos para cliente, dirección de servicio y técnico principal. Una orden de trabajo sin asignar o sin dirección utiliza null.
Los eventos de aprobación contienen id de aprobación, job_id, estado, franja elegida y hora de respuesta. chosen_slot puede ser una cadena JSON o texto libre antiguo; parsea de forma defensiva.
workorder.completed contiene:
- orden de trabajo y cliente compactos;
- la visita completada más reciente, si está disponible;
- materiales utilizados con SKU, precio de venta, cantidad e importe de la línea;
- duración del trabajo;
- total de materiales en EUR.
El precio del material es el precio de venta en el momento en que se construye el payload del webhook, no necesariamente un precio histórico fijado.
Entrega y reintentos
Los webhooks son at least once. Haz que tu receptor sea idempotente respecto a X-Manisma-Delivery. Una recepción correcta es cualquier respuesta 2xx. No se siguen redirecciones y cada intento tiene un timeout de 10 segundos.
En caso de error temporal hay un máximo de tres intentos: inmediato, a los 60 segundos y otros 300 segundos después. Se conserva el mismo delivery-id. Los reintentos se ejecutan actualmente en el proceso de servicio y no sobreviven a un redespliegue; por tanto, revisa también el registro de entregas en Conexiones para localizar entradas pending que hayan quedado pendientes.
Guarda primero el delivery-id en la misma transacción que tu procesamiento y responde después rápidamente con 2xx. Mueve el procesamiento lento a tu propia cola.