Si tu app aún hace polling para saber si una factura fue timbrada o si un pago fue exitoso, estás desperdiciando recursos y añadiendo latencia innecesaria. Los webhooks resuelven exactamente eso: gigstack te notifica en tiempo real cuando algo ocurre, y tu sistema reacciona. Este tutorial te lleva del registro del endpoint hasta el manejo robusto de eventos en producción 2026.
La arquitectura event-driven con webhooks tiene tres ventajas concretas frente al polling:
La gigstack API v2 soporta 18 tipos de eventos en 5 categorías: Payments, Invoices, Receipts, Customers y Services. Un solo webhook puede suscribirse a múltiples eventos simultáneamente.
Obtén tu API token en app.gigstack.pro/settings?subtab=api. Todas las requests requieren Bearer token:
// config/gigstack.js - Node.js 20+ / 2026
const GIGSTACK_API_URL = 'https://api.gigstack.io/v2';
const GIGSTACK_TOKEN = process.env.GIGSTACK_API_KEY;
export const gigstackHeaders = {
'Authorization': `Bearer ${GIGSTACK_TOKEN}`,
'Content-Type': 'application/json',
};
Usa POST /webhooks para crear tu endpoint. La URL debe ser HTTPS en producción. Suscríbete solo a los eventos que necesitas para reducir ruido y mejorar trazabilidad:
// scripts/create-webhook.js
import { gigstackHeaders, GIGSTACK_API_URL } from '../config/gigstack.js';
async function createWebhook() {
const payload = {
url: 'https://tuapp.com/webhooks/gigstack',
events: [
'payment.created',
'payment.succeeded',
'payment.canceled',
'invoice.created',
'invoice.failed',
'receipt.completed',
],
description: 'Produccion - eventos de pagos y facturacion 2026',
status: 'active',
};
const res = await fetch(`${GIGSTACK_API_URL}/webhooks`, {
method: 'POST',
headers: gigstackHeaders,
body: JSON.stringify(payload),
});
const data = await res.json();
console.log('Webhook creado:', data.data.id); // wh_xxxxxxxx
return data.data;
}
createWebhook();
La respuesta incluye el id del webhook (prefijo wh_), que usaras para actualizarlo o eliminarlo.
Tu servidor debe responder con 2xx en menos de 5 segundos. La regla de oro: acusa recibo rapido, procesa despues.
// server/webhooks.js - Express 4.x + Node.js 20
import express from 'express';
const router = express.Router();
// En memoria para dev; usa Redis en produccion
const processedEvents = new Set();
router.post('/gigstack', express.json(), async (req, res) => {
const event = req.body;
// 1. Acusa recibo INMEDIATAMENTE
res.status(200).json({ received: true });
// 2. Idempotencia: evita procesar el mismo evento dos veces
if (processedEvents.has(event.id)) {
console.log(`Evento duplicado ignorado: ${event.id}`);
return;
}
processedEvents.add(event.id);
// 3. Despacha segun tipo de evento
try {
await handleGigstackEvent(event);
} catch (err) {
console.error(`Error procesando ${event.id}:`, err);
// No relanzar - ya respondiste 200, loguea para revision manual
}
});
export default router;
gigstack envia el payload completo del objeto que disparo el evento dentro de data.object. El campo livemode: true indica produccion; false es sandbox (test API key).
// server/handlers/gigstack-events.js
async function handleGigstackEvent(event) {
const { type, data, livemode } = event;
const obj = data.object;
console.log(`[gigstack] Evento: ${type} | livemode: ${livemode}`);
switch (type) {
case 'payment.succeeded':
// Pago confirmado - activa entrega, envia email, actualiza DB
await db.payments.update(
{ externalId: obj.id },
{ status: 'paid', paidAt: new Date(obj.created_at) }
);
await emailService.sendPaymentConfirmation({
to: obj.customer?.email,
amount: obj.amount,
});
break;
case 'invoice.created':
// Factura timbrada ante el SAT - guarda UUID y PDF
await db.invoices.upsert({
gigstackId: obj.id,
uuid: obj.uuid, // UUID CFDI 4.0
xmlUrl: obj.xml_url,
pdfUrl: obj.pdf_url,
status: 'stamped',
});
break;
case 'invoice.failed':
// Error al timbrar - notifica al equipo para revision
console.error('Timbrado fallido:', obj.cfdi_error_code);
await alertService.notify(
`Factura ${obj.id} fallo: ${obj.cfdi_error_description}`
);
break;
case 'receipt.completed':
// Cliente completo su autofactura - sincroniza con ERP
await erpSync.pushInvoice({ receiptId: obj.id, uuid: obj.uuid });
break;
case 'payment.canceled':
await db.payments.update(
{ externalId: obj.id },
{ status: 'canceled' }
);
break;
default:
console.log(`Evento no manejado: ${type}`);
}
}
export { handleGigstackEvent };
Para produccion, reemplaza el Set en memoria por Redis. TTL de 48 horas cubre cualquier ventana de reintentos de gigstack:
// utils/idempotency.js - con ioredis
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
export async function isAlreadyProcessed(eventId) {
const key = `gigstack:event:${eventId}`;
// NX = solo setea si no existe; null = ya existia
const result = await redis.set(key, '1', 'EX', 172800, 'NX');
return result === null;
}
// Uso en el endpoint:
// if (await isAlreadyProcessed(event.id)) return;
Para probar el flujo completo sin deploy, levanta un tunel ngrok hacia tu servidor local:
# 1. Levanta tu servidor Express
node server/index.js
# 2. En otra terminal, crea el tunel
ngrok http 3000
# Resultado: https://abc123.ngrok-free.app
# 3. Registra el webhook con la URL del tunel
curl -X POST https://api.gigstack.io/v2/webhooks \
-H "Authorization: Bearer $GIGSTACK_TEST_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://abc123.ngrok-free.app/webhooks/gigstack",
"events": ["payment.succeeded", "invoice.created"],
"status": "active"
}'
Usa tu test API key para que los eventos tengan livemode: false y no afecten datos reales. El staging comparte la misma URL base: https://api.gigstack.io/v2.
// Listar webhooks activos
const res = await fetch(
`${GIGSTACK_API_URL}/webhooks?status=active&limit=50`,
{ headers: gigstackHeaders }
);
const { data: webhooks } = await res.json();
console.table(
webhooks.map(w => ({ id: w.id, url: w.url, eventos: w.events.length }))
);
// Desactivar temporalmente sin eliminar
await fetch(`${GIGSTACK_API_URL}/webhooks/wh_xxxxxxxx`, {
method: 'PUT',
headers: gigstackHeaders,
body: JSON.stringify({ status: 'inactive' }),
});
// Eliminar definitivamente
await fetch(`${GIGSTACK_API_URL}/webhooks/wh_xxxxxxxx`, {
method: 'DELETE',
headers: gigstackHeaders,
});
Cuando ocurre un evento, gigstack envia un HTTP POST a tu endpoint con esta estructura:
{
"id": "evt_1234567890",
"type": "payment.succeeded",
"created": 1709090576567,
"livemode": true,
"data": {
"object": {
"id": "payment_1234567890",
"amount": 1000.00,
"status": "succeeded"
}
}
}
El campo id (prefijo evt_) es tu clave de idempotencia. livemode distingue produccion de sandbox. data.object contiene el recurso completo que disparo el evento.
Payments: payment.created, payment.updated, payment.succeeded, payment.canceled, payment.deleted, payment.upcoming_due_date
Invoices: invoice.created, invoice.canceled, invoice.failed
Receipts: receipt.created, receipt.updated, receipt.completed, receipt.deleted
Customers: customer.created, customer.updated, customer.deleted
Services: service.created, service.updated, service.deleted
Con los webhooks configurados, tu sistema reacciona automaticamente a cada pago, factura y recibo sin intervencion manual, con cumplimiento SAT incluido. gigstack se encarga del timbrado CFDI 4.0; tu te enfocas en tu logica de negocio. Comienza en gigstack.pro y conecta tu primer webhook en minutos.
Usa event.id (prefijo evt_) como clave unica en Redis con TTL de 48h. Antes de procesar cualquier evento, verifica si ese ID ya existe en Redis: si existe, ignora el evento. Esta es la implementacion de idempotencia del Paso 5. Sin esto, un reintento puede disparar emails duplicados o actualizar saldos dos veces.
Estas usando tu test API key en lugar de la production key. El webhook hereda el modo del API key con el que fue creado. Verifica en app.gigstack.pro/settings?subtab=api que el token corresponde al entorno correcto. Los webhooks creados con test key solo reciben eventos de sandbox.
Falta el middleware JSON en Express. Agrega express.json() antes del handler o como middleware global. Sin el, el body llega como buffer y el parse falla silenciosamente. Tambien verifica que el Content-Type del request entrante sea application/json.
Si. Agrega el parametro team al crear el webhook con el ID del equipo destino. Tu API key debe pertenecer a un equipo con gigstack Connect habilitado y el equipo destino debe compartir la misma cuenta de facturacion. Referencia completa en la documentacion oficial.
Recursos: Webhooks gigstack API Docs · gigstack API Reference · webhook.site · ngrok Docs · gigstack Connect Guide
