Devs
February 20, 2026

Stripe + gigstack con Código: Tutorial Paso a Paso con Código Real | gigstack API 2026

Si usas Stripe como procesador de pagos y necesitas emitir CFDIs automáticamente en México, esta es la integración que tu stack necesitaba. En este tutorial conectarás Stripe con gigstack usando Node.js para que cada payment_intent.succeeded genere una factura timbrada ante el SAT sin intervención manual.

¿Por qué Stripe + gigstack?

Stripe maneja el cobro. gigstack maneja el cumplimiento fiscal mexicano: CFDI 4.0, timbrado SAT y complementos de pago. La combinación elimina el loop manual de cobro → crear factura → enviar PDF → registrar en SAT. Con esta integración, ese flujo completo ocurre en segundos, automáticamente.

Requisitos previos:

Paso 1: Configurar el entorno

Instala las dependencias necesarias:

npm init -y
npm install stripe express dotenv axios

Crea tu archivo .env:

STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
GIGSTACK_API_KEY=your_gigstack_api_key
GIGSTACK_BASE_URL=https://api.gigstack.io/v2
PORT=3000

Paso 2: Crear el cliente en gigstack

Antes de generar facturas, el cliente debe existir en gigstack con su información fiscal. Este endpoint valida el RFC contra el SAT (verificación EFOS incluida).

// gigstack-client.js
const axios = require('axios');
require('dotenv').config();

const gigstackApi = axios.create({
  baseURL: process.env.GIGSTACK_BASE_URL,
  headers: {
    'Authorization': `Bearer ${process.env.GIGSTACK_API_KEY}`,
    'Content-Type': 'application/json'
  }
});

async function createOrGetClient(stripeCustomer) {
  try {
    // Verificar si ya existe por external_id
    const search = await gigstackApi.get(
      `/clients?external_id=${stripeCustomer.id}`
    );
    if (search.data.data.length > 0) {
      return search.data.data[0];
    }
  } catch (err) {
    // No encontrado, continuar con creación
  }

  // Crear nuevo cliente con datos fiscales
  const response = await gigstackApi.post('/clients', {
    name: stripeCustomer.metadata.razon_social || stripeCustomer.name,
    rfc: stripeCustomer.metadata.rfc,
    email: stripeCustomer.email,
    tax_system: stripeCustomer.metadata.regimen_fiscal || '601',
    cfdi_use: stripeCustomer.metadata.uso_cfdi || 'G03',
    address: {
      zip: stripeCustomer.metadata.codigo_postal,
      country: 'MEX'
    },
    external_id: stripeCustomer.id
  });

  console.log('Cliente creado en gigstack:', response.data.data.id);
  return response.data.data;
}

module.exports = { gigstackApi, createOrGetClient };

Nota 2026: La API v2 de gigstack valida automáticamente el RFC contra el EFOS (lista negra SAT). Si el RFC está en esa lista, el endpoint retorna 400 con RFC_IN_EFOS_LIST.

Paso 3: Crear la factura al recibir el pago

Este es el corazón de la integración. Cuando Stripe dispara payment_intent.succeeded, llamamos a gigstack para crear y timbrar el CFDI 4.0 automáticamente.

// invoice-generator.js
const { gigstackApi } = require('./gigstack-client');

async function createInvoiceFromStripePayment(paymentIntent, gigstackClientId) {
  const amountMXN = paymentIntent.amount / 100; // Stripe usa centavos

  // Calcular base sin IVA (16%)
  const subtotal = parseFloat((amountMXN / 1.16).toFixed(2));

  const invoicePayload = {
    client_id: gigstackClientId,
    payment_form: '03', // Transferencia electrónica
    items: [
      {
        description: paymentIntent.metadata.description || 'Servicio de software',
        product_key: paymentIntent.metadata.sat_product_key || '81112501',
        unit_key: 'E48',
        quantity: 1,
        unit_price: subtotal,
        taxes: [
          {
            type: 'IVA',
            rate: 0.16,
            factor: 'Tasa',
            withholding: false
          }
        ]
      }
    ],
    currency: paymentIntent.currency === 'mxn' ? 'MXN' : 'USD',
    // exchange_rate es opcional: gigstack usa el tipo de cambio del día
    external_id: paymentIntent.id, // Idempotencia: evita duplicados
    notes: `Pago procesado vía Stripe. ID: ${paymentIntent.id}`
  };

  const response = await gigstackApi.post('/invoices/income', invoicePayload);

  console.log(`CFDI timbrado - Folio: ${response.data.data.folio_number}`);
  console.log(`UUID SAT: ${response.data.data.uuid}`);

  return response.data.data;
}

module.exports = { createInvoiceFromStripePayment };

Paso 4: Servidor de Webhooks

Conecta todo en un servidor Express que escuche los eventos de Stripe y accione gigstack:

// server.js
const express = require('express');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const { gigstackApi, createOrGetClient } = require('./gigstack-client');
const { createInvoiceFromStripePayment } = require('./invoice-generator');
require('dotenv').config();

const app = express();

