☰ Menú · Webhooks
Guías

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
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"]
  }'
Respuesta · 201
{
  "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

curl
POST /loyaltyfy/webhooks HTTP/1.1
Content-Type: application/json
User-Agent: Loyaltyfy-Webhooks/1.0
Loyaltyfy-Signature: t=1791310488,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
JSON
{
  "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.

Node.js
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);
}
Python
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
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.

Dudas sobre una integración: api@loyaltyfy.io. Respondemos en un día hábil.