> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tryprofound.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Integración con Cloudflare (Worker)

> Configura e implementa un Cloudflare Worker que captura datos de solicitudes HTTP y los reenvía a Profound Agent Analytics.

## Descripción general

La integración utiliza un Cloudflare Worker que se ejecuta como middleware para recopilar metadatos de las solicitudes y enviarlos a nuestra API de Agent Analytics. El Worker captura información importante de las solicitudes, como direcciones IP, user agents y referrers, sin afectar el manejo real de la solicitud.

## Requisitos previos

* Una cuenta de Cloudflare con acceso a Workers

* Node.js instalado en tu máquina de desarrollo

* Acceso a la configuración de Cloudflare de tu dominio

* Un Log Ingestion Token de Profound para Agent Analytics

<Tip>
  ¿Usas el plan Cloudflare Enterprise? Consulta nuestra <a href="https://docs.tryprofound.com/agent-analytics/cloudflare_logpush" target="_self">guía de integración con Cloudflare Logpush</a>. Logpush siempre es preferible a un Worker: funciona fuera de banda y no puede afectar el tráfico en vivo.
</Tip>

<Warning>
  Un Worker en una ruta `*` se sitúa en la ruta de **cada** solicitud a ese hostname, por lo que puede afectar el tráfico de producción. Usa el código siguiente tal cual y, en particular:

  * Nunca clones la respuesta ni leas el cuerpo de la respuesta dentro del Worker. Almacenar cuerpos en búfer puede superar el límite de memoria de 128 MB del isolate del Worker, lo que Cloudflare muestra al visitante (o crawler) como [Error 1102 `Worker exceeded resource limits`](https://developers.cloudflare.com/support/troubleshooting/http-status-codes/cloudflare-1xxx-errors/error-1102/).

  * Mantén la llamada de registro dentro de `ctx.waitUntil()` e ignora sus errores para que una solicitud de registro lenta o fallida nunca se convierta en un 5xx.

  * Mantén las rutas sensibles completamente fuera del Worker. La lista de rutas del código siguiente solo omite el registro; las solicitudes siguen pasando por el Worker. Para sacar por completo las rutas de checkout, carrito y administración del Worker, restringe el propio patrón de ruta (o añade una ruta de mayor prioridad que no esté vinculada al Worker).

  Los síntomas de un Worker que incumple estas reglas son respuestas 5xx intermitentes en respuestas grandes o transmitidas por streaming, y consecuencias posteriores como rechazos en Google Merchant Center / Google Ads ("destination not working", "image not accessible") cuando `AdsBot-Google` encuentra uno de esos errores durante un rastreo.
</Warning>

<Note>
  ¿Tienes una tienda en Shopify? No pongas un Worker delante. En su lugar, usa una de las opciones de log drain de nuestra <a href="https://docs.tryprofound.com/agent-analytics/shopify" target="_self">guía de Shopify</a>, que recopila los registros fuera de banda.
</Note>

<img src="https://mintcdn.com/profound-37face47/dU1U8PbY91PjiXyk/images/agent-analytics/cloudflare_worker/worker_setup.png?fit=max&auto=format&n=dU1U8PbY91PjiXyk&q=85&s=03401e0694f16e2b2076d70eb045e534" alt="Logpush detectado" width="3680" height="2390" data-path="images/agent-analytics/cloudflare_worker/worker_setup.png" />

## Guía de implementación

<Steps>
  <Step title="Configura tu entorno de desarrollo">
    Crea un nuevo proyecto de Worker e instala las dependencias:

    ### Crea un nuevo proyecto de Worker

    Primero, usa la CLI de Cloudflare Worker para crear un nuevo proyecto de Worker:

    En este ejemplo, usamos la versión `2.37.4` de la CLI de Cloudflare Worker. Puedes usar la versión más reciente, pero puede haber algunas diferencias menores en el proceso.

    ```bash theme={null}
    npm create cloudflare@2.37.4 -- log-collector
    ```

    Después de que npm se inicie, se te pedirá que selecciones un punto de partida. Selecciona "Hello World" como categoría inicial.

    <img src="https://mintcdn.com/profound-37face47/dU1U8PbY91PjiXyk/images/agent-analytics/cloudflare_worker/create-cloudflare-application-step1-1.png?fit=max&auto=format&n=dU1U8PbY91PjiXyk&q=85&s=49f53028fff87639f1bbe844d9591ade" alt="Cloudflare Worker paso 1 - Elegir punto de partida" width="867" height="307" data-path="images/agent-analytics/cloudflare_worker/create-cloudflare-application-step1-1.png" />

    Selecciona la plantilla de worker "Hello World".

    <img src="https://mintcdn.com/profound-37face47/dU1U8PbY91PjiXyk/images/agent-analytics/cloudflare_worker/create-cloudflare-application-step1-2.png?fit=max&auto=format&n=dU1U8PbY91PjiXyk&q=85&s=f225e20e3063a3c6dfd647c8d8835e09" alt="Cloudflare Worker paso 1 - Seleccionar plantilla de Worker" width="526" height="232" data-path="images/agent-analytics/cloudflare_worker/create-cloudflare-application-step1-2.png" />

    Selecciona el lenguaje "TypeScript".

    <img src="https://mintcdn.com/profound-37face47/dU1U8PbY91PjiXyk/images/agent-analytics/cloudflare_worker/create-cloudflare-application-step1-3.png?fit=max&auto=format&n=dU1U8PbY91PjiXyk&q=85&s=96537f92fa73752faa35b98a68351484" alt="Cloudflare Worker paso 1 - Seleccionar lenguaje" width="491" height="260" data-path="images/agent-analytics/cloudflare_worker/create-cloudflare-application-step1-3.png" />

    Ahora la CLI creará un nuevo proyecto con el nombre `log-collector` e instalará las dependencias necesarias.

    Selecciona Git para el control de versiones.

    <img src="https://mintcdn.com/profound-37face47/dU1U8PbY91PjiXyk/images/agent-analytics/cloudflare_worker/create-cloudflare-application-step2-1.png?fit=max&auto=format&n=dU1U8PbY91PjiXyk&q=85&s=7d671bd7a44948a1f6ec8264a89c0a39" alt="Cloudflare Worker paso 2 - Seleccionar control de versiones" width="604" height="255" data-path="images/agent-analytics/cloudflare_worker/create-cloudflare-application-step2-1.png" />

    Implementa la aplicación ahora. La aplicación se implementará con un dominio de desarrollo de Cloudflare y no afectará tu entorno de producción.

    <img src="https://mintcdn.com/profound-37face47/dU1U8PbY91PjiXyk/images/agent-analytics/cloudflare_worker/create-cloudflare-application-step3-1.png?fit=max&auto=format&n=dU1U8PbY91PjiXyk&q=85&s=96bd057ccbecc1d746170780e362b8b7" alt="Cloudflare Worker paso 3 - Implementar aplicación" width="347" height="73" data-path="images/agent-analytics/cloudflare_worker/create-cloudflare-application-step3-1.png" />

    La CLI de Cloudflare te pedirá que inicies sesión y selecciones la cuenta en la que quieres implementar la aplicación. Elige la cuenta a la que está asociado tu dominio.

    Cloudflare debería abrir automáticamente una ventana del navegador con "Hello World". Puedes navegar al directorio del proyecto una vez implementada la aplicación.

    ```bash theme={null}
    cd log-collector
    ```
  </Step>

  <Step title="Configura tu Worker">
    Edita tu archivo `wrangler.json` para configurar la variable de entorno `PROFOUND_API_URL` y la vinculación de ruta:

    <Warning>
      Reemplaza el patrón `example.com/*` por tu dominio real. Usa la URL de tu sitio objetivo (normalmente el sitio de marketing). Por ejemplo, si tu sitio de marketing es `https://www.example.com`, debes usar `www.example.com/*` como patrón.
      El `zone_name` debe ser tu dominio canónico sin [www](http://www).
    </Warning>

    <Tip>
      Si no estás seguro de la configuración correcta, contacta con el [soporte de Profound](mailto:support@tryprofound.com).
    </Tip>

    ```json wrangler.json theme={null}
    {
      "$schema": "node_modules/wrangler/config-schema.json",
      "name": "log-collector",
      "main": "src/index.ts",
      "compatibility_date": "2025-01-29",
      "observability": {
        "enabled": true
      },
      "route": {
        "pattern": "example.com/*",
        "zone_name": "example.com"
      },
      "vars": { "PROFOUND_API_URL": "https://artemis.api.tryprofound.com/v1/logs/cloudflare_worker" }
    }
    ```

    Luego copia el código TypeScript en `src/index.ts`:

    ```typescript src/index.ts theme={null}
    /**
     * Cloudflare Worker for Log Collection
     *
     * Forwards request metadata to Profound's log collection API. The origin
     * response body is never read or modified, and every error in the logging
     * path is swallowed, so logging cannot alter the response. Failures are
     * logged with enough detail to tell a rejected request apart from a
     * timeout or a connection error.
     */

    export interface Env {
        PROFOUND_API_URL: string;
        PROFOUND_LOG_INGESTION_TOKEN: string;
    }

    // Paths that are not logged. Matched on full path segments, so '/cart' does
    // not match '/cartography'.
    const EXCLUDED_PATHS = ['/checkout', '/cart', '/admin', '/api'];

    // Characters kept from a rejected response body when logging the failure.
    const DETAIL_LIMIT = 512;

    // Percent-decoded and lowercased so encoded variants such as '/%63heckout'
    // and '/checkout%2Fpayment' match the same way the origin routes them.
    function normalizePath(pathname: string): string {
        try {
            return decodeURIComponent(pathname).toLowerCase();
        } catch {
            return pathname.toLowerCase();
        }
    }

    function isExcluded(pathname: string): boolean {
        const path = normalizePath(pathname);
        return EXCLUDED_PATHS.some(
            (excluded) => path === excluded || path.startsWith(`${excluded}/`),
        );
    }

    export default {
        async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
            const response = await fetch(request);

            const skip = response.status === 101 || isExcluded(new URL(request.url).pathname);

            if (!skip) {
                ctx.waitUntil(
                    sendLog(request, response, env).catch((error: unknown) =>
                        console.error(
                            'Failed to send logs:',
                            error instanceof Error ? `${error.name}: ${error.message}` : error,
                        ),
                    ),
                );
            }

            return response;
        },
    } satisfies ExportedHandler<Env>;

    async function sendLog(request: Request, response: Response, env: Env) {
        const requestUrl = new URL(request.url);

        // +2 for ': ' and +2 for '\r\n' per header
        const headerSize = Array.from(response.headers.entries()).reduce(
            (total, [key, value]) => total + key.length + value.length + 4,
            0,
        );

        // Derived from content-length so the body is never buffered. Streamed or
        // chunked responses have no content-length and are reported as headers only.
        // HEAD and bodyless statuses advertise a content-length that is never sent,
        // so their body size is counted as zero.
        const contentLength = Number(response.headers.get('content-length'));
        const transmitsBody =
            request.method !== 'HEAD' && response.status !== 204 && response.status !== 304;
        const bodySize = transmitsBody && Number.isFinite(contentLength) ? contentLength : 0;
        const bytes = headerSize + bodySize;

        const logData = {
            timestamp: Date.now(),
            host: requestUrl.hostname,
            method: request.method,
            pathname: requestUrl.pathname,
            query_params: Object.fromEntries(requestUrl.searchParams),
            ip: request.headers.get('cf-connecting-ip'),
            userAgent: request.headers.get('user-agent'),
            referer: request.headers.get('referer'),
            bytes,
            status: response.status,
        };

        const logResponse = await fetch(env.PROFOUND_API_URL, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-API-Key': env.PROFOUND_LOG_INGESTION_TOKEN,
            },
            body: JSON.stringify([logData]),
            signal: AbortSignal.timeout(5000),
        });

        // Only the first chunk of a rejected response is read, and only up to
        // DETAIL_LIMIT characters of it are kept, so a misconfigured URL that
        // returns a large body can never fill the isolate. The origin response
        // is still never read.
        if (!logResponse.ok) {
            const reader = logResponse.body?.getReader();
            const chunk = await reader?.read();
            await reader?.cancel();

            const detail = chunk?.value
                ? new TextDecoder().decode(chunk.value).slice(0, DETAIL_LIMIT)
                : '';

            console.error(`Log ingestion rejected the request: ${logResponse.status} ${detail}`);
            return;
        }

        await logResponse.body?.cancel();
    }
    ```

    <Note>
      El Worker registra dos fallos distintos. `Log ingestion rejected the request: <status> <body>` significa que Profound recibió la solicitud y la rechazó. Suele tratarse de un problema de autenticación o de payload, y el estado y el cuerpo indican cuál. `Failed to send logs: <name>: <message>` significa que la solicitud nunca se completó. `TimeoutError` corresponde al tiempo de espera de cinco segundos. Otros nombres apuntan a un problema de conexión o de configuración. Ambos mensajes van a los registros del Worker. Puedes leerlos en Workers & Pages > tu Worker > Logs.
    </Note>

    <Note>
      `bytes` se deriva del encabezado de respuesta `content-length` en lugar del cuerpo de la respuesta. Leer el cuerpo (por ejemplo, mediante `response.clone()` y `blob()`) obliga a Cloudflare a almacenar todo el payload en búfer en el isolate del Worker, lo que falla con un 5xx en respuestas grandes o transmitidas por streaming. Este Worker nunca lee ni modifica el cuerpo de la respuesta. Transmite la respuesta del origen sin cambios, pero sigue estando en la ruta de la solicitud, por lo que los fallos de obtención del origen aparecen igual que sin él. Por tanto, `bytes` cuenta los encabezados más `content-length`, y solo cuando la respuesta realmente transmite un cuerpo (no `HEAD`, `204` ni `304`).
    </Note>
  </Step>

  <Step title="Implementa tu Worker">
    ### Inicia sesión en Cloudflare

    Usa la CLI de Wrangler para implementar el Worker:

    ```bash theme={null}
    # Login to Cloudflare
    npx wrangler login
    ```

    ### Configura el Log Ingestion Token de Profound

    Secrets es una función de Cloudflare Workers que te permite almacenar información sensible, como los Log Ingestion Tokens, en un entorno seguro.

    ```bash theme={null}
    # Configure the Log Ingestion Token secret
    npx wrangler secret put PROFOUND_LOG_INGESTION_TOKEN
    ```

    Se te pedirá que introduzcas el Log Ingestion Token. Copia y pega el token y pulsa Enter.

    <img src="https://mintcdn.com/profound-37face47/dU1U8PbY91PjiXyk/images/agent-analytics/cloudflare_worker/configure-api-key.png?fit=max&auto=format&n=dU1U8PbY91PjiXyk&q=85&s=7ea6fb5162663140e753640037802093" alt="Configurar Log Ingestion Token" width="195" height="62" data-path="images/agent-analytics/cloudflare_worker/configure-api-key.png" />

    ### Implementa el Worker

    ```bash theme={null}
    # Deploy the Worker
    npx wrangler deploy
    ```
  </Step>

  <Step title="Prueba tu implementación">
    Verifica que tu Worker funciona correctamente:

    Ve a **Agent Analytics** en Profound y comprueba que los registros aparecen en el panel **Logs**. El filtro de registros de IA está activado de forma predeterminada. Desactívalo con el filtro situado en la parte superior del panel **Logs**.

    Si aparecen registros, todo está listo. Deberías ver cómo se completan los datos en el panel de Analytics.
  </Step>
</Steps>

## Solución de problemas

* Si los registros no aparecen, verifica que la variable de entorno `PROFOUND_API_URL` y el secreto `PROFOUND_LOG_INGESTION_TOKEN` estén configurados correctamente

* `Log ingestion rejected the request: 401` o `403` en los registros del Worker: el secreto `PROFOUND_LOG_INGESTION_TOKEN` falta, está mal escrito o no es el token emitido para este sitio. Vuelve a configurarlo con `npx wrangler secret put PROFOUND_LOG_INGESTION_TOKEN`

* `Failed to send logs: TimeoutError`: la solicitud de registro superó el tiempo de espera de cinco segundos. Es normal que haya un pequeño número de estos errores y solo te cuestan las líneas de registro afectadas, ya que el tiempo de espera mantiene la llamada de registro alejada de tus visitantes. Si la tasa es sostenida, conviene informar al [soporte de Profound](mailto:support@tryprofound.com) con algunos valores de `requestId` de los registros del Worker

* `Failed to send logs:` con cualquier otro nombre de error: compara `PROFOUND_API_URL` con el valor del paso de configuración anterior. Una URL que se resuelve en un hostname de tu propia zona de Cloudflare hace que el Worker se llame a sí mismo, lo que falla en cada solicitud

* Revisa Cloudflare Workers > Analytics en busca de errores de ejecución

* Asegúrate de que tu patrón de ruta coincida con la configuración de tu dominio

* Verifica que el Worker está recibiendo solicitudes revisando las métricas del panel de Cloudflare

* 5xx intermitentes tras implementar el Worker: revisa Metrics > Errors > Invocation Statuses en busca de `Exceeded Memory` (Error 1102) o `Script threw exception` (Error 1101). Ambos significan que el propio Worker está fallando, no tu origen. Confirma que estás ejecutando el código anterior (sin `clone()`, sin lecturas del cuerpo) y que la llamada de registro está envuelta en `ctx.waitUntil()` con un `.catch()`

* Rechazos en Google Ads / Merchant Center como "destination not working" o "image not accessible" tras implementar el Worker: provienen de `AdsBot-Google` o del fetcher de Merchant Center al recibir uno de los errores del Worker mencionados. Corrige los errores del Worker y luego solicita una nueva revisión

* Para descartar por completo el Worker, elimina la vinculación de ruta (`npx wrangler triggers delete` o elimina la ruta en el panel) y confirma que los errores se detienen

## Recursos adicionales

* [Documentación de Cloudflare Workers](https://developers.cloudflare.com/workers/)

* [Documentación de la CLI de Wrangler](https://developers.cloudflare.com/workers/wrangler/)

* Contacta con [support@tryprofound.com](mailto:support@tryprofound.com) para preguntas relacionadas con la API

## Consideraciones de seguridad

* Almacena los Log Ingestion Tokens como secretos en entornos de producción

* Rota los Log Ingestion Tokens con regularidad

* Supervisa el uso y los registros del Worker para detectar patrones inusuales
