# Install with a Cloudflare Worker

See how much of your site's traffic is AI agents, and which ones, even if a site builder like Webflow, Framer or Shopify hosts your site. A small Cloudflare Worker sits in front of your site and passes every request through unchanged. For each page load, it sends a privacy-reduced event to Punchline Analytics after the response has gone out.

- **Nothing changes for your visitors.** No cookies, no script on your pages, and responses pass through untouched.
- **It sees what Google Analytics can't:** crawlers, AI fetchers and coding agents that never run JavaScript.
- **Never breaks your site.** If Punchline is misconfigured or unreachable, requests continue normally.

The Worker can also [serve AI agents markdown](https://www.punchlineanalytics.com/docs/edge-markdown) when you turn that on in Site settings: same pages, easier for assistants to read. Until then it changes nothing.

## Will it work with my site?

It works if your domain can run through Cloudflare's proxy (the orange cloud in Cloudflare's DNS settings):

| Platform | Works? | Notes |
|---|---|---|
| Webflow | Yes | Supported by Webflow. Follow Webflow's Cloudflare guide for the DNS records |
| Framer | Yes | Follow Framer's "proxy with Cloudflare" guide |
| Shopify | Yes, with a caveat | Works through Cloudflare's Orange-to-Orange mode. Shopify's domain settings may show a "Cloudflare proxy" warning even though the store works |
| WordPress, Ghost, most hosts | Yes | Any origin you can put behind Cloudflare |
| Squarespace, Wix | **No** | They don't allow proxying. Use the [browser snippet](https://www.punchlineanalytics.com/docs/install-snippet) instead |

## Install from the Cloudflare dashboard (no code tools needed)

You'll need:
- your site on Cloudflare (the free plan is fine)
- your `PUNCHLINE_DSN` value (we send it when your site is set up)

**1. Put your domain on Cloudflare and proxy it.** If your domain isn't on Cloudflare yet, add it (Cloudflare walks you through changing nameservers at your registrar). Then follow your platform's guide above, so the record for your site, usually `www`, is **Proxied** (orange cloud).

**2. Create the Worker.** In Cloudflare, go to **Workers & Pages → Create → Create Worker**. Name it `punchline` and click **Deploy**. Then click **Edit code**, replace everything with the contents of [`punchline-worker.js`](https://www.punchlineanalytics.com/downloads/punchline-worker.js), and click **Deploy** again.

**3. Add your DSN as a secret.** Go to the Worker's **Settings → Variables and secrets → Add**. Choose type **Secret**, name `PUNCHLINE_DSN`, value as sent to you. Click **Deploy**.

**4. Route your site through the Worker.** Go to the Worker's **Settings → Domains & Routes → Add → Route**, choose your domain, and enter the route `www.example.com/*` (use your own hostname). Set **Failure mode** to **Fail open (proceed)**. Then if the Worker ever hits Cloudflare's free-plan limit, visitors still reach your site; only tracking pauses.

**5. Check it's working.** Visit a few pages of your site. They should appear in your Punchline dashboard within about a minute.

### Free-plan limits

Cloudflare's free Workers plan includes 100,000 requests a day, and every request to the routed hostname counts, including images and scripts served from it. Most builders (Webflow, Shopify) serve assets from their own CDN domains, so usually only page loads count. If you get close to the limit, Cloudflare's Workers Paid plan raises it a lot. With "Fail open", hitting the limit never takes your site down.

## Install with wrangler (developers)

```bash
npm install @punchline-analytics/cloudflare
```

```ts
// src/index.ts
import { punchline } from "@punchline-analytics/cloudflare";

export default punchline();
```

```jsonc
// wrangler.jsonc
{
  "name": "punchline",
  "main": "src/index.ts",
  "compatibility_date": "2026-09-01",
  "routes": [{ "pattern": "www.example.com/*", "zone_name": "example.com" }]
}
```

```bash
npx wrangler secret put PUNCHLINE_DSN
npx wrangler deploy
```

Already have a Worker? Wrap its handler instead:

```ts
import { withPunchline } from "@punchline-analytics/cloudflare";

export default withPunchline(async (request, env, ctx) => {
  // …your existing Worker…
  return fetch(request);
});
```

## What gets sent

For each page load (`GET`/`HEAD` for pages, plus `.txt`, `.md` and `.xml` files such as `robots.txt` and `llms.txt`), one event is sent, server to server:

- The host, the path and `utm_*` values. Every other query parameter, and the `#fragment`, are removed before sending.
- The referrer's host only, the `Accept` header (cut to 200 characters), and whether the browser sent `Sec-GPC: 1`.
- Cloudflare's country code for the request, and Cloudflare's verified-bot category when it gives one.
- Web Bot Auth signature headers, when an agent sends them.
- The response's status code and size (from `Content-Length`), sent once the response is known. This is what lets the [Agent content](https://www.punchlineanalytics.com/docs/agent-content) report show whether agents actually got your pages.
- The visitor's IP address and user agent. The Punchline collector uses these in memory to classify the request and to compute a daily visitor hash, then discards them; they're never stored or logged.

Not tracked: images, scripts, styles, other assets, POST requests, or in-page navigations and prefetches.

## Options

```ts
punchline({
  dsn: "...",            // default: env.PUNCHLINE_DSN
  timeoutMs: 3000,       // give up sending after this long
  serverTiming: true,    // add the overhead to Server-Timing: `punchline;dur=…` (default: env.PUNCHLINE_SERVER_TIMING=1)
  onError: (err) => {},  // configuration or network errors (default: ignored)
});
```
