Webhooks
Suscríbete a eventos de pago y checkout, verifica la firma y maneja los reintentos. Una sola integración cubre pagos en línea y presenciales.
Los webhooks avisan a tu backend en el momento en que algo ocurre: un pago se aprueba, se rechaza, se reembolsa o se disputa. Son la forma recomendada de conocer el resultado de un pago, tanto en línea como presencial.
Registrar un endpoint
Crea un endpoint de webhook como recurso de tu comercio. El secreto de firma
(whsec_…) se devuelve una sola vez, al crearlo: guárdalo para verificar las
entregas.
curl -X POST https://api.vecnet.io/api/webhooks \
-H "Authorization: Token vk_live_1a2b3c4d5e6f7a8b9c0d" \
-H "Content-Type: application/json" \
-d '{
"url": "https://tienda.example.com/webhooks/vecnet",
"events": ["payment.approved", "payment.refunded", "payment.chargeback"]
}'{
"id": "whk_4d9c2b",
"url": "https://tienda.example.com/webhooks/vecnet",
"events": ["payment.approved", "payment.refunded", "payment.chargeback"],
"status": "active",
"secret": "whsec_test_0000000000000000000000000000abcd"
}El secret (whsec_…) solo aparece en esta respuesta. Guárdalo ahora: lo
necesitas para verificar cada entrega y no se puede recuperar después. Si lo
pierdes, rótalo con POST /api/webhooks/:id/rotate.
El sobre del evento
Cada entrega es un sobre JSON. data contiene el objeto del que trata el evento
(la transacción, la sesión o la liga).
{
"id": "evt_0d1e2f3a",
"object": "event",
"event": "payment.approved",
"merchant_id": "mrc_3f9a2c1b",
"created_at": "2026-06-22T14:30:00.000Z",
"data": {
"id": "txn_9f3c2a71",
"rail": "online_cnp",
"amount_cents": 49900,
"currency": "MXN",
"status": "approved",
"correlation_id": "corr_b8e21f3a4c5d6e7f"
}
}Encabezados de la entrega
| Encabezado | Descripción |
|---|---|
X-Vecnet-Event | El tipo de evento, por ejemplo payment.approved. |
X-Vecnet-Delivery | Un id único de entrega (whd_…), estable entre reintentos. |
X-Vecnet-Signature-Timestamp | Segundos Unix en que se firmó la entrega. |
X-Vecnet-Signature-V1 | Firma HMAC de la entrega (ve abajo). |
Verificar la firma
Cada entrega se firma con el secreto whsec_… de tu endpoint. La firma es el
HMAC-SHA256 en hexadecimal de "{timestamp}.{rawBody}", donde rawBody son los
bytes exactos del cuerpo de la petición:
X-Vecnet-Signature-V1 = hex( HMAC_SHA256( whsec, `${timestamp}.${rawBody}` ) )Verifica sobre el cuerpo crudo (no sobre un objeto re-serializado), compara en tiempo constante y rechaza las entregas con una marca de tiempo demasiado antigua.
import crypto from "node:crypto";
function verify(req, secret) {
const ts = req.headers["x-vecnet-signature-timestamp"];
const sig = req.headers["x-vecnet-signature-v1"];
const raw = req.rawBody; // los bytes exactos recibidos
// Rechaza entregas viejas (ventana de 5 minutos).
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${ts}.${raw}`)
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}Captura el cuerpo crudo de la petición antes de que cualquier middleware de JSON lo re-serialice. Una firma calculada sobre un cuerpo re-codificado no va a coincidir.
Responder y reintentos
Devuelve un 2xx rápido para confirmar la entrega. Cualquier respuesta que no
sea 2xx (o un tiempo de espera agotado) se trata como falla y se reintenta con
espera creciente:
30 s → 2 min → 10 min → 1 h (hasta 5 intentos)Cada intento tiene un tiempo de espera corto, así que haz el trabajo lento de forma asíncrona: confirma primero, procesa después.
Las entregas son idempotentes. El id X-Vecnet-Delivery es estable entre
reintentos del mismo evento, así que deduplica por él antes de actuar.
Catálogo de eventos
| Evento | Cuándo se dispara |
|---|---|
payment.approved | Un pago fue aprobado (fondos capturados). |
payment.pending | Un pago está pendiente (por ejemplo, en espera de un pago en efectivo). |
payment.declined | Un pago fue rechazado. |
payment.failed | Un pago falló por un error. |
payment.expired | Una sesión o liga expiró. |
payment.cancelled | Un pago fue cancelado o anulado. |
payment.refunded | Un pago fue reembolsado por completo. |
payment.chargeback | Un pago fue disputado / tuvo un contracargo. |
payment.duplicate | Un intento repetido de un pago ya procesado. |
payment_link.created | Se creó una liga de pago. |
payment_link.paid | Se pagó una liga de pago. |
checkout_session.created | Se creó una sesión de checkout alojado. |
checkout_session.completed | Una sesión de checkout alojado llegó a un estado final. |
El catálogo vigente siempre está disponible en GET /api/webhooks/events.
Un solo resultado real puede disparar más de un evento. Por ejemplo, una
sesión expirada dispara payment.expired y payment.failed. Suscríbete
exactamente a los eventos que manejas.
Probar un endpoint
Envía una entrega firmada de prueba (webhook.test) para validar tu
verificación de firma antes de recibir tráfico real:
curl -X POST https://api.vecnet.io/api/webhooks/whk_4d9c2b/test \
-H "Authorization: Token vk_live_1a2b3c4d5e6f7a8b9c0d"Inspeccionar entregas
Las entregas pasadas y su historial de intentos están disponibles como recurso de tu comercio. Cada entrega guarda el payload exacto, el código HTTP de tu respuesta y los primeros caracteres de su cuerpo:
curl https://api.vecnet.io/api/webhook-deliveries \
-H "Authorization: Token vk_live_1a2b3c4d5e6f7a8b9c0d"Si tu endpoint estuvo caído, vuelve a enviar una entrega con su payload original:
curl -X POST https://api.vecnet.io/api/webhook-deliveries/whd_9a41bc/redeliver \
-H "Authorization: Token vk_live_1a2b3c4d5e6f7a8b9c0d"Conciliación
Cómo se ingieren, deduplican, atribuyen y exponen como transacciones las ventas de terminal con tarjeta presente — incluidas anulaciones, reembolsos y metadata de punto de venta.
Trazabilidad y bitácora de auditoría
Cómo Vecnet registra una bitácora de solo escritura — cada petición y cada evento de pago, enlazados por correlation_id y request_id.