• Docs
  • MCP
  • Pricing
Live demo
Get started
  • Get started in 3 steps
  • Track custom goals
  • Use tracking parameters
  • Filter your data
Installation guides
  • All guides
  • Next.js
  • React, Vite and React Router
  • Vue.js
  • Astro
  • WordPress
  • Shopify
  • Webflow
  • Framer
  • Squarespace
  • Wix
  • Google Tag Manager
  • Lovable, Bolt and v0
Revenue attribution
  • Connect a provider
  • Attribution guide
  • Stripe Checkout API
  • Stripe PaymentIntent API
  • Stripe Payment Links
  • Stripe other methods
  • Polar Checkout API
  • Polar Checkout Links
  • Polar other methods
  • Lemon Squeezy Checkout API
  • Lemon Squeezy Payment Links
  • Lemon Squeezy other methods
  • Paddle Checkout API
  • Paddle Overlay Checkout
  • Paddle other methods
  • Dodo Payments
  • Payment API
Proxy guides
  • Get started
  • Next.js
  • Vercel
  • Netlify
  • Cloudflare
  • Nginx
  • Managed proxy
Advanced features
  • Script configuration
  • GDPR and cookieless tracking
  • User identification
  • Exclude visits
  • Error tracking
  • Session replay
  • Bot traffic tracking
  • Export data
  • Public embed
  • Metrics explained
  • Cross-domain tracking
MCP
  • Get started
  • Tool reference
  • OAuth reference
API reference
  • Get started
  • Authentication and scopes
Account
  • Get account
Websites
  • List websites
  • Get website
Analytics
  • Get metadata
  • Get overview
  • Get time series
  • Get realtime
  • Get realtime map
  • Get goals
Breakdowns
  • Get pages
  • Get referrers
  • Get campaigns
  • Get countries
  • Get regions
  • Get cities
  • Get browsers
  • Get operating systems
  • Get devices
Errors
  • List errors
Bot traffic
  • Get bot traffic summary
  • Get bot traffic pages
  • Report a crawler request
Visitors
  • List visitors
  • Get visitor
Payments
  • Create a payment
  • Delete a payment
Troubleshooting
  • Fix CSP blocking the script
  • No data showing
  • Duplicate payments
Docs menu
Get started
  • Get started in 3 steps
  • Track custom goals
  • Use tracking parameters
  • Filter your data
Installation guides
  • All guides
  • Next.js
  • React, Vite and React Router
  • Vue.js
  • Astro
  • WordPress
  • Shopify
  • Webflow
  • Framer
  • Squarespace
  • Wix
  • Google Tag Manager
  • Lovable, Bolt and v0
Revenue attribution
  • Connect a provider
  • Attribution guide
  • Stripe Checkout API
  • Stripe PaymentIntent API
  • Stripe Payment Links
  • Stripe other methods
  • Polar Checkout API
  • Polar Checkout Links
  • Polar other methods
  • Lemon Squeezy Checkout API
  • Lemon Squeezy Payment Links
  • Lemon Squeezy other methods
  • Paddle Checkout API
  • Paddle Overlay Checkout
  • Paddle other methods
  • Dodo Payments
  • Payment API
Proxy guides
  • Get started
  • Next.js
  • Vercel
  • Netlify
  • Cloudflare
  • Nginx
  • Managed proxy
Advanced features
  • Script configuration
  • GDPR and cookieless tracking
  • User identification
  • Exclude visits
  • Error tracking
  • Session replay
  • Bot traffic tracking
  • Export data
  • Public embed
  • Metrics explained
  • Cross-domain tracking
MCP
  • Get started
  • Tool reference
  • OAuth reference
API reference
  • Get started
  • Authentication and scopes
Account
  • Get account
Websites
  • List websites
  • Get website
Analytics
  • Get metadata
  • Get overview
  • Get time series
  • Get realtime
  • Get realtime map
  • Get goals
Breakdowns
  • Get pages
  • Get referrers
  • Get campaigns
  • Get countries
  • Get regions
  • Get cities
  • Get browsers
  • Get operating systems
  • Get devices
Errors
  • List errors
Bot traffic
  • Get bot traffic summary
  • Get bot traffic pages
  • Report a crawler request
Visitors
  • List visitors
  • Get visitor
Payments
  • Create a payment
  • Delete a payment
Troubleshooting
  • Fix CSP blocking the script
  • No data showing
  • Duplicate payments
Datasense

Cookieless analytics with live visitors, console errors and exports, served from your own domain.

Product

  • Features
  • Error tracking
  • AI crawler tracking
  • Revenue attribution
  • Ad-block proxy
  • Pricing
  • Live demo

Docs

  • Get started
  • Install guides
  • Growth guides
  • Ad-block proxy
  • MCP server
  • REST API

Compare

  • vs Google Analytics
  • vs Plausible
  • vs DataFast
  • vs Fathom
  • All alternatives

Company

  • FAQ
  • Terms
  • Privacy
  • Refunds
  • Cookies
  • DPA
© 2026 DatasenseNo cookies were set on this page.

