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
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
{
"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
# 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 = truePut 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
// 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.
| Rule | Used for | Needed |
|---|---|---|
/api/chatbots/help/self/bootstrap | Resolving which chatbot this page serves | Always |
/api/chat/* | Streaming replies, and voice input from the mic button | Always |
/api/v2/conversations/public/* | Loading a conversation and listing the visitor's recent chats | Always |
/api/v2/messages/* | Thumbs up / thumbs down on a reply | Always |
/api/leadGeneration/* | The lead capture form | Only with lead generation on |
/api/data-source/* | Source names behind citations | Only with citations on |
/api/upload/presigned-url | Opening a citation's source file | Only with citations on |
/api/custom-actions/* | Submitting an action form | Only with form actions |
/api/chatbots/customer-support | Submitting the support form | Only with a support email set |
/api/integrations/helpdesk/ticket | Creating a helpdesk ticket | Only 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.