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.
Suscribirte
Sección titulada «Suscribirte»POST /public/v1/webhook-subscriptionsAuthorization: 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://localhostsolo 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.
Eventos
Sección titulada «Eventos»| 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) |
Payload
Sección titulada «Payload»{ "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.
Verificar la firma (Node, sin dependencias)
Sección titulada «Verificar la firma (Node, sin dependencias)»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.
Responder rápido
Sección titulada «Responder rápido»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.
Reintentos
Sección titulada «Reintentos»| 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.
Garantías
Sección titulada «Garantías»- Al menos una vez, nunca con orden garantizado entre eventos. Deduplica por
id(el del payload, no el de la entrega HTTP). app.uninstalledse 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).