---
title: Webhooks
description: El contrato de eventos y la firma Notta-Signature para reaccionar a cambios de estado de un DTE sin polling.
url: https://notta.cl/docs/webhooks
related:
  - https://notta.cl/docs/api/dtes
  - https://notta.cl/docs/errors
  - https://notta.cl/docs/agentes
  - https://notta.cl/docs/rate-limits
---

> Documentación de Notta en markdown. Índice completo: https://notta.cl/llms.txt — corpus entero: https://notta.cl/llms-full.txt

# Webhooks

Suscribite a eventos para reaccionar en tiempo real cuando un DTE cambia de estado, en vez de pollear.

> **Estado actual:**
El contrato de webhooks (eventos + firma `Notta-Signature`) está definido y es estable: programa tu consumer contra él desde ahora. El endpoint de suscripción `POST /api/v1/webhooks` **aún no está habilitado** — suscribite cuando se active. Mientras tanto consulta el estado con `GET /api/v1/dtes/:id` (ver [Referencia API de DTEs](/docs/api/dtes)).

## Suscripción (contrato objetivo)

Así se va a ver la suscripción cuando el endpoint se habilite — hoy esta request responde `404`:

```bash
curl -X POST https://app.notta.cl/api/v1/webhooks \
  -H "Authorization: Bearer ntt_cert_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "url": "https://tuapp.com/webhooks/notta",
    "events": ["dte.emitted", "dte.status_changed", "dte.rejected"]
  }'
```

## Tipos de evento

| Evento | Cuándo |
|---|---|
| `dte.emitted` | El DTE fue firmado y encolado al SII |
| `dte.status_changed` | El estado SII cambió (p. ej. `queued` → `EPR`) |
| `dte.accepted` | El SII aceptó el DTE (`EPR`, terminal) |
| `dte.rejected` | El SII rechazó el DTE (`RFR`/`RCT`/`RSC` — ver [Catálogo de errores](/docs/errors)) |

## Payload

```json
{
  "id": "evt_01957a91-...",
  "type": "dte.status_changed",
  "created_at": "2026-05-13T14:33:02.000Z",
  "data": {
    "dte_id": "01957a91-0b50-7000-8000-000000000001",
    "tipo_dte": 33,
    "folio": 1234,
    "sii_status": "EPR"
  }
}
```

## Verificación de firma

Cada entrega incluye un header `Notta-Signature` con un HMAC-SHA256 del cuerpo crudo, firmado con tu webhook secret. Verifica la firma antes de confiar en el payload:

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

function verify(rawBody: string, signature: string, secret: string): boolean {
  const expected = createHmac("sha256", secret).update(rawBody).digest("hex");
  return timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
```

## De dónde sale el secret

El webhook secret se va a entregar **una sola vez** en la respuesta de la creación de la suscripción (`POST /api/v1/webhooks`). Guárdalo en tu secret manager en ese momento: no va a ser recuperable después, solo rotable.

## Reintentos de entrega

La policy de reintentos (cantidad, backoff, ventana de deadletter) se va a documentar aquí cuando la entrega saliente se habilite. Hasta entonces, diseñá tu handler **idempotente por `id` de evento**: cualquier policy de entrega implica que un mismo evento puede llegar más de una vez.

## Próximos pasos

- **[Referencia API de DTEs](/docs/api/dtes)** — `GET /dtes/:id` y `POST /dtes/:id/refresh-status`, el camino para seguir el estado hoy.
- [Catálogo de errores](/docs/errors) — los rechazos SII que vas a recibir en `dte.rejected`.
- [Agentes](/docs/agentes) — patrones para que un agente LLM reaccione a los eventos.
- [Límites de tasa](/docs/rate-limits) — el patrón de backoff si sigues el estado por polling.
