Webhooks
Cambios de estado
Beartrack le hace un POST a tu servidor cada vez que uno de tus envíos cambia de estado. No hay que consultar nada: el evento llega solo.
Cómo funciona
Nos das una URL. Cada vez que un envío tuyo se mueve, mandamos un POST con un sobre JSON firmado. Tú respondes 2xx y listo; cualquier otra cosa la reintentamos.
Sólo recibes los envíos que te corresponden: si eres un comercio, los tuyos; si eres un operador logístico, los de tu operación. Puedes además acotar por tipo de pedido o por estado.
Responde rápido y procesa después
Guarda el evento y devuelve2xx de inmediato. Si tardas más de 10 segundos cortamos la conexión y lo damos por fallido, aunque lo hayas procesado bien.El sobre
Lo mismo para todos los eventos. Los campos nuevos nacen opcionales y ninguno se renombra.
{
"id": "evt_9f2c4b8a1d3e4f5a6b7c8d9e0f1a2b3c",
"type": "package.status_changed",
"apiVersion": "2026-09-01",
"createdAt": "2026-09-06T18:22:31.004Z",
"data": {
"packageId": "66f1a3c9e4b0a1d2f3c40011",
"followingNumber": 268012,
"orderId": "123456",
"source": "Shopify",
"status": { "code": 4, "name": "delivered" },
"previousStatus": { "code": 3, "name": "in_transit" },
"occurredAt": "2026-09-06T18:22:30.881Z",
"company": { "id": "66e0aaaaaaaaaaaaaaaaaaaa" },
"logisticsCompany": { "id": "66c4bbbbbbbbbbbbbbbbbbbb" }
}
}Y estas cabeceras:
X-Beartrack-Eventpackage.status_changedTipo de evento. Hoy sólo existe este.
X-Beartrack-Deliverydlv_66f1a3c9e4b0a1d2f3c40011Identifica esta entrega. Es estable entre reintentos, así que te sirve para deduplicar directamente.
X-Beartrack-Subscription66e0aaaaaaaaaaaaaaaaaaaaQué suscripción originó la entrega. Útil si tienes más de una apuntando al mismo sitio.
X-Beartrack-Signaturet=1788669751,v1=8f3c…Firma HMAC-SHA256. Verifícala siempre antes de procesar el cuerpo.
occurredAt es cuándo se movió el envío; createdAt es cuándo armamos el sobre. Si veníamos reintentando, entre uno y otro puede haber minutos. Para ordenar eventos usa occurredAt.Estados
Un estado que no está en esta tabla no dispara webhook.
| code | name | Qué pasó |
|---|---|---|
| 0 | created | Pedido creado |
| 1 | collected | Recolectado en el comercio |
| 2 | at_warehouse | Escaneado en bodega |
| 3 | in_transit | En ruta de reparto |
| 30 | in_transit | Navegando a la dirección |
| 4 | delivered | Entregado |
| 5 | cancelled | Cancelado |
| 6 | delivery_failed | Intento de entrega fallido |
| 7 | delivery_failed | Intento de entrega fallido |
| 21 | return_received | Recibido para devolución |
| 22 | return_at_warehouse | En bodega para devolución |
| 31 | return_in_transit | En ruta de devolución |
| 41 | returned | Devolución completada |
6 y 7 comparten el nombre delivery_failed a propósito: el número no distingue el motivo de forma confiable. Lo único cierto en ambos es que hubo un intento y el paquete no se entregó.Verificar la firma
Hazlo siempre, antes de leer el cuerpo.
La cabecera X-Beartrack-Signature trae t=<unix>,v1=<hmac>. El v1 es el HMAC-SHA256 de "<t>.<cuerpo crudo>" con tu secreto.
import { createHmac, timingSafeEqual } from 'node:crypto'
// OJO: el cuerpo CRUDO, no el objeto ya parseado. Volver a serializar no
// reproduce los mismos bytes y la firma deja de coincidir.
export function verificar(cuerpoCrudo, cabecera, secreto) {
const partes = Object.fromEntries(
cabecera.split(',').map((p) => p.split('=')),
)
const { t, v1 } = partes
if (!t || !v1) return false
// Rechaza lo viejo: sin esto, un request capturado se puede reenviar siempre.
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false
const esperado = createHmac('sha256', secreto)
.update(`${t}.${cuerpoCrudo}`)
.digest('hex')
const a = Buffer.from(esperado)
const b = Buffer.from(v1)
return a.length === b.length && timingSafeEqual(a, b)
}Dos detalles que rompen la verificación
Verifica sobre el cuerpo crudo, no sobre el JSON re-serializado: dos serializaciones del mismo objeto no producen los mismos bytes. Y compara en tiempo constante (timingSafeEqual, hash_equals, compare_digest), no con ===.Reintentos
La entrega es at-least-once: prepárate para recibir el mismo evento dos veces.
Sin respuesta, 5xx, 408 o 429
hasta 12 intentosSe trata como transitorio. Backoff exponencial con jitter, del orden de segundos hasta un tope de 30 minutos.
400, 401, 403 o 422
hasta 4 intentosTope bajo: si rechazas el cuerpo o la firma, reintentar no lo arregla. Los pocos intentos cubren el caso de un secreto recién rotado que todavía no configuraste.
404, 410 y demás 4xx
hasta 1 intentoLa URL no existe. Reintentar no la crea, así que la entrega se descarta de inmediato.
Cómo deduplicar
Guarda elX-Beartrack-Delivery y descarta el que ya viste. Es estable entre reintentos del mismo evento, así que te sirve tal cual. El id del cuerpo funciona igual.Pedir un webhook
Todavía no es autogestionado
Escríbenos a [email protected] con la URL que va a recibir los eventos y, si quieres acotarlo, qué estados o qué tipos de pedido te interesan. Te devolvemos el secreto de firma una sola vez.La URL tiene que ser https y responder en menos de 10 segundos. Puedes registrar más de una.