Seguridad
Seguridad con Constaia, cómo guardar y rotar claves de API, verificar webhooks, proteger tu endpoint de subida y no fiarte de veredictos del navegador.
Esta página reúne lo que tienes que hacer tú para que una integración con Constaia sea segura. Lo que hace Constaia con tus datos está en almacenamiento y privacidad.
Claves de API
Solo en el servidor
Una clave ck_live_ gasta créditos y da acceso a todos los análisis de tu cuenta. Trátala como una contraseña:
- Nunca en código de navegador, apps móviles, apps de escritorio distribuidas ni repositorios. Cualquier cosa que llega al dispositivo del usuario se puede extraer.
- Tu frontend o tu app móvil suben el fichero a tu backend, y es tu backend el que llama a Constaia.
- El SDK de JavaScript lanza
secret_key_in_browsersi detecta una claveck_en un navegador, y el widget rechaza cualquier atributo que parezca una clave (secret_key_rejected).
Próximamente: claves publicables
Todavía no hay claves publicables para usar desde el navegador. Hoy, todas las claves son secretas.
Dónde guardarlas
En variables de entorno o en un gestor de secretos, nunca en el código:
| Entorno | Dónde |
|---|---|
| Local | Un fichero .env incluido en .gitignore. |
| Vercel, Netlify, Render, Fly… | Las variables de entorno del proyecto. |
| AWS | AWS Secrets Manager o SSM Parameter Store. |
| Google Cloud | Secret Manager. |
| Kubernetes | Un Secret montado como variable de entorno. |
| Equipos | Un gestor de secretos compartido (Doppler, 1Password, Vault…). |
Los SDK leen CONSTAIA_API_KEY por defecto:
CONSTAIA_API_KEY=ck_test_...import { Constaia } from "@constaia/sdk";
export const constaia = new Constaia(); // process.env.CONSTAIA_API_KEYLas claves se guardan en Constaia como hash y solo se muestran una vez, al crearlas. Si la pierdes, crea otra.
Una clave por entorno
- Desarrollo, CI y staging: claves
ck_test_. No gastan créditos y dan resultados deterministas. Ver modo test. - Producción: una clave
ck_live_que solo exista en producción. - Una clave distinta por aplicación o servicio, para poder revocar una sin afectar a las demás y saber quién la usaba.
Las claves no tienen permisos por recurso: cualquier clave de una cuenta puede leer y borrar los análisis de esa cuenta. Si necesitas aislar datos (por ejemplo, dos clientes o dos productos), usa cuentas distintas.
Rotar una clave
Las claves pueden convivir, así que puedes rotar sin cortes:
Crea una clave nueva en el panel → Claves de API.
Actualiza la variable de entorno y despliega.
Comprueba que el tráfico usa la nueva (el panel muestra el último uso de cada clave).
Revoca la antigua en el panel. A partir de ese momento devuelve 401 invalid_api_key.
Rota periódicamente y siempre que alguien con acceso deje el equipo.
Si se filtra una clave
- Revócala inmediatamente en el panel. No esperes a tener la nueva desplegada: unos minutos de error son mejor que un tercero gastando tus créditos o leyendo tus análisis.
- Crea una nueva y despliégala.
- Revisa el uso (
GET /v1/usageo el panel) y el registro de auditoría de la cuenta. - Si la clave llegó a un repositorio, elimínala también del historial; revocarla es lo que la invalida.
- Si ves uso que no reconoces, escribe a hola@constaia.com.
Webhooks
- Verifica siempre la firma (
webhook-signature) con el secretowhsec_…del endpoint, sobre el cuerpo sin procesar. Los SDK lo hacen por ti. - Rechaza timestamps de más de 5 minutos (
webhook-timestamp), para evitar que se reenvíe un evento antiguo. Los SDK aplican esa tolerancia. - Deduplica por
webhook-id: es estable entre reintentos. - Solo https: la API no acepta endpoints
http. - Guarda el secreto como la clave de API: en variables de entorno.
Verifica la firma en lugar de filtrar por IP: esta documentación no publica IPs de salida fijas de Constaia.
import express from "express";
import { Constaia, WebhookVerificationError } from "@constaia/sdk";
const constaia = new Constaia();
const app = express();
const seen = new Set<string>(); // en producción, tu base de datos
app.post("/webhooks/constaia", express.raw({ type: "application/json" }), async (req, res) => {
try {
const event = await constaia.webhooks.verify(req.body, req.headers, process.env.CONSTAIA_WEBHOOK_SECRET!);
const id = req.header("webhook-id")!;
if (!seen.has(id)) {
seen.add(id);
// procesa event.type / event.data en segundo plano
}
res.sendStatus(200);
} catch (err) {
if (err instanceof WebhookVerificationError) return res.sendStatus(400);
throw err;
}
});
app.listen(3000);Más detalles y ejemplos en PHP y Python en webhooks.
file_url
Cuando mandas file_url, Constaia descarga el fichero desde sus servidores:
- Solo
https. - No se permiten direcciones privadas ni locales (
400 invalid_file_url). - Tiempo máximo de 15 s y 20 MB.
Si generas URLs firmadas de tu propio almacenamiento para que Constaia las descargue, dales una caducidad corta (unos minutos basta).
Protege tu endpoint de subida
El endpoint de tu backend que recibe el fichero y llama a Constaia gasta tus créditos. Protégelo como cualquier acción de pago:
- Autenticación: que solo lo usen usuarios con sesión (o un token de un solo uso ligado a la inscripción).
- Límite por usuario e IP: por ejemplo, unos pocos análisis por minuto.
- CSRF: si usas cookies de sesión, exige un token CSRF (el widget admite cabeceras propias con el atributo
headers). - Decide tú las opciones:
expectycheckslos fija el servidor. Lo que mande el navegador es, como mucho, una pista. - Valida tamaño y tipo antes de reenviar (20 MB, JPEG/PNG/WEBP/HEIC/PDF) para no gastar peticiones en vano.
import express from "express";
import multer from "multer";
import rateLimit from "express-rate-limit";
import { Constaia } from "@constaia/sdk";
import { requireSession } from "./auth"; // tu middleware de sesión
const constaia = new Constaia();
const upload = multer({ limits: { fileSize: 20 * 1024 * 1024 } });
export const router = express.Router();
router.post(
"/api/constaia",
requireSession,
rateLimit({ windowMs: 60_000, limit: 5 }),
upload.single("file"),
async (req, res) => {
if (!req.file) return res.status(400).json({ error: { message: "Falta el fichero." } });
const analysis = await constaia.analyze(
{ file: req.file.buffer, filename: req.file.originalname },
{ expect: ["es_dni", "es_nie", "passport"], metadata: { user_id: String(req.session.userId) } },
);
await saveAnalysisForUser(req.session.userId, analysis.id, analysis.verdict?.status ?? null);
res.json(analysis);
},
);
declare function saveAnalysisForUser(userId: string, analysisId: string, status: string | null): Promise<void>;No te fíes de lo que vuelve del navegador
El navegador puede mostrar el veredicto, pero no debe ser quien lo decide. Si el formulario final envía
verdict: "valid" o un analysis_id, un usuario puede cambiarlo.
- Guarda en tu servidor el
idy el veredicto en el momento en que llamas a Constaia (como en el ejemplo anterior). - Al procesar el formulario, usa lo que guardaste, o vuelve a leer el análisis con
GET /v1/analyses/{id}y comprueba que pertenece a ese usuario (por ejemplo, conmetadata). - Para el resultado de análisis asíncronos, fíate del webhook verificado, no del cliente.
Otras medidas
- TLS: la API solo se sirve por HTTPS. No desactives la verificación de certificados en tu cliente HTTP.
- Registro de auditoría: la cuenta registra las acciones sensibles (quién, desde qué IP y qué acción).
- Logs: registra
X-Request-Id, no la clave ni el contenido de los documentos.
Informar de una vulnerabilidad
Si encuentras un problema de seguridad en Constaia, escríbenos a hola@constaia.com con los detalles para reproducirlo. No lo publiques hasta que lo hayamos corregido.
Siguientes pasos
Versionado y changelog
Cómo versiona Constaia su API (v1 en la ruta) y sus SDK, qué cambios compatibles pueden llegar sin aviso y cómo escribir código que no se rompa con ellos.
SLA y estado
Endpoint de salud de la API de Constaia, cómo pedir soporte con X-Request-Id, qué SLA hay por plan y cómo hacer tu integración resistente a fallos.