> ## 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.

# Cloudflare setup

> Route AI assistant traffic to Profound Dynamic Bot Rendering with a single Cloudflare Worker, keeping human and search-crawler traffic on your origin.

This guide adds one Cloudflare Worker to your zone. The Worker inspects the `User-Agent`, forwards matching AI assistant requests to Profound, and passes everything else straight through to your origin.

The Worker calls Profound's general endpoint, `GET https://concierge.tryprofound.com/v1/concierge`, and supplies the original host and path as headers.

<Warning>
  A Worker on a `/*` route runs on every request to that hostname, so it sits in your production request path. The code retries your origin for fetch failures, a response-header deadline, and statuses `400`, `401`, `403`, `500`, `502`, `503`, and `504`. It passes other statuses through, including redirects, `404`, and `429`. Adapt the public-page allowlist and latency budget, then test on a staging hostname first. A failure after response streaming starts cannot transparently replace a partially sent body.
</Warning>

## Prerequisites

* A Cloudflare account with Workers enabled on the zone
* An existing proxied DNS record for the hostname and an origin reachable through it. Use a [Worker Route](https://developers.cloudflare.com/workers/configuration/routing/routes/), not a Worker Custom Domain.
* An inventory of existing and overlapping Worker routes. Preserve their logic rather than replacing them. If composing them requires a design change, stop and review that separately.
* A recorded route failure mode and capacity check against [Workers limits](https://developers.cloudflare.com/workers/platform/limits/). Use fail-open for quota exhaustion only where bypassing the Worker preserves existing security. Fail-closed can affect human traffic too, before the JavaScript catch runs.
* Permission to add Worker routes for the hostname
* Your Dynamic Bot Rendering API key, a registered domain, serving policy, and a known published test page. See [Before you start](/dynamic-bot-rendering/overview#before-you-start).

## Setup

<Steps>
  <Step title="Create the Worker">
    In the Cloudflare dashboard, open **Workers & Pages → Create → Create Worker**. Name it `profound-bot-rendering` and deploy the placeholder so the Worker exists.
  </Step>

  <Step title="Add the Worker code">
    For the new Worker, open **Edit code** and add the following. Replace the four example paths with reviewed public pages. Do not overwrite existing Worker logic.

    ```js theme={null}
    const CONCIERGE_ENDPOINT = 'https://concierge.tryprofound.com/v1/concierge';

    // Keep this list in sync with the supported assistants in the Profound docs.
    const BOT_RE = /duckassistbot|chatgpt-user|gemini-deep-research|perplexity-user|amzn-user|mistralai-user|claude-user|claude-code|codex/i;

    // Explicit public pages only. Review each path before extending this list.
    const PUBLIC_PATHS = new Set(['/', '/pricing', '/docs', '/docs/getting-started']);
    const UPSTREAM_TIMEOUT_MS = 2000; // Example header deadline; leave time for origin fallback.
    const FORWARDED_HEADERS = ['user-agent', 'accept', 'accept-encoding', 'accept-language'];

    // Statuses that mean "Profound could not serve this" rather than "the origin
    // said so". 404 is deliberately absent: Profound fails open internally and
    // returns your origin's own status, so a 404 is your real 404.
    const FAILOVER_STATUSES = new Set([400, 401, 403, 500, 502, 503, 504]);

    async function failOpen(request) {
      // A subrequest to the same zone goes to the origin without re-running this Worker.
      const originResponse = await fetch(request);
      const response = new Response(originResponse.body, originResponse);
      response.headers.set('x-concierge-cdn-failover', '1');
      return response;
    }

    export default {
      async fetch(request, env) {
        const url = new URL(request.url);
        const userAgent = request.headers.get('user-agent') || '';

        // Loop protection: Profound stamps this on its own origin fetches.
        // Passing it through means those fetches reach the origin directly.
        if (request.headers.has('x-concierge-request')) return fetch(request);

        if (request.method !== 'GET') return fetch(request);
        if (request.headers.has('cookie') || request.headers.has('authorization')) return fetch(request);

        const isAssistant = BOT_RE.test(userAgent);
        if (!isAssistant || !PUBLIC_PATHS.has(url.pathname)) return fetch(request);

        // Copy only required negotiation headers, never viewer credentials or identity.
        const headers = new Headers();
        for (const name of FORWARDED_HEADERS) {
          const value = request.headers.get(name);
          if (value !== null) headers.set(name, value);
        }

        headers.set('x-concierge-api-key', env.PROFOUND_CONCIERGE_KEY);
        headers.set('x-concierge-host', url.hostname);
        headers.set('x-concierge-url', url.pathname + url.search);

        const controller = new AbortController();
        const timeout = setTimeout(() => controller.abort(), UPSTREAM_TIMEOUT_MS);
        try {
          const response = await fetch(CONCIERGE_ENDPOINT, {
            method: 'GET',
            headers,
            redirect: 'manual', // pass redirects through instead of following them
            cache: 'no-store', // also verify the endpoint owner's cache policy
            signal: controller.signal,
          });

          if (FAILOVER_STATUSES.has(response.status)) {
            if (response.body) void response.body.cancel().catch(() => {});
            controller.abort();
            return failOpen(request);
          }
          const result = new Response(response.body, response);
          result.headers.set('cache-control', 'no-store');
          return result;
        } catch {
          return failOpen(request);
        } finally {
          clearTimeout(timeout);
        }
      },
    };
    ```

    The example allows 2 seconds for Profound response headers. Choose a bounded value that leaves enough time for your origin response within the assistant's latency budget. The deadline ends when headers arrive; it does not bound response-body streaming. Workers supports [AbortController](https://developers.cloudflare.com/workers/runtime-apis/web-standards/).
  </Step>

  <Step title="Store the API key as a secret">
    Open **Settings → Variables and Secrets**, add a secret named `PROFOUND_CONCIERGE_KEY`, and paste your API key. Save and deploy.

    Use a secret rather than a plaintext environment variable. Secrets are write-only once saved, so the key stays hidden from the dashboard.

    To do the same with Wrangler:

    ```bash theme={null}
    npx wrangler secret put PROFOUND_CONCIERGE_KEY
    ```
  </Step>

  <Step title="Attach the Worker to your hostname">
    Open **Settings → Domains & Routes → Add route**, enter the pattern for your page traffic, and select the zone.

    ```text theme={null}
    www.example.com/*
    ```

    If your site serves assets from the same hostname, the Worker still runs on them and returns them with a plain origin fetch. To keep the Worker out of that path entirely, narrow the route pattern instead of relying on the code's path check.
  </Step>

  <Step title="Verify">
    Check routing, then separately verify the known published page and excluded traffic:

    ```bash theme={null}
    curl -sD - -o /dev/null https://www.example.com/pricing -A 'chatgpt-user'
    ```

    Check the HTTP status, a valid `x-concierge-request-id`, and `x-concierge-bot-kind: chatgpt-user`. On your published test page, also require `x-concierge-cache: HIT` and the expected HTML body. A normal browser request should stay on your origin route. Test non-`GET`, API, asset, and credential-bearing requests too.

    Full checks and header meanings are in [Verify and troubleshoot](/dynamic-bot-rendering/verify-and-troubleshoot).
  </Step>
</Steps>

## Caching

The Worker's upstream call must never be cached. Every page uses the same upstream URL, `https://concierge.tryprofound.com/v1/concierge`, and the host, path, and query that identify the page travel only in trusted headers. If that URL were cached without those headers in the cache key, different pages or hosts would collide, and adding `User-Agent` to the key would not fix it.

The example uses the [`cache: 'no-store'` fetch setting](https://developers.cloudflare.com/workers/runtime-apis/fetch/) and marks returned Profound responses `Cache-Control: no-store`. [Cloudflare evaluates caching on the fetch subrequest URL](https://developers.cloudflare.com/workers/reference/how-the-cache-works/), not the viewer URL, so cache rules on your own zone do not apply to the upstream call.

Before rollout, send repeated assistant requests to two published paths and a query variant of each, and confirm every response carries a fresh `x-concierge-request-id`. Also check any other cache layer in front of your site for assistant and human separation, and for what happens to cached entries after you publish a replacement, unpublish, or pause. Do not add a `User-Agent`-only cache key as a workaround. If you need to cache Profound's responses at your edge, contact Profound support first: the cache key has to include the host, path, and query identity and be invalidated on publication changes.

## How the Worker protects your site

* **Upstream headers are allowlisted.** For routed requests, the Worker copies only `User-Agent`, `Accept`, `Accept-Encoding`, and `Accept-Language`, then sets the trusted key, host, and path. It does not copy client-supplied `x-concierge-*` identity or credentials. Requests carrying cookies or authorization bypass Profound entirely. Ordinary bypass requests retain their headers for your origin.
* **Loop protection is not authentication.** The marker is spoofable; never use it to bypass WAF rules or access controls. See the [public-content prerequisites](/dynamic-bot-rendering/overview#before-you-start).
* **Loop protection is two-sided.** Profound stamps `x-concierge-request` on its own origin fetches, and the Worker passes those straight through. Profound also rejects any inbound request carrying that header, so even a misconfigured rule breaks a single request instead of looping.
* **The key never reaches the browser.** It lives in a Worker secret and is only attached on the upstream call.
* **Redirects are passed through.** `redirect: 'manual'` returns a redirect to the assistant instead of following it, so redirect handling stays your origin's decision.
