# Capture AI crawlers on Cloudflare.

> Connect a Cloudflare zone to Virent AI Crawler Analytics with a Worker (most sites) or Logpush (Enterprise).

Canonical: https://virent.app/docs/integrations/cloudflare
Last updated: 2026-10-19

A Worker reports AI crawlers from a proxied zone without blocking responses, on any plan. On Enterprise, Logpush sends complete edge logs, including cached responses, with no code.

## Choose a method

| Method | Plans | Coverage |
| --- | --- | --- |
| [Workers](#workers) (most sites) | Any plan | Reports supported AI crawlers from a proxied zone without blocking responses |
| [Logpush](#logpush) (recommended on Enterprise) | Enterprise | Passive, complete edge-log coverage, including cached responses |

Both need the site's DNS record proxied (orange cloud). You need your site ID and server secret from **Settings → Connection** (see [Hosting integrations](/docs/integrations/overview#before-you-start)). In the dashboard, **Connect analytics → Hosting integrations → Cloudflare** shows these steps with your values filled in.

## Workers

### Set up with your coding agent

Paste this prompt into Claude Code, Codex or another coding agent. Replace the site ID and example.com with your own.

```text
Install Virent AI crawler analytics on example.com with a Cloudflare Worker.

Site ID: <your-site-id>
Use the full Virent server secret from the Connect dialog. Do not use the masked preview "vha_sk_••••" as the real value.

Steps:
1. Create a Worker in the Cloudflare dashboard.
2. Replace the starter code with:

```javascript
const SITE_ID = "<your-site-id>"
const INGEST = "https://virent.app/v1/ingest/bot"

// Send eligible requests for classification by Virent.
const ASSET = /\.(?!(?:html?|txt|md|xml|json|pdf)$)[a-z0-9]+$/i

export default {
  async fetch(request, env, ctx) {
    const response = await fetch(request)
    ctx.waitUntil(report(request, response, env.VIRENT_INGEST_SECRET).catch(() => {}))
    return response
  },
}

async function report(request, response, secret) {
  if (request.method !== "GET") return

  const url = new URL(request.url)
  const userAgent = request.headers.get("user-agent") || ""
  if (ASSET.test(url.pathname)) return

  const cf = request.cf || {}

  await fetch(INGEST, {
    method: "POST",
    headers: {
      "content-type": "application/json",
      "x-api-key": secret,
    },
    body: JSON.stringify({
      siteId: SITE_ID,
      url: url.origin + url.pathname,
      host: url.host,
      path: url.pathname,
      query: {},
      method: request.method,
      statusCode: response.status,
      userAgent,
      accept: request.headers.get("accept"),
      acceptLanguage: request.headers.get("accept-language"),
      referer: null,
      country: cf.country || null,
      requestId: request.headers.get("cf-ray") || crypto.randomUUID(),
      timestamp: new Date().toISOString(),
    }),
  })
}
```

3. Add a route of *example.com/* on the zone serving the site.
4. Under DNS → Records, keep the site A, AAAA, or CNAME record proxied (orange cloud).

Reporting must run in ctx.waitUntil. Do not hard-code the secret in a committed repo if you can use Worker secrets instead.
```

### Create a Worker

In the [Cloudflare dashboard](https://dash.cloudflare.com), open Workers, click **Create application**, start with **Hello World**, choose any Worker name, then deploy it.

### Store the connection secret

Run this for the Worker and paste your Virent server secret when prompted. The secret stays out of the Worker source.

```sh
npx wrangler secret put VIRENT_INGEST_SECRET
```

### Paste the tracking code

Open **Edit code**, replace the starter code with this snippet and your site ID, and deploy again. Reporting runs in `ctx.waitUntil` and never blocks the site response.

```javascript
const SITE_ID = "<your-site-id>"
const INGEST = "https://virent.app/v1/ingest/bot"

// Send eligible requests for classification by Virent.
const ASSET = /\.(?!(?:html?|txt|md|xml|json|pdf)$)[a-z0-9]+$/i

export default {
  async fetch(request, env, ctx) {
    const response = await fetch(request)
    ctx.waitUntil(report(request, response, env.VIRENT_INGEST_SECRET).catch(() => {}))
    return response
  },
}

async function report(request, response, secret) {
  if (request.method !== "GET") return

  const url = new URL(request.url)
  const userAgent = request.headers.get("user-agent") || ""
  if (ASSET.test(url.pathname)) return

  const cf = request.cf || {}

  await fetch(INGEST, {
    method: "POST",
    headers: {
      "content-type": "application/json",
      "x-api-key": secret,
    },
    body: JSON.stringify({
      siteId: SITE_ID,
      url: url.origin + url.pathname,
      host: url.host,
      path: url.pathname,
      query: {},
      method: request.method,
      statusCode: response.status,
      userAgent,
      accept: request.headers.get("accept"),
      acceptLanguage: request.headers.get("accept-language"),
      referer: null,
      country: cf.country || null,
      requestId: request.headers.get("cf-ray") || crypto.randomUUID(),
      timestamp: new Date().toISOString(),
    }),
  })
}
```

### Route your domain through it

On the Worker, open **Domains**, add a **Route** such as `*example.com/*`, and point it at the zone serving your site.

### Verify your proxy status

Under **DNS → Records**, make sure the site's A, AAAA or CNAME record has the orange cloud and is set to **Proxied**. Gray-cloud traffic bypasses the Worker. Then send a [test crawler request](/docs/analytics/ai-crawlers#verify).

## Logpush

### Set up with your coding agent

Paste this prompt into Claude Code, Codex or another coding agent. Replace the site ID and example.com with your own.

```text
Configure Cloudflare Logpush so example.com sends HTTP request logs to Virent AI crawler analytics.

Site ID: <your-site-id>
Use the full Virent server secret from the Connect dialog. Do not use the masked preview "vha_sk_••••" as the real value.

Steps:
1. In the example.com zone, create a Logpush job with HTTP destination https://virent.app/api/ingest/cloudflare-logpush?header_x-api-key=%3Cpaste-your-server-secret%3E.
2. Dataset: HTTP requests.
3. Job name: Virent Crawler Logs.
4. Fields: ClientRequestHost, ClientRequestMethod, ClientRequestURI, EdgeResponseStatus, EdgeStartTimestamp, RayID, ClientRequestUserAgent, ClientRequestReferer, ClientIP, ClientCountry, WorkerSubrequest.
5. Keep DNS proxied (orange cloud).

WorkerSubrequest lets Virent ignore the site's own Worker subrequests. Do not commit the server secret.
```

### Select a destination

In your site's zone, create a Logpush job, select **HTTP destination**, paste this endpoint and continue. Replace the `header_x-api-key` value with your server secret (URL-encoded); the dashboard fills it in after **Rotate + reveal**.

```text
https://virent.app/api/ingest/cloudflare-logpush?header_x-api-key=%3Cpaste-your-server-secret%3E
```

### Select the dataset

Choose **HTTP requests**.

### Configure the Logpush job

| Setting | Value |
| --- | --- |
| Job name | `Virent Crawler Logs` |
| Fields | `ClientRequestHost`, `ClientRequestMethod`, `ClientRequestURI`, `EdgeResponseStatus`, `EdgeStartTimestamp`, `RayID`, `ClientRequestUserAgent`, `ClientRequestReferer`, `ClientIP`, `ClientCountry`, `WorkerSubrequest` |

Keep the sampling rate at 1.0 (100%). `WorkerSubrequest` lets Virent ignore requests made by your own Workers so they are not counted twice. Raw client IPs are hashed immediately and never stored.

### Verify your proxy status

Under **DNS → Records**, make sure the site's A, AAAA or CNAME record has the orange cloud and is set to **Proxied**. Then send a [test crawler request](/docs/analytics/ai-crawlers#verify).
