Integraciones

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.

Cifrado zero-knowledge en todos los casos. La API solo acepta datos ya cifrados en el cliente. El servidor nunca recibe el texto en claro. Consulta el modelo de seguridad para más detalles.

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étodoEndpointDescripción
GET/api/healthComprobación de disponibilidad del servidor
GET/api/configConfiguración pública: plan, TTL por defecto, límites
POST/api/secretsCrear un nuevo secreto cifrado
GET/api/secrets/:id/metaComprobar si un secreto existe sin consumirlo
POST/api/secrets/:id/consumeConsumir (leer y destruir) un secreto
GET/api/organizations/:slug/configPolí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.

Las API Keys son opcionales para crear secretos. Solo son necesarias cuando quieres controlar el uso (créditos por organización) o identificar el origen de los secretos en los logs de auditoría.

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.

Zero-knowledge en los webhooks. Los payloads nunca incluyen el contenido de los secretos. Solo contienen metadatos: tipo de evento, timestamp, hash del secreto, hash de IP y ID de organización.

Configuración

Configura la URL del webhook desde el panel de administración (/adminConfiguraciónaudit_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"
  }
}
EventoDescripción
secret_createdSecreto creado con éxito
secret_consumedSecreto leído y destruido
secret_passphrase_failedIntento fallido de passphrase
secret_link_key_failedClave de enlace inválida
organization_upsertedOrganización creada o actualizada
organization_disabledOrganización deshabilitada
api_key_createdNueva API Key generada
api_key_revokedAPI Key revocada
demo_quota_exceededUsuario excedió el límite demo
webhook_testPrueba 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

  1. Crea una cuenta gratuita en resend.com (capa gratuita: 3.000 emails/mes).
  2. Verifica tu dominio en el panel de Resend (Domains → Add Domain). Tendrás que añadir unos registros DNS.
  3. Genera una API Key en API Keys → Create API Key.
  4. Añade las variables en tu .env o 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 (/adminConfiguració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" }
Si no configuras RESEND_API_KEY, el envío de emails se omite silenciosamente (skipped). La aplicación sigue funcionando con todas sus funciones.

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.

¿Para quién es esto? La integración de Stripe es relevante si operas tu propia plataforma de distribución de licencias. Para instalaciones de cliente (self-hosted), no necesitas Stripe.

Configuración

  1. Crea una cuenta en stripe.com y accede al Dashboard → Developers → API Keys.
  2. Copia la clave secreta (sk_live_... o sk_test_... para pruebas) y añádela al .env:
    STRIPE_SECRET_KEY=sk_test_xxxxxxxxxxxx
  3. 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).
    Copia los IDs de precio (price_xxxx) y añádelos al .env:
    STRIPE_STARTER_PRICE_ID=price_xxxxxxxxxxxx
    STRIPE_PRO_PRICE_ID=price_xxxxxxxxxxxx
  4. Activa HQ_MODE=true en el .env para 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:

  1. En el Dashboard de Stripe, ve a Developers → Webhooks → Add endpoint.
  2. Añade tu endpoint:
    https://tu-dominio.com/api/billing/webhook
  3. Selecciona los eventos:
    • checkout.session.completed — emite la licencia tras el pago
    • customer.subscription.deleted — revoca la licencia si se cancela la suscripción
  4. Copia el Signing secret (whsec_...) y añádelo al .env:
    STRIPE_WEBHOOK_SECRET=whsec_xxxxxxxxxxxx

Con esto configurado, cuando un cliente complete el pago:

  1. Stripe notifica a /api/billing/webhook
  2. ZetiSecret genera y firma automáticamente una licencia JWT
  3. El cliente recibe su LICENSE_KEY por email
  4. 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/..." }