Cómo integrar un PSE en tu
software de facturación electrónica
Tutorial completo para conectar tu sistema de facturación a la API de un PSE autorizado por SUNAT. Desde obtener el token hasta recibir el CDR, con ejemplos reales de código.
1. Prerequisitos antes de empezar
Antes de escribir una línea de código, necesitas tener listo lo siguiente:
- ✓ Cuenta en Smart PSE — regístrate en panel.smartpse.pe/register. La cuenta gratuita incluye 30 firmas de prueba.
- ✓ Empresa creada en el panel — añade la empresa (RUC) en la sección Empresas. Recibirás un usuario CPE y token automáticamente.
- ✓ Autorización en SOL — el RUC emisor debe autorizar a Smart PSE desde SOL → Comprobantes → Información del PSE → Alta servicio. Las instrucciones completas te las brindamos al adquirir un paquete.
- ✓ XML UBL 2.1 válido — tu sistema debe generar el XML sin firmar siguiendo el estándar UBL 2.1 de SUNAT. Smart PSE no genera el XML, solo lo firma.
Para pruebas iniciales puedes usar el entorno demo (/api/cpe/generar-demo y
/api/cpe/enviar-demo) que apunta al entorno beta de SUNAT.
No requiere autorización SOL en producción y no consume firmas de tu pack.
2. Paso 1 — Autenticación (obtener token JWT)
Cada empresa en Smart PSE tiene un usuario y contraseña CPE únicos (generados automáticamente al crear la empresa en el panel). Los encontrarás en el detalle de la empresa.
Con estas credenciales obtienes un JWT que dura 10 minutos:
POST https://panel.smartpse.pe/api/auth/cpe/token
Content-Type: application/json
{
"usuario": "ABCD1234", ← usuario CPE de la empresa
"password": "ZPQ98UAN" ← token de la empresa (actúa como contraseña)
}
// Respuesta 200
{
"token_acceso": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expira_en": 600
}
Ejemplo en PHP con cURL:
function obtenerToken(string $usuario, string $password): string
{
$ch = curl_init('https://panel.smartpse.pe/api/auth/cpe/token');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode(['usuario' => $usuario, 'password' => $password]),
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_SSL_VERIFYPEER => true,
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
if (!isset($response['token_acceso'])) {
throw new RuntimeException('Error al obtener token: ' . json_encode($response));
}
return $response['token_acceso'];
}
3. Paso 2 — Firmar el XML (endpoint /generar)
Con el token JWT, envías el XML sin firmar (en base64) y recibes el XML firmado.
El nombre del archivo sigue el patrón SUNAT:
RUC-TipoDoc-Serie-Numero
(ej: 20611544791-01-F001-00000123).
POST https://panel.smartpse.pe/api/cpe/generar
Authorization: Bearer {token_acceso}
Content-Type: application/json
{
"tipo_integracion": 0,
"nombre_archivo": "20611544791-01-F001-00000123",
"contenido_archivo": "base64(xml_sin_firmar)"
}
// Respuesta 200
{
"estado": 200,
"mensaje": "XML firmado correctamente",
"xml": "base64(zip_con_xml_firmado)",
"codigo_hash": "DigestValue del SignedInfo"
}
function firmarXml(string $token, string $nombreArchivo, string $xmlSinFirmar): string
{
$payload = [
'tipo_integracion' => 0,
'nombre_archivo' => $nombreArchivo,
'contenido_archivo' => base64_encode($xmlSinFirmar),
];
$ch = curl_init('https://panel.smartpse.pe/api/cpe/generar');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer ' . $token,
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_SSL_VERIFYPEER => true,
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
if (($response['estado'] ?? 0) !== 200) {
throw new RuntimeException('Error al firmar: ' . ($response['mensaje'] ?? 'desconocido'));
}
// xml es base64 de un ZIP que contiene el XML firmado
return base64_decode($response['xml']);
}
4. Paso 3 — Enviar a SUNAT (endpoint /enviar)
Con el XML firmado (base64), llamas a /api/cpe/enviar.
Smart PSE lo envía a SUNAT y te devuelve el CDR (Constancia de Recepción) como XML en base64.
POST https://panel.smartpse.pe/api/cpe/enviar
Authorization: Bearer {token_acceso}
Content-Type: application/json
{
"nombre_xml_firmado": "20611544791-01-F001-00000123",
"contenido_xml_firmado": "base64(xml_firmado)"
}
// Respuesta 200 — comprobante aceptado
{
"estado": 200,
"mensaje": "Aceptado por SUNAT",
"cdr": "base64(xml_del_cdr)",
"rechazado": false,
"ticket": null
}
// Respuesta 200 — comprobante rechazado por SUNAT
{
"estado": 200,
"mensaje": "0133 — El RUC del emisor no está autorizado como PSE",
"cdr": "base64(xml_del_cdr)",
"rechazado": true,
"ticket": null
}
Importante: el campo cdr contiene
el XML del CDR directamente (no el ZIP). Puedes parsearlo con DOMDocument para leer el código
de respuesta y descripción de SUNAT.
Flujo completo en PHP:
// Flujo completo: firmar + enviar a SUNAT
function emitirComprobante(
string $rucEmpresa,
string $usuario,
string $password,
string $nombreArchivo,
string $xmlSinFirmar
): array {
$token = obtenerToken($usuario, $password);
$xmlFirmado = firmarXml($token, $nombreArchivo, $xmlSinFirmar);
$payload = [
'nombre_xml_firmado' => $nombreArchivo,
'contenido_xml_firmado' => base64_encode($xmlFirmado),
];
$ch = curl_init('https://panel.smartpse.pe/api/cpe/enviar');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer ' . $token,
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_SSL_VERIFYPEER => true,
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
return [
'aceptado' => !($response['rechazado'] ?? true),
'mensaje' => $response['mensaje'],
'cdr_xml' => base64_decode($response['cdr'] ?? ''),
];
}
5. Entorno demo: probar sin afectar producción
Para pruebas usa los endpoints /api/cpe/generar-demo y
/api/cpe/enviar-demo.
Apuntan al entorno beta de SUNAT (e-beta.sunat.gob.pe),
no consumen firmas de tu pack y no afectan los registros de SUNAT producción.
La empresa también debe estar en modo Demo en el panel (al crear o editar la empresa selecciona "entorno: demo").
6. Manejo de errores comunes
0111
El RUC del emisor no ha completado la autorización en SOL. El cliente debe ir a SOL → Comprobantes → Información del PSE → Alta servicio. Las instrucciones completas se brindan al adquirir un paquete.
2335
Error interno del PSE (no de tu integración). Contacta soporte.
0102
Las credenciales SOL del emisor (RUC secundario) no son válidas. Verifica que el usuario CPE de la empresa en Smart PSE sea el correcto.
HTTP 422
Algún campo del request está mal formado. Verifica que
nombre_archivo siga el patrón
RUC-TipoDoc-Serie-Numero y que
contenido_archivo sea base64 válido.
7. Resúmenes diarios y comunicaciones de baja
Los resúmenes diarios de boletas (RC), comunicaciones de baja (RA) y reversiones (RR) son asíncronos: SUNAT devuelve un ticket en lugar del CDR inmediato.
El flujo es ligeramente diferente:
-
1.
Llamas a
/api/cpe/enviarcon el XML del resumen. Recibes"ticket": "2026040700001"en lugar del CDR. - 2. Esperas unos minutos (SUNAT procesa el resumen).
-
3.
Consultas el ticket con
GET /api/cpe/consultar/{nombre_archivo}. Cuando SUNAT lo procesa, recibes el CDR igual que con una factura.
Para más detalles sobre los resúmenes, consulta la documentación completa de la API.
¿Listo para empezar?
Crea tu cuenta en Smart PSE, añade tu primera empresa y prueba la integración en el entorno demo en menos de una hora. Sin tarjeta de crédito.