LealUp Docs
API y Webhooks

Ingesta de eventos

Enviar eventos de uso a LealUp — schema, batching, deduplicación y cómo alimentan el health score.

La ingesta de eventos es la forma como LealUp sabe qué están haciendo los usuarios en tu producto. Alimenta la dimensión Uso del producto del health score y activa playbooks basados en comportamiento.

Conceptos

  • Evento: un hecho que pasó (ej. "user_logged_in", "report_exported", "feature_X_clicked").
  • Customer: a qué cliente corresponde el evento (required). Lo identificás con tu ID interno (external_customer_id) o con el UUID interno de LealUp (customer_id).
  • User (opcional): qué usuario específico dentro del cliente disparó el evento.
  • Properties (opcional): metadata del evento (reporte tipo X, monto Y, etc.).

Endpoint

POST /v1/ingest/events

Auth: header X-API-Key. Body: array de eventos, hasta 100 por batch.

Schema de un evento

Recomendado — identificá al cliente con tu propio ID:

{
  "external_customer_id": "acme_42",  // tu ID interno del cliente
  "external_source": "internal",       // opcional, default "internal"
  "event_name": "report_exported",     // required, snake_case
  "user_id": "u_12345",                // opcional
  "timestamp": "2026-04-15T14:25:00Z", // opcional, default = ahora
  "properties": {                      // opcional
    "report_type": "quarterly",
    "format": "pdf",
    "size_mb": 2.3
  }
}

Alternativa — si ya tenés el UUID de LealUp en tu sistema:

{
  "customer_id": "01HCUS...",          // UUID interno de LealUp
  "event_name": "report_exported",
  "timestamp": "2026-04-15T14:25:00Z"
}

Tip: usá external_customer_id con tu propio ID (CRM, base de datos, lo que sea). LealUp resuelve internamente al cliente correcto. No necesitás guardar UUIDs nuestros en tu sistema.

Validaciones

  • Referencia al cliente — exactamente uno entre customer_id y external_customer_id por evento. Si no envías ninguno → 422 customer_reference_required. Si envías ambos, gana customer_id.
  • external_customer_id — debe existir como cliente con external_id igual en tu workspace. Si no resuelve → error per-event unknown_external_customer. La búsqueda es tenant-scoped: el ID de un workspace nunca resuelve en otro.
  • external_source — string, default "internal". Útil si tenés clientes desde múltiples sistemas (ej. "salesforce", "stripe", "hubspot"); cada uno mantiene su propio espacio de IDs.
  • customer_id — UUID válido. Si no es UUID → error per-event invalid_customer_id. Si no existe en el workspace → unknown_customer.
  • event_name — snake_case, max 255 chars.
  • timestamp — ISO 8601 con timezone. Aceptamos hasta 30 días en el pasado; eventos más viejos se rechazan (usa endpoint de backfill separado).
  • properties — JSON object, max 10KB por evento.

Batching

Envía batches, no uno por uno:

POST /v1/ingest/events
X-API-Key: sk_live_...
Content-Type: application/json

{
  "events": [
    {"external_customer_id": "acme_42", "event_name": "login", "timestamp": "..."},
    {"external_customer_id": "acme_42", "event_name": "page_view", "timestamp": "..."},
    ...hasta 100
  ]
}

Respuesta (202 Accepted):

{
  "accepted": 98,
  "rejected": 2,
  "errors": [
    {"index": 5, "event_name": "login", "code": "unknown_external_customer", "message": "external_customer_id 'foo' (source='internal') not found for this tenant"},
    {"index": 42, "event_name": "report_exported", "code": "unknown_customer", "message": "customer_id '01HCUS...' not found for this tenant"}
  ],
  "message": "Accepted 98 events; 2 rejected"
}

Los eventos válidos se procesan; los rejected quedan listados con su código por evento (unknown_external_customer, unknown_customer, invalid_customer_id) para que los corrijas. Si ningún evento del batch tiene referencia al cliente, devolvemos 422 con código customer_reference_required.

Rate limits

  • 60 batches/min por workspace (hasta 1000 eventos cada uno = 60k eventos/min en burst).
  • 1M eventos/día en plan Starter, 10M en Growth, 100M en Scale, ilimitado en Enterprise.
  • Excesos → 429 Too Many Requests con Retry-After.

Deduplicación

Usa external_id para prevenir duplicados:

{
  "customer_id": "01HCUS...",
  "event_name": "purchase_completed",
  "external_id": "order_12345",    // tu ID interno
  "timestamp": "...",
  "properties": {"amount_usd": 99}
}

Si reenviás el mismo external_id + customer_id, LealUp ignora el duplicado silenciosamente. Útil en reintentos con backoff.

