Ir al contenido
Pide tu alta

Webhooks

En corto

Te suscribes a eventos y Wink POS te avisa con un POST firmado (Wink-Signature) apenas pasa algo — payload delgado, solo ids. Verifica la firma, responde 2xx rápido y vuelve a pedir el recurso con tus scopes: los reintentos llegan hasta 8 veces en 24 horas.

Cuando algo cambia en el comercio, cada app suscrita recibe un POST firmado con el tipo de evento y los ids afectados. El payload es delgado a propósito: vuelve a pedir el recurso por la API con tus propios scopes.

POST /public/v1/webhook-subscriptions
Authorization: Bearer wpa_...
Content-Type: application/json
{ "url": "https://mi-app.example.com/webhooks/wink-pos", "events": ["sale.completed", "stock.changed"] }
  • La URL debe ser HTTPS (http://localhost solo fuera de producción, para probar). Un destino que resuelve a una IP privada o de loopback se rechaza.
  • El secreto de firma (whsec_…) se devuelve una sola vez, al crear la suscripción. Guárdalo en tu servidor: lo necesitas para verificar cada entrega.
  • Cada evento que pides exige el permiso para leer su recurso (ver tabla abajo). Pedir uno sin ese permiso responde 400 event_scope_not_allowed.

GET /public/v1/webhook-subscriptions lista las tuyas; DELETE /public/v1/webhook-subscriptions/:id cancela una.

Evento Permiso que necesitas
product.created, product.updated, product.deleted, price.changed catalog.view
stock.changed inventory.view
sale.completed, sale.voided sales.view
contact.created, contact.updated contacts.view
app.uninstalled ninguno (te avisa de tu propia desinstalación)
{
"id": "3fae2b1e-4c2a-4e9a-9a1a-6a7c4e1a2b3c",
"type": "sale.completed",
"occurredAt": "2026-11-03T14:05:00.000Z",
"businessId": "b2c1a3d4-...",
"data": { "ids": { "saleId": "a91fcb20-..." } }
}

Nunca viaja el recurso completo: ningún campo de dinero, cantidad o dato personal. Vuelve a pedirlo por /public/v1 con el id.

import crypto from 'node:crypto';
function verify(secret, header, rawBody) {
const [tPart, vPart] = header.split(',');
const t = tPart.slice(2); // "t=..."
const v1 = vPart.slice(3); // "v1=..."
const expected = crypto
.createHmac('sha256', secret)
.update(`${t}.${rawBody}`)
.digest('hex');
const freshEnough = Math.abs(Date.now() / 1000 - Number(t)) <= 300; // 5 min
return (
freshEnough &&
expected.length === v1.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1))
);
}
// Encabezado: Wink-Signature: t=<epoch>,v1=<hmac-sha256 hex de "t.body">
// app.post('/webhooks/wink-pos', (req, res) => {
// if (!verify(secret, req.header('Wink-Signature'), req.rawBody)) return res.sendStatus(401);
// res.sendStatus(200); // responde rápido, procesa después
// ...
// });

Usa siempre el cuerpo crudo (sin parsear) para calcular el HMAC: un framework que reserializa el JSON antes de llegar a tu handler puede cambiar el byte a byte y romper la verificación. Rechaza cualquier firma con más de 5 minutos de antigüedad.

Responde 2xx en menos de 5 segundos. Cualquier otra cosa (incluido un redirect: no se siguen) cuenta como fallo. Si tu procesamiento es lento, respóndele a Wink POS de inmediato y procesa la entrega en segundo plano de tu lado.

Intento Espera desde el fallo anterior
1 inmediato
2 1 min
3 5 min
4 30 min
5 2 h
6 6 h
7 24 h
8 (último) 24 h

Tras el 8.º intento la entrega queda dead y no se vuelve a intentar.

Pausa: una suscripción sin una entrega exitosa en 3 días seguidos se pausa — no se te encolan más eventos — y recibes un correo. Revisa y corrige tu endpoint, y vuelve a crear la suscripción.

  • Al menos una vez, nunca con orden garantizado entre eventos. Deduplica por id (el del payload, no el de la entrega HTTP).
  • app.uninstalled se envía antes de borrar tus suscripciones: es tu última oportunidad de enterarte de que el comercio te desinstaló. Úsalo para limpiar lo que guardas de ese comercio (tienes 30 días según las políticas).