Guía de integraciones
ZetiSecret expone una REST API completa que permite crear y consumir secretos desde cualquier lenguaje o herramienta. Esta guía cubre las integraciones disponibles: API REST, API Keys, webhook de auditoría, email transaccional y facturación con Stripe.
REST API
La API base de ZetiSecret es pública (no requiere autenticación para crear/consumir secretos) y sigue convenciones REST estándar. Todas las respuestas son JSON.
| Método | Endpoint | Descripción |
|---|---|---|
GET | /api/health | Comprobación de disponibilidad del servidor |
GET | /api/config | Configuración pública: plan, TTL por defecto, límites |
POST | /api/secrets | Crear un nuevo secreto cifrado |
GET | /api/secrets/:id/meta | Comprobar si un secreto existe sin consumirlo |
POST | /api/secrets/:id/consume | Consumir (leer y destruir) un secreto |
GET | /api/organizations/:slug/config | Política de una organización |
Crear un secreto
El cifrado debe realizarse en el cliente antes de llamar a la API. El flujo completo en JavaScript usando Web Crypto API:
// 1. Generar par de claves ECDH efímero
const { privateKey, publicKey } = await crypto.subtle.generateKey(
{ name: 'ECDH', namedCurve: 'P-256' },
true,
['deriveKey']
);
// 2. Exportar clave pública para enviar al servidor
const rawPublicKey = await crypto.subtle.exportKey('raw', publicKey);
const encodedPublicKey = btoa(String.fromCharCode(...new Uint8Array(rawPublicKey)))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
// 3. Derivar clave AES desde clave privada efímera
const sharedKey = await crypto.subtle.deriveKey(
{ name: 'ECDH', public: publicKey },
privateKey,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt']
);
// 4. Cifrar el secreto
const iv = crypto.getRandomValues(new Uint8Array(12));
const encoded = new TextEncoder().encode('mi-secreto-aqui');
const ciphertext = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, sharedKey, encoded);
const encodedCiphertext = btoa(String.fromCharCode(...new Uint8Array(ciphertext)))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
const encodedIv = btoa(String.fromCharCode(...iv))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
// 5. Enviar al servidor
const res = await fetch('https://app.zetisecret.com/api/secrets', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
ciphertext: encodedCiphertext,
iv: encodedIv,
publicKey: encodedPublicKey,
ttlHours: 24, // 1–168 horas
organizationSlug: 'mi-org' // opcional
})
});
const { id, url, expiresAt } = await res.json();
// url = 'https://app.zetisecret.com/s/ABC123'
// Adjunta #privateKey al enlace para que el destinatario pueda descifrar:
const secretUrl = url + '#' + encodedPrivateKey;
Respuesta (201 Created):
{
"ok": true,
"id": "ABC123xyz...",
"expiresAt": "2026-06-01T12:00:00.000Z",
"url": "https://app.zetisecret.com/s/ABC123xyz...",
"organization": { "slug": "mi-org", "name": "Mi Empresa" }
}
Consumir un secreto
Para leer y destruir un secreto, primero comprueba sus metadatos y luego consúmelo:
// Comprobar si existe
const meta = await fetch('/api/secrets/ABC123/meta').then(r => r.json());
// meta.ok === true → existe; meta.requiresPassphrase indica si tiene passphrase
// Consumir (el secreto se elimina tras esta llamada)
const result = await fetch('/api/secrets/ABC123/consume', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({})
});
const { ciphertext, iv, publicKey } = await result.json();
// Descifrar localmente con la clave privada del fragmento URL
// (ver documentación del cliente JavaScript en GitHub)
Organizaciones
Si tu instancia usa organizaciones, puedes consultarla política antes de crear secretos:
GET /api/organizations/mi-org/config
{
"ok": true,
"organization": { "slug": "mi-org", "name": "Mi Empresa" },
"policy": {
"maxTtlHours": 48,
"maxSecretBytes": 65536,
"requirePassphrase": false
}
}
API Keys
Las API Keys permiten el uso programático autenticado de la API. Útiles para pipelines CI/CD, scripts de despliegue o integraciones con sistemas internos.
Generar una API Key
Desde el panel de administración (/admin → sección API Keys), haz clic en + Nueva API Key. También puedes crearla vía API:
POST /api/admin/api-keys
Headers: x-admin-token: TU_ADMIN_TOKEN
{
"name": "CI Pipeline",
"organizationSlug": "mi-org", // opcional — asocia la key a una org
"creditsRemaining": 1000 // opcional — null = ilimitada
}
Respuesta (201 Created):
{
"ok": true,
"apiKey": "zs_aBcDeFgH...", // Guárdala — no se puede recuperar
"name": "CI Pipeline",
"note": "Guarda esta clave ahora — no se puede recuperar."
}
Usar la API Key
Incluye la API Key en el header Authorization de las peticiones:
POST /api/secrets
Authorization: Bearer zs_aBcDeFgH...
Content-Type: application/json
{ "ciphertext": "...", "iv": "...", "publicKey": "...", "ttlHours": 1 }
Ejemplo con curl:
curl -s -X POST https://app.zetisecret.com/api/secrets \
-H 'Authorization: Bearer zs_aBcDeFgH...' \
-H 'Content-Type: application/json' \
-d '{"ciphertext":"...","iv":"...","publicKey":"...","ttlHours":1}'
Cuando se agoten los créditos (si configuraste un límite), la API devuelve 402 Payment Required:
{ "ok": false, "error": "Créditos de API agotados. Contacta con soporte para recargar." }
Webhook de auditoría
ZetiSecret puede enviar eventos de auditoría en tiempo real a cualquier endpoint HTTPS. Ideal para integrar con Slack, Microsoft Teams, tu SIEM (Splunk, Elastic, Wazuh) o cualquier sistema de alertas.
Configuración
Configura la URL del webhook desde el panel de administración (/admin → Configuración → audit_webhook_url), o directamente en el fichero .env:
AUDIT_WEBHOOK_URL=https://hooks.slack.com/services/T.../B.../...
También puedes configurarla sin reiniciar el servidor usando el endpoint de ajustes:
PATCH /api/admin/system/settings
Headers: x-admin-token: TU_ADMIN_TOKEN
{ "audit_webhook_url": "https://tu-siem.empresa.com/webhooks/zetisecret" }
Para probar que el webhook funciona, usa el botón 🔗 Probar webhook en el panel o vía API:
POST /api/admin/system/test-webhook Headers: x-admin-token: TU_ADMIN_TOKEN
Payload de eventos
Todos los eventos tienen la misma estructura base:
{
"event": "secret_created",
"ts": "2026-05-26T10:30:00.000Z",
"requestId": "3f8a1b2c-...",
"organizationId": "abc123" | null,
"secretIdHash": "sha256:abc...",
"ipHash": "sha256:def...",
"metadata": {
"hasPassphrase": false,
"expiresAt": "2026-05-27T10:30:00.000Z"
}
}
| Evento | Descripción |
|---|---|
secret_created | Secreto creado con éxito |
secret_consumed | Secreto leído y destruido |
secret_passphrase_failed | Intento fallido de passphrase |
secret_link_key_failed | Clave de enlace inválida |
organization_upserted | Organización creada o actualizada |
organization_disabled | Organización deshabilitada |
api_key_created | Nueva API Key generada |
api_key_revoked | API Key revocada |
demo_quota_exceeded | Usuario excedió el límite demo |
webhook_test | Prueba manual del webhook |
Ejemplos de integración
Slack (Incoming Webhook):
Configura un Incoming Webhook en tu workspace de Slack y usa la URL directamente en AUDIT_WEBHOOK_URL. ZetiSecret enviará el JSON de cada evento al canal configurado.
Make / Zapier:
Crea un escenario con el trigger "Webhook" y pega la URL generada en audit_webhook_url. Podrás filtrar eventos y conectarlos con Google Sheets, Jira, PagerDuty, etc.
SIEM (Splunk HTTP Event Collector):
# En Splunk, crea un HEC token y usa: AUDIT_WEBHOOK_URL=https://splunk.empresa.com:8088/services/collector/event # El HEC de Splunk espera el campo "event" dentro de un objeto padre. # Para compatibilidad total, configura un proxy HTTP ligero que adapte el formato.
Email transaccional (Resend)
ZetiSecret usa Resend como proveedor de email transaccional. El email se usa para notificaciones de licencia y emails de prueba del sistema.
Configuración
-
Crea una cuenta gratuita en resend.com (capa gratuita: 3.000 emails/mes).
-
Verifica tu dominio en el panel de Resend (Domains → Add Domain). Tendrás que añadir unos registros DNS.
-
Genera una API Key en API Keys → Create API Key.
-
Añade las variables en tu
.envo en el asistente de instalación:RESEND_API_KEY=re_xxxxxxxxxxxx RESEND_FROM_EMAIL=ZetiSecret <noreply@tu-dominio.com>
Prueba el envío desde el panel de administración (/admin → Configuración → ✉️ Enviar email de prueba), o vía API:
POST /api/admin/system/test-email
Headers: x-admin-token: TU_ADMIN_TOKEN
{ "to": "test@ejemplo.com" }
Facturación con Stripe
Si despliegas ZetiSecret en modo HQ (HQ_MODE=true), puedes habilitar la facturación con Stripe para vender licencias directamente desde tu instancia. Esta integración es opcional y solo necesaria en el servidor central de distribución de licencias.
Configuración
-
Crea una cuenta en stripe.com y accede al Dashboard → Developers → API Keys.
-
Copia la clave secreta (
sk_live_...osk_test_...para pruebas) y añádela al.env:STRIPE_SECRET_KEY=sk_test_xxxxxxxxxxxx
-
Crea los productos en Stripe Dashboard → Products:
- Crea un producto ZetiSecret Starter con precio recurrente (anual, €199).
- Crea un producto ZetiSecret Professional con precio recurrente (anual, €699).
price_xxxx) y añádelos al.env:STRIPE_STARTER_PRICE_ID=price_xxxxxxxxxxxx STRIPE_PRO_PRICE_ID=price_xxxxxxxxxxxx
-
Activa
HQ_MODE=trueen el.envpara habilitar los endpoints de facturación y el portal de cliente.
Webhook de Stripe
Para procesar pagos en tiempo real (emisión automática de licencias tras la compra), configura el webhook de Stripe:
-
En el Dashboard de Stripe, ve a Developers → Webhooks → Add endpoint.
-
Añade tu endpoint:
https://tu-dominio.com/api/billing/webhook
-
Selecciona los eventos:
checkout.session.completed— emite la licencia tras el pagocustomer.subscription.deleted— revoca la licencia si se cancela la suscripción
-
Copia el Signing secret (
whsec_...) y añádelo al.env:STRIPE_WEBHOOK_SECRET=whsec_xxxxxxxxxxxx
Con esto configurado, cuando un cliente complete el pago:
- Stripe notifica a
/api/billing/webhook - ZetiSecret genera y firma automáticamente una licencia JWT
- El cliente recibe su
LICENSE_KEYpor email - El cliente introduce la LICENSE_KEY en su instalación self-hosted
Para crear una sesión de checkout desde tu frontend:
POST /api/billing/checkout
Content-Type: application/json
{
"planKey": "starter", // 'starter' | 'professional'
"customerEmail": "admin@acme.com",
"customerName": "ACME Corp",
"successUrl": "https://tu-dominio.com/pricing?success=1",
"cancelUrl": "https://tu-dominio.com/pricing?cancelled=1"
}
// Respuesta:
{ "ok": true, "checkoutUrl": "https://checkout.stripe.com/pay/..." }