Devs

Webhooks Event-Driven: Tutorial Paso a Paso con Código Real | gigstack API 2026

Aprende a implementar webhooks event-driven con gigstack para automatizar facturación en tiempo real. Tutorial con código Node.js ejecutable, best practices y manejo de errores.

· Mariana Barreto· Actualizado

Webhooks Event-Driven: Tutorial Paso a Paso

TL;DR

  • Qué vas a aprender: Implementar webhooks event-driven para automatizar facturación en tiempo real cuando ocurren eventos en tu plataforma (pagos, suscripciones, reembolsos)
  • Tiempo estimado: 30-40 minutos
  • Stack técnico: Node.js + Express + gigstack API + webhooks bidireccionales

El problema técnico: sincronización asíncrona y consistencia de datos

La mayoría de developers empiezan integrando facturación con polling o consultas programadas:

// ❌ Mal approach: polling cada 5 minutos
setInterval(async () => {
  const payments = await getNewPayments();
  for (const payment of payments) {
    await createInvoice(payment);
  }
}, 300000);

Problemas con este approach:

  • Latencia de hasta 5 minutos entre pago y factura
  • Consume recursos innecesariamente
  • Difícil manejar retries y errores
  • No escala bien con volumen

Antes intenté conectar una API de facturación tradicional. Tenías que implementar toda la lógica de sincronización, retry, idempotencia, y además aprender toda la parte fiscal. No eres contador, es frustrante como dev. gigstack te quita esa carga fiscal y te da webhooks nativos para event-driven architecture.

¿Qué hace gigstack diferente?

gigstack usa webhooks bidireccionales:

  1. Tú envías webhooks a gigstack: Cuando ocurre un evento en tu plataforma (pago exitoso), notificas a gigstack
  2. gigstack te envía webhooks: Cuando se completa la factura (timbrado exitoso, error, cancelación), gigstack te notifica

Esto crea un flujo completamente asíncrono y resiliente:

Tu app → [pago exitoso] → gigstack → [factura timbrada] → Tu app

Pre-requisitos

  • Cuenta en gigstack (Regístrate aquí)
  • API key (obtenerla en app.gigstack.pro/settings?subtab=api)
  • Node.js 16+ y npm instalados
  • Conocimientos: JavaScript básico, REST APIs, Express.js
  • Endpoint público HTTPS para recibir webhooks (puedes usar ngrok para testing local)

Tutorial paso a paso con código

Paso 1: Setup inicial del proyecto

Instala las dependencias necesarias:

mkdir gigstack-webhooks-demo
cd gigstack-webhooks-demo
npm init -y
npm install express axios dotenv crypto

Crea tu archivo .env:

GIGSTACK_API_KEY=tu_api_key_aqui
WEBHOOK_SECRET=tu_secret_para_verificar_webhooks
PORT=3000

Estructura básica del servidor (index.js):

require('dotenv').config();
const express = require('express');
const crypto = require('crypto');
const axios = require('axios');

const app = express();
app.use(express.json());

const GIGSTACK_API_URL = 'https://api.gigstack.pro/v1';
const GIGSTACK_API_KEY = process.env.GIGSTACK_API_KEY;

// Configuración axios para gigstack
const gigstackClient = axios.create({
  baseURL: GIGSTACK_API_URL,
  headers: {
    'Authorization': `Bearer ${GIGSTACK_API_KEY}`,
    'Content-Type': 'application/json'
  }
});

app.listen(process.env.PORT, () => {
  console.log(`Servidor corriendo en puerto ${process.env.PORT}`);
});

Paso 2: Implementar endpoint para recibir webhooks de gigstack

Cuando gigstack completa una factura, te envía un webhook. Necesitas un endpoint para recibirlo:

// Middleware para verificar firma del webhook
function verifyWebhookSignature(req, res, next) {
  const signature = req.headers['x-gigstack-signature'];
  const timestamp = req.headers['x-gigstack-timestamp'];
  
  if (!signature || !timestamp) {
    return res.status(401).json({ error: 'Missing signature headers' });
  }
  
  // Verificar que el webhook no sea muy antiguo (prevenir replay attacks)
  const currentTime = Math.floor(Date.now() / 1000);
  if (Math.abs(currentTime - timestamp) > 300) { // 5 minutos tolerancia
    return res.status(401).json({ error: 'Webhook timestamp too old' });
  }
  
  // Crear firma esperada
  const payload = JSON.stringify(req.body);
  const expectedSignature = crypto
    .createHmac('sha256', process.env.WEBHOOK_SECRET)
    .update(`${timestamp}.${payload}`)
    .digest('hex');
  
  // Comparación segura para prevenir timing attacks
  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))) {
    return res.status(401).json({ error: 'Invalid signature' });
  }
  
  next();
}

// Endpoint para recibir webhooks de gigstack
app.post('/webhooks/gigstack', verifyWebhookSignature, async (req, res) => {
  const { event_type, data } = req.body;
  
  console.log(`📨 Webhook recibido: ${event_type}`);
  
  try {
    switch (event_type) {
      case 'invoice.issued':
        await handleInvoiceIssued(data);
        break;
      
      case 'invoice.failed':
        await handleInvoiceFailed(data);
        break;
      
      case 'invoice.cancelled':
        await handleInvoiceCancelled(data);
        break;
      
      default:
        console.log(`⚠️ Evento no manejado: ${event_type}`);
    }
    
    // Siempre responder 200 rápidamente
    res.status(200).json({ received: true });
    
  } catch (error) {
    console.error('Error procesando webhook:', error);
    // Aún así devolver 200 para evitar reintentos innecesarios
    res.status(200).json({ received: true, error: error.message });
  }
});

// Handlers para cada tipo de evento
async function handleInvoiceIssued(invoiceData) {
  console.log(`✅ Factura emitida: ${invoiceData.invoice_id}`);
  console.log(`UUID: ${invoiceData.uuid}`);
  console.log(`PDF: ${invoiceData.pdf_url}`);
  
  // Aquí actualizas tu base de datos
  await updatePaymentInDatabase({
    payment_id: invoiceData.metadata?.payment_id,
    invoice_id: invoiceData.invoice_id,
    invoice_uuid: invoiceData.uuid,
    invoice_pdf: invoiceData.pdf_url,
    invoice_xml: invoiceData.xml_url,
    status: 'invoiced'
  });
  
  // Opcional: notificar al cliente
  await sendInvoiceEmailToCustomer(invoiceData);
}

async function handleInvoiceFailed(invoiceData) {
  console.error(`❌ Error en factura: ${invoiceData.invoice_id}`);
  console.error(`Razón: ${invoiceData.error_message}`);
  
  // Log del error para debugging
  await logInvoiceError({
    payment_id: invoiceData.metadata?.payment_id,
    error: invoiceData.error_message,
    error_code: invoiceData.error_code
  });
  
  // Notificar al equipo interno
  await notifyInvoiceError(invoiceData);
}

async function handleInvoiceCancelled(invoiceData) {
  console.log(`🚫 Factura cancelada: ${invoiceData.invoice_id}`);
  
  await updatePaymentInDatabase({
    payment_id: invoiceData.metadata?.payment_id,
    status: 'invoice_cancelled'
  });
}

Paso 3: Enviar eventos a gigstack desde tu app

Cuando ocurre un pago en tu plataforma, envías un evento a gigstack para que genere la factura:

// Endpoint que maneja pagos exitosos en tu app
app.post('/payments/webhook', async (req, res) => {
  const payment = req.body; // Webhook de Stripe, Conekta, etc.
  
  if (payment.status === 'succeeded') {
    try {
      // Crear factura en gigstack
      const invoice = await createInvoiceInGigstack(payment);
      
      console.log(`📄 Factura creada: ${invoice.data.invoice_id}`);
      
      // Guardar referencia en tu DB
      await saveInvoiceReference(payment.id, invoice.data.invoice_id);
      
      res.status(200).json({ success: true });
      
    } catch (error) {
      console.error('Error creando factura:', error.response?.data || error.message);
      
      // Log error pero no fallar el pago
      await logError('invoice_creation_failed', {
        payment_id: payment.id,
        error: error.message
      });
      
      res.status(200).json({ success: true, invoice_error: true });
    }
  }
});

async function createInvoiceInGigstack(payment) {
  return await gigstackClient.post('/invoices', {
    // Información del cliente
    customer: {
      email: payment.customer.email,
      name: payment.customer.name,
      rfc: payment.customer.rfc || 'XAXX010101000', // RFC genérico si no lo tienes
      tax_system: payment.customer.tax_system || '616' // Régimen por defecto
    },
    
    // Items de la factura
    items: payment.line_items.map(item => ({
      description: item.description,
      quantity: item.quantity || 1,
      unit_price: item.amount / 100, // Convertir de centavos a pesos
      // gigstack calcula automáticamente impuestos
    })),
    
    // Metadata para tracking
    metadata: {
      payment_id: payment.id,
      payment_method: payment.payment_method,
      source: 'webhook_automation'
    },
    
    // URL del webhook donde gigstack te notificará
    webhook_url: 'https://tu-dominio.com/webhooks/gigstack'
  });
}

Paso 4: Testing con ngrok

Para probar webhooks localmente, usa ngrok:

# Instalar ngrok
npm install -g ngrok

# Exponer tu servidor local
ngrok http 3000

Ngrok te dará una URL pública como https://abc123.ngrok.io. Úsala como webhook_url en tus requests a gigstack.

Prueba el flujo completo:

# Simular un pago exitoso
curl -X POST http://localhost:3000/payments/webhook \
  -H "Content-Type: application/json" \
  -d '{
    "id": "pay_test_123",
    "status": "succeeded",
    "customer": {
      "email": "test@example.com",
      "name": "Juan Pérez",
      "rfc": "XAXX010101000"
    },
    "line_items": [{
      "description": "Suscripción Premium",
      "amount": 49900
    }]
  }'

Deberías ver:

  1. Log en tu servidor: “📄 Factura creada: inv_xxx”
  2. Después de ~5-10 segundos: “📨 Webhook recibido: invoice.issued”
  3. Log final: “✅ Factura emitida: inv_xxx”

Errores comunes y soluciones

❌ Error 1: “Invalid webhook signature”

Causa: La firma del webhook no coincide o el timestamp es muy antiguo.

Solución:

// Verificar que estás usando el secret correcto
console.log('Webhook secret:', process.env.WEBHOOK_SECRET);

// Verificar formato del payload
console.log('Payload recibido:', JSON.stringify(req.body));

// Verificar timestamp
const timestamp = req.headers['x-gigstack-timestamp'];
const age = Math.floor(Date.now() / 1000) - timestamp;
console.log(`Webhook age: ${age} seconds`);

❌ Error 2: Webhooks duplicados procesándose múltiples veces

Causa: gigstack reintenta webhooks si no recibe respuesta 200.

Solución: Implementar idempotencia:

const processedWebhooks = new Set(); // En producción usar Redis/DB

app.post('/webhooks/gigstack', verifyWebhookSignature, async (req, res) => {
  const webhookId = req.headers['x-gigstack-webhook-id'];
  
  // Verificar si ya procesamos este webhook
  if (processedWebhooks.has(webhookId)) {
    console.log('⏭️ Webhook duplicado, ignorando');
    return res.status(200).json({ received: true, duplicate: true });
  }
  
  processedWebhooks.add(webhookId);
  
  // Procesar webhook...
  // Responder rápido
  res.status(200).json({ received: true });
});

❌ Error 3: “RFC inválido” al crear factura

Causa: RFC no cumple formato del SAT o no existe en padrón.

Solución: Validar RFC antes de enviar:

function validateRFC(rfc) {
  // Persona física: 13 caracteres
  // Persona moral: 12 caracteres
  // RFC genérico: XAXX010101000
  const rfcRegex = /^[A-ZÑ&]{3,4}\d{6}[A-Z0-9]{3}$/;
  
  if (!rfcRegex.test(rfc)) {
    return 'XAXX010101000'; // RFC genérico público
  }
  
  return rfc.toUpperCase();
}

// Usar al crear factura
customer: {
  rfc: validateRFC(payment.customer.rfc)
}

❌ Error 4: Webhook timeout (gigstack no recibe respuesta)

Causa: Procesamiento muy lento en el webhook handler.

Solución: Procesar asíncronamente:

app.post('/webhooks/gigstack', verifyWebhookSignature, async (req, res) => {
  const { event_type, data } = req.body;
  
  // ✅ Responder inmediatamente
  res.status(200).json({ received: true });
  
  // Procesar después (no bloquear respuesta)
  setImmediate(async () => {
    try {
      await processWebhookEvent(event_type, data);
    } catch (error) {
      console.error('Error procesando webhook:', error);
      // Loggear para retry manual si es necesario
    }
  });
});

Best practices

1. Seguridad

  • Siempre verificar firmas: No confíes en webhooks sin verificar la firma HMAC
  • HTTPS obligatorio: gigstack solo envía webhooks a URLs HTTPS en producción
  • Rotate secrets: Cambia tu webhook secret periódicamente

2. Performance

  • Responder rápido: Webhook handlers deben responder 200 en <5 segundos
  • Procesamiento async: Usa queues (Bull, BeeQueue) para procesar eventos pesados
  • Batch updates: Agrupa actualizaciones a DB en lugar de hacer N queries individuales

3. Reliability

  • Idempotencia: Usar IDs únicos para prevenir procesamiento duplicado
  • Retry logic: gigstack reintenta webhooks fallidos (429, 5xx), pero debes manejar edge cases
  • Dead letter queue: Guardar webhooks que fallan después de N reintentos para revisión manual

4. Monitoring

// Logging estructurado para debugging
const logger = {
  webhookReceived: (eventType, webhookId) => {
    console.log(JSON.stringify({
      event: 'webhook_received',
      type: eventType,
      webhook_id: webhookId,
      timestamp: new Date().toISOString()
    }));
  },
  
  webhookProcessed: (eventType, duration) => {
    console.log(JSON.stringify({
      event: 'webhook_processed',
      type: eventType,
      duration_ms: duration,
      timestamp: new Date().toISOString()
    }));
  }
};

5. Testing

  • Usa sandbox de gigstack para testing sin consumir folios reales
  • Implementa test mode flag en webhooks
  • Simula reintentos y fallos en tus tests

Próximos pasos

Una vez que dominas webhooks básicos con gigstack, explora:

  • Complementos de pago: Facturar en parcialidades con complementos automáticos
  • Webhooks bidireccionales avanzados: Sincronización de estado en tiempo real
  • Multi-tenant con gigstack Connect: Facturación para marketplaces
  • Retry policies personalizadas: Configurar comportamiento de reintentos
  • Event sourcing: Usar webhooks como source of truth para event store

Recursos

Prueba webhooks en sandbox ahora

Crea tu cuenta gratuita en gigstack.pro y obtén acceso inmediato a:

  • Ambiente de pruebas completo (sandbox)
  • Webhooks de testing con simulator
  • Logs de webhooks en tiempo real en el dashboard
  • Ejemplos de código en Node.js, Python, PHP
  • Documentación interactiva de API

Event-driven facturación en 30 minutos. Sin overhead fiscal. Con webhooks nativos que simplemente funcionan.

Porque la arquitectura asíncrona no debería ser la parte difícil de tu integración.