Guide

External Event Sources Fuentes de Eventos Externas

Feed any external conversion source — Everflow, GA4, a generic webhook — into RELO. Every source converges at relo.events through one shared normalizer, so it shows up in analytics with zero per-source code. Alimenta cualquier fuente de conversiones externa — Everflow, GA4, un webhook genérico — a RELO. Toda fuente converge en relo.events a través de un solo normalizador compartido, así que aparece en analytics sin código por fuente.

1. Overview 1. Resumen

Any external conversion source — an affiliate network like Everflow, Google Analytics 4, or a partner's own generic webhook — converges at the same place: the relo.events table. They all flow through one shared normalizer keyed off event_name. Sources differ at ingestion (each has its own wire format and adapter) but converge at relo.events. Cualquier fuente de conversiones externa — una red de afiliados como Everflow, Google Analytics 4, o el webhook genérico de un partner — converge en el mismo lugar: la tabla relo.events. Todas pasan por un solo normalizador compartido basado en event_name. Las fuentes difieren en la ingesta (cada una tiene su propio formato y adaptador) pero convergen en relo.events.

Because the event-driven analytics layer already reads relo.events by event_name, a new source needs zero per-source analytics code. Once a conversion lands as the right event (e.g. purchase or lead_submit), the KPIs, funnel, payouts and partner reporting work for free. Como la capa de analytics event-driven ya lee relo.events por event_name, una fuente nueva no requiere nada de código de analytics por fuente. Una vez que una conversión cae como el evento correcto (p.ej. purchase o lead_submit), los KPIs, el funnel, los pagos y el reporte por partner funcionan gratis.

// Sources differ at ingestion, converge at relo.events

Everflow postback   ─┐
GA4  MP / BigQuery  ─┼─▶ (adapter) ─▶ ingest.BuildEvent ─▶ relo.events ─▶ event-driven analytics
generic webhook     ─┘                  (shared normalizer)              (reads by event_name)

2. How it works 2. Cómo funciona

Each adapter does the same five steps. Only the first step (parsing the wire format) is source-specific. Cada adaptador hace los mismos cinco pasos. Solo el primero (parsear el formato) es específico de la fuente.

1

Adapter parses the payload El adaptador parsea el payload

The source's wire format (Everflow query params, a GA4 Measurement Protocol body, a generic JSON post) is parsed into a flat payload map. El formato de la fuente (query params de Everflow, un body de GA4 Measurement Protocol, un POST JSON genérico) se parsea a un payload plano.

2

Normalize via ingest.BuildEvent(payload, clientId, config) Normaliza vía ingest.BuildEvent(payload, clientId, config)

The data-source id in the URL selects the field mapping from client_data_sources.extra_config. The normalizer applies the event_map rename, picks the value/currency, derives a stable event_id and harvests candidate attribution fields — producing one relo.events row. El id de la fuente en la URL selecciona el mapeo de campos de client_data_sources.extra_config. El normalizador aplica el rename de event_map, toma value/currency, deriva un event_id estable y recolecta campos candidatos de atribución — produciendo una fila de relo.events.

3

Matcher resolves partner attribution El matcher resuelve la atribución de partner

The candidate fields (and the partner_param you configured) are run through the matcher against the client's attribution rules. A match sets partner_id; no match leaves it NULL. Los campos candidatos (y el partner_param que configuraste) pasan por el matcher contra las reglas de atribución del cliente. Un match fija partner_id; sin match queda NULL.

4

Write to ClickHouse Escribe a ClickHouse

The normalized event is written to relo.events. From here the event-driven analytics picks it up automatically by event_name. El evento normalizado se escribe a relo.events. Desde aquí el analytics event-driven lo toma automáticamente por event_name.

5

Optional partner S2S postback re-fire Re-disparo opcional de postback S2S al partner

When a partner is attributed, RELO can re-fire that partner's server-to-server postback. This is gated by key_events.fire_postback — a no-op unless the admin opted that key event into postback dispatch. Cuando hay partner atribuido, RELO puede re-disparar el postback server-to-server de ese partner. Esto está controlado por key_events.fire_postback — no hace nada salvo que el admin haya activado ese key event para el envío de postback.

3. The field-mapping config 3. La config de mapeo de campos

One JSONB object — client_data_sources.extra_config — drives every source. It is edited through the admin Tracking sources field-mapping editor (no SQL needed). An empty config makes the source behave as legacy pass-through; any non-empty key turns on full normalization. Un solo objeto JSONB — client_data_sources.extra_config — maneja todas las fuentes. Se edita desde el editor de mapeo de campos en Tracking sources del admin (sin SQL). Una config vacía deja la fuente en modo pass-through legacy; cualquier llave activa la normalización completa.

Key PurposePropósito
source Adapter id and matcher source tag ("everflow", "ga4", "generic"). Id del adaptador y etiqueta de fuente para el matcher ("everflow", "ga4", "generic").
event_map Renames source event names to RELO event names, e.g. Sale → purchase, generate_lead → lead_submit. Renombra los nombres de evento de la fuente a nombres RELO, p.ej. Sale → purchase, generate_lead → lead_submit.
event_name_key Payload key holding the source event name. Defaults to event_name. For Everflow use "event". Llave del payload con el nombre del evento. Default event_name. Para Everflow usa "event".
value_field Payload key holding the monetary value. Llave del payload con el valor monetario.
currency_field Payload key holding the currency code. Llave del payload con el código de moneda.
currency Static currency fallback used when currency_field is empty or missing. Moneda fija usada como fallback cuando currency_field está vacío o ausente.
partner_param Payload key carrying the affiliate / sub id, fed to the matcher for attribution. Llave del payload con el id de afiliado / sub, que se pasa al matcher para atribución.
dedup_field Payload key whose value becomes a stable event_id. Empty → random ULID. Llave del payload cuyo valor se vuelve un event_id estable. Vacío → ULID aleatorio.
event_time_field Payload key holding the source timestamp. Empty → now. Llave del payload con el timestamp de la fuente. Vacío → ahora.

Example extra_config for an Everflow source: Ejemplo de extra_config para una fuente Everflow:

{
  "source": "everflow",
  "event_name_key": "event",
  "event_map": {
    "Lead": "lead_submit",
    "Sale": "purchase"
  },
  "value_field": "amount",
  "currency": "MXN",
  "partner_param": "sub1",
  "dedup_field": "transaction_id"
}

4. Everflow adapter 4. Adaptador Everflow

Everflow fires a server postback per conversion. Endpoint: Everflow dispara un postback de servidor por conversión. Endpoint:

https://ingest.relo.mx/everflow/:dataSourceId

It accepts a GET with query params (Everflow's default) or a POST JSON body that overlays the query. Set source_type='everflow' on the data source and point the offer's server postback URL in Everflow at it, using Everflow macros: Acepta un GET con query params (el default de Everflow) o un body POST JSON que sobrescribe el query. Pon source_type='everflow' en la fuente y apunta la URL de postback de servidor de la oferta en Everflow hacia él, usando macros de Everflow:

https://ingest.relo.mx/everflow/42?event=Sale&sub1={sub1}&amount={amount}&transaction_id={transaction_id}

With the example config above, event=Sale maps to purchase, amount becomes the value, sub1 drives attribution, and transaction_id becomes the dedup event_id. Con la config de ejemplo de arriba, event=Sale mapea a purchase, amount es el valor, sub1 maneja la atribución, y transaction_id se vuelve el event_id de dedup.

5. GA4 adapter 5. Adaptador GA4

Relay GA4 Measurement Protocol hits to: Reenvía hits de GA4 Measurement Protocol a:

https://ingest.relo.mx/ga4/:dataSourceId  (POST)

The POST body is the GA4 Measurement Protocol shape — one request carries N events: El body del POST es la forma de GA4 Measurement Protocol — una petición lleva N eventos:

{
  "client_id": "GA1.2.1234567890.1700000000",
  "events": [
    {
      "name": "generate_lead",
      "params": {
        "value": 50,
        "currency": "MXN",
        "sub2": "affiliate_relo",
        "transaction_id": "lead-98765"
      }
    }
  ]
}

Each event is flattened — its params plus event_name (the GA4 name) and the GA client/user ids — and run through the same normalizer. An event_map like { "generate_lead": "lead_submit" } renames it to a RELO event the admin can pick as a key event. Cada evento se aplana — sus params más event_name (el nombre GA4) y los ids de cliente/usuario de GA — y pasa por el mismo normalizador. Un event_map como { "generate_lead": "lead_submit" } lo renombra a un evento RELO que el admin puede elegir como key event.

BigQuery export clients: GA4 properties that only export to BigQuery can use the batch bridge scripts/ga4_bigquery_pull.py, which reads the export table and replays each row through this same /ga4/:dataSourceId endpoint — so the batch path needs no extra ingest code. The dedup_field makes re-running a day idempotent. Clientes con export a BigQuery: las propiedades GA4 que solo exportan a BigQuery pueden usar el bridge por lotes scripts/ga4_bigquery_pull.py, que lee la tabla de export y reproduce cada fila por este mismo endpoint /ga4/:dataSourceId — así el camino por lotes no necesita código de ingesta extra. El dedup_field hace idempotente re-correr un día.

6. Generic webhook 6. Webhook genérico

For any JSON conversion from a source without a dedicated adapter (Shopify, WooCommerce, a custom backend), POST it to: Para cualquier conversión JSON de una fuente sin adaptador dedicado (Shopify, WooCommerce, un backend propio), haz POST a:

https://ingest.relo.mx/postback/generic?client_id=N

With an empty extra_config, the generic endpoint behaves as legacy pass-through (event type custom, raw event_name, no value or attribution). Fill in the field mapping to get the same full normalization — event_map, value/currency, dedup and attribution — as the dedicated adapters. Con un extra_config vacío, el endpoint genérico actúa como pass-through legacy (tipo custom, event_name crudo, sin valor ni atribución). Llena el mapeo de campos para obtener la misma normalización completa — event_map, value/currency, dedup y atribución — que los adaptadores dedicados.

7. Dedup & attribution notes 7. Notas de dedup & atribución

Deduplication Deduplicación

The dedup_field value becomes a stable event_id: the source transaction id is hashed into a fixed-length, ClickHouse-safe token, so a re-sent postback maps to the same id and does not double-count. Leave it empty and each event gets a random ULID (no dedup). El valor de dedup_field se vuelve un event_id estable: el id de transacción de la fuente se hashea a un token de longitud fija seguro para ClickHouse, así un postback reenviado mapea al mismo id y no cuenta doble. Si lo dejas vacío, cada evento recibe un ULID aleatorio (sin dedup).

Attribution Atribución

Partner attribution resolves via the matcher against the client's attribution rules. The normalizer harvests well-known fields from the payload (sub1..5, utm_source, utm_campaign, campaign, media_source, affiliate_id, cid, pid, and others) plus whatever you set as partner_param, and passes them to the matcher tagged with the source. A match sets partner_id; otherwise the event is stored unattributed. La atribución de partner se resuelve vía el matcher contra las reglas de atribución del cliente. El normalizador recolecta campos conocidos del payload (sub1..5, utm_source, utm_campaign, campaign, media_source, affiliate_id, cid, pid, entre otros) más lo que pongas en partner_param, y los pasa al matcher etiquetado con la fuente. Un match fija partner_id; si no, el evento se guarda sin atribuir.