Webhooks
Eventos y firmas
Reaccioná a cambios en el workspace sin hacer polling.
Crear un endpoint
Desde Configuración → Developers, un admin u owner puede registrar una URL y elegir a qué eventos suscribirse. Al crearlo, Loona genera un secreto (whsec_...) que se muestra una sola vez — se usa para verificar la firma de cada entrega.
Requiere el mismo entitlement que la API pública (plan Pro, Business o Custom). Ver Acceso por plan.
Eventos disponibles
| Evento | Grupo |
|---|---|
client.created | Clientes |
client.updated | Clientes |
client.status_changed | Clientes |
contact.created | Contactos |
deal.created | Pipeline |
deal.stage_changed | Pipeline |
deal.won | Pipeline |
deal.lost | Pipeline |
billing.created | Facturación |
billing.paid | Facturación |
billing.overdue | Facturación |
task.created | Tareas |
task.completed | Tareas |
work.created | Proyectos |
member.invited | Equipo |
Headers de cada entrega
| Header | Contenido |
|---|---|
x-synera-signature | HMAC-SHA256 del payload, en hex. |
x-synera-timestamp | Unix timestamp (segundos) usado en la firma. Rechazá entregas con un timestamp fuera de una ventana razonable (por ejemplo, 5 minutos) para evitar ataques de replay. |
x-synera-delivery-id | Id estable de la entrega, igual en todos los reintentos. Usalo para deduplicar. |
idempotency-key | El mismo valor que x-synera-delivery-id, con el nombre que la mayoría de los frameworks ya reconoce. |
x-synera-attempt | Número de intento (1-based). Sólo diagnóstico. |
x-synera-event | El tipo de evento, para rutear sin parsear el body. |
Verificar la firma
La firma es un HMAC-SHA256 sobre ${timestamp}.${body} (el timestamp entra en la firma, no sólo en un header, para que una entrega interceptada no se pueda reproducir más tarde con otro timestamp).
const crypto = require("node:crypto")
function verifyLoonaWebhook(rawBody, signatureHeader, timestampHeader, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestampHeader}.${rawBody}`, "utf8")
.digest("hex")
return crypto.timingSafeEqual(
Buffer.from(expected, "hex"),
Buffer.from(signatureHeader, "hex"),
)
}Usá el body crudo
Reintentos
Hasta 5 intentos por entrega, con backoff de 1, 5, 15 y 45 minutos entre cada uno (~2 horas en total). Alcanza para cubrir un deploy de tu endpoint sin quedar reintentando contra algo que ya no existe.
Rotar el secreto
Rotar el secreto de un endpoint invalida las firmas del secreto anterior de inmediato — no hay un período de gracia con los dos secretos válidos a la vez. Es lo correcto si el secreto se filtró, pero significa que tenés que actualizar tu verificador en el mismo momento en que rotás.