☰ Menú · Webhooks
Webhooks
Recibe un POST por HTTPS cuando pasa algo: se aplica un ticket en cualquier punto, se desbloquea una recompensa, llega un pedido desde una mesa.
Los eventos cubren todo lo que se hace a través de la API en este local, tanto desde tu integración como desde cualquier otra (otra caja, un quiosco, un conector de reservas). Suscríbete solo a los eventos que uses.
Añade un endpoint
En Desarrolladores → Webhooks o con la API. La URL debe ser HTTPS pública. El secreto de firma se devuelve una sola vez, al crear el endpoint.
curl https://loyaltyfy.io/api/v1/webhook_endpoints \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://pos.example.com/loyaltyfy/webhooks",
"events": ["transaction.completed", "order.created"]
}'{
"object": "webhook_endpoint",
"id": "01c2a784-4bcf-43b9-8ac9-ddbad7a25341",
"url": "https://pos.example.com/loyaltyfy/webhooks",
"description": null,
"events": ["transaction.completed", "order.created"],
"status": "enabled",
"disabled_reason": null,
"livemode": false,
"created_at": "2026-10-06T18:17:32.381+00:00",
"secret": "whsec_UvjHvXfPk80ITYl_xPeAtskI0WjR8-PS"
}Qué recibes
POST /loyaltyfy/webhooks HTTP/1.1
Content-Type: application/json
User-Agent: Loyaltyfy-Webhooks/1.0
Loyaltyfy-Signature: t=1791310488,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd{
"id": "evt_5a1f0c9e2b7d4e3f81a0c6d2",
"object": "event",
"type": "order.created",
"created": 1791310488,
"livemode": true,
"data": {
"object": {
"object": "order",
"id": "0c69a81c-0fbc-4333-b26c-789566b9bea4",
"number": 10,
"status": "new",
"table": "7",
...
}
}
}data.object es el mismo objeto que devuelve la API (una transacción, un socio, un pedido), tal como estaba en el momento del evento. Responde con cualquier 2xx en menos de 10 segundos. Haz el trabajo lento después de responder.
Verifica la firma
Loyaltyfy-Signature contiene una marca de tiempo t y una firma v1: el HMAC-SHA256 de t, un punto y el cuerpo sin procesar de la petición, con el secreto de tu endpoint como clave, en hexadecimal. Calcúlalo sobre los bytes exactos que recibiste, antes de parsear el JSON, compara en tiempo constante y rechaza las marcas de tiempo con más de cinco minutos.
import crypto from "node:crypto";
// rawBody: the request body exactly as received (a string or Buffer, not parsed JSON)
export function verifyLoyaltyfy(rawBody, header, secret, toleranceSec = 300) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(parts.v1 ?? "");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}import hmac, hashlib, time
def verify_loyaltyfy(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
t = int(parts.get("t", "0"))
if abs(time.time() - t) > tolerance:
return False
signed = f"{t}.".encode() + raw_body
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))Reintentos
Si tu endpoint no responde 2xx a tiempo, reintentamos al cabo de 1 minuto, 5 minutos, 30 minutos, 2 horas, 6 horas, 12 horas y 24 horas. Tras 20 entregas fallidas seguidas, el endpoint se desactiva y el local ve el motivo en el panel; vuelve a activarlo con PATCH /v1/webhook_endpoints/{id} y enabled: true.
Por los reintentos, un evento puede llegar más de una vez, y los eventos pueden llegar desordenados. Usa el id del evento para descartar duplicados y las marcas de tiempo del propio objeto para decidir cuál es más reciente.
Prueba un endpoint
curl -X POST https://loyaltyfy.io/api/v1/webhook_endpoints/01c2a784-4bcf-43b9-8ac9-ddbad7a25341/test \
-H "Authorization: Bearer sk_test_..."
# { "object": "webhook_test", "delivered": true, "status_code": 200, "error": null }La lista completa de eventos está en la referencia de eventos.