Cuando integras facturación CFDI en tu aplicación, necesitas saber cuándo ocurren eventos importantes: una factura se timbró exitosamente, una cancelación fue aceptada, un complemento de pago se generó. Los webhooks son la solución elegante para recibir estas notificaciones en tiempo real.
En esta guía técnica te explicamos cómo implementar webhooks con la API de gigstack, qué eventos están disponibles y las mejores prácticas para construir integraciones robustas.
Un webhook es una llamada HTTP que un servidor hace a tu aplicación cuando ocurre un evento específico. En lugar de hacer polling constante ("¿ya se timbró?", "¿ya se canceló?"), configuras una URL y gigstack te notifica automáticamente.
La API de gigstack emite webhooks para los siguientes eventos:
Tu endpoint debe aceptar requests POST con body JSON. Ejemplo en Node.js/Express:
const express = require('express');
const app = express();
app.use(express.json());
app.post('/webhooks/gigstack', (req, res) => {
const event = req.body;
console.log('Evento recibido:', event.type);
console.log('Datos:', event.data);
// Procesa el evento según su tipo
switch(event.type) {
case 'invoice.created':
handleInvoiceCreated(event.data);
break;
case 'invoice.cancelled':
handleInvoiceCancelled(event.data);
break;
case 'payment.complement_created':
handleComplementCreated(event.data);
break;
}
// Responde 200 para confirmar recepción
res.status(200).json({ received: true });
});
app.listen(3000);
Desde el panel de gigstack o via API:
POST https://api.gigstack.io/v2/webhooks
Authorization: Bearer tu_jwt_token
Content-Type: application/json
{
"url": "https://tu-dominio.com/webhooks/gigstack",
"events": ["invoice.created", "invoice.cancelled", "payment.complement_created"],
"active": true
}
gigstack envía un evento de prueba al registrar el webhook. Verifica en tus logs que llegó correctamente.
Todos los webhooks tienen esta estructura base:
{
"id": "evt_abc123",
"type": "invoice.created",
"created_at": "2026-02-10T15:30:00Z",
"data": {
// Datos específicos del evento
}
}
{
"id": "evt_inv_xyz789",
"type": "invoice.created",
"created_at": "2026-02-10T15:30:00Z",
"data": {
"invoice_id": "inv_abc123",
"uuid": "A1B2C3D4-E5F6-7890-ABCD-EF1234567890",
"folio": "F-001234",
"serie": "A",
"total": 1160.00,
"subtotal": 1000.00,
"currency": "MXN",
"client": {
"id": "cli_def456",
"name": "Empresa SA de CV",
"rfc": "EMP123456ABC"
},
"status": "valid",
"xml_url": "https://storage.gigstack.io/invoices/xyz/invoice.xml",
"pdf_url": "https://storage.gigstack.io/invoices/xyz/invoice.pdf",
"stamped_at": "2026-02-10T15:30:00Z"
}
}
{
"id": "evt_can_abc456",
"type": "invoice.cancelled",
"created_at": "2026-02-10T16:45:00Z",
"data": {
"invoice_id": "inv_abc123",
"uuid": "A1B2C3D4-E5F6-7890-ABCD-EF1234567890",
"cancellation_reason": "02",
"substitute_uuid": "B2C3D4E5-F6A7-8901-BCDE-F12345678901",
"cancelled_at": "2026-02-10T16:45:00Z"
}
}
gigstack espera una respuesta 2xx dentro de 5 segundos. Si tu procesamiento es largo, responde inmediatamente y procesa en background:
app.post('/webhooks/gigstack', async (req, res) => {
// Responde inmediatamente
res.status(200).json({ received: true });
// Procesa en background
processEventAsync(req.body);
});
Los webhooks pueden enviarse más de una vez (reintentos por timeout, etc.). Usa el event.id para detectar duplicados:
const processedEvents = new Set();
function handleWebhook(event) {
if (processedEvents.has(event.id)) {
console.log('Evento duplicado, ignorando:', event.id);
return;
}
processedEvents.add(event.id);
// Procesa el evento...
}
En producción, usa Redis o tu base de datos para persistir los IDs procesados.
gigstack firma cada webhook con un secret. Verifica la firma para asegurar que el request es auténtico:
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
app.post('/webhooks/gigstack', (req, res) => {
const signature = req.headers['x-gigstack-signature'];
if (!verifyWebhookSignature(req.body, signature, WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Procesa el evento...
});
Si tu procesamiento falla, guarda el evento en una cola para reintentarlo después:
async function handleWebhook(event) {
try {
await processEvent(event);
} catch (error) {
console.error('Error procesando evento:', error);
await saveToRetryQueue(event);
}
}
Guarda logs de todos los webhooks recibidos. Te ayudará a debuggear y auditar:
app.post('/webhooks/gigstack', async (req, res) => {
const event = req.body;
// Log completo
await db.webhookLogs.create({
event_id: event.id,
event_type: event.type,
payload: JSON.stringify(event),
received_at: new Date(),
ip_address: req.ip
});
// Procesa...
});
async function handleInvoiceCreated(data) {
const { client, pdf_url, uuid } = data;
await sendEmail({
to: client.email,
subject: 'Tu factura está lista',
body: 'Descarga tu CFDI: ' + pdf_url,
attachments: [{ url: pdf_url }]
});
console.log('Email enviado para factura:', uuid);
}
async function handleInvoiceCreated(data) {
await db.invoices.updateOne(
{ internal_id: data.invoice_id },
{
$set: {
uuid: data.uuid,
status: 'stamped',
xml_url: data.xml_url,
pdf_url: data.pdf_url,
stamped_at: data.stamped_at
}
}
);
}
async function handleInvoiceCreated(data) {
// Crea el asiento contable
await accountingSystem.createEntry({
date: data.stamped_at,
description: 'Factura ' + data.folio,
debit: { account: 'cuentas_por_cobrar', amount: data.total },
credit: { account: 'ingresos', amount: data.subtotal },
credit: { account: 'iva_trasladado', amount: data.total - data.subtotal }
});
}
async function handleInvoiceCancelled(data) {
// Notifica al equipo de finanzas
await slack.sendMessage({
channel: '#finanzas',
text: '⚠️ Factura cancelada: ' + data.uuid + '\nMotivo: ' + data.cancellation_reason
});
// Actualiza tu sistema
await db.invoices.updateOne(
{ uuid: data.uuid },
{ $set: { status: 'cancelled', cancelled_at: data.cancelled_at } }
);
}
Desde app.gigstack.pro puedes ver:
Para probar webhooks en localhost, usa ngrok o similar:
ngrok http 3000
# Obtienes una URL como https://abc123.ngrok.io
# Úsala como webhook URL en gigstack
gigstack reintenta webhooks fallidos con backoff exponencial: 1 min, 5 min, 30 min, 2 hrs, 12 hrs. Después de 24 horas sin éxito, el webhook se marca como fallido pero puedes reenviarlo manualmente.
Sí. Al crear el webhook, especificas qué eventos quieres recibir. No recibirás eventos no suscritos.
Puedes configurar hasta 10 webhooks activos por cuenta. Si necesitas más, contacta soporte.
El payload incluye URLs al XML y PDF, no el contenido directamente. Esto mantiene el payload ligero. Descarga los archivos desde las URLs proporcionadas.
Los webhooks son esenciales para construir integraciones de facturación robustas y en tiempo real. Con gigstack, recibes notificaciones instantáneas de todos los eventos importantes sin polling ni complejidad.
Consulta la documentación completa en docs.gigstack.io y empieza a integrar webhooks en tu aplicación.