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

employee.created, employee.updated, employee.terminated, checkin.created, absence.requested, absence.approved, absence.denied, absence.cancelled, vacation_balance.updated, report.completed, ping.

Payload del evento

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

data lleva ids públicos + estado (mínimo); rehidrata vía GET el recurso completo cuando necesites todos los campos.

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).

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