DenserAIDenserAI Docs
Deploy

Help Page Proxy

Serve your help page from your own domain - Next.js, Vercel, Netlify, or a Node.js server.

You can put the help page on a path of your own site — yourdomain.com/help — by proxying it. Visitors never see a denser.ai URL and never see your chatbot ID.

The path is always /help and is not configurable, so a site that already serves its own page at /help cannot proxy one today.

Open Deploy → Help page → Set up, scroll to Serve it from your own domain, and copy the snippet for your platform. Each one is pre-filled with your chatbot ID.

The bootstrap rule must come first

Proxied, the visitor's URL carries no chatbot ID, so the page asks for /api/chatbots/help/self/bootstrap and the first rewrite is what swaps self for your real ID. If any /api rule of your own is matched ahead of it, the placeholder reaches denser.ai unchanged, no chatbot resolves, and every visitor gets "This help page isn't available". Keep the bootstrap rule at the top of the list.

Open Deploy → Help page → Manage, scroll to Serve it from your own domain, and copy the snippet for your platform. The snippets on this page are generic; the ones in the dashboard are pre-filled with your chatbot ID.

The bootstrap rule must come first

Proxied, the visitor's URL carries no chatbot ID, so the page asks for /api/chatbots/help/self/bootstrap and the first rewrite is what swaps self for your real ID. If any /api rule of your own is matched ahead of it, the placeholder reaches denser.ai unchanged, no chatbot resolves, and every visitor gets "This help page isn't available". Keep the bootstrap rule at the top of the list.

Next.js

next.config.js
module.exports = {
  async rewrites() {
    // beforeFiles, not a plain array. A returned array is treated as
    // afterFiles, which is checked only AFTER your own routes — an app with
    // something like app/api/[...slug]/route.ts would answer these paths
    // itself and the help page would never load. beforeFiles runs first, and
    // only for the paths listed below.
    return {
      beforeFiles: [
        // Bootstrap must come first — it is the only rule that swaps the
        // "self" placeholder for your chatbot id, so the id never appears
        // in your visitors' URLs. Anything of your own that matches this path
        // ahead of it sends the placeholder to denser.ai unchanged, and the
        // page reports itself unavailable.
        {
          source: "/api/chatbots/help/self/bootstrap",
          destination: "https://denser.ai/api/chatbots/help/{chatbotId}/bootstrap",
        },
        // The help page is always served at /help; the path is not
        // configurable. These two rules take precedence over a /help page of
        // your own, so a site that already uses that path cannot proxy one.
        { source: "/help", destination: "https://denser.ai/u/help/{chatbotId}" },
        {
          source: "/help/:path*",
          destination: "https://denser.ai/u/help/{chatbotId}/:path*",
        },
        // SPA bundle and static assets ONLY. Do NOT rewrite all of /u/* —
        // yourdomain.com/u/anything would boot the SPA with basename /u and
        // render Denser's dashboard chrome on your own domain.
        { source: "/u/assets/:path*", destination: "https://denser.ai/u/assets/:path*" },
        { source: "/u/images/:path*", destination: "https://denser.ai/u/images/:path*" },
        { source: "/u/favicon.ico", destination: "https://denser.ai/u/favicon.ico" },
        // Only the API prefixes the help page calls, so your own /api routes
        // are left alone. Delete any whose feature this chatbot does not use.
        { source: "/api/chat/:path*", destination: "https://denser.ai/api/chat/:path*" }, // streaming replies and voice input
        { source: "/api/v2/conversations/public/:path*", destination: "https://denser.ai/api/v2/conversations/public/:path*" }, // the visitor's own conversations
        { source: "/api/v2/messages/:path*", destination: "https://denser.ai/api/v2/messages/:path*" }, // thumbs up / thumbs down on a reply
        { source: "/api/leadGeneration/:path*", destination: "https://denser.ai/api/leadGeneration/:path*" }, // lead capture form, if enabled
        { source: "/api/data-source/:path*", destination: "https://denser.ai/api/data-source/:path*" }, // source names behind citations, if enabled
        { source: "/api/upload/presigned-url", destination: "https://denser.ai/api/upload/presigned-url" }, // opening a citation's source file, if citations are enabled
        { source: "/api/custom-actions/:path*", destination: "https://denser.ai/api/custom-actions/:path*" }, // action form submissions, if enabled
        { source: "/api/chatbots/customer-support", destination: "https://denser.ai/api/chatbots/customer-support" }, // support form, if enabled
        { source: "/api/integrations/helpdesk/ticket", destination: "https://denser.ai/api/integrations/helpdesk/ticket" }, // helpdesk tickets, if enabled
      ],
      afterFiles: [],
      fallback: [],
    };
  },
};

These rewrites take precedence over your own routes

They are declared in beforeFiles, which Next.js checks before your app's own routes — deliberately, because an array returned from rewrites() is afterFiles instead, and a project with a catch-all such as app/api/[...slug]/route.ts would then answer these paths itself and the help page would never load. The consequence: if you already serve your own page at /help, this rewrite wins.

Vercel

vercel.json
{
  "rewrites": [
    {
      "source": "/api/chatbots/help/self/bootstrap",
      "destination": "https://denser.ai/api/chatbots/help/{chatbotId}/bootstrap"
    },
    { "source": "/help", "destination": "https://denser.ai/u/help/{chatbotId}" },
    { "source": "/help/:path*", "destination": "https://denser.ai/u/help/{chatbotId}/:path*" },
    { "source": "/u/assets/:path*", "destination": "https://denser.ai/u/assets/:path*" },
    { "source": "/u/images/:path*", "destination": "https://denser.ai/u/images/:path*" },
    { "source": "/u/favicon.ico", "destination": "https://denser.ai/u/favicon.ico" },
    { "source": "/api/chat/:path*", "destination": "https://denser.ai/api/chat/:path*" },
    { "source": "/api/v2/conversations/public/:path*", "destination": "https://denser.ai/api/v2/conversations/public/:path*" },
    { "source": "/api/v2/messages/:path*", "destination": "https://denser.ai/api/v2/messages/:path*" },
    { "source": "/api/leadGeneration/:path*", "destination": "https://denser.ai/api/leadGeneration/:path*" },
    { "source": "/api/data-source/:path*", "destination": "https://denser.ai/api/data-source/:path*" },
    { "source": "/api/upload/presigned-url", "destination": "https://denser.ai/api/upload/presigned-url" },
    { "source": "/api/custom-actions/:path*", "destination": "https://denser.ai/api/custom-actions/:path*" },
    { "source": "/api/chatbots/customer-support", "destination": "https://denser.ai/api/chatbots/customer-support" },
    { "source": "/api/integrations/helpdesk/ticket", "destination": "https://denser.ai/api/integrations/helpdesk/ticket" }
  ]
}

vercel.json rewrites run after your filesystem

Unlike the Next.js snippet, vercel.json rewrites are only consulted once nothing else has handled the request, and there is no beforeFiles equivalent. A static file or a Serverless Function of yours at any of these paths will answer first and the rule will never fire. If this is a Next.js project, use the Next.js rules instead — beforeFiles runs inside your app and does take precedence. Otherwise, make sure you do not serve anything of your own at these paths.

Netlify

netlify.toml
# Paste this ABOVE your own [[redirects]]. Netlify takes the first rule that
# matches, and force = true is what lets each one win over a file or function
# you already serve at that path.
#
# Bootstrap must come first — it is the only rule that swaps the
# "self" placeholder for your chatbot id, so the id never appears in your
# visitors' URLs. Anything of your own that matches this path ahead of it sends
# the placeholder to denser.ai unchanged, and the page reports itself
# unavailable.
[[redirects]]
  from = "/api/chatbots/help/self/bootstrap"
  to = "https://denser.ai/api/chatbots/help/{chatbotId}/bootstrap"
  status = 200
  force = true

# The help page is always served at /help; the path is not configurable.
# force = true means these two rules win over a /help page of your own, so a
# site that already uses that path cannot proxy one.
[[redirects]]
  from = "/help"
  to = "https://denser.ai/u/help/{chatbotId}"
  status = 200
  force = true

[[redirects]]
  from = "/help/*"
  to = "https://denser.ai/u/help/{chatbotId}/:splat"
  status = 200
  force = true

# SPA bundle and static assets ONLY. Do NOT proxy all of /u/* —
# yourdomain.com/u/anything would boot the SPA with basename /u and render
# Denser's dashboard chrome on your own domain.
[[redirects]]
  from = "/u/assets/*"
  to = "https://denser.ai/u/assets/:splat"
  status = 200
  force = true

[[redirects]]
  from = "/u/images/*"
  to = "https://denser.ai/u/images/:splat"
  status = 200
  force = true

[[redirects]]
  from = "/u/favicon.ico"
  to = "https://denser.ai/u/favicon.ico"
  status = 200
  force = true

# Only the API prefixes the help page calls, so your own /api routes are left
# alone. Delete any whose feature this chatbot does not use.
# streaming replies and voice input
[[redirects]]
  from = "/api/chat/*"
  to = "https://denser.ai/api/chat/:splat"
  status = 200
  force = true

# the visitor's own conversations
[[redirects]]
  from = "/api/v2/conversations/public/*"
  to = "https://denser.ai/api/v2/conversations/public/:splat"
  status = 200
  force = true

# thumbs up / thumbs down on a reply
[[redirects]]
  from = "/api/v2/messages/*"
  to = "https://denser.ai/api/v2/messages/:splat"
  status = 200
  force = true

# lead capture form, if enabled
[[redirects]]
  from = "/api/leadGeneration/*"
  to = "https://denser.ai/api/leadGeneration/:splat"
  status = 200
  force = true

# source names behind citations, if enabled
[[redirects]]
  from = "/api/data-source/*"
  to = "https://denser.ai/api/data-source/:splat"
  status = 200
  force = true

# opening a citation's source file, if citations are enabled
[[redirects]]
  from = "/api/upload/presigned-url"
  to = "https://denser.ai/api/upload/presigned-url"
  status = 200
  force = true

# action form submissions, if enabled
[[redirects]]
  from = "/api/custom-actions/*"
  to = "https://denser.ai/api/custom-actions/:splat"
  status = 200
  force = true

# support form, if enabled
[[redirects]]
  from = "/api/chatbots/customer-support"
  to = "https://denser.ai/api/chatbots/customer-support"
  status = 200
  force = true

# helpdesk tickets, if enabled
[[redirects]]
  from = "/api/integrations/helpdesk/ticket"
  to = "https://denser.ai/api/integrations/helpdesk/ticket"
  status = 200
  force = true

Put these above your own redirects

Netlify takes the first rule that matches, and force = true is what lets each one win over a file or function you already serve at that path — so these blocks belong above your own [[redirects]]. As with Next.js, that means a /help page of your own would be shadowed.

Node.js

server.js
// Express + http-proxy-middleware v3
const express = require("express");
const { createProxyMiddleware } = require("http-proxy-middleware");

const app = express();
const DENSER_ORIGIN = "https://denser.ai";
const CHATBOT_ID = "{chatbotId}";

const forward = (pathFilter, pathRewrite) =>
  createProxyMiddleware({
    target: DENSER_ORIGIN,
    changeOrigin: true,
    pathFilter,
    ...(pathRewrite ? { pathRewrite } : {}),
  });

// Register all of this BEFORE your own routers — Express takes the first
// handler that responds.
//
// Register the bootstrap rule first. It is the only rule that swaps the
// "self" placeholder for your chatbot id, so the id never appears in your
// visitors' URLs. Anything of your own that matches this path ahead of it
// sends the placeholder to denser.ai unchanged, and the page reports itself
// unavailable.
app.use(
  forward("/api/chatbots/help/self/bootstrap", {
    "^.*$": `/api/chatbots/help/${CHATBOT_ID}/bootstrap`,
  }),
);

// The help page itself, always at /help — the path is not configurable. These
// rules are registered first, so they win over a /help page of your own, and a
// site that already uses that path cannot proxy one.
app.use(
  forward(["/help", "/help/**"], { "^/help": `/u/help/${CHATBOT_ID}` }),
);

// SPA bundle and static assets ONLY. Do NOT proxy all of /u/** —
// yourdomain.com/u/anything would boot the SPA with basename /u and render
// Denser's dashboard chrome on your own domain.
app.use(
  forward([
    "/u/assets/**",
    "/u/images/**",
    "/u/favicon.ico",
  ]),
);

// Only the API prefixes the help page calls, so your own /api routes are left
// alone. Delete any whose feature this chatbot does not use.
app.use(
  forward([
    "/api/chat/**", // streaming replies and voice input
    "/api/v2/conversations/public/**", // the visitor's own conversations
    "/api/v2/messages/**", // thumbs up / thumbs down on a reply
    "/api/leadGeneration/**", // lead capture form, if enabled
    "/api/data-source/**", // source names behind citations, if enabled
    "/api/upload/presigned-url", // opening a citation's source file, if citations are enabled
    "/api/custom-actions/**", // action form submissions, if enabled
    "/api/chatbots/customer-support", // support form, if enabled
    "/api/integrations/helpdesk/ticket", // helpdesk tickets, if enabled
  ]),
);

app.listen(3000, () => {
  console.log("Help page proxy listening on http://localhost:3000/help");
});

Register these before your own routers

Express takes the first handler that responds, so these app.use calls must come before your own routes. Nothing else is needed — registration order is the whole mechanism here.

Never proxy all of /u/*

Rewrite /u/assets/*, /u/images/* and /u/favicon.ico only — those are the three paths the page's HTML references. Proxying all of /u/* would make yourdomain.com/u/anything boot the Denser app with basename /u and render Denser's dashboard on your own domain.

What each API rule is for

The snippets do not proxy all of /api/*. Each rule is scoped to an endpoint the help page calls, so the rest of your own /api routes keep working — which matters most on Next.js, where your app's API already lives there.

RuleUsed forNeeded
/api/chatbots/help/self/bootstrapResolving which chatbot this page servesAlways
/api/chat/*Streaming replies, and voice input from the mic buttonAlways
/api/v2/conversations/public/*Loading a conversation and listing the visitor's recent chatsAlways
/api/v2/messages/*Thumbs up / thumbs down on a replyAlways
/api/leadGeneration/*The lead capture formOnly with lead generation on
/api/data-source/*Source names behind citationsOnly with citations on
/api/upload/presigned-urlOpening a citation's source fileOnly with citations on
/api/custom-actions/*Submitting an action formOnly with form actions
/api/chatbots/customer-supportSubmitting the support formOnly with a support email set
/api/integrations/helpdesk/ticketCreating a helpdesk ticketOnly with a helpdesk connected

Delete the rules for features this chatbot does not use — they are listed separately so you can.

Indexing a proxied page

A proxied help page is your URL, not ours, so it is not in Denser's sitemap and Denser cannot submit it for you. If you want search engines to index it, add https://yourdomain.com/help to your own sitemap and link to it from your site the way you would any other page.

Next steps

On this page