Webhooks

Webhooks

Cuando SUNAT acepta o rechaza un documento, te avisamos inmediatamente con un POST firmado a tu URL. Tu backend no tiene que hacer polling.

Cómo funciona

  1. Registras un endpoint en /app/webhooks con la URL pública de tu backend y los eventos que te interesan.
  2. Al crear, te entregamos UNA vez el signing_secret con prefijo whsec_*. Guárdalo en tu vault de secrets — no podemos recuperarlo.
  3. Cuando dispara un evento, te enviamos un POST con header Fiscal-Web-Signature: t=1234,v1=abcd… que tu backend debe verificar antes de procesar.
  4. Esperas un 2xx en menos de 10s. Cualquier otra cosa = retry.

Validación SSRF al registrar

Rechazamos URLs en rangos privados (10.0.0.0/8, 192.168.x.x, 127.0.0.1, link-local). Para testing local usa webhook.site o un tunnel público (ngrok, Cloudflare Tunnel).

Payload

Todos los eventos tienen el mismo shape (RFC-ish): metadata al nivel raíz + data con el cuerpo específico del evento.

POST a tu URL
{
  "event": "document.accepted",
  "event_id": "evt_01HXYZ...",
  "tenant_id": "01ARZ3...",
  "occurred_at": "2026-05-15T18:32:04Z",
  "data": {
    "document_id": "01ARZ3NDEKTSV4RRFFQ69G5FAV",
    "kind": "invoice",
    "series": "F001",
    "number": 1,
    "state": "accepted",
    "cdr": { "code": "0", "description": "ACEPTADO" }
  }
}

El event_id es estable: si recibes el mismo dos veces (por retries o replay), trátalos como duplicados idempotentes.

Retries y dead letter

Si tu endpoint devuelve algo distinto de 2xx, reintentamos con backoff exponencial. Política:

  • 8 reintentos en ~45 horas.
  • Backoff: 30s, 2m, 10m, 30m, 2h, 6h, 12h, 24h.
  • Tras los 8 fallos el evento queda como dead y aparece en /app/webhooks → deliveries con status dead.
  • Si tu endpoint acumula 10 deliveries dead consecutivos, lo deshabilitamos automáticamente y avisamos al Owner por email.

Replay manual

Desde /app/webhooks puedes re-disparar una delivery con un click. El mismo event_id llega de nuevo (idempotencia se mantiene en tu lado).

Siguientes pasos