Bot and AI crawler tracking

See which pages ChatGPT, Claude, Perplexity, Google and training crawlers read. Your server reports crawler requests with a few lines of middleware.

Crawlers don't run JavaScript, so no analytics script sees them. Your middleware reports each request whose user agent looks like a crawler, and Datasense classifies it and checks its IP. Included with Pro and Team.

What you see

  • Requests over time: AI answers (an assistant fetching a page because a user asked), Indexing, Training and Other bots.
  • Providers and crawlers: OpenAI, Anthropic, Perplexity, Google and others.
  • The pages they read, how often they fetch robots.txt, llms.txt, sitemaps and .md pages, and the pages that returned 404.
  • IP verification: requests whose IP contradicts the crawler's published ranges are marked spoofed and hidden by default.

Set it up

Settings → Bot traffic shows the same code with your site ID. Human requests are never sent.

Next.js

proxy.ts
// proxy.ts (middleware.ts before Next.js 16)
import { NextResponse, type NextFetchEvent, type NextRequest } from "next/server"

const CRAWLER = /bot|crawl|spider|slurp|-user\b|claude|anthropic|cohere|meta-external|facebookexternalhit|google|bingpreview/i

export function proxy(request: NextRequest, event: NextFetchEvent) {
  const userAgent = request.headers.get("user-agent") ?? ""
  if (request.method === "GET" && CRAWLER.test(userAgent)) {
    const host = request.headers.get("x-forwarded-host") ?? request.headers.get("host")
    // Report in the background; never delay or fail the page.
    event.waitUntil(
      fetch("https://usedatasense.com/api/crawls", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          websiteId: "YOUR_SITE_ID",
          href: `https://${host}${request.nextUrl.pathname}`,
          ai: {
            userAgent,
            // Only trust these headers behind a proxy that sets them (Vercel, Cloudflare, nginx).
            ip: request.headers.get("cf-connecting-ip") ?? request.headers.get("x-real-ip") ??
              request.headers.get("x-forwarded-for")?.split(",")[0]?.trim(),
            source: "nextjs",
          },
        }),
        signal: AbortSignal.timeout(2000),
      }).catch(() => {}),
    )
  }
  return NextResponse.next()
}

export const config = {
  // Skip assets, but keep robots.txt, llms.txt and sitemaps: crawlers read those first.
  matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
}

Express

server.js
const CRAWLER = /bot|crawl|spider|slurp|-user\b|claude|anthropic|cohere|meta-external|facebookexternalhit|google|bingpreview/i

// Before your routes. Reports after the response is sent, with its status code.
app.use((req, res, next) => {
  const userAgent = req.get("user-agent") ?? ""
  if (req.method === "GET" && CRAWLER.test(userAgent)) {
    res.on("finish", () => {
      fetch("https://usedatasense.com/api/crawls", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          websiteId: "YOUR_SITE_ID",
          href: `${req.protocol}://${req.get("host")}${req.path}`,
          // Behind a proxy, set app.set("trust proxy", 1) so req.ip is the crawler's address.
          ai: { userAgent, ip: req.ip, statusCode: res.statusCode, source: "express" },
        }),
        signal: AbortSignal.timeout(2000),
      }).catch(() => {})
    })
  }
  next()
})

Cloudflare

worker.js
const CRAWLER = /bot|crawl|spider|slurp|-user\b|claude|anthropic|cohere|meta-external|facebookexternalhit|google|bingpreview/i

export default {
  async fetch(request, env, ctx) {
    const response = await fetch(request) // or your existing handler
    const userAgent = request.headers.get("user-agent") ?? ""
    if (request.method === "GET" && CRAWLER.test(userAgent)) {
      const url = new URL(request.url)
      ctx.waitUntil(
        fetch("https://usedatasense.com/api/crawls", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({
            websiteId: "YOUR_SITE_ID",
            href: url.origin + url.pathname,
            ai: {
              userAgent,
              ip: request.headers.get("cf-connecting-ip"),
              statusCode: response.status,
              source: "cloudflare",
            },
          }),
        }).catch(() => {}),
      )
    }
    return response
  },
}

Any backend

HTTP
# From your server, for each request whose user agent looks like a crawler.
# Use a short timeout and ignore failures. Send the path without its query string.
curl -X POST https://usedatasense.com/api/crawls \
  -H "Content-Type: application/json" \
  -d '{
    "websiteId": "YOUR_SITE_ID",
    "href": "https://example.com/docs/get-started",
    "ai": {
      "userAgent": "<the request's full User-Agent>",
      "ip": "<the crawler's IP>",
      "statusCode": 200
    }
  }'

# 200 {"success":true,"tracked":true}   recorded
# 200 {"success":true,"tracked":false}  not a known crawler, ignored

The request format is in the crawl report reference. Read the results with the bot traffic summary endpoint.

Read this page as markdownllms.txt
PreviousSession replayNext Export data

On this page

  • What you see
  • Set it up
  • Next.js
  • Express
  • Cloudflare
  • Any backend
Sign in