GuíaEjemplosReferencia API
apisunat.netIngresar

Primeros pasos

  • API SUNAT
  • Inicio rápido
  • Integra con IA
  • Autenticación
  • Planes y límites

Configurar el RUC

  • Emisores
  • Establecimientos y series

Emitir

  • Documentos
  • Idempotencia
  • Estados
  • Correos
  • Catálogos

Integración

  • Errores
  • Webhooks
  • Cambios

Webhooks

Recibe en tu servidor los cambios de estado de tus documentos.

Un webhook es una URL de tu servidor a la que API SUNAT envía un POST cada vez que un documento cambia de estado. Así no necesitas consultar la API para saber si SUNAT aceptó un comprobante.

Configurar

Los webhooks se crean en el panel de apisunat.net, en tu workspace en modo API:

  • Cada webhook pertenece a un modo: Prueba o Producción. Se crea en el modo que tengas elegido en el panel y solo recibe los eventos de los documentos de ese modo.
  • Hasta 10 webhooks por modo.
  • La URL debe ser HTTPS y pública (no se aceptan IP privadas ni localhost).
  • Cada webhook elige qué eventos recibe y tiene su propio secreto whsec_... para verificar la firma. Puedes ver y rotar el secreto en el panel.
  • El botón Probar envía un evento webhook.test.

Prueba y producción

Un documento emitido con una clave asu_test_ solo genera eventos para los webhooks de prueba, y uno emitido con una clave asu_live_ solo para los de producción. Para recibir los eventos de tus pruebas, cambia el panel a Prueba y crea ahí un webhook (puede ser la misma URL que usas en producción; tendrá su propio secreto).

Los webhooks que ya existían antes del 2026-10-28 quedaron en Producción: siguen recibiendo los eventos reales y ya no reciben los de prueba.

El campo livemode del cuerpo indica el modo del evento: true en producción y false en prueba. Úsalo para no mezclar un evento de prueba con tus datos reales.

Eventos

EventoCuándo
document.acceptedSUNAT aceptó el documento.
document.observedSUNAT lo aceptó con observaciones.
document.rejectedSUNAT lo rechazó.
document.failedNo se pudo completar el envío a SUNAT (estado error).
document.voidedEl documento quedó anulado.

Los eventos se envían para facturas, boletas, notas, retenciones y guías emitidas por la API.

Cuerpo

http
POST /tu-webhook HTTP/1.1
Content-Type: application/json
User-Agent: API-SUNAT-Webhooks/1.0
X-Apisunat-Event: document.accepted
X-Apisunat-Id: 0b7c1f8e-2d4a-4e6b-9f10-3a2b1c0d9e8f
X-Apisunat-Attempt: 1
X-Apisunat-Signature: t=1793116800,v1=5f2b8c...
json
{
  "id": "0b7c1f8e-2d4a-4e6b-9f10-3a2b1c0d9e8f",
  "type": "document.accepted",
  "createdAt": "2026-10-27T15:04:07.000Z",
  "livemode": true,
  "data": {
    "id": "3f8c1d2e-6b1a-4c55-9d0e-2a7b3c4d5e6f",
    "type": "01",
    "fullNumber": "F001-125",
    "status": "accepted",
    "environment": "produccion",
    "issuerRuc": "20123456789",
    "sunat": { "ticket": null, "responseCode": "0", "description": "La Factura numero F001-125, ha sido aceptada", "errorCode": null, "errorMessage": null, "attempts": 1 }
  }
}

data es el mismo documento que devuelve GET /documents/{id} (aquí se muestra recortado). id identifica el evento: úsalo para no procesar dos veces la misma entrega. livemode es true para documentos reales (clave asu_live_) y false para documentos de prueba (clave asu_test_).

Verificar la firma

X-Apisunat-Signature tiene la forma t=<timestamp>,v1=<firma>. La firma es el HMAC-SHA256 en hexadecimal de <timestamp>.<cuerpo crudo>, usando como clave el secreto completo del webhook (whsec_...).

  1. Lee el cuerpo crudo, antes de convertirlo a JSON.
  2. Calcula el HMAC y compáralo con v1 en tiempo constante.
  3. Rechaza la entrega si t tiene más de 5 minutos de antigüedad.
js
import { createHmac, timingSafeEqual } from "node:crypto";
import express from "express";

const app = express();
const secret = process.env.APISUNAT_WEBHOOK_SECRET; // whsec_...

app.post("/webhooks/apisunat", express.raw({ type: "application/json" }), (req, res) => {
  const header = req.get("x-apisunat-signature") ?? "";
  const { t, v1 } = Object.fromEntries(header.split(",").map((part) => part.split("=")));
  const expected = createHmac("sha256", secret).update(`${t}.${req.body}`).digest("hex");
  const valid =
    v1 &&
    v1.length === expected.length &&
    timingSafeEqual(Buffer.from(v1), Buffer.from(expected)) &&
    Math.abs(Date.now() / 1000 - Number(t)) < 300;
  if (!valid) return res.status(400).end();

  const event = JSON.parse(req.body);
  // Guarda event.id para ignorar duplicados y procesa en segundo plano.
  res.status(200).end();
});

Entregas y reintentos

  • Una entrega cuenta como recibida cuando tu servidor responde un 2xx en menos de 8 segundos.
  • No se siguen redirecciones.
  • Si falla, se reintenta a los 1, 5, 30, 120 y 360 minutos. Después del sexto intento se marca como fallida.
  • X-Apisunat-Attempt indica el número de intento.
  • Puede llegar más de una entrega del mismo evento: usa id para ignorar duplicados.
  • El orden de llegada no está garantizado. Si necesitas el estado más reciente, consulta GET /documents/{id}.

Responde rápido (por ejemplo, encola el evento y respóndele 200) y procesa después.

Errores< PreviousCambiosNext >

Powered by heyo

On this page

ConfigurarPrueba y producciónEventosCuerpoVerificar la firmaEntregas y reintentos