Skip to Content
Webhooks

Webhooks

En lugar de hacer polling, suscríbete a eventos salientes: Kinmu envía un POST a tu endpoint HTTPS cuando ocurre algo relevante. Cada entrega va firmada con la convención Standard Webhooks  — verifica siempre la firma antes de procesar el evento.

La gestión de endpoints requiere el scope webhooks:manage y se hace desde el dashboard o vía los endpoints /v1/webhook-endpoints.

Suscripción

Crea un endpoint indicando tu URL (HTTPS) y los eventos que te interesan:

curl -s -X POST "https://api.kinmu.app/v1/webhook-endpoints" \ -H "Authorization: Bearer kinmu_sk_live_…" \ -H "Content-Type: application/json" \ -d '{ "url": "https://tu-app.example.com/webhooks/kinmu", "events": ["absence.approved", "checkin.created"], "description": "Sync con nuestro ERP" }'

La respuesta incluye el secreto de firma (whsec_…), visible una sola vez. Guárdalo: lo necesitas para verificar las firmas.

Gestión disponible: GET (lista), GET /{id}, PATCH /{id} (eventos/estado), DELETE /{id}, POST /{id}/test (envía un ping), GET /{id}/deliveries (últimas entregas) y POST /{id}/deliveries/{deliveryId}/retry (reencola una entrega concreta). Máximo 5 endpoints activos por empresa.

Eventos

EventoCuándo se emitedata
employee.created · employee.updated · employee.terminatedAlta, cambio o baja de un empleado.id y status.
checkin.createdSe registra un fichaje (entrada, salida, inicio o fin de pausa).id del evento ({uuid}:{tipo}) y employee_id.
absence.requestedSe crea una solicitud pendiente.id, employee_id, type y status.
absence.approved · absence.denied · absence.cancelledSe decide o cancela una ausencia. Importar una ausencia ya aprobada emite absence.approved, sin absence.requested previo.id, employee_id, type y status.
vacation_balance.updatedSe aprueban o cancelan unas vacaciones.Igual que una fila de GET /v1/vacation-balances.
report.completedTermina la generación de un informe.id y status; pide GET /v1/reports/{id} para la download_url.
pingLo envías tú con POST /v1/webhook-endpoints/{id}/test.{ "message": "pong" }.

Los eventos absence.* incluyen el tipo de ausencia (data.type) solo si el endpoint se creó con una key que tenga org:ausencias:read, porque el tipo puede revelar una baja médica. Los endpoints creados antes del 28-10-2026 lo siguen recibiendo.

Los webhooks se detienen cuando la cuenta pierde el servicio. Si se desactiva el complemento Public API, los endpoints pasan a disabled y no se reactivan solos al volver a activarlo. Mientras la suscripción no esté activa no se envía ningún evento.

Payload del evento

{ "id": "evt_…", "type": "absence.approved", "created_at": "…", "company_id": "…", "data": { … } }

data es deliberadamente pequeño en la mayoría de eventos. Cuando necesites el recurso completo, pídelo por GET con el id que recibes.

Verificación de la firma

Cada entrega llega con tres headers:

HeaderContenido
webhook-idId único del evento (idempotencia en tu receptor).
webhook-timestampTimestamp Unix del envío.
webhook-signaturev1,<firma> (puede haber varias separadas por espacio).

Algoritmo: firma = base64( HMAC_SHA256( key, "{webhook-id}.{webhook-timestamp}.{body_crudo}" ) ), prefijada con v1,. La key es la parte tras whsec_ del secreto (mostrado una sola vez al crear el webhook), decodificada de base64 a bytes. Firma sobre el cuerpo crudo (bytes exactos recibidos), no sobre el JSON re-serializado.

Verificación (rechaza si no coincide o si webhook-timestamp difiere > 5 min de tu reloj):

const crypto = require('crypto'); function verify(secret, headers, rawBody) { const id = headers['webhook-id']; const ts = headers['webhook-timestamp']; if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; // ±5 min const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64'); const expected = 'v1,' + crypto .createHmac('sha256', key) .update(`${id}.${ts}.${rawBody}`) .digest('base64'); return headers['webhook-signature'] .split(' ') .some(sig => crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))); }

Verifica y firma sobre el cuerpo crudo (bytes exactos recibidos), no sobre el JSON re-serializado — cualquier reformateo invalida la firma. Usa el webhook-id como clave de idempotencia en tu receptor para descartar reenvíos.

Reintentos

Ante fallo (o respuesta ≠ 2xx) Kinmu reintenta con backoff: 1 m, 5 m, 30 m, 2 h, 12 h, 24 h. Responde 2xx rápido; procesa async si tardas. Un endpoint que falla 7 días seguidos se auto-desactiva.

Puedes reencolar manualmente una entrega fallida con POST /v1/webhook-endpoints/{id}/deliveries/{deliveryId}/retry (usa el id de la entrega que ves en GET /{id}/deliveries).

Solo se pueden reintentar entregas con status=failed; cualquier otro estado devuelve 409 conflict. test y retry admiten 30 peticiones por minuto entre los dos; por encima, responden 429 con Retry-After.

Buenas prácticas

Responde rápido, procesa en background

Verifica la firma, encola el trabajo y responde 2xx de inmediato.

Verifica siempre la firma

No proceses ningún payload sin verificar webhook-signature. Trata el secreto como una credencial.

Sé idempotente

Deduplica por webhook-id. Los reintentos son normales.

Prueba con ping

Usa POST /v1/webhook-endpoints/{id}/test para recibir un evento ping y validar tu integración de punta a punta.

Last updated on