Saltar al contenido
astro

Contrato 2026-10-01

Firma y cabeceras

Cada llamada trae HMAC-SHA256 del cuerpo crudo. Acepta la petición solo si la firma y la hora coinciden.

Astro firma el cuerpo con un secreto que empieza por whsec_. El negocio lo ve una sola vez, al generarlo en la cuenta, y te lo entrega por un canal seguro. Guárdalo fuera del repositorio.

Cabeceras

Cabecera Valor
Content-Type application/json
User-Agent Astro-Integraciones/1.0
X-Astro-Event El mismo valor que evento en el cuerpo
X-Astro-Delivery Identificador de este intento. Cambia si Astro reintenta
Idempotency-Key Igual a id_evento. Se mantiene entre reintentos
X-Astro-Signature t=<unix>,v1=<hex>

X-Astro-Delivery sirve para tu bitácora. La clave para no duplicar un pedido es Idempotency-Key.

Cuerpo

El cuerpo es JSON en UTF-8, compacto y sin espacios. Los acentos van como caracteres, no como \u00e9.

Campo Qué es
version Versión del contrato. Hoy 2026-10-01
evento ping, catalogo.consultar, transaccion.crear o transaccion.cancelar
id_evento UUID de la operación. En un pedido o una cita se mantiene en cada reintento
fecha Momento del envío, en ISO 8601 con zona horaria
tenant_id Cuenta del negocio en Astro
sucursal { "codigo", "nombre" }, o null si el negocio tiene una sola sede
data Datos de la operación. Cambian según el evento

Ignora los campos que no reconozcas. Astro también ignora campos de más en tu respuesta.

Cómo comprobar la firma

  1. Lee el cuerpo como bytes, antes de interpretarlo como JSON.
  2. De la cabecera X-Astro-Signature, toma el t y todos los v1.
  3. Rechaza la petición si t no es un número, si no hay ningún v1, o si t se aleja más de 300 segundos de la hora actual.
  4. Calcula HMAC-SHA256 de t, un punto y el cuerpo. La clave es el secreto en UTF-8.
  5. Acepta si ese hexadecimal coincide con algún v1. Compara en tiempo constante.

Durante las 24 horas posteriores a una rotación, la cabecera trae dos v1: el secreto nuevo y el anterior. Con el secreto que tengas guardado basta con que uno coincida.

Si la firma no coincide, responde HTTP 401. Astro lo toma como fallo de autenticación y vuelve a intentar. Reserva el 401 para ese caso.

La firma cubre los bytes recibidos. Si vuelves a serializar el JSON antes de verificar, los espacios o el orden cambian y una petición válida parece falsa.

Ejemplo en Python

import hashlib
import hmac
import time

VENTANA_SEGUNDOS = 300


def verificar(cuerpo: bytes, cabecera: str, secreto: str) -> bool:
    marcas = []
    firmas = []
    for item in cabecera.split(","):
        clave, _, valor = item.partition("=")
        clave, valor = clave.strip(), valor.strip()
        if clave == "t":
            marcas.append(valor)
        elif clave == "v1" and valor:
            firmas.append(valor)
    if len(marcas) != 1 or not marcas[0].isdigit() or not firmas:
        return False
    timestamp = int(marcas[0])
    if abs(time.time() - timestamp) > VENTANA_SEGUNDOS:
        return False
    esperado = hmac.new(
        secreto.encode("utf-8"),
        f"{timestamp}.".encode("utf-8") + cuerpo,
        hashlib.sha256,
    ).hexdigest()
    return any(hmac.compare_digest(esperado, firma) for firma in firmas)

Los mismos pasos valen en cualquier lenguaje que tenga HMAC-SHA256.

Direcciones que Astro acepta

El negocio configura el origen, no la ruta. Astro llama a {origen}/astro/producto/webhook y a {origen}/astro/pedido/webhook.

Ese origen tiene que usar https y un dominio que resuelva a una IP pública. Astro rechaza http, direcciones privadas, usuario o contraseña en la URL, y parámetros de consulta. La comprobación de la IP se hace en cada llamada.