import { Aside } from "@astrojs/starlight/components";

Cualquiera puede mandarle un `POST` a tu endpoint haciéndose pasar por
NovaFE. La firma es cómo confirmas que una entrega es legítima antes de
actuar sobre ella.

## Los headers

| Header               | Contenido                                                          |
| -------------------- | ------------------------------------------------------------------ |
| `X-NovaFE-Event`     | El tipo de evento, por ejemplo `ecf.accepted`.                     |
| `X-NovaFE-Delivery`  | El id de esta entrega. Estable entre reintentos del mismo intento. |
| `X-NovaFE-Timestamp` | Segundos Unix del envío.                                           |
| `X-NovaFE-Signature` | `sha256=<hex>`.                                                    |

## La firma

```
<hex> = HMAC_SHA256(tu secret, "{X-NovaFE-Timestamp}.{cuerpo crudo del request}")
```

El separador es un punto literal entre el timestamp y el cuerpo, tal cual
llegó, sin volver a serializarlo tú.

## Cómo verificar

<Aside type="caution" title="No compares strings con ==">
  Usa una comparación en tiempo constante (`hmac.compare_digest` en Python,
  `crypto.timingSafeEqual` en Node, etc.). Comparar con `==` filtra, por cuánto
  tarda la comparación, cuántos caracteres acertaste, y eso es exactamente lo
  que un ataque de timing explota.
</Aside>

1. Lee `X-NovaFE-Timestamp` y rechaza la entrega si `|ahora − timestamp| > 300` segundos. Esto evita que alguien reproduzca una entrega vieja capturada.
2. Recalcula el HMAC sobre `"{timestamp}.{cuerpo crudo}"` con tu `secret`.
3. Compara el resultado, en tiempo constante, contra el valor después de `sha256=` en `X-NovaFE-Signature`.

```text
firma_esperada = "sha256=" + hex(HMAC_SHA256(secret, timestamp + "." + cuerpo))
si firma_esperada != X-NovaFE-Signature (en tiempo constante):
    rechazar la entrega
```

## Idempotencia: vas a recibir repetidos

La entrega es **al menos una vez**: un mismo evento puede llegarte más de
una vez (ver [Entrega y reintentos](/webhooks/entrega-reintentos/)).
Deduplica usando el `id` del sobre (el campo `id` del cuerpo, o el header
`X-NovaFE-Delivery`) antes de aplicar el efecto del evento. Guardar los
últimos ids procesados en tu base, aunque sea por un par de días, alcanza.

## No hay garantía de orden

`ecf.accepted` puede llegarte antes que `ecf.submitted` para el mismo
comprobante: cada evento sigue su propio camino de reintentos, así que uno
que falló y quedó en backoff puede llegar después de otro más reciente que
salió bien al primer intento. Determina el estado en tu sistema a partir de
`data.object.status` (el estado que trae el propio payload), nunca a partir
del orden en que llegaron las entregas.