Ir al contenido

Webhooks

Los webhooks son el mecanismo push de Pickwise: en lugar de que tu sistema haga polling, Pickwise te notifica vía HTTP POST cuando ocurren eventos (cambio de estado de una orden, issue detectado, stock actualizado, etc.).

Esta es la guía más crítica del portal. Prestale especial atención a la validación de firma y al sistema de retry.

Un webhook es una URL tuya que Pickwise llama cuando pasa algo. Tu endpoint recibe un POST con un JSON describiendo el evento y headers de seguridad/trazabilidad.

Comparado con polling:

  • Más rápido: latencia de segundos en lugar de minutos.
  • Más barato en rate limit: no consumís cupo haciendo GETs.
  • Más simple lógicamente: reaccionás al evento en lugar de mantener estado de “qué ya vi”.

El costo es operacional: tu endpoint tiene que estar up, validar firma y manejar reintentos.

  1. Registrás un endpoint en Pickwise con POST /webhooks.
  2. Cuando ocurre un evento, Pickwise envía un POST firmado a tu URL.
  3. Tu endpoint valida la firma HMAC y responde 2xx rápido.
  4. Procesás el evento (de forma asincrónica, idealmente).
EventoSe dispara cuando…Payload
order.status_changedUna orden cambia de estado (ej: PENDING → IN_PICKING)Ver abajo
order.issue_detectedSe detecta un problema en una orden (producto no encontrado, stock insuficiente)Ver abajo
order.issue_resolvedUn problema previamente detectado fue resueltoVer abajo
order.items_adjustedEl equipo de almacén ajustó los items de la ordenSimilar a issue_detected
product.stock_updatedEl stock de un producto cambió (post-picking o ajuste manual)Ver abajo

POST /webhookspermisos: webhooks:manage

Ventana de terminal
curl -X POST https://api-{CLIENTE}.pickwise.com.ar/api/v1/public/webhooks \
-H "Authorization: Bearer pk_live_xxxx" \
-H "Content-Type: application/json" \
-d '{
"url": "https://mi-erp.com/pickwise/webhook",
"events": [
"order.status_changed",
"order.issue_detected",
"order.issue_resolved",
"product.stock_updated"
],
"secret": "mi-secret-para-firmar-al-menos-32-chars"
}'
Content-Type: application/json
X-Pickwise-Signature: sha256=a1b2c3d4e5f6...
X-Pickwise-Event: order.status_changed
X-Pickwise-Timestamp: 2026-03-07T18:00:00Z
X-Pickwise-Delivery-Id: 550e8400-e29b-41d4-a716-446655440000
HeaderUso
X-Pickwise-SignatureFirma HMAC-SHA256. Validarla siempre.
X-Pickwise-EventNombre del evento. Útil para routing sin parsear el body.
X-Pickwise-TimestampCuándo se disparó en Pickwise. Útil para detectar eventos muy viejos.
X-Pickwise-Delivery-IdUUID único del intento. Usalo para dedupe.

La firma se calcula como HMAC-SHA256 del body raw con tu secret. Para validarla correctamente:

  1. Usá el body raw (bytes tal como llegaron), no el body re-serializado después de un JSON.parse.
  2. Usá una función de comparación tiempo-constante (timingSafeEqual / hash_equals). Comparar con === o == es vulnerable a timing attacks.
import crypto from 'crypto';
import express from 'express';
const app = express();
// Importante: usar raw body para el HMAC, no body parseado
app.post(
'/webhooks/pickwise',
express.raw({ type: 'application/json' }),
(req, res) => {
const signature = req.header('X-Pickwise-Signature');
const secret = process.env.PICKWISE_WEBHOOK_SECRET;
if (!verifySignature(req.body, signature, secret)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body.toString('utf8'));
// Dedupe por delivery-id (idempotencia)
const deliveryId = req.header('X-Pickwise-Delivery-Id');
if (alreadyProcessed(deliveryId)) {
return res.status(200).send('OK'); // No reprocesar
}
// Responder rápido, procesar async
res.status(200).send('OK');
processEventAsync(event, deliveryId);
}
);
function verifySignature(rawBody, signature, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
{
"event": "order.status_changed",
"timestamp": "2026-03-07T18:00:00Z",
"data": {
"externalId": "ERP-ORD-98765",
"orderNumber": "OC-2026-00543",
"previousStatus": "PACKED",
"status": "DISPATCHED",
"updatedAt": "2026-03-07T18:00:00Z"
}
}
{
"event": "order.issue_detected",
"timestamp": "2026-03-07T14:00:00Z",
"data": {
"externalId": "ERP-ORD-98770",
"orderNumber": "OC-2026-00548",
"status": "PENDING_VALIDATION",
"hasIssues": true,
"issues": [
{
"type": "UNRESOLVED_PRODUCT",
"productExternalId": "ERP-PROD-UNKNOWN",
"productSku": "SKU-NO-ENCONTRADO"
}
]
}
}
{
"event": "order.issue_resolved",
"timestamp": "2026-03-07T15:00:00Z",
"data": {
"externalId": "ERP-ORD-98770",
"orderNumber": "OC-2026-00548",
"status": "PENDING",
"hasIssues": false,
"resolvedIssues": ["UNRESOLVED_PRODUCT"]
}
}
{
"event": "product.stock_updated",
"timestamp": "2026-03-07T18:15:00Z",
"data": {
"productId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"sku": "AURICULAR-BT500",
"previousStock": 150,
"newStock": 148,
"reason": "ORDER_PICKED"
}
}
CampoDescripción
productIdUUID interno del producto en Pickwise
skuSKU del producto
previousStockStock antes del cambio
newStockStock después del cambio
reasonMotivo (ORDER_PICKED, MANUAL_ADJUSTMENT, INVENTORY_SYNC)

Tu endpoint debe:

  1. Responder con código 2xx (200, 201, 202, 204) dentro de 10 segundos.
  2. Aceptar POST con Content-Type: application/json.
  3. Ser accesible vía HTTPS en producción.
  4. Ser idempotente — podés recibir el mismo evento más de una vez.

Si tu endpoint tarda > 10s o devuelve no-2xx, se considera fallido y entra al sistema de retry.

Si tu endpoint falla (timeout, 5xx, conexión rechazada), Pickwise reintenta automáticamente con backoff:

IntentoDemora aproximada
11 minuto
215 minutos
31 hora
43 horas
56 horas

Después de 5 intentos fallidos, el evento se mueve a la Dead Letter Queue (DLQ).

Los eventos en DLQ se pueden listar y re-enviar.

GET /webhooks/{id}/dead-letters
POST /webhooks/{id}/replay

Acepta un body opcional con filtros (rango de fechas, tipo de evento, etc.) para replay selectivo. Ver la referencia del endpoint para el schema completo.

GET /webhooks/{id}/deliveries

Para probar tu endpoint sin esperar un evento real:

POST /webhooks/{id}/test
{ "event": "order.status_changed" }

Envía un payload sintético (marcado con "test": true) sin reintentos. Útil para validar signing y conectividad.

MétodoEndpointDescripción
GET/webhooksListar tus webhooks
GET/webhooks/{id}Detalle de un webhook
PATCH/webhooks/{id}Actualizar URL, eventos, activar/desactivar
DELETE/webhooks/{id}Eliminar un webhook
SíntomaCausa probableSolución
”Signature verification failed”Secret equivocadoRegenerar webhook o verificar env vars
”Signature verification failed”Parsear body antes del HMACUsar raw body
Eventos no lleganWebhook desactivadoGET /webhooks/{id}, revisar isActive
Eventos no lleganURL incorrectaPATCH /webhooks/{id} con la URL buena
Eventos no lleganFirewall / ACL bloquea IPs de PickwisePedir lista de IPs a soporte y whitelistear
429 en mis deliveriesMi endpoint tiene rate limit propioLevantar o exceptuar la IP de Pickwise
Evento duplicadoRetry automático por timeout previoDedupe con delivery-id
Evento muy viejo llega ahoraReplay desde DLQ, o retry después de 6hRevisar X-Pickwise-Timestamp y decidir si procesarlo