Factura CFDI 4.0 automática cuando entra el pago: guía para devs con Stripe y Mercado Pago (México, 2026) — Cesar Ayala
← Todos los artículos

Factura CFDI 4.0 automática cuando entra el pago: guía para devs con Stripe y Mercado Pago (México, 2026)

No puedes timbrar un CFDI por tu cuenta: vas a través de un PAC. En un webhook de pago verificado (nunca el redirect), llamas a la API de tu PAC con los datos fiscales del receptor para timbrar el CFDI 4.0, recibes el XML timbrado, el PDF y el UUID, lo envías por correo y guardas el XML. ¿Sin datos fiscales? Emite público en general con el RFC XAXX010101000.

¿Por qué todo tutorial de pagos se detiene antes de la factura?

Abre cualquier tutorial gringo de Stripe. Llega hasta payment_intent.succeeded, te felicita por “aceptar tu primer pago” y termina. En México eso no es el final: la transacción legal no está cerrada hasta que existe un CFDI 4.0 timbrado. El cobro y la factura son dos cosas distintas, y el segundo paso es justo el que ningún tutorial extranjero te cuenta, porque es 100% mexicano.

El CFDI 4.0 (Comprobante Fiscal Digital por Internet) es la factura electrónica obligatoria del SAT: un XML con los datos de la operación que debe validarse y timbrarse —recibir el sello del SAT más un UUID o folio fiscal— para tener validez. No es un PDF bonito; es un documento fiscal regulado.

El otro problema es que casi todo el contenido que sí habla de CFDI es marketing: te enseña el “qué” solo para venderte su producto. Aquí no. Esta guía es vendor-neutral y code-first: te muestro el flujo del ingeniero y las decisiones reales, no un pitch. He cableado CFDI de verdad en producción en Nixbly y FinHOA, y esto completa el pilar de “cómo aceptar pagos en México” que ya armamos: pasarela (Stripe, Mercado Pago) → splits y suscripciones → ahora la capa fiscal.

Una honestidad por adelantado: soy ingeniero, no contador. Te doy el patrón técnico exacto, pero para casos límite fiscales (cancelaciones raras, regímenes especiales, retenciones) habla con tu contador. Y como las reglas del SAT cambian, confirma siempre las vigentes en 2026.

¿Por qué no puedes timbrar tú mismo un CFDI? (PAC + CSD, para ingenieros)

Esta es la parte de la arquitectura que sorprende a todo dev: no puedes timbrar directo con el SAT. El timbrado pasa obligatoriamente a través de un PAC (Proveedor Autorizado de Certificación). El PAC es quien tiene la autorización para sellar tu XML y devolverte el folio fiscal del SAT.

Para emitir necesitas tu CSD (Certificado de Sello Digital): el .cer, el .key y la contraseña que te entrega el SAT. Es tu sello digital de emisor. La buena noticia para devs: las APIs PAC modernas pueden guardar y administrar tu CSD por ti, así no manejas archivos .key en tu backend.

El menú vendor-neutral de APIs REST que envuelven al PAC:

  • Facturapi — muy orientada a developers, multi-RFC, sandbox gratis con una secret key de prueba.
  • Facturama — REST + librerías PHP/.NET/Java/JS/Ruby.
  • Fiscalapi — SDKs C#/Python/JS/PHP/Java/Go.
  • Factura.com, Timbrify, Enlace Fiscal — más opciones.
  • Capas no-code de autofacturación (p. ej. gigstack) si no quieres tocar código.

Todas hacen lo mismo en el fondo: timbran el mismo patrón. Elige por DX y precio, no por marketing.

Un CFDI 4.0 de ingreso contiene, como mínimo:

  • Emisor: tu RFC + régimen fiscal.
  • Receptor: RFC, nombre/razón social, código postal, régimen fiscal y Uso del CFDI.
  • Conceptos: cada línea con su ClaveProdServ y ClaveUnidad.
  • Impuestos: IVA, normalmente 16% (confirma la tasa vigente).
  • Tipo de comprobante I (Ingreso), forma de pago y método de pago.

Algo que ahorra muchos fallos: la mayoría de estas APIs validan el RFC + régimen + código postal del receptor contra el SAT —y contra la lista negra del 69-B— antes de timbrar. Úsalo.

¿Cómo timbrar un CFDI desde un webhook verificado de Stripe o Mercado Pago?

La disciplina central: dispara la facturación desde el webhook verificado por firma, nunca desde el redirect del navegador. El redirect lo puede falsificar un usuario o nunca llegar (cierra la pestaña). El webhook firmado es la única fuente confiable de “el dinero entró”. Si no verificas la firma, no factures. Repaso de eso en verificar la firma del webhook de Stripe.

Primero verificas el evento. Solo después armas y timbras el CFDI.

import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);

export async function stripeWebhook(req, res) {
  let event;
  try {
    // Verifica la firma ANTES de tocar nada fiscal
    event = stripe.webhooks.constructEvent(
      req.rawBody,
      req.headers["stripe-signature"],
      process.env.STRIPE_WEBHOOK_SECRET
    );
  } catch (err) {
    return res.status(400).send(`Firma invalida: ${err.message}`);
  }

  if (event.type !== "payment_intent.succeeded") return res.json({ ok: true });

  const pi = event.data.object;
  // Confirma que SI hubo cobro real antes de facturar
  if (pi.status !== "succeeded" || pi.amount_received <= 0) {
    return res.json({ ok: true });
  }
  // Idempotencia: un webhook puede llegar dos veces
  if (await yaFacturado(pi.id)) return res.json({ ok: true });

  // Devuelve 200 rapido; timbra en una cola si el PAC tarda
  await encolarTimbrado(pi.id);
  return res.json({ ok: true });
}

Luego, ya con los datos fiscales del receptor (los que capturaste en checkout o en tu portal), llamas a la API del PAC. Estos nombres de campo son ilustrativos al estilo Facturapi — revisa la documentación de tu PAC para los campos exactos.

// Ilustrativo — confirma los campos exactos con tu PAC
const factura = await pac.invoices.create({
  customer: {
    legal_name: "ACME SA DE CV",       // razon social tal cual en su CSF
    tax_id: "ACM010101AB1",            // RFC del receptor
    tax_system: "601",                  // regimen fiscal del receptor
    address: { zip: "72000" },          // codigo postal del receptor
  },
  use: "G03",                           // Uso del CFDI (gastos en general)
  payment_form: "04",                   // forma de pago (tarjeta de credito)
  payment_method: "PUE",                // pago en una sola exhibicion
  items: [{
    quantity: 1,
    product: {
      description: "Suscripcion plan Pro (mensual)",
      // ClaveProdServ y ClaveUnidad son ILUSTRATIVAS:
      // eligelas del catalogo del SAT segun el servicio real
      product_key: "81112101",          // ClaveProdServ
      unit_key: "E48",                  // ClaveUnidad (unidad de servicio)
      price: 499.0,                      // subtotal; el IVA 16% se calcula encima
      tax_included: false,
      taxes: [{ type: "IVA", rate: 0.16 }],
    },
  }],
});

// Te regresa: XML timbrado, PDF y el UUID / folio fiscal
await guardarXML(factura.uuid, factura.xml_url, pi.id);
await enviarPorCorreo(cliente.email, factura.pdf_url, factura.xml_url);

Con Mercado Pago el flujo fiscal es idéntico; solo cambia la verificación del webhook (validas la firma de MP y consultas el payment por su id) y el payload. Una vez verificado el evento, el resto —armar, timbrar, guardar XML, enviar— es exactamente el mismo código.

Pipeline de autofacturacion: del pago al CFDI timbrado

Webhook verificadoStripe payment_intent.succeeded / MP payment, firma validada
Arma el CFDI 4.0Receptor (RFC, régimen, CP, Uso) + conceptos + IVA
El PAC timbraValida vs SAT y 69-B, sella con el folio fiscal
XML + PDF + UUIDRecibes el documento legal timbrado
Entrega y guardaCorreo al cliente + guarda el XML por UUID
Todo arranca en el webhook verificado, nunca en el redirect.

Sin datos fiscales en el checkout: público en general vs el portal de autofactura

La realidad: la mayoría de tus clientes no te dan su RFC en el momento de pagar. Pagan y se van. Tienes dos patrones.

Si no hay datos fiscales, emites a PÚBLICO EN GENERAL con el RFC genérico XAXX010101000. Los valores exactos importan, y aquí no hay margen de improvisar:

  • ReceptorNombre debe ser exactamente PUBLICO EN GENERAL (mayúsculas, sin acentos).
  • Régimen fiscal 616 (Sin obligaciones fiscales).
  • Uso del CFDI S01 (Sin efectos fiscales).
  • Código postal del receptor = el mismo de tu lugar de expedición.
  • En el cobro de contado vía webhook, público en general va como PUE (si emites factura global con periodicidad, confirma ese caso con tu PAC y tu contador).
// Fallback publico en general — sin datos fiscales del cliente
const facturaPG = await pac.invoices.create({
  customer: {
    legal_name: "PUBLICO EN GENERAL",   // exacto, mayusculas, sin acentos
    tax_id: "XAXX010101000",            // RFC generico
    tax_system: "616",                   // Sin obligaciones fiscales
    address: { zip: process.env.CP_EMISOR }, // CP de tu lugar de expedicion
  },
  use: "S01",                           // Sin efectos fiscales
  payment_form: "04",
  payment_method: "PUE",                // cobro de contado: PUE
  items: [/* mismos conceptos con ClaveProdServ + ClaveUnidad + IVA */],
});

Patrón A — portal de autofactura: tras pagar, mandas al cliente a una página donde captura su RFC + Uso del CFDI + régimen + código postal y obtiene un CFDI nominativo. Patrón B — público en general por default: facturas todo a público en general y dejas que el cliente pida su factura nominativa dentro del periodo fiscal. La mayoría de los SaaS hacen una mezcla: nominativo si el dato llegó en checkout, público en general si no, y un portal para los que lo pidan después.

Detalle que tumba más timbrados que ningún otro: valida el RFC + régimen + código postal del receptor contra su CSF (constancia de situación fiscal) antes de timbrar. Un solo carácter que no coincida con su constancia rechaza el timbrado.

Público en general vs factura nominativa

Público en general (sin datos)

  • RFC XAXX010101000
  • Nombre: PUBLICO EN GENERAL (sin acentos)
  • Régimen 616 — Sin obligaciones
  • Uso S01 — Sin efectos fiscales
  • CP = el de tu expedicion · cobro de contado va PUE

Nominativa via autofactura

  • El cliente captura su RFC real
  • Elige su Uso del CFDI
  • Indica su régimen fiscal
  • Da su código postal
  • Validas vs su CSF antes de timbrar
Valores exactos del SAT para el RFC genérico, frente al flujo de autofactura.

¿PUE o PPD? La decisión del complemento de pago que los SaaS hacen mal

Aquí está el detalle fiscal que casi todos los que construimos SaaS pasamos por alto, y que tiene que ver con método de pago.

PUE (Pago en Una sola Exhibición): el cliente paga completo al momento de emitir. Resultado: un solo CFDI de Ingreso, sin complemento. Listo.

PPD (Pago en Parcialidades o Diferido): crédito o pagos en parcialidades. Aquí emites un CFDI de Ingreso por el total y, cada vez que efectivamente recibes dinero, debes emitir un Complemento de Pago (REP — Recibo Electrónico de Pago). El SAT lo exige al recibir el pago, no antes.

Para suscripciones (plan anual cobrado mensual) tienes dos caminos: una factura PUE mensual por cada cargo (lo más simple, lo que hace la mayoría de los SaaS) o una factura PPD anual + complementos de pago mensuales.

Mi opinión de ingeniero: default a PUE por pago salvo que tengas una razón real para no hacerlo. Es muchísimo menos código y menos obligaciones ante el SAT. Si te vas por PPD, tu webhook tiene que disparar también un complemento de pago en cada cobro recurrente —no olvides el REP de cada parcialidad— o quedas mal con el SAT.

PUE o PPD: la regla de decision

PUEPagado completo al emitir → un solo CFDI de Ingreso, sin complemento
PPDCredito/parcialidades → CFDI por el total + un Complemento de Pago (REP) en CADA recepcion de dinero
SuscripcionDefault: factura PUE mensual por cargo (menos código). Alternativa: PPD anual + REP mensuales
Si eliges PPDEl webhook también debe emitir el REP en cada cobro recurrente
Como ingeniero, default a PUE por pago salvo que tengas un motivo real para PPD.

¿Qué guardas, qué entregas y qué haces si tienes que cancelar?

Ya timbraste. Ahora la realidad operativa.

Guarda el XML timbrado. Ese es el documento legal, no el PDF. El PDF es la versión humana; el XML es lo que vale ante el SAT. Persístelo de forma durable, indexado por UUID. Si solo guardaste el PDF, no tienes la factura.

Entrega ambos. Manda al cliente el XML + PDF por correo, y muestra el UUID / folio fiscal en tu dashboard y en los recibos. Tus clientes empresariales necesitan el XML para su contabilidad.

Cancelaciones tienen su propio flujo SAT: requieren acuse y un motivo de cancelación, y desde CFDI 4.0 el receptor puede tener que aceptar la cancelación. No es un simple DELETE. Aquí, en serio, apóyate en tu contador.

Reconcilia. Liga cada UUID de vuelta al id del pago (Stripe / Mercado Pago) para que reembolsos y disputas mapeen a la factura correcta. Sin ese vínculo, una devolución es una pesadilla contable.

Y el recordatorio que no caduca: la tasa de IVA y la versión del CFDI las define el SAT y cambian. Confirma las reglas vigentes y la documentación de tu PAC en 2026.

Arma la autofacturacion de punta a punta

  1. 1 · PAC + CSDElige un PAC orientado a developers y carga tu Certificado de Sello Digital
  2. 2 · Valida al receptorRFC + régimen + CP contra el SAT y el 69-B antes de timbrar
  3. 3 · Timbra en el webhook verificadoDispara solo desde el evento con firma validada, nunca el redirect
  4. 4 · Entrega y guardaCorreo con PDF + XML y guarda el XML indexado por UUID
El orden que sigo en produccion para cada nuevo proyecto.

Preguntas frecuentes: RFC, fallos de timbrado, sandbox y costos

¿Puedo probar sin gastar timbres reales? Sí. Los PAC orientados a developers tienen sandbox con una secret key de prueba; los documentos emitidos en pruebas no generan costo. Desarrolla contra CFDIs de prueba antes de pasar a producción.

¿Cuál es el fallo de timbrado más común? Que el RFC + régimen + código postal del receptor no coincidan con su CSF. Por eso se valida antes de timbrar.

¿Cuánto cuesta una factura? Alrededor de $0.60–$1.50 MXN por CFDI en PACs de pago por timbre como Facturapi, sin paquetes que comprar por adelantado (confirma el precio vigente en 2026; algunos esquemas incluyen una suscripción mensual base).

¿Necesito mi propio CSD? Sí, timbras bajo el CSD de tu RFC. Pero muchas APIs PAC lo guardan y administran por ti.

¿Stripe vs Mercado Pago cambia el flujo de CFDI? No. El flujo fiscal es idéntico; solo cambian la verificación del webhook y el payload. Dispara desde el evento verificado en cualquiera de los dos. Si todavía estás eligiendo pasarela, aquí comparo Stripe vs Mercado Pago vs Conekta en México.

Cierre: lanza la capa fiscal, delega los casos límite

El patrón en una línea: webhook verificado → arma el CFDI 4.0 → el PAC timbra → guarda el XML + entrega PDF/UUID. Eso es todo lo que tu backend tiene que orquestar.

Elige el PAC que prefieras: el patrón es portable. No te encadenes al marketing de un solo proveedor. Haz default a PUE por pago, valida el RFC antes de timbrar y trata el XML como tu fuente de verdad.

Y otra vez, claro: soy ingeniero, no contador. Para cancelaciones, PPD y casos límite de régimen, mete a tu contador y confirma las reglas vigentes del SAT. Tú pones la plomería técnica; él pone el criterio fiscal.

Más guías de pagos e integraciones para México en /es/pagos-latam/.

Fuentes oficiales: SAT · Documentación de Facturapi · API de Facturama · Guía de llenado del CFDI global (SAT) (confirma la versión vigente)