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

# Amazon CloudFront setup

> Route AI assistant traffic to Profound Dynamic Bot Rendering with a single CloudFront Function and an origin group, without Lambda@Edge.

This guide connects an existing CloudFront distribution to Dynamic Bot Rendering. This setup adds one CloudFront Function on viewer request to select an origin for eligible AI assistant page requests.

This setup uses no Lambda\@Edge. Two properties support this integration:

* With viewer `Host` forwarding disabled, CloudFront uses the custom origin domain for its `Host` header and TLS Server Name Indication (SNI). Preserve this setting in both policies in step 3.
* Profound's CloudFront endpoint returns `cache-control: no-store` itself, so CloudFront never stores a second, staler copy of content Profound already caches.

<Note>
  Requests routed to Profound use the CloudFront endpoint `GET https://concierge.tryprofound.com/v1/concierge/cloudfront/{path}`. You never construct that URL by hand. The origin's **Origin path** setting prepends the prefix, and the request URI passes through untouched.
</Note>

## Why the design looks like this

Two CloudFront rules shape everything below:

1. A viewer-request function can't change which cache behavior a request matches. CloudFront selects the behavior before the function runs, and rewriting the URI doesn't re-match it. Assistant traffic therefore stays in the same behavior as human traffic, rather than a dedicated behavior with its own cache policy.
2. A viewer-request function can switch the origin, including building a per-request origin group with failover (`cf.createRequestOriginGroup`, runtime `cloudfront-js-2.0`).

Two consequences follow:

* Routing happens by swapping the origin, not the behavior. Because **Origin path** is a per-origin setting, a failover to your default origin automatically uses the original, unprefixed path. No path rewriting is needed.
* Assistant and human requests share the behavior's cache policy, so the cache key must be split with a marker header (step 3). Without it, CloudFront can serve a human-cached copy to an assistant, or the reverse.

## Prerequisites

* An existing CloudFront distribution

* AWS Identity and Access Management (IAM) permissions for CloudFront distributions, functions, and cache policies

* Your Dynamic Bot Rendering API key, a registered domain, an effective serving policy, and a known published test page. See [Before you start](/dynamic-bot-rendering/overview#before-you-start).

* Public, non-personalized pages only. The example allows `/`, `/pricing`, `/docs`, and `/docs/getting-started`. Replace these with reviewed public pages; do not include APIs, authentication, account, admin, or framework-resource paths.

* Record existing function associations, origins, and policies for rollback. Preserve existing security, authentication, and rewrite logic. If combining it requires a routing design change, stop and review that separately.

* Confirm that firewall rules, bot challenges, and origin allowlists permit both assistant traffic and Profound's origin/scraper fetches. The loop marker is not an authentication or firewall bypass.

## Setup

<Steps>
  <Step title="Create the Profound origin">
    Open **CloudFront → Distributions → your distribution → Origins → Create origin**.

    | Setting       | Value                       |
    | ------------- | --------------------------- |
    | Origin domain | `concierge.tryprofound.com` |
    | Origin path   | `/v1/concierge/cloudfront`  |
    | Name          | `Profound_Origin`           |
    | Protocol      | HTTPS only                  |

    Add two custom origin headers:

    | Header                | Value                                                |
    | --------------------- | ---------------------------------------------------- |
    | `x-concierge-api-key` | Your Dynamic Bot Rendering API key                   |
    | `x-concierge-host`    | Your public site host, for example `www.example.com` |

    CloudFront overwrites any viewer-sent header of the same name when forwarding to this origin, so a client can't spoof the API key or the host. The function in step 2 also strips them, for defense in depth.

    <Note>
      **Serving more than one hostname from this distribution?** A static `x-concierge-host` only works for a single public host, and static origin headers win over anything the function sets. For a multi-domain distribution, omit that custom header here, uncomment the marked line in the function below, and include `x-concierge-host` in the cache key in step 3 so it is also forwarded. Forwarding alone does not separate cached responses for different hosts. Review host identity for human and fallback responses too before enabling multiple hosts.
    </Note>
  </Step>

  <Step title="Create the viewer request function">
    Open **CloudFront → Functions → Create function**. Name it `profound-bot-rendering` and choose the runtime `cloudfront-js-2.0`. Origin groups require the 2.0 runtime.

    ```js theme={null}
    import cf from 'cloudfront';

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

    // Replace with reviewed public, non-personalized page paths.
    var PUBLIC_PATHS = ['/', '/pricing', '/docs', '/docs/getting-started'];
    var PUBLIC_HOSTS = ['www.example.com'];

    function handler(event) {
        var request = event.request;
        var headers = request.headers;

        // Never trust these from a client. The API key and host come from the
        // origin's custom headers; the route marker feeds the cache key, so a
        // spoofed value could pollute the assistant cache entry.
        delete headers['x-concierge-api-key'];
        delete headers['x-concierge-host'];
        delete headers['x-concierge-url'];
        delete headers['x-concierge-route'];

        // Loop protection: Profound stamps x-concierge-request on its own origin
        // fetches. Never route those back to Profound.
        if (request.method !== 'GET' || headers['x-concierge-request']) {
            return request;
        }

        // CloudFront exposes cookies separately from request.headers.
        if (headers.authorization || Object.keys(request.cookies || {}).length > 0) {
            return request;
        }

        var ua = headers['user-agent'] ? headers['user-agent'].value : '';

        var isAssistant = BOT_RE.test(ua);
        var isPublicPage = PUBLIC_PATHS.indexOf(request.uri) !== -1;
        var host = headers.host ? headers.host.value : '';

        if (!isAssistant || !isPublicPage || PUBLIC_HOSTS.indexOf(host) === -1) {
            return request;
        }

        // Split the cache key. This header is in the behavior's cache policy,
        // so assistant responses never collide with human cache entries.
        headers['x-concierge-route'] = { value: 'bot' };

        // Multi-domain distributions only (see Step 1): derive the host from
        // the viewer Host header instead of a static origin custom header.
        // headers['x-concierge-host'] = { value: headers.host.value };

        // Try Profound first; fall back to your own origin if Profound is down
        // or misconfigured. CloudFront accepts 400, 403, 404, 416, 429, 500,
        // 502, 503 and 504 as failover criteria. 401 is not allowed, so a 401
        // from a bad API key surfaces to the assistant instead of failing over.
        // 404 is deliberately omitted: Profound fails open internally and
        // returns your origin's own status, so a 404 is your real 404 and
        // retrying would double-fetch. 429 is also omitted and passes through.
        cf.createRequestOriginGroup({
            originIds: [
                { originId: 'Profound_Origin' },
                { originId: 'YOUR_DEFAULT_ORIGIN' }
            ],
            failoverCriteria: {
                statusCodes: [400, 403, 500, 502, 503, 504]
            }
        });

        return request;
    }
    ```

    Replace `YOUR_DEFAULT_ORIGIN` with your distribution's existing default origin ID and `PUBLIC_HOSTS` with the registered hosts for this origin, then select **Publish**. For the static host header, list only that one host. Keep the exact public-path allowlist aligned with your site's routes. Requests with cookies or authorization keep their existing routing; the function does not send them to Profound.

    The function routes only `GET`; all other methods return to existing routing. CloudFront origin groups support failover for `GET`, `HEAD`, and `OPTIONS`. The configured `503` criterion enables connection-failure failover; `504` enables response-timeout failover. Before deployment, set and record a bounded primary-origin connection-attempt, connection-timeout, and response-timeout budget that leaves time for the fallback request. CloudFront defaults can spend 30 seconds on connection attempts alone (three attempts of 10 seconds). Test the resulting total latency; do not assume failover is immediate. See [CloudFront origin failover](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/high_availability_origin_failover.html).
  </Step>

  <Step title="Update the cache and origin request policies">
    CloudFront strips any viewer header or query string the behavior's policies don't include. This happens before the origin request, regardless of what the function decided. Three settings are required on the behavior serving your pages.

    **1. Add `x-concierge-route` to the cache-key headers.** AWS managed cache policies can't be edited. If the behavior uses one, copy every setting into a new custom policy first, then add the header. Keep **Minimum TTL** (time to live) at `0`. In the multi-domain variant, include the trusted `x-concierge-host` in cache identity as well as forwarding it. The bot marker alone does not distinguish hosts.

    **2. Include the query strings your pages vary on** (or "All") in the cache key. Cache-key query strings also reach the origin. An origin request policy can forward additional query strings without keying them, but responses that vary on those values can then share a cache entry. If neither policy includes a query string, CloudFront strips it. Verify both forwarding and cache identity for each supported variant.

    **3. Forward the viewer `User-Agent` through the origin request policy.** By default, CloudFront replaces `User-Agent` with `Amazon CloudFront` on origin requests. Profound would then classify every routed request as an unknown agent and permanently fail open, with no renders served and none scheduled. Use a custom header allowlist for `User-Agent` and, when required, `Accept` and `Accept-Language` (plus `x-concierge-host` in the multi-domain case). Preserve required compression settings. Do not use **AllViewer**: viewer `Host` forwarding can override the service hostname and break origin routing or TLS. Exclude viewer `Host`, `Cookie`, and `Authorization` from both the cache and origin request policies; cache-key values are forwarded too. These policies apply to human traffic as well. If the existing behavior needs these headers or cookies, stop and review compatibility before changing it. CloudFront documents per-origin `originOverrides` for `hostHeader` and `sni` in [request origin groups](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/helper-functions-origin-modification.html); a customer origin requiring a different host needs that compatibility reviewed separately. This example does not choose or add an override. Profound's origin fetches do not forward viewer credentials, and published pages are not per-session or per-language variants.

    <Warning>
      For this setup, add `User-Agent` to the origin request policy rather than the cache key. An origin request policy applies to the whole behavior, so CloudFront now forwards the viewer `User-Agent` on human requests too. If your origin varies its HTML by `User-Agent` (separate mobile and desktop markup, for example) without corresponding cache-key variation, CloudFront can serve one cached variant to other viewers. Confirm that the existing cache key already accounts for your origin's variants. Locale, geography, and other header-dependent content also need review. If this requires a cache design change, stop before deployment. Raw `User-Agent` keying can substantially reduce cache reuse.
    </Warning>
  </Step>

  <Step title="Associate the function with the behavior">
    Open **Behaviors → your page behavior → Edit**. Set the cache policy from step 3, then under **Function associations** set **Viewer request** to your `profound-bot-rendering` CloudFront Function. For a behavior without existing edge logic, no origin request or origin response association is needed. Do not remove existing associations. Merge and preserve existing viewer-request logic before associating this function; stop if that requires a design decision.

    Save and wait for the distribution to finish deploying.
  </Step>

  <Step title="Verify">
    ```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 the expected `x-concierge-bot-kind`. On your known published page, also require `x-concierge-cache: HIT` and the expected rendered body. Test normal browser traffic, non-GET methods, excluded paths, and credential-bearing requests against their existing behavior.

    Test CDN-level failover only in staging or an isolated distribution. Record the original origin settings, configure an unreachable test origin, and repeat an uncached assistant request. Expect the default-origin response after the configured timeout, with no `x-concierge-request-id`. Restore the original settings and wait for propagation. A `401` does not trigger failover; the configured `400` and `403` statuses do, which can mask configuration errors. Cached fallback responses can persist until their TTL expires.

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

## How the cache split behaves

* Human requests never carry the marker, because the function strips any client-supplied copy. The marker separates their entries from assistant entries, but query and header policy changes can affect human cache hit rates.
* Eligible assistant requests carry `x-concierge-route: bot` and get their own entries.
* Profound's responses set `cache-control: no-store`, so with Minimum TTL at `0` the assistant-keyed entries are only ever populated by failover responses from your own origin: correct content, expiring on your origin's own TTL.

<Accordion title="Known gap: failover responses can linger after recovery">
  During a full Profound outage, failed-over origin responses can sit in the assistant-keyed cache until their TTL expires, delaying the return to rendered content after recovery.

  Validate that this recovery delay is acceptable for your existing TTLs. Changing how fallback responses are cached requires a separate design review; this guide does not add an edge function to change that behavior.
</Accordion>

## Rolling back

Restore the recorded viewer-request association and any policies changed for this setup. Use **No association** only if there was no previous function. Allow the configuration to propagate, then verify the original routing and cache behavior. Previously cached fallback responses can remain until their TTL expires or they are invalidated through your normal process.
