Webhooks

Suscripción a eventos, verificación de la firma HMAC-SHA256, reintentos y procesamiento sin duplicados.

En esta página

Un webhook es una solicitud HTTP que Chainlink envía a otro sistema cuando ocurre un evento en el almacén: una recepción finalizada, un embarque despachado, un cambio de existencias. Elimina la necesidad de consultar la API periódicamente. Esta página describe la suscripción, el formato de las entregas, la verificación de la firma y la política de reintentos; el catálogo de eventos describe cada evento.

Requisitos: plan Professional o Enterprise y el alcance webhooks:manage (o, en la web, Configuración › Webhooks).

1. Suscripción

Registre la URL que recibirá los avisos y los eventos requeridos (catálogo de eventos; operaciones en la referencia).

La respuesta trae data.secret, el secreto de firma. Solo aparece en esta respuesta: guárdelo en el gestor de secretos. También se puede enviar un secret propio de 16 caracteres o más.

URL aceptadas

  • Solo https://, sin usuario ni contraseña en la URL. Se acepta cualquier puerto.
  • El dominio debe resolver únicamente a direcciones públicas. Se rechazan las privadas (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fc00::/7), locales (127.0.0.0/8, ::1, localhost), de enlace local (169.254.0.0/16, fe80::/10), CGNAT (100.64.0.0/10), multicast, reservadas y las de metadatos de la nube (169.254.169.254), también dentro de una dirección IPv6.
  • La comprobación se repite en cada entrega, sobre la dirección a la que Chainlink se conecta: si el dominio cambia a una dirección interna, la entrega se rechaza y cuenta como falla.
  • Chainlink no sigue redirecciones: una respuesta 3xx cuenta como falla. Registre la URL final.

Una URL que no cumple responde 422 validation_failed con el motivo en error.message; la pantalla de la web aplica las mismas reglas.

Crear una suscripción
curl -X POST "https://empresa.chainlink.mx/api/v1/webhooks" \
  -H "Authorization: Bearer $CHAINLINK_TOKEN" \
  -H "Accept: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://erp.example.com/chainlink/webhooks",
    "events": [
        "receipt.completed",
        "picking.completed",
        "stock.updated"
    ]
}'

2. Formato de la entrega

Cada entrega es un POST con cuerpo JSON y estas cabeceras:

CabeceraContenido
X-Chainlink-Signaturet=<unix>,v1=<firma hex>
X-Chainlink-EventEl evento, por ejemplo receipt.completed
X-Chainlink-DeliveryIdentificador de la entrega; se repite en cada reintento
Cuerpo
{
  "event": "receipt.completed",
  "version": 1,
  "data": { "id": 5531, "status": "finalized", "lines": [ { "item_code": "P-1001", "qty": 48, "batch": "L2610", "serials": [] } ] },
  "timestamp": 1791302400
}

3. Verificación de la firma

La firma prueba que el aviso proviene de Chainlink y que no se alteró. Es un HMAC-SHA256, en hexadecimal, de "<t>.<cuerpo crudo>" con el secreto de la suscripción. Verifíquela antes de procesar el aviso:

  1. Lea t y v1 de X-Chainlink-Signature.
  2. Calcule el HMAC sobre el cuerpo tal como llegó, antes de decodificar el JSON.
  3. Compare en tiempo constante y rechace la entrega si t difiere en más de 5 minutos del reloj local.
Verificar la firma
// Laravel: el cuerpo crudo es $request->getContent().
[$t, $v1] = sscanf((string) $request->header('X-Chainlink-Signature'), 't=%d,v1=%s');
$expected = hash_hmac('sha256', $t . '.' . $request->getContent(), env('CHAINLINK_WEBHOOK_SECRET'));

abort_unless($v1 !== null && hash_equals($expected, $v1) && abs(time() - $t) < 300, 401);

$event = json_decode($request->getContent(), true);

4. Respuesta y procesamiento único

  • Responda 2xx en menos de 15 segundos; cualquier otra respuesta o un tiempo mayor cuenta como falla. Guarde el aviso y procéselo en segundo plano.
  • Guarde X-Chainlink-Delivery y descarte las entregas ya procesadas: un reintento llega con el mismo identificador.
  • No dependa del orden de llegada. Para obtener el estado más reciente, consulte el recurso por API.

5. Reintentos y desactivación

  • Una entrega fallida se reintenta hasta 5 veces, con esperas de 10 s, 30 s, 2 min y 10 min.
  • Cada hora, Chainlink vuelve a encolar las entregas fallidas de las últimas 24 horas.
  • Tras 10 fallas consecutivas, la suscripción se desactiva (is_active: false). Corrija el endpoint y reactívela con PATCH /webhooks/{id} y {"is_active": true}.

Pruebas en desarrollo

Exponga el servidor local con un túnel (por ejemplo ngrok o Cloudflare Tunnel), suscriba esa URL y provoque el evento en la instancia de pruebas: cree un producto para product.created o finalice una recepción para receipt.completed.