Saltar al contenido
Krezkadev

Webhooks

Recibe el resultado de SUNAT para cada comprobante en tiempo real, con firma HMAC y reintentos.

La emisión es asíncrona: POST /invoices responde al instante y SUNAT contesta después. Los webhooks son la forma de enterarte sin consultar.

Cómo funciona

Registra un endpoint HTTPS con POST /webhook_endpoints, suscríbete a los eventos que te interesen y responde con 2xx en menos de 5 segundos. Si tu servidor falla, reintentamos con backoff exponencial hasta 24 horas.

Eventos

EventoCuándo se disparaQué hacer
invoice.sentEl XML firmado salió hacia SUNAT.Nada. Informativo.
invoice.acceptedSUNAT aceptó el comprobante (CDR 0).Marca la venta como facturada; descarga el PDF para el cliente.
invoice.observedAceptado con observaciones (CDR 4xxx).Es válido. Revisa el mensaje y corrige en los siguientes.
invoice.rejectedSUNAT lo rechazó (CDR 2xxx/3xxx).Corrige los datos y emite un comprobante nuevo.
invoice.voidedLa comunicación de baja fue aceptada.Revierte la venta en tu sistema.
credit_note.acceptedSUNAT aceptó la nota de crédito.Aplica el ajuste al comprobante afectado.
credit_note.rejectedSUNAT rechazó la nota.Revisa el motivo y vuelve a emitirla.

Formato del envío

HTTP
HTTP
POST /webhooks/krezka HTTP/1.1
Content-Type: application/json
Krezka-Event: invoice.accepted
Krezka-Invoice-Id: inv_3xK8p2Qm
Krezka-Signature: t=1762095845,v1=8b7c…e2
Krezka-Attempt: 1
JSON
JSON
{
  "id": "evt_5Qp8Lx2m",
  "type": "invoice.accepted",
  "created_at": "2026-11-02T15:04:09Z",
  "data": {
    "id": "inv_3xK8p2Qm",
    "object": "invoice",
    "full_number": "F001-00001234",
    "status": "accepted",
    "external_id": "INV-10482",
    "sunat": { "response_code": "0", "accepted_at": "2026-11-02T15:04:09Z", "cdr_available": true }
  }
}

El data es el comprobante completo, igual que en GET /invoices/{id}.

Verifica la firma

Cada request incluye la cabecera Krezka-Signature. Calcula el HMAC-SHA256 del cuerpo crudo con el secret de tu endpoint y compáralo en tiempo constante.

ts
ts
import { createHmac, timingSafeEqual } from "node:crypto";

export function verify(rawBody: string, header: string, secret: string) {
  const [t, v1] = header.split(",").map((p) => p.split("=")[1]);
  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  return timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}

Idempotencia del lado tuyo

Un evento puede llegar más de una vez. Usa id del evento para descartar duplicados.