Cómo alimentan el health score

Frecuencia de uso

  • Días/semana con al menos un evento → dimensión Uso del producto.
  • Una caída brusca vs baseline de 30 días → puede gatillar alerta.

Amplitud

  • Número de eventos distintos (diversidad) → señal de adopción.
  • Clientes que usan 2 features tienen menor amplitud que los que usan 10.

Usuarios activos

  • DAU / MAU por cliente → señal de adopción horizontal (más usuarios activos = más sticky).

Eventos marcados como "aha moment"

Puedes marcar eventos específicos en Admin → Salud → Aha moments:

  • Ej. first_report_shared → signifiant positive signal.
  • Ej. billing_error_encountered → negative signal.

Ejemplos prácticos

Ingesta server-side (Node.js)

import { LealUpClient } from "@lealup/sdk";

const client = new LealUpClient({ apiKey: process.env.LEALUP_KEY! });

// En tu backend, después de cualquier acción relevante.
// Usá tu propio ID de cliente — no necesitás guardar UUIDs de LealUp.
async function trackEvent(
  yourCustomerId: string,
  userId: string,
  event: string,
  properties?: Record<string, unknown>,
) {
  await client.events.ingest({
    events: [{
      external_customer_id: yourCustomerId,
      user_id: userId,
      event_name: event,
      timestamp: new Date().toISOString(),
      properties,
    }],
  });
}

Batching para volumen alto

Si generas >100 eventos/seg, no envíes uno por uno. Usa un buffer en memoria con flush periódico:

class EventBuffer {
  private buffer: Event[] = [];
  private flushInterval = 5_000; // 5 seg

  constructor(private client: LealUpClient) {
    setInterval(() => this.flush(), this.flushInterval);
  }

  add(event: Event) {
    this.buffer.push(event);
    if (this.buffer.length >= 500) this.flush(); // flush si llenamos
  }

  async flush() {
    if (this.buffer.length === 0) return;
    const batch = this.buffer.splice(0, 1000);
    await this.client.events.ingest({ events: batch });
  }
}

Ingesta desde PostHog / Mixpanel

Si ya mandás eventos a otro product analytics, no los reingestes. Usa la integración nativa:

LealUp extrae los eventos relevantes (no todos) para no hinchar el score.

Backfill histórico

Para cargar eventos más antiguos que 30 días:

POST /v1/events/backfill

Mismo schema, acepta timestamps hasta 2 años atrás. Rate-limited más agresivamente, corre async. Útil para onboarding (cargar históricos una vez).

Eventos sistema (no los generas tú)

LealUp genera internamente algunos eventos que aparecen en el timeline pero no necesitas enviar:

  • email_sent / email_received — desde Gmail.
  • meeting_held — desde Calendar.
  • ticket_opened / ticket_closed — desde Zendesk/Jira.
  • health_changed — cuando el score cruza umbral.
  • task_created / task_completed — desde playbooks.

No los dupliques con ingesta manual.

Qué NO enviar

  • Eventos ruidosos (button_hovered, scroll). No aportan a health, saturan storage.
  • PII innecesaria — no mandes nombres, emails o direcciones en properties salvo que los necesites.
  • Payload pesado — si properties es >10KB, lo rechazamos. Usa IDs y relaciona después.

Reglas de higiene

  • Usa snake_case en event_name y keys de properties.
  • Mantén una lista de eventos válidos documentada en tu repo (taxonomía).
  • No renombres eventos existentes — pierde historia. Si necesitas, crea uno nuevo (report_exported_v2).
  • Test en dev antes de prod.

Debugging

"No veo mis eventos"

  • GET /v1/events?customer_id=X&from=2026-04-15 — lista los eventos registrados.
  • Verifica customer_id — errores silenciosos si el customer no existe en ese workspace.
  • Revisa Admin → Datos → Ingesta → Logs para ver rejections recientes.

"Health score no cambia"

  • El score se recalcula cada hora por default (config en Admin → Salud).
  • Si mandaste eventos recién, puede tardar hasta 1h. Usa POST /v1/customers/{id}/health/recalculate para forzar.

"Demasiados errores unknown_external_customer o customer_id not found"

  • Los customers se crean via POST /v1/customers o CSV import, antes de mandar eventos.
  • Si usás external_customer_id, asegurate de que el cliente exista con external_id igual y external_source que coincida (default "internal").
  • Si tu flow es "usuario se registra en tu app → mandar evento", asegurate de crear el customer primero (patrón provisioning).

Siguiente

  • Webhooks — recibir señales salientes cuando pasan cosas en LealUp.

On this page