> For the complete documentation index, see [llms.txt](https://docs.zipnova.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.zipnova.com/carriers/endpoints/validar-la-firma-de-zipnova.md).

# Validar la firma de Zipnova

Cómo verificar que un pedido a tu Carrier lo envió Zipnova, con HMAC-SHA256.

Todas las llamadas de Zipnova a tu Carrier (cotización y notificaciones) van firmadas con el `hmac_secret` de tu Carrier. Valida la firma antes de procesar cualquier pedido.

## Encabezados

| Encabezado            | Contenido                                                             |
| --------------------- | --------------------------------------------------------------------- |
| `X-Zipnova-Timestamp` | Timestamp Unix del envío.                                             |
| `X-Zipnova-Signature` | `v1=` seguido de la firma en hexadecimal.                             |
| `X-Zipnova-Event`     | Solo en notificaciones: el evento, igual al campo `event` del cuerpo. |

## Cómo validarla

{% stepper %}
{% step %}

### Toma el cuerpo crudo

Usa el cuerpo exactamente como llegó, sin volver a serializar el JSON.
{% endstep %}

{% step %}

### Calcula la firma esperada

`hex(HMAC-SHA256(hmac_secret, "{X-Zipnova-Timestamp}.{cuerpo crudo}"))`
{% endstep %}

{% step %}

### Compárala

Compárala con el valor de `X-Zipnova-Signature` sin el prefijo `v1=`, con una comparación de tiempo constante.
{% endstep %}

{% step %}

### Verifica el timestamp

Se recomienda rechazar los pedidos con más de 5 minutos de diferencia respecto de la hora actual.
{% endstep %}
{% endstepper %}

{% tabs %}
{% tab title="Node.js" %}

```javascript
import crypto from 'node:crypto';

export function isValidZipnovaRequest(rawBody, headers, hmacSecret) {
  const timestamp = headers['x-zipnova-timestamp'];
  const signature = (headers['x-zipnova-signature'] || '').replace(/^v1=/, '');
  if (!timestamp || !signature) return false;
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;

  const expected = crypto
    .createHmac('sha256', hmacSecret)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex');

  const a = Buffer.from(expected);
  const b = Buffer.from(signature);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
```

{% endtab %}

{% tab title="Python" %}

```python
import hashlib
import hmac
import time


def is_valid_zipnova_request(raw_body: bytes, headers: dict, hmac_secret: str) -> bool:
    timestamp = headers.get('X-Zipnova-Timestamp', '')
    signature = headers.get('X-Zipnova-Signature', '').removeprefix('v1=')
    if not timestamp.isdigit() or not signature:
        return False
    if abs(time.time() - int(timestamp)) > 300:
        return False

    message = timestamp.encode() + b'.' + raw_body
    expected = hmac.new(hmac_secret.encode(), message, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)
```

{% endtab %}

{% tab title="PHP" %}

```php
function isValidZipnovaRequest(string $rawBody, array $headers, string $hmacSecret): bool
{
    $timestamp = $headers['X-Zipnova-Timestamp'] ?? '';
    $signature = preg_replace('/^v1=/', '', $headers['X-Zipnova-Signature'] ?? '');
    if (!ctype_digit($timestamp) || $signature === '') {
        return false;
    }
    if (abs(time() - (int) $timestamp) > 300) {
        return false;
    }

    $expected = hash_hmac('sha256', $timestamp . '.' . $rawBody, $hmacSecret);
    return hash_equals($expected, $signature);
}
```

{% endtab %}
{% endtabs %}

## Antes de poner tus endpoints en producción

* Zipnova valida las URLs de cotización y de notificaciones al registrarlas o actualizarlas en el Carrier Hub (validación de DNS).
* Zipnova no sigue redirecciones: la URL registrada tiene que responder directamente.
* El callback de cotización tiene un timeout total de 500 ms. Las notificaciones tienen 3 s (1 s de conexión) y se reintentan hasta 5 veces con espera creciente (30 s, 2 min, 10 min y 15 min) si no responden `2xx`.
* Responde rápido y procesa el trabajo pesado después: para una notificación basta con un `2xx` que confirme la recepción.
