Integrar facturación electrónica implica manejar información fiscal sensible: RFCs, certificados digitales, montos de operaciones. Un error de seguridad puede exponer datos de tus clientes o permitir la emisión fraudulenta de facturas.
Esta guía cubre las mejores prácticas de seguridad al integrar APIs de facturación CFDI, desde autenticación hasta protección de datos en tránsito y reposo.
La API de gigstack usa JSON Web Tokens (JWT) para autenticación. Entender cómo funcionan es crucial para una integración segura.
Un JWT es un token firmado digitalmente que contiene información sobre tu identidad y permisos. Tiene tres partes separadas por puntos:
Los tokens se generan desde el panel de gigstack:
Incluye el token en el header Authorization de cada request:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Ejemplo en código:
const response = await fetch('https://api.gigstack.io/v2/invoices', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + process.env.GIGSTACK_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify(invoiceData)
});
El error más común: hardcodear tokens en el código. Esto expone tus credenciales si el repositorio es público o se filtra.
❌ Incorrecto:
const API_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';
✅ Correcto:
const API_KEY = process.env.GIGSTACK_API_KEY;
Usa variables de entorno para almacenar credenciales:
# .env (nunca commitear este archivo)
GIGSTACK_API_KEY=tu_token_aqui
GIGSTACK_WEBHOOK_SECRET=tu_secret_aqui
Agrega .env a tu .gitignore:
# .gitignore
.env
.env.local
.env.production
Para ambientes de producción, usa servicios especializados:
Ejemplo con AWS Secrets Manager:
import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';
const client = new SecretsManagerClient({ region: 'us-east-1' });
async function getApiKey() {
const response = await client.send(
new GetSecretValueCommand({ SecretId: 'gigstack/api-key' })
);
return JSON.parse(response.SecretString).apiKey;
}
Los Certificados de Sello Digital (CSD) son la "firma digital" para timbrar facturas. Su protección es crítica.
gigstack almacena los certificados de tus usuarios de forma segura:
Si tu aplicación maneja certificados (no recomendado):
// NUNCA hacer esto:
const privateKey = fs.readFileSync('./certificados/clave.key');
const password = 'mi_password_123'; // ❌ Expuesto
// Mejor práctica:
const privateKey = await getFromSecretManager('csd/client_123/key');
const password = await getFromSecretManager('csd/client_123/password');
Todas las comunicaciones con la API de gigstack usan HTTPS/TLS 1.3. Nunca intentes conectar por HTTP plano.
Verifica certificados en tu cliente HTTP:
// Node.js: verificación habilitada por defecto
// Solo deshabilitar en desarrollo con APIs locales
if (process.env.NODE_ENV === 'production') {
// fetch ya verifica certificados
}
// Si usas axios, nunca hacer esto en producción:
// rejectUnauthorized: false ❌
Para máxima seguridad, puedes verificar el certificado específico de gigstack:
import https from 'https';
const agent = new https.Agent({
ca: fs.readFileSync('./gigstack-ca.pem'),
checkServerIdentity: (host, cert) => {
// Verificar fingerprint del certificado
if (cert.fingerprint !== 'AA:BB:CC:...:expected_fingerprint') {
throw new Error('Certificate mismatch');
}
}
});
fetch('https://api.gigstack.io/v2/invoices', { agent });
Los webhooks de gigstack incluyen una firma para verificar autenticidad.
Sin validación, un atacante podría enviar webhooks falsos a tu endpoint, haciéndote creer que una factura se creó cuando no fue así.
import crypto from 'crypto';
const WEBHOOK_SECRET = process.env.GIGSTACK_WEBHOOK_SECRET;
function verifyWebhookSignature(payload, signature) {
const expectedSignature = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(JSON.stringify(payload))
.digest('hex');
// Comparación segura contra timing attacks
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
app.post('/webhooks/gigstack', (req, res) => {
const signature = req.headers['x-gigstack-signature'];
if (!signature) {
console.warn('Webhook sin firma recibido');
return res.status(401).json({ error: 'Missing signature' });
}
if (!verifyWebhookSignature(req.body, signature)) {
console.warn('Webhook con firma inválida');
return res.status(401).json({ error: 'Invalid signature' });
}
// Firma válida, procesar webhook
processWebhook(req.body);
res.status(200).json({ received: true });
});
Si almacenas datos fiscales en tu base de datos, encripta los campos sensibles:
import crypto from 'crypto';
const ENCRYPTION_KEY = Buffer.from(process.env.ENCRYPTION_KEY, 'hex');
const IV_LENGTH = 16;
function encrypt(text) {
const iv = crypto.randomBytes(IV_LENGTH);
const cipher = crypto.createCipheriv('aes-256-cbc', ENCRYPTION_KEY, iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
return iv.toString('hex') + ':' + encrypted;
}
function decrypt(text) {
const [ivHex, encrypted] = text.split(':');
const iv = Buffer.from(ivHex, 'hex');
const decipher = crypto.createDecipheriv('aes-256-cbc', ENCRYPTION_KEY, iv);
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
// Uso
const rfcEncrypted = encrypt('XAXX010101000');
await db.clients.create({ rfc_encrypted: rfcEncrypted });
// Lectura
const client = await db.clients.findOne({ id: 123 });
const rfc = decrypt(client.rfc_encrypted);
Nunca loguees datos fiscales completos:
// ❌ Incorrecto
console.log('Creando factura para:', client.rfc, client.razonSocial);
// ✅ Correcto
console.log('Creando factura para cliente:', client.id);
console.log('RFC:', maskRFC(client.rfc)); // XAXX****1000
function maskRFC(rfc) {
return rfc.substring(0, 4) + '****' + rfc.substring(rfc.length - 4);
}
gigstack aplica rate limiting para proteger la infraestructura:
async function requestWithRetry(endpoint, options, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
const response = await fetch(endpoint, options);
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After') || 60;
console.log('Rate limited, esperando ' + retryAfter + ' segundos...');
await sleep(retryAfter * 1000);
continue;
}
return response;
}
throw new Error('Max retries exceeded');
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
Protege tu propio endpoint de webhooks:
import rateLimit from 'express-rate-limit';
const webhookLimiter = rateLimit({
windowMs: 60 * 1000, // 1 minuto
max: 100, // máximo 100 requests
message: { error: 'Too many requests' }
});
app.post('/webhooks/gigstack', webhookLimiter, (req, res) => {
// ...
});
Mantén registro de todas las operaciones fiscales:
async function createInvoiceWithAudit(invoiceData, userId) {
const startTime = Date.now();
try {
const invoice = await createInvoice(invoiceData);
await db.auditLog.create({
action: 'invoice.create',
user_id: userId,
resource_id: invoice.uuid,
client_rfc: maskRFC(invoiceData.clientRfc),
amount: invoice.total,
status: 'success',
duration_ms: Date.now() - startTime,
ip_address: req.ip,
user_agent: req.headers['user-agent'],
timestamp: new Date()
});
return invoice;
} catch (error) {
await db.auditLog.create({
action: 'invoice.create',
user_id: userId,
status: 'error',
error_message: error.message,
duration_ms: Date.now() - startTime,
timestamp: new Date()
});
throw error;
}
}
Configura alertas para eventos sospechosos:
La seguridad en facturación electrónica no es opcional. Una brecha puede resultar en emisión fraudulenta de facturas, exposición de datos fiscales de clientes, y problemas legales serios. Implementa estas prácticas desde el día uno de tu integración.