Saltar al contenido principal

Recipe: Tracking de conversiones end-to-end

El funnel completo en Neuroon tiene tres eventos clave:

  1. track:click — el usuario interactúa con un resultado en el widget. Lo emite el widget automáticamente, sin código tuyo.
  2. track:add-to-cart — opcional, derivado del cart bridge.
  3. track:conversion — pedido pagado / completado. Tienes que llamarlo tú server-side desde el handler de tu backend.

Por qué la conversión va server-side

Confiar solo en un pixel JS pierde una parte significativa de las conversiones:

BloqueoImpacto típico
Adblockers (uBlock, Ghostery, Brave Shields)25-40 % en mercados europeos.
Safari ITP + iOS Private Relayimpacto residual sobre cookies.
CSPs estrictas (banca, gobierno)bloquean cualquier pixel third-party.
Service workers offlineno envían beacons antes de cerrar tab.

Llamando al endpoint server-to-server desde el handler que confirma el pedido eliminas esos cuatro vectores.

El endpoint

POST/api/plugin/shops/{shopId}/track/conversion
POST /api/plugin/shops/shop_xxxxxxxx/track/conversion HTTP/1.1
Host: api.neuroon.ai
X-Shop-API-Key: sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Origin: https://your-shop.example
Content-Type: application/json

{
"orderId": "ORD-2026-0001",
"orderValue": 199.95,
"currency": "EUR",
"conversions": [
{ "productId": "SKU-001", "searchLogId": "log_aaa", "quantity": 2, "lineTotal": 79.98 },
{ "productId": "SKU-042", "searchLogId": "log_bbb", "quantity": 1, "lineTotal": 119.97 }
]
}

searchLogId es obligatorio (@NotBlank en ConversionItem, ShopRequestDTO.java:92). Lo emite el widget en cada track:click y lo guarda en una cookie en el navegador del comprador. Tu servidor debe leer esa cookie cuando se confirma el pedido y mapear cada productId con el searchLogId correspondiente. Sin searchLogId la API responde 400. El plugin oficial de WordPress lo hace en wordpress-plugin/neuroon-search/includes/class-neuroon-conversion-tracking.php:335-374.

Idempotencia: Neuroon deduplica por orderId. Reenviar la misma conversión no infla métricas.

Implementaciones

track-conversion.js
import fetch from 'node-fetch';

export async function trackConversion(order) {
const payload = {
  orderId:    String(order.id),
  orderValue: Number(order.total),
  currency:   order.currency,
  conversions: order.lines.map((l) => ({
    productId: String(l.sku),
    quantity:  Number(l.quantity),
    lineTotal: Number(l.unitPrice * l.quantity),
  })),
};

try {
  const r = await fetch(
    `${process.env.NEUROON_API_URL}/api/plugin/shops/${process.env.NEUROON_SHOP_ID}/track/conversion`,
    {
      method: 'POST',
      headers: {
        'X-Shop-API-Key': process.env.NEUROON_API_KEY,
        'Origin':         process.env.NEUROON_ORIGIN,
        'Content-Type':   'application/json',
      },
      body: JSON.stringify(payload),
    },
  );
  if (!r.ok) console.warn('Neuroon conversion tracking failed', r.status);
} catch (err) {
  console.warn('Neuroon conversion tracking error', err);
}
}

// Uso desde tu handler de pedido confirmado
export async function onOrderConfirmed(order) {
// 1) marca el pedido como pagado en tu BD
// 2) trackea (fire-and-forget)
trackConversion(order); // sin await: no bloqueamos la respuesta al cliente
}

Outbox para reintentos

Si tu integración no puede tolerar pérdida de eventos (ej. tienda enterprise), persiste el payload en una tabla de outbox y reintenta con un job:

CREATE TABLE neuroon_conversion_outbox (
order_id VARCHAR(64) PRIMARY KEY,
payload JSON NOT NULL,
attempts INT NOT NULL DEFAULT 0,
last_error VARCHAR(1024),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
synced_at TIMESTAMP NULL
);

Job que procesa cada N minutos:

async function flushOutbox() {
const rows = await db.query(`
SELECT * FROM neuroon_conversion_outbox
WHERE synced_at IS NULL AND attempts < 5
ORDER BY created_at LIMIT 100`);

for (const row of rows) {
try {
await trackConversionRaw(row.payload);
await db.query(`UPDATE neuroon_conversion_outbox SET synced_at = NOW() WHERE order_id = $1`, [row.order_id]);
} catch (err) {
await db.query(`
UPDATE neuroon_conversion_outbox
SET attempts = attempts + 1, last_error = $1
WHERE order_id = $2`, [err.message, row.order_id]);
}
}
}

Validar

Tras procesar un pedido de prueba:

# El endpoint no devuelve la conversión en GET, pero puedes verificar que el dashboard
# de Neuroon refleja la nueva conversión en el plazo de unos segundos.
curl -s "https://dev-api.neuroon.ai/api/plugin/shops/$NEUROON_SHOP_ID/products/SKU-001" \
-H "X-Shop-API-Key: $NEUROON_API_KEY"

Errores frecuentes

SíntomaCausaSolución
400 con orderValue must be >= 0orderValue negativo (refunds)Usa el endpoint de tracking de refunds.
Conversión no aparece en dashboardorigin no coincide o shopId cruzado de entornosVerifica Origin y que la API Key sea del entorno correcto.
Conversiones duplicadasReenvíos sin idempotency key estableUsa siempre el mismo orderId por pedido (no timestamps).

Próximos pasos