Ir al contenido

Webhooks

Los webhooks envían eventos de una tienda a una URL receptora mediante POST con JSON. Su disponibilidad y los eventos permitidos dependen del nivel de API de la tienda.

Podés administrar webhooks desde /admin/configuracion/api?tab=webhooks o mediante las operaciones públicas declaradas en la referencia API. Confirmá allí los scopes y el nivel vigente de cada operación.

Si usás las operaciones públicas, autenticalas con una API key de la tienda.

{
"url": "https://integracion.example.com/events",
"eventos": ["orden.creada", "stock.actualizado"]
}

En producción, la URL receptora debe:

  • usar HTTPS y ser pública;
  • no incluir usuario ni contraseña embebidos;
  • no resolver a localhost, redes privadas, direcciones link-local ni otras direcciones internas;
  • aceptar POST con JSON; y
  • responder en menos de 10 segundos.

Los tipos de evento y su disponibilidad se publican en los schemas del contrato OpenAPI. Consultá la referencia API para obtener el enum vigente y no mantengas una lista paralela en tu integración.

Al crear un webhook se genera un secret con prefijo whsec_. Se devuelve una sola vez y no aparece al listar webhooks, por lo que debés copiarlo inmediatamente y guardarlo en un gestor de secretos del servidor.

Los máximos por nivel se declaran en x-plan-limits. Leé esa extensión en la operación correspondiente para obtener el valor vigente.

Los eventos usan este formato general:

{
"evento": "orden.creada",
"tienda_id": 123,
"created_at": "2026-08-15T12:00:00.000Z",
"data": {}
}
  • evento identifica el tipo de evento.
  • tienda_id identifica la tienda.
  • created_at indica cuándo ocurrió.
  • data contiene los datos específicos del evento.

Los eventos procesados por la cola incluyen:

Content-Type: application/json
User-Agent: Wendo-Webhooks/1.0
X-Wendo-Event: orden.creada
X-Wendo-Delivery-Id: 12345
X-Wendo-Signature: sha256=<hex>
X-Wendo-Tienda-Id: 123

Los nombres de los headers HTTP son case-insensitive. El ping de prueba es una excepción respecto de X-Wendo-Delivery-Id, como se explica más adelante.

X-Wendo-Signature tiene este formato:

sha256=<HMAC-SHA256 hexadecimal del body JSON exacto usando el secret>

La firma cubre el body, no los headers. Para verificarla de forma segura:

  1. Leé primero el raw body y conservá exactamente los bytes recibidos.
  2. No parsees y vuelvas a serializar el JSON antes de verificar.
  3. Calculá el HMAC-SHA256 con el secret del webhook.
  4. Compará la firma en tiempo constante.
  5. Rechazá el request si la firma falta o no coincide.
  6. Verificá la firma antes de ejecutar cualquier efecto de negocio.

No registres el secret ni el body completo cuando pueda contener datos sensibles.

import { createHmac, timingSafeEqual } from 'node:crypto';
export function verifyWendoWebhook(
rawBody: string,
receivedSignature: string | null,
secret: string,
): boolean {
if (!receivedSignature) return false;
const expectedSignature =
`sha256=${createHmac('sha256', secret)
.update(rawBody, 'utf8')
.digest('hex')}`;
const received = Buffer.from(receivedSignature, 'utf8');
const expected = Buffer.from(expectedSignature, 'utf8');
return (
received.length === expected.length
&& timingSafeEqual(received, expected)
);
}

El secret debe permanecer en una variable privada del servidor, nunca en una variable pública.

Este ejemplo verifica la firma antes de parsear y deja explícito el punto de deduplicación. processEvent representa tu lógica de negocio; el schema concreto de event.data depende del evento y no se completa aquí.

export async function handleWebhook(request: Request): Promise<Response> {
const rawBody = await request.text();
const signature = request.headers.get('x-wendo-signature');
if (!verifyWendoWebhook(rawBody, signature, process.env.WENDO_WEBHOOK_SECRET!)) {
return new Response('Invalid signature', { status: 401 });
}
const event = JSON.parse(rawBody);
const deliveryId = request.headers.get('x-wendo-delivery-id');
// Verificar si deliveryId ya fue procesado antes de ejecutar efectos.
// Guardar el resultado dentro de la misma transacción del negocio.
await processEvent(event);
return new Response(null, { status: 204 });
}

Cualquier respuesta final 2xx se considera una entrega exitosa. Los errores de red, los timeouts y las respuestas no exitosas se reintentan. Cada intento tiene un timeout de 10 segundos y una entrega realiza hasta 5 intentos totales:

Intento Momento normal
1 Envío inicial.
2 1 minuto después.
3 5 minutos después.
4 30 minutos después.
5 2 horas después.

Después del quinto fallo, la entrega queda agotada. Guardá el evento de forma durable, respondé 2xx y procesá las tareas pesadas de manera asíncrona para evitar timeouts. Al diseñar respuestas de error, consultá también la guía de errores.

Los webhooks pueden repetirse debido a los reintentos. X-Wendo-Delivery-Id permanece asociado a la misma entrega y debe usarse como clave de idempotencia:

  1. Antes de ejecutar efectos, verificá si el delivery ID ya fue procesado.
  2. Guardá el ID procesado dentro de la misma transacción que la operación de negocio.
  3. Si el ID ya existe, no repitas el efecto y respondé 2xx.

X-Wendo-Delivery-Id no es una garantía criptográfica anti-replay. Actualmente no se envía un timestamp firmado y no debe asumirse una ventana anti-replay basada en tiempo. Esta deduplicación es independiente de la cabecera Idempotency-Key usada por algunas operaciones de la API; consultá idempotencia para distinguir ambos mecanismos.

Desde el administrador podés enviar un ping para comprobar la recepción y la firma. El ping:

  • usa el evento ping;
  • se firma con el mismo HMAC-SHA256;
  • incluye X-Wendo-Event, X-Wendo-Signature y X-Wendo-Tienda-Id;
  • es un intento directo y no usa la cola normal de reintentos; y
  • puede no incluir X-Wendo-Delivery-Id.

El receptor no debe exigir un delivery ID para aceptar un evento ping. Sí debe verificar su firma antes de procesarlo.