Skip to main content
POST /cfdi/timbrar-nomina genera, sella y timbra un CFDI 4.0 tipo N (recibo de nómina) con el complemento Nómina 1.2 del SAT. Emites un recibo por empleado y por periodo de pago. Es un endpoint distinto de POST /cfdi/timbrar porque el cuerpo es distinto: no envías conceptos, subtotal ni total. Envías las percepciones, deducciones y otros pagos del periodo y los datos del empleado, y Ipsofactura arma el comprobante completo con la estructura que exige la guía de llenado del SAT.
La referencia campo por campo está en POST /cfdi/timbrar-nomina, generada directamente del código de la API. Esta guía explica las reglas que la referencia no puede expresar.

Qué arma Ipsofactura por ti

Un CFDI de nómina tiene casi todos los atributos del comprobante fijos por norma. No los envías y no son configurables: Los totales del comprobante se calculan a partir de tus totales del complemento:
Del receptor sólo envías rfc, nombre y domicilio_fiscal (código postal). El régimen y el uso de CFDI los fija el endpoint. serie, folio, fecha y lugar_expedicion siguen los mismos defaults que POST /cfdi/timbrar.

Ejemplo: nómina ordinaria de sueldos

Una quincena de un empleado bajo régimen de sueldos (tipo_regimen 02), con ISR retenido y subsidio para el empleo:
Request
Response 201
La respuesta es la misma que la de cualquier timbrado. El XML timbrado, con el nodo nomina12:Nomina completo, se descarga con GET /cfdi/{id}/xml.

Los totales deben cuadrar con el detalle

Los tres totales son obligatorios, incluso en cero, y cada uno debe ser exactamente la suma de su detalle. Si no coinciden, la respuesta es 400 NOMINA_TOTALS_MISMATCH y no se consume timbre:
400 NOMINA_TOTALS_MISMATCH
Te pedimos los totales en vez de calcularlos porque son los que tu sistema de nómina ya determinó: si no cuadran, lo más probable es que el detalle que nos mandas no sea el que pagaste, y eso conviene detectarlo antes de que exista un CFDI.

Qué validamos antes de llamar al PAC

Todo lo que sigue responde 400 VALIDATION_ERROR con el nombre del campo en message, sin consumir timbre. Son las reglas de la guía de llenado y de la matriz de errores de nómina del SAT que dependen sólo del payload.

Periodo y días

  • fecha_inicial_pago ≤ fecha_final_pago, y fecha_pago ≥ fecha_inicial_pago.
  • num_dias_pagados > 0. Acepta fracción: 7.5 se timbra como 7.500 (el SAT exige entero o exactamente tres decimales; nosotros hacemos la conversión).

Empleado

El bloque del registro patronal va completo o no va. Si envías empleador.registro_patronal, son obligatorios num_seguridad_social, fecha_inicio_rel_laboral, antiguedad (si la omites la calculamos, ver abajo), riesgo_puesto y salario_diario_integrado. Si no lo envías, ninguno de esos cinco debe aparecer. Es una regla literal de la guía de llenado; en la práctica una nómina de sueldos (contrato 01 a 08) siempre lleva registro patronal y el bloque completo, y una de asimilados a salarios (tipo_regimen 09, contrato 99) no lleva ninguno. El tipo de contrato decide el régimen y si hay registro patronal. Con tipo_contrato 01 a 08 (una relación laboral), tipo_regimen debe ser 02, 03 o 04 y empleador.registro_patronal es obligatorio. Con 09, 10 o 99, tipo_regimen va de 05 a 99 y el registro patronal no debe venir. La periodicidad sigue al tipo de nómina, no al régimen. Una nómina ordinaria (O) no admite periodicidad_pago 99; una extraordinaria (E) la exige. antiguedad la calculamos si la omites. Cuando hay registro patronal y no envías antiguedad, la calculamos como el lapso en años, meses y días entre fecha_inicio_rel_laboral y fecha_final_pago: del 2020-03-01 al 2026-09-15, P6Y6M14D. Si la envías, la respetamos tal cual, y el SAT la rechaza con NOM55 si no es ese lapso. fecha_inicio_rel_laboral no puede ser posterior a fecha_final_pago.

Catálogos

Cualquier clave fuera del catálogo del SAT responde 400 con la lista de valores aceptados en message. No la degradamos ni la enviamos al PAC. La clave de cada percepción, deducción y otro pago es tu clave interna. El SAT la exige de 3 a 15 caracteres: una clave interna de uno o dos caracteres (1, P1) se rechaza con 400. Si tu catálogo interno usa claves cortas, rellénalas (001, P01).

Horas extra

horas_extra cuelga de la percepción que las paga, y el SAT sólo lo admite bajo tipo_percepcion 019. Enviarlo en cualquier otra percepción es 400.
Percepción 019 con horas extra

Otros pagos y el subsidio para el empleo

Es la regla que más nóminas rechaza, así que va primero: en una nómina de sueldos (tipo_regimen 02) el SAT exige un otro pago con tipo_otro_pago 002 (subsidio para el empleo), aunque el subsidio sea cero, salvo que la nómina traiga un 007 (ISR ajustado por subsidio) o un 008 (subsidio entregado que no correspondía). Sin esa línea el SAT responde NOM105. Validamos esto antes de timbrar.
Subsidio causado pero no entregado (importe en cero)
subsidio_causado no es lo mismo que importe: importe es lo que le entregaste al empleado y subsidio_causado lo que le corresponde según la tabla del subsidio vigente. El SAT compara ambos. Los topes de esa tabla (NOM101, NOM108) cambian cada año con la Resolución Miscelánea, así que no los validamos nosotros: los aplica el SAT al timbrar.

Finiquitos, indemnizaciones y jubilación

Un finiquito o una liquidación se timbra como cualquier otro recibo: sus percepciones van en percepciones[] junto con el sueldo del último periodo. Normalmente es una nómina extraordinaria (tipo_nomina E, periodicidad_pago 99). Lo que cambia es que ciertas claves de percepción exigen un bloque de datos adicional: Cada bloque va una vez por recibo, dentro de nomina, no dentro de cada percepción: describe el total de esas percepciones aunque las separes en varias líneas. Como toda nómina extraordinaria, un finiquito lleva periodicidad_pago 99. Los subtotales de percepciones los calculamos nosotros por clave: el sueldo por un lado, la separación por otro y la jubilación por otro. Tú sólo mandas total_percepciones, que sigue siendo la suma de todas las percepciones.
Finiquito: último sueldo, prima de antigüedad e indemnización
En separacion_indemnizacion los cinco campos son obligatorios y num_anios_servicio va de 0 a 99. ingreso_acumulable e ingreso_no_acumulable los determina tu sistema de nómina conforme a la guía de llenado del SAT; nosotros los transportamos sin recalcularlos. Una jubilación se paga en una exhibición o en parcialidades, nunca de las dos formas: En los dos casos, ingreso_acumulable e ingreso_no_acumulable son obligatorios.
Pensión en parcialidades (044)
Todas estas reglas responden 400 antes de timbrar: Para un jubilado usa tipo_regimen 03: no lleva subsidio para el empleo, que es sólo para 02 (ver la sección anterior).

Lo que valida el SAT y nosotros no

Estas reglas dependen de datos que no están en tu payload (el padrón del SAT, la relación laboral, las tablas fiscales). Llegan como 400 VALIDATION_ERROR después de llamar al PAC, sin consumir timbre, con el código del SAT (NOMxx) en sat_code:
Rechazo del SAT en nómina
Las que más aparecen al integrar:

Cobertura actual

Timbra hoy, verificado contra el ambiente de pruebas del PAC:
  • Nómina ordinaria y extraordinaria (tipo_nomina O / E).
  • Régimen de sueldos (02) con subsidio para el empleo, y asimilados a salarios (09).
  • Percepciones con horas extra (019), incapacidades, otros pagos con subsidio al empleo (002) y compensación de saldos a favor (004).
  • Nómina sin deducciones, y días pagados fraccionarios.
  • Finiquitos: separación e indemnización (022, 023, 025), y jubilación, pensión o retiro en una exhibición (039) o en parcialidades (044). Ver la sección Finiquitos, indemnizaciones y jubilación.
Todavía no soportamos:
  • Percepción 045 (acciones o títulos) y subcontratación (SubContratacion en el receptor).
  • GET /cfdi/{id} devuelve el comprobante (un concepto “Pago de nómina”) pero no el detalle del complemento. Para leer percepciones y deducciones de un recibo ya timbrado descarga el XML con GET /cfdi/{id}/xml.
GET /cfdi/{id}/pdf de un tipo N devuelve un recibo de nómina: patrón y empleado, periodo y días pagados, percepciones, deducciones, otros pagos e incapacidades, el neto a pagar y el timbre con su QR. El PDF se genera una vez y se conserva, así que un recibo cuyo PDF pediste antes de que existiera esta plantilla sigue entregando el documento original.

Idempotencia, reintentos y cancelación

Se comportan igual que en POST /cfdi/timbrar:
  • Reenviar el mismo idempotency_key con el mismo payload devuelve el primer resultado; la misma serie + folio funciona igual. Ver Recuperación tras timeout.
  • Una clave estable natural es nomina-<num_empleado>-<periodo>.
  • La nómina se timbra únicamente por el PAC que acepta el complemento Nómina 1.2, así que no tiene el respaldo de un segundo PAC: si ese proveedor no está disponible recibes 503 y debes reintentar con la misma clave.
  • Un recibo se cancela con POST /cfdi/cancelar como cualquier CFDI. Ver Cancelación.
  • El webhook cfdi.timbrado se dispara igual que para cualquier comprobante. Ver Webhooks.

En el sandbox

El sandbox responde 201 con un UUID simulado y aplica todas las validaciones de esta guía que dependen del payload: totales, catálogos, periodo, CURP, bloque del registro patronal y reglas de otros pagos. Úsalo para probar el contrato de la API y tu manejo de errores. Lo que no puedes verificar ahí:
  • Las reglas del SAT de la sección anterior (NOM55, NOM34, NOM42, identidad del receptor contra el padrón): el sandbox no las aplica.
El XML que devuelve el sandbox para un tipo N sí incluye el complemento nomina12:Nomina completo, con la misma estructura que el de producción, así que puedes validar tu parser y la estructura del complemento contra el XSD del SAT desde el sandbox. El sello y el timbre son simulados y no tienen validez fiscal.
Un CFDI de nómina timbrado en producción es un hecho fiscal real: entra al prellenado de la declaración anual del empleado y al expediente del patrón. Para tu primera nómina real, timbra un solo recibo y valídalo antes de correr la nómina completa.