Guía para desarrolladores · CFDI 4.0

Integra CFDI 4.0 en tu app
con Node.js y Python

De cero a tu primera factura timbrada en menos de 10 minutos, con código listo para usar en sandbox.

Código probado 7 de octubre de 2026 12 min de lectura

Integrar facturación electrónica en México suele implicar XML, certificados, reglas fiscales y la disponibilidad de un PAC. Con Ipsofactura, tu aplicación se conecta a una sola API y la plataforma se encarga del timbrado CFDI 4.0.

En esta guía vas a autenticarte, enviar una factura de ingreso, interpretar la respuesta y dejar tu integración lista para operar de forma segura en producción. Todos los ejemplos funcionan con Node.js 18+ y Python 3.9+.

ℹ️
Trabaja primero en sandboxUsa https://sandbox.api.ipsofactura.com. Los CFDI generados ahí no tienen validez fiscal y no consumen timbres de producción.

El flujo completo

Tu backend prepara los datos fiscales, llama a Ipsofactura y recibe el CFDI timbrado. La misma integración funciona en producción cambiando la base URL y la API key.

Antes de empezar

Necesitas lo siguiente:

  • Una cuenta de Ipsofactura con acceso al sandbox.
  • Una API key de sandbox. Se envía en el header x-api.
  • Una empresa emisora y su Certificado de Sello Digital registrados.
  • Node.js 18 o superior, o Python 3.9 o superior.
🔐
La API key solo vive en tu backendNo la incluyas en JavaScript del navegador, apps móviles, repositorios ni logs. Cárgala desde una variable de entorno o un gestor de secretos.

1. Configura el cliente

Guarda tus credenciales en variables de entorno. Los ejemplos usan IPSO_API_KEY y apuntan al sandbox.

const API_KEY = process.env.IPSO_API_KEY;
const BASE_URL = 'https://sandbox.api.ipsofactura.com';

if (!API_KEY) {
  throw new Error('Falta IPSO_API_KEY');
}
import os

API_KEY = os.environ["IPSO_API_KEY"]
BASE_URL = "https://sandbox.api.ipsofactura.com"

2. Construye la factura

Este ejemplo emite un CFDI de ingreso por $1,000 MXN más IVA. Los RFC son datos de prueba del SAT; sustitúyelos por los datos de tu operación cuando pases a producción.

factura.json
{
  "rfc_emisor": "EKU9003173C9",
  "tipo_comprobante": "I",
  "forma_pago": "03",
  "metodo_pago": "PUE",
  "moneda": "MXN",
  "exportacion": "01",
  "subtotal": 1000.00,
  "total": 1160.00,
  "receptor": {
    "rfc": "URE180429TM6",
    "nombre": "UNIVERSIDAD ROBOTICA ESPANOLA",
    "domicilio_fiscal": "65000",
    "regimen_fiscal": "601",
    "uso_cfdi": "G03"
  },
  "conceptos": [{
    "clave_prod_serv": "84111506",
    "cantidad": 1,
    "clave_unidad": "ACT",
    "descripcion": "Servicio de desarrollo de software",
    "valor_unitario": 1000.00,
    "importe": 1000.00,
    "objeto_imp": "02",
    "impuestos": {
      "traslados": [{
        "base": 1000.00,
        "impuesto": "002",
        "tipo_factor": "Tasa",
        "tasa_o_cuota": "0.160000",
        "importe": 160.00
      }]
    }
  }],
  "impuestos": {
    "total_impuestos_trasladados": 160.00,
    "traslados": [{
      "base": 1000.00,
      "impuesto": "002",
      "tipo_factor": "Tasa",
      "tasa_o_cuota": "0.160000",
      "importe": 160.00
    }]
  }
}
💡
Los catálogos del SAT son códigos, no texto libre601 es el régimen General de Ley Personas Morales, G03 es Gastos en general y 002 identifica IVA. Valida estos valores antes de enviar el request.

3. Timbra el CFDI

Envía el objeto a POST /cfdi/timbrar. La cabecera Idempotency-Key evita que un reintento accidental genere una segunda operación.

import { readFile } from 'node:fs/promises';
import { randomUUID } from 'node:crypto';

const factura = JSON.parse(
  await readFile('factura.json', 'utf8')
);

const response = await fetch(`${BASE_URL}/cfdi/timbrar`, {
  method: 'POST',
  headers: {
    'x-api': API_KEY,
    'Content-Type': 'application/json',
    'Idempotency-Key': randomUUID(),
  },
  body: JSON.stringify(factura),
});

const data = await response.json();
if (!response.ok) {
  throw new Error(`${data.code}: ${data.message}`);
}

console.log('UUID:', data.uuid);
console.log('CFDI:', data.id);
import json
import uuid
import requests

with open("factura.json", encoding="utf-8") as archivo:
    factura = json.load(archivo)

response = requests.post(
    f"{BASE_URL}/cfdi/timbrar",
    headers={
        "x-api": API_KEY,
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json=factura,
    timeout=30,
)

data = response.json()
if not response.ok:
    raise RuntimeError(f"{data['code']}: {data['message']}")

print("UUID:", data["uuid"])
print("CFDI:", data["id"])

Respuesta exitosa

201 Created
{
  "id": "6c5c64d8-f9ce-4c26-8cb7-5e6db3a1ab31",
  "uuid": "6128396F-C09B-4EC6-8699-43DA5A244971",
  "folio": "1",
  "fecha_timbrado": "2026-10-07T18:42:07",
  "rfc_pac": "AAA010101AAA",
  "pac_provider": "SandboxPacHandler",
  "numero_certificado_sat": "30001000000500003416",
  "sello_cfdi": "SANDBOX_sello_cfdi_...",
  "sello_sat": "..."
}

Guarda tanto id como uuid. El primero identifica el recurso dentro de Ipsofactura; el segundo es el folio fiscal del CFDI ante el SAT.

✅
Tu primera factura ya está timbradaCon el id puedes descargar el XML y el PDF, consultar el estatus o iniciar una cancelación desde los endpoints correspondientes.

Idempotencia: reintentar sin duplicar

Un timeout no significa necesariamente que el timbrado falló: la respuesta pudo perderse después de que el PAC certificara el CFDI. Por eso cada operación debe tener una llave de idempotencia estable.

Primer intentoorden-8472
La conexión expira
→
Reintento seguroorden-8472
Devuelve el mismo resultado

Genera la llave a partir de un identificador único de tu negocio —por ejemplo, el ID de la factura o de la orden— y reutilízala en todos los intentos de esa misma operación. No generes una llave nueva dentro del ciclo de reintentos.

async function timbrar(factura, facturaId) {
  return fetch(`${BASE_URL}/cfdi/timbrar`, {
    method: 'POST',
    headers: {
      'x-api': API_KEY,
      'Content-Type': 'application/json',
      'Idempotency-Key': `factura-${facturaId}`,
    },
    body: JSON.stringify(factura),
  });
}
def timbrar(factura, factura_id):
    return requests.post(
        f"{BASE_URL}/cfdi/timbrar",
        headers={
            "x-api": API_KEY,
            "Idempotency-Key": f"factura-{factura_id}",
        },
        json=factura,
        timeout=30,
    )

Manejo de errores y reintentos

No todos los errores se resuelven igual. Corrige los errores de datos antes de repetir la llamada y reserva los reintentos automáticos para rate limits y fallas temporales.

HTTPQué significaAcción¿Reintentar?
400Payload o dato fiscal inválidoCorrige el campo indicadoNo
401API key inválida o revocadaVerifica la credencial y el ambienteNo
402Saldo insuficienteAgrega timbres a la cuentaNo
404Empresa o CFDI no encontradoValida el identificadorNo
409Conflicto con un recurso existenteConsulta el recurso creadoNo
422El SAT o el PAC rechazó el CFDILee el código fiscal y corrigeNo
429Límite de solicitudes alcanzadoRespeta Retry-AfterSí
500Error interno temporalReintenta con backoffSí
503Servicio temporalmente no disponibleReintenta con la misma llaveSí
⏱️
Usa backoff exponencial con jitterEspera aproximadamente 1, 2, 4 y 8 segundos entre intentos, agrega una variación aleatoria y limita el total. Reutiliza siempre la misma llave de idempotencia.

Qué hace el enrutamiento multi-PAC

Tu integración no necesita conocer la API particular de cada PAC. Ipsofactura valida la solicitud y la envía a una ruta disponible. Si una ruta no puede atenderla antes de confirmar el timbrado, la plataforma puede seleccionar otra sin cambiar el contrato de tu aplicación.

El failover no sustituye la idempotencia de tu lado. Conserva el mismo identificador durante toda la operación para que un timeout ambiguo pueda reconciliarse sin riesgo de duplicidad.

Sandbox y producción

SandboxProducción
Base URLsandbox.api.ipsofactura.comapi.ipsofactura.com
CredencialAPI key de pruebasAPI key de producción
RFC y CSDDatos de prueba del SATDatos reales del emisor
Validez fiscalNoSí
CostoSin consumo de timbres$0.10 MXN + IVA por timbre

Checklist para salir a producción

  • La API key está en un gestor de secretos y nunca llega al frontend.
  • El CSD del emisor está vigente y corresponde al RFC configurado.
  • Los importes, impuestos y catálogos del SAT se validan antes del request.
  • Cada factura tiene una llave de idempotencia estable.
  • Los errores 400 y 422 se guardan para corrección, no se reintentan en ciclo.
  • Los errores temporales usan backoff exponencial y un máximo de intentos.
  • Tu sistema conserva el id, el uuid y el XML timbrado.
  • Probaste el flujo completo de timbrado, descarga, consulta y cancelación.

Empieza a timbrar hoy

Prueba la integración en sandbox y lleva tu primer CFDI de código a producción con una sola API.

Solicitar acceso a Ipsofactura →