Accesos y datos
- Acceso al panel de Kapture con una organizacion activa.
- Endpoint HTTPS que pueda recibir POST JSON.
- Secret compartido para HMAC SHA-256.
- Logs del receptor disponibles para inspeccionar headers y body.
Onboarding tecnico
Esta ruta lleva desde cero hasta una recepcion valida en produccion. El enfoque es org-level, que es la superficie publica que el panel expone hoy.
1. Preparacion
Antes de tocar codigo, conviene confirmar estas piezas. Sin ellas, el debugging se vuelve ruidoso muy rapido.
2. Configuracion
La configuracion publica vive en el documento de organizacion y se administra desde el panel en `settings/organization/general`.
| Campo | Ubicacion | Uso |
|---|---|---|
webhookEnabled |
organizations/{orgId} |
Activa o apaga la emision del webhook saliente. |
webhookUrl |
organizations/{orgId} |
URL HTTPS destino del POST firmado. |
webhookSecret |
organizations/{orgId} |
Secret compartido usado para firmar el body. |
webhookEvents |
organizations/{orgId} |
Lista de eventos habilitados: `record.created`, `expedient.completed`. |
webhookUpdatedAt |
organizations/{orgId} |
Marca de tiempo de la ultima actualizacion. |
Si una organizacion antigua tiene URL y secret, pero no `webhookEvents`, el backend resuelve por compatibilidad solo `record.created`. Si se quiere `expedient.completed`, hay que guardar explicitamente el array de eventos.
3. Receptor
El requisito tecnico mas importante es conservar el body crudo antes de parsear JSON. El ejemplo siguiente usa Express con `express.raw()`.
import crypto from 'crypto';
import express from 'express';
const app = express();
const secret = process.env.KAPTURE_WEBHOOK_SECRET;
app.post(
'/kapture/webhook',
express.raw({ type: 'application/json' }),
(req, res) => {
const signature = req.header('x-kapture-signature');
const event = req.header('x-kapture-event');
const rawBody = req.body;
if (!secret || !signature || !Buffer.isBuffer(rawBody)) {
return res.status(400).send('invalid request');
}
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
const [algorithm, digest] = signature.split('=');
const valid =
algorithm === 'sha256' &&
digest &&
crypto.timingSafeEqual(Buffer.from(digest, 'hex'), Buffer.from(expected, 'hex'));
if (!valid) {
return res.status(401).send('invalid signature');
}
const payload = JSON.parse(rawBody.toString('utf8'));
const idempotencyKey = `${payload.event}:${payload.id}`;
console.log({ event, idempotencyKey, payload });
return res.status(202).send('accepted');
}
);
app.listen(3000);
El receptor deberia confirmar rapido con `202` o `200` y mover el trabajo pesado a una cola, job interno o proceso asincrono propio.
4. Seguridad
La firma se calcula sobre el JSON serializado exacto que sale del backend. Cualquier mutacion del body antes de verificar rompe el digest.
5. Pruebas
Los dos eventos requieren escenarios distintos, asi que lo mejor es validarlos por separado.
6. Produccion
El quickstart deja la integracion viva, pero la postura exacta de retries depende del canal y del trigger que emite el evento. La pagina de arquitectura detalla esas diferencias.