> ## Documentation Index
> Fetch the complete documentation index at: https://docs.emite.do/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> JSON plano, HMAC-SHA256 sobre el body crudo. Hoy se emiten los estados fiscales finales.

## Crear

```bash theme={null}
curl -X POST https://api.emite.do/api/v1/client/webhooks \
  -H "X-API-Key: $EMITE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://tu-erp.example/webhooks/ecf",
    "events": ["accepted", "conditionally_accepted", "rejected"]
  }'
```

El `secret` se devuelve **una vez**. Mínimo 16 caracteres si lo envías tú.

El módulo Odoo escucha `POST https://<odoo>/webhooks/ecf` (JSON plano, no JSON-RPC). `web.base.url` tiene que ser alcanzable desde `api.emite.do`.

## Eventos que sí se envían

| Evento                   | Significado                        |
| ------------------------ | ---------------------------------- |
| `accepted`               | DGII aceptó                        |
| `conditionally_accepted` | Aceptado con observaciones         |
| `rejected`               | DGII rechazó                       |
| `webhook.test`           | Solo `POST .../webhooks/{id}/test` |

Al crear el webhook también se aceptan `*`, `ecf.*`, `signed`, `submitted` y `failed`. **Hoy el worker de cliente no dispara** `signed`, `submitted` ni `failed`. Si los suscribes, no llegan notificaciones de esos pasos. El estado intermedio se consulta con `GET /api/v1/ecf/{public_id}`.

## Payload

Campos en la raíz. No hay wrapper `data`. Los nulos se omiten.

```json theme={null}
{
  "event": "accepted",
  "public_id": "01JXXXXX...",
  "company_rnc": "130478031",
  "ecf_type": "31",
  "encf": "E310000000001",
  "status": "accepted",
  "track_id": "DGII-TRACK-123",
  "track_number": "DGII-TRACK-123",
  "security_code": "ABC123",
  "dgii_security_code": "ABC123",
  "timestamp": "2026-08-15T10:01:30"
}
```

Puede venir `dgii_response`, `electronic_stamp` y `sign_date` cuando el acuse los trae.

Headers:

* `Content-Type: application/json`
* `X-ECF-Event: <evento>`
* `X-ECF-Signature: sha256=<hmac-sha256(secret, body crudo)>`

No hay `X-ECF-Timestamp` ni `X-ECF-Delivery-Id` en el envío vivo.

<Warning>
  Firma el **body crudo**. Parsear y volver a serializar JSON cambia espacios y rompe el HMAC.
</Warning>

```python theme={null}
import hashlib
import hmac

def verify_signature(raw_body: bytes, header_value: str, secret: str) -> bool:
    if not header_value or not header_value.startswith("sha256="):
        return False
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(header_value, f"sha256={expected}")
```

Si tu endpoint no responde `2xx`, hay reintentos exponenciales (backoff hasta 24 h). Dry-run: `POST /api/v1/client/webhooks/{id}/test`. Historial: `.../deliveries`.