// CRÍTICO: raw body para verificar firma de Stripe
app.post(
  '/webhook/stripe',
  express.raw({ type: 'application/json' }),
  async (req, res) => {
    const sig = req.headers['stripe-signature'];
    let event;

    try {
      event = stripe.webhooks.constructEvent(
        req.body,
        sig,
        process.env.STRIPE_WEBHOOK_SECRET
      );
    } catch (err) {
      console.error('Firma inválida:', err.message);
      return res.status(400).send(`Webhook Error: ${err.message}`);
    }

    if (event.type === 'payment_intent.succeeded') {
      const paymentIntent = event.data.object;

      // Solo facturar si el cliente proporcionó su RFC
      if (!paymentIntent.metadata?.rfc) {
        return res.json({ received: true, invoiced: false });
      }

      try {
        // 1. Recuperar cliente de Stripe
        const stripeCustomer = await stripe.customers.retrieve(
          paymentIntent.customer
        );
        stripeCustomer.metadata.rfc = paymentIntent.metadata.rfc;
        stripeCustomer.metadata.razon_social =
          paymentIntent.metadata.razon_social;
        stripeCustomer.metadata.codigo_postal =
          paymentIntent.metadata.codigo_postal;

        // 2. Crear/obtener cliente en gigstack
        const gigstackClient = await createOrGetClient(stripeCustomer);

        // 3. Generar CFDI
        const invoice = await createInvoiceFromStripePayment(
          paymentIntent,
          gigstackClient.id
        );

        // 4. Obtener archivos (PDF + XML)
        const files = await gigstackApi.get(
          `/invoices/income/${invoice.id}/files`
        );
        console.log('PDF:', files.data.data.pdf_url);
        console.log('XML:', files.data.data.xml_url);

        return res.json({
          received: true,
          invoiced: true,
          folio: invoice.folio_number,
          uuid: invoice.uuid
        });
      } catch (error) {
        console.error(
          'Error al generar CFDI:',
          error.response?.data || error.message
        );
        // Siempre responde 200 a Stripe para evitar reintentos no deseados
        // Loggea el error para revisión manual
        return res.json({
          received: true,
          invoiced: false,
          error: error.message
        });
      }
    }

    res.json({ received: true });
  }
);

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

Paso 5: Registrar webhook de gigstack para errores SAT

Los CFDIs pueden fallar por validaciones del PAC después de enviados (RFC inválido, complemento incorrecto, etc.). Suscríbete a los eventos de gigstack para capturar estos casos:

// setup-gigstack-webhook.js
const { gigstackApi } = require('./gigstack-client');

async function registerGigstackWebhook() {
  const response = await gigstackApi.post('/webhooks', {
    url: 'https://tu-dominio.com/webhook/gigstack',
    events: [
      'invoice.created',
      'invoice.error',
      'invoice.cancelled'
    ],
    secret: 'tu_webhook_secret_gigstack'
  });

  console.log('Webhook registrado:', response.data.data.id);
}

registerGigstackWebhook();

Paso 6: Testing en Staging

gigstack usa la misma URL base para staging; solo cambia tu API key por la de pruebas. Para Stripe, usa el Stripe CLI para simular eventos localmente sin necesidad de deploy:

# Instalar Stripe CLI
brew install stripe/stripe-cli/stripe

# Autenticar
stripe login

# Escuchar y reenviar a tu servidor local
stripe listen --forward-to localhost:3000/webhook/stripe

# En otra terminal, disparar evento con metadata de RFC
stripe trigger payment_intent.succeeded \
  --add payment_intent:metadata.rfc=XAXX010101000 \
  --add payment_intent:metadata.razon_social="Empresa Demo SA de CV" \
  --add payment_intent:metadata.codigo_postal=06600

Un CFDI exitoso retorna un uuid real timbrado en el ambiente de pruebas del PAC. Verifica el estado en status.gigstack.io.

Consideraciones de producción

Idempotencia: El campo external_id en el payload de gigstack evita duplicados si Stripe reintenta el webhook. gigstack retorna 409 si ya existe una factura con ese external_id. Siempre úsalo con el ID del payment_intent.

Rate limiting: La API v2 de gigstack acepta hasta 100 requests/minuto por API key. Para procesar pagos en batch, implementa una cola con BullMQ + Redis en lugar de llamadas síncronas directas.

Pagos en USD: Envía currency: 'USD' en el payload y omite exchange_rate. gigstack obtiene automáticamente el tipo de cambio oficial del Banxico para el CFDI. Solo incluye exchange_rate si tienes un tipo de cambio contractual específico.

FAQ Técnica

1. ¿Por qué recibo error 400 con "RFC_IN_EFOS_LIST"?

El RFC del cliente está en la lista de contribuyentes con operaciones simuladas del SAT. gigstack valida contra el EFOS automáticamente. No es posible emitir CFDI a estos RFC. Soluciones: notifica al cliente que su RFC está bloqueado por el SAT, o usa RFC genérico de público en general (XAXX010101000) si el monto es menor a $2,000 MXN.

2. El webhook de Stripe llega pero la firma falla. ¿Qué reviso?

Verifica que estés usando express.raw() antes de cualquier middleware express.json() global para esa ruta. Si el body se parsea como JSON antes de la verificación de firma, Stripe lanza WebhookSignatureVerificationError. Segundo, confirma que el STRIPE_WEBHOOK_SECRET corresponda al endpoint específico registrado en el dashboard de Stripe (cada endpoint tiene su propio secret).

3. ¿Qué pasa si gigstack está caído cuando llega el webhook de Stripe?

El servidor responde 200 a Stripe (como en el código de este tutorial) y loggea el error. Stripe no reintentará si recibe 200. Implementa un worker que revise cada hora los payment_intent de Stripe que no tengan invoice_id en su metadata, y procese los pendientes. Monitorea el estado de la API en status.gigstack.io.

4. ¿Cómo evito generar una factura si el cliente ya la solicitará por el portal de autofactura?

Agrega un campo de metadata en Stripe al crear el PaymentIntent: facturacion_automatica: 'false'. En tu webhook, revisa ese valor antes de llamar a gigstack. Los clientes que prefieran autofacturar por su cuenta pueden indicarlo en el checkout; los demás reciben la factura automáticamente.

Recursos

Blogs que te pueden gustar