API
Timbrar Retencion

timbrarRetencion

Esta sección detalla la operación del servicio de timbrado para Comprobantes Fiscales de Retenciones e Información de Pagos.

Descripción de la Operación

Esta operación permite timbrar un CFDI de Retenciones e Información de Pagos (también conocido como “constancia de retenciones”). Este tipo de comprobante se utiliza para amparar las retenciones de impuestos efectuadas por los contribuyentes.

El servicio recibe el XML de la retención (versión 2.0), lo valida y lo timbra ante el SAT, devolviendo el XML completo con el Timbre Fiscal Digital (TFD) incorporado.

Parámetros de Entrada (Input)

Parámetro Tipo de Dato Descripción
apikey string Credencial de acceso al servicio (Solicita aquí).
xml string Contenido del documento XML de la constancia de retenciones (versión 2.0).

Parámetros de Salida (Output) - RespuestaTimbrado

La respuesta de esta operación es idéntica a la de la operación timbrar.

Atributo Tipo de Dato Descripción
code string Código de respuesta de la operación.
message string Mensaje detallado de la respuesta.
data string XML de la retención timbrada en caso de éxito.

Ejemplo de Código

A continuación se presenta un ejemplo de cómo construir la solicitud y procesar la respuesta.

Solicitud (Request)

Herramienta svcutil

Descarga e instala la herramienta svcutil

Ejecuta el comando siguiente (DESARROLLO)

svcutil.exe https://dev.facturaloplus.com/ws/servicio.do?wsdl /out:ServicioTimbradoClient.cs /config:app.config

Esto genera dos archivos: ServicioTimbradoClient.cs y la configuración en app.config

Implementación

 private static string GetXmlRetencion() => """ 
      <?xml version="1.0" encoding="UTF-8"?>
      <retenciones:Retenciones 
          xmlns:retenciones="http://www.sat.gob.mx/esquemas/retencionpago/2" 
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
          xsi:schemaLocation="http://www.sat.gob.mx/esquemas/retencionpago/2 http://www.sat.gob.mx/esquemas/retencionpago/2/retencionpagov2.xsd" 
          Version="2.0" FolioInt="123" FechaExp="2025-07-05T10:00:00" LugarExpRetenc="64000" CveRetenc="14">
          <retenciones:Emisor RfcE="ABC010101XYZ" NomDenRazSocE="Empresa Emisora SA de CV" RegimenFiscalE="601"/>
          <retenciones:Receptor NacionalidadR="Nacional">
              <retenciones:Nacional RfcR="XAXX010101000" NomDenRazSocR="Receptor Nacional SA de CV" DomicilioFiscalR="67890"/>
          </retenciones:Receptor>
          <retenciones:Periodo MesIni="06" MesFin="06" Ejerc="2025"/>
          <retenciones:Totales montoTotOperacion="10000" montoTotGrav="10000" montoTotExent="0" montoTotRet="1000">
              <retenciones:ImpRetenidos BaseRet="10000" Impuesto="01" montoRet="1000" TipoPagoRet="Pago definitivo"/>
          </retenciones:Totales>
      </retenciones:Retenciones>
      """;

 public async Task<RespuestaTimbrado> TimbrarRetencionAsync(string apiKey, string xml)
  {
      using var client = new ServicioTimbradoWSPortTypeClient("ServicioTimbradoWSPort");
      try
      {
          var response = await client.timbrarRetencionAsync(apiKey, xml);
          return new RespuestaTimbrado
          {
              Code = response.code,
              Message = response.message,
              Data = response.data
          };
      }
      catch (Exception ex)
      {
         Console.WriteLine($"Error al timbrar retención: {ex.Message}");
         throw;
      }
  }

  public class RespuestaTimbrado
  {
      public string? Code { get; set; }
      public string? Message { get; set; }
      public string? Data { get; set; }
  }

 // Ejemplo de uso
 public async Task EjemploUsoTimbrarRetencionAsync()
 {
     string apiKey = "TU_API_KEY_AQUI";
     string xml = GetXmlRetencion();

     var resultado = await TimbrarRetencionAsync(apiKey, xml);

     if (resultado?.Code == "200")
     {
         Console.WriteLine("¡Timbrado de Retención Exitoso!");
         Console.WriteLine(resultado.Data);
     }
     else
     {
         Console.WriteLine($"Error: {resultado?.Code} - {resultado?.Message}");
     }
 }

Herramienta wsimport

Java incluye la herramienta wsimport en el JDK para generar las clases cliente a partir de un WSDL.

Ejecuta el siguiente comando en tu terminal para el ambiente de DESARROLLO:

wsimport -keep -p com.facturaloplus.cliente https://dev.facturaloplus.com/ws/servicio.do?wsdl

-keep: Conserva los archivos fuente .java generados.

-p: Especifica el paquete (package) donde se guardarán las clases.

Implementación

import java.util.concurrent.CompletableFuture;

public class TimbrarRetencionService {

  // ... (Definición de ExecutorService, Logger, etc.)

  public String generarXmlRetencion() {
      return """
      <?xml version="1.0" encoding="UTF-8"?>
      <retenciones:Retenciones 
          xmlns:retenciones="http://www.sat.gob.mx/esquemas/retencionpago/2" 
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
          xsi:schemaLocation="http://www.sat.gob.mx/esquemas/retencionpago/2 http://www.sat.gob.mx/esquemas/retencionpago/2/retencionpagov2.xsd" 
          Version="2.0" FolioInt="123" FechaExp="2025-07-05T10:00:00" LugarExpRetenc="64000" CveRetenc="14">
          <retenciones:Emisor RfcE="ABC010101XYZ" NomDenRazSocE="Empresa Emisora SA de CV" RegimenFiscalE="601"/>
          <retenciones:Receptor NacionalidadR="Nacional">
              <retenciones:Nacional RfcR="XAXX010101000" NomDenRazSocR="Receptor Nacional SA de CV" DomicilioFiscalR="67890"/>
          </retenciones:Receptor>
          <retenciones:Periodo MesIni="06" MesFin="06" Ejerc="2025"/>
          <retenciones:Totales montoTotOperacion="10000" montoTotGrav="10000" montoTotExent="0" montoTotRet="1000">
              <retenciones:ImpRetenidos BaseRet="10000" Impuesto="01" montoRet="1000" TipoPagoRet="Pago definitivo"/>
          </retenciones:Totales>
      </retenciones:Retenciones>
      """;
  }

  public CompletableFuture<RespuestaTimbrado> timbrarRetencionAsync(String apiKey, String xml) {
      return CompletableFuture.supplyAsync(() -> {
          try {
              ServicioTimbradoWS service = new ServicioTimbradoWS();
              ServicioTimbradoWSPortType port = service.getServicioTimbradoWSPort();
              
              Respuesta response = port.timbrarRetencion(apiKey, xml);

              RespuestaTimbrado resultado = new RespuestaTimbrado();
              resultado.setCode(response.getCode());
              resultado.setMessage(response.getMessage());
              resultado.setData(response.getData());
              return resultado;

          } catch (Exception ex) {
              throw new RuntimeException("Error al timbrar retención", ex);
          }
      }, executor);
  }

  // Ejemplo de uso
  public static void main(String[] args) {
      TimbrarRetencionService service = new TimbrarRetencionService();
      String apiKey = "TU_API_KEY_AQUI";
      String xml = service.generarXmlRetencion();

      service.timbrarRetencionAsync(apiKey, xml).whenComplete((resultado, ex) -> {
          if (ex != null) {
              System.err.println("Error: " + ex.getMessage());
          } else if ("200".equals(resultado.getCode())) {
              System.out.println("¡Timbrado de Retención Exitoso!");
              System.out.println(resultado.getData());
          } else {
              System.err.println("Error: " + resultado.getCode() + " - " + resultado.getMessage());
          }
          service.shutdown();
      }).join();
  }
}

Herramienta Zeep

Para interactuar con servicios SOAP en Python, la librería zeep es una excelente opción. Proporciona una interfaz limpia y moderna.

Instala la librería usando pip:

pip install zeep

Implementación

El siguiente código muestra una implementación robusta utilizando zeep y asyncio para realizar llamadas asíncronas al servicio web.

import asyncio
from zeep.asyncio import AsyncClient

def generar_xml_retencion() -> str:
    return """\
      <?xml version="1.0" encoding="UTF-8"?>
      <retenciones:Retenciones 
          xmlns:retenciones="http://www.sat.gob.mx/esquemas/retencionpago/2" 
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
          xsi:schemaLocation="http://www.sat.gob.mx/esquemas/retencionpago/2 http://www.sat.gob.mx/esquemas/retencionpago/2/retencionpagov2.xsd" 
          Version="2.0" FolioInt="123" FechaExp="2025-07-05T10:00:00" LugarExpRetenc="64000" CveRetenc="14">
          <retenciones:Emisor RfcE="ABC010101XYZ" NomDenRazSocE="Empresa Emisora SA de CV" RegimenFiscalE="601"/>
          <retenciones:Receptor NacionalidadR="Nacional">
              <retenciones:Nacional RfcR="XAXX010101000" NomDenRazSocR="Receptor Nacional SA de CV" DomicilioFiscalR="67890"/>
          </retenciones:Receptor>
          <retenciones:Periodo MesIni="06" MesFin="06" Ejerc="2025"/>
          <retenciones:Totales montoTotOperacion="10000" montoTotGrav="10000" montoTotExent="0" montoTotRet="1000">
              <retenciones:ImpRetenidos BaseRet="10000" Impuesto="01" montoRet="1000" TipoPagoRet="Pago definitivo"/>
          </retenciones:Totales>
      </retenciones:Retenciones>
    """

class TimbradoService:
    def __init__(self, wsdl_url: str):
        self.wsdl_url = wsdl_url
        self.async_client = AsyncClient(self.wsdl_url)

    async def timbrar_retencion_async(self, api_key: str, xml: str) -> RespuestaTimbrado:
        response = await self.async_client.service.timbrarRetencion(
            apikey=api_key,
            xml=xml
        )
        return RespuestaTimbrado(
            code=response.code,
            message=response.message,
            data=response.data
        )

async def main():
    service = TimbradoService("https://dev.facturaloplus.com/ws/servicio.do?wsdl")
    api_key = "TU_API_KEY_AQUI";
    xml = generar_xml_retencion();

    resultado = await service.timbrar_retencion_async(api_key, xml);

    if resultado.code == "200":
        print("¡Timbrado de Retención Exitoso!");
        print(resultado.data);
    else:
        print(f"Error: {resultado.code} - {resultado.message}");

if __name__ == "__main__":
    asyncio.run(main());

Herramienta SoapClient

PHP tiene soporte nativo para SOAP a través de la extensión SOAP. Asegúrate de que la extensión php-soap esté habilitada en tu archivo php.ini.

Implementación

El siguiente código muestra una implementación orientada a objetos para consumir el servicio de timbrado.

<?php
function generarXmlRetencion(): string {
    return <<<'XML'
      <?xml version="1.0" encoding="UTF-8"?>
      <retenciones:Retenciones 
          xmlns:retenciones="http://www.sat.gob.mx/esquemas/retencionpago/2" 
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
          xsi:schemaLocation="http://www.sat.gob.mx/esquemas/retencionpago/2 http://www.sat.gob.mx/esquemas/retencionpago/2/retencionpagov2.xsd" 
          Version="2.0" FolioInt="123" FechaExp="2025-07-05T10:00:00" LugarExpRetenc="64000" CveRetenc="14">
          <retenciones:Emisor RfcE="ABC010101XYZ" NomDenRazSocE="Empresa Emisora SA de CV" RegimenFiscalE="601"/>
          <retenciones:Receptor NacionalidadR="Nacional">
              <retenciones:Nacional RfcR="XAXX010101000" NomDenRazSocR="Receptor Nacional SA de CV" DomicilioFiscalR="67890"/>
          </retenciones:Receptor>
          <retenciones:Periodo MesIni="06" MesFin="06" Ejerc="2025"/>
          <retenciones:Totales montoTotOperacion="10000" montoTotGrav="10000" montoTotExent="0" montoTotRet="1000">
              <retenciones:ImpRetenidos BaseRet="10000" Impuesto="01" montoRet="1000" TipoPagoRet="Pago definitivo"/>
          </retenciones:Totales>
      </retenciones:Retenciones>
XML;
}

class TimbradoService {
    private string $wsdlUrl;

    public function __construct(string $wsdlUrl) {
        $this->wsdlUrl = $wsdlUrl;
    }

    public function timbrarRetencion(string $apiKey, string $xml): RespuestaTimbrado {
        $respuesta = new RespuestaTimbrado();
        try {
            $soapClient = new SoapClient($this->wsdlUrl, ['trace' => 1, 'exceptions' => true]);
            $params = [
                'apikey'  => $apiKey,
                'xml' => $xml
            ];

            $response = $soapClient->timbrarRetencion($params);
            
            $respuesta->code = $response->return->code ?? null;
            $respuesta->message = $response->return->message ?? null;
            $respuesta->data = $response->return->data ?? null;

        } catch (Exception $e) {
            $respuesta->code = "CLIENT_ERROR";
            $respuesta->message = $e->getMessage();
        }
        return $respuesta;
    }
}

// Ejemplo de uso
$service = new TimbradoService("https://dev.facturaloplus.com/ws/servicio.do?wsdl");
$apiKey = "TU_API_KEY_AQUI";
$xml = generarXmlRetencion();

$resultado = $service->timbrarRetencion($apiKey, $xml);

header('Content-Type: text/plain');
if ($resultado->code === '200') {
    echo "¡Timbrado de Retención Exitoso!\n";
    echo $resultado.data;
} else {
    echo "Error: {$resultado.code} - {$resultado.message}\n";
}
?>

Respuesta (Response)

La estructura de la respuesta SOAP es la misma que la de la operación timbrar.

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope ...>
   <SOAP-ENV:Body>
      <ns1:timbrarRetencionResponse xmlns:ns1="urn:ws_api">
         <return xsi:type="tns:RespuestaTimbrado">
            <code xsi:type="xsd:string">CÓDIGO</code>
            <message xsi:type="xsd:string">MENSAJE</message>
            <data xsi:type="xsd:string"><![CDATA[XML DE RETENCIÓN TIMBRADO]]></data>
         </return>
      </ns1:timbrarRetencionResponse>
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Códigos de respuesta

Los códigos de respuesta para el timbrado de CFDI de retenciones son importantes para entender el resultado de la solicitud. A continuación se detallan los códigos más comunes que puedes recibir al realizar una solicitud de timbrado de retenciones a través de la API.

Código Descripción
200 Solicitud procesada con éxito
300 API KEY Inválida o inexistente
T301 XML mal formado
T302 El sello del emisor no es válido
T303 El RFC del CSD del emisor no corresponde al RFC del Emisor
T304 CSD del Emisor ha sido revobado
T305 La fecha de emisión no está dentro de la vigencia del CSD del Emisor
T306 La llave utilizada para sellar debe ser un CSD
T307 El CFDI contiene un timbre previo
T308 El CSD del emisor no ha sido firmado por uno de los Certificados de autoridad del SAT
T401 El rango de la fecha de generación no debe de ser mayor a 72 horas para la emisión del timbre
T402 RFC del emisor no se encuentra en el régimen de contribuyentes (Lista de validación de régimen) LCO