Configuration Reference - knowtific/helppuff GitHub Wiki
Every field of helppuff.json. Generated from the schema helppuff validates against, so it is
always exact; helppuff schema prints the same as JSON Schema. For what these files are and how
they relate to the dashboard, see Configuration.
Secrets are never values here: a field marked { env } takes the name of an environment
variable, e.g. { "env": "OPENAI_API_KEY" }, whose value lives in .env and on the Worker.
| Field | Type | Default | Description |
|---|---|---|---|
$schema |
string | The JSON Schema for editors and agents. Written by helppuff. | |
format |
integer | The helppuff.json format version. Absent means 1; helppuff upgrade updates it when a release changes the format. · ≥ 1 |
|
site (required)
|
string | Site id: lowercase letters, digits and dashes. Appears in the embed snippet. | |
name (required)
|
string | The business name, as visitors see it. · 1–60 chars | |
website |
string | The website the assistant is for, and learns from. · URL | |
origins (required)
|
string[] | Every origin the widget may be embedded on. The preview page is added automatically. · ≥ 1 items | |
model |
object | Who writes the answers. Leave it out for Workers AI. Changed only here (or with helppuff model set), then helppuff deploy. |
|
model.provider (required)
|
"workers-ai" | "openai-compatible" | "openai" | "gemini" | "anthropic" | "custom"
|
"workers-ai" Workers AI, on your Cloudflare account. The default: no key, on the Free plan. · "openai-compatible" Any OpenAI-compatible /chat/completions API with tools. · "openai" OpenAI. · "gemini" Google Gemini (its OpenAI-compatible API). · "anthropic" Anthropic Claude (the Messages API). · "custom" Your own model: a TypeScript file whose default export is defineModel({ id, chat }). See the wiki's Custom model page. |
|
model.model |
string | A Workers AI model id. Default: GLM-4.7 Flash. · 1–200 chars | |
model.budget |
object | Spend guards. Other providers bill you directly; cap them with security.limits.messagesPerSitePerDay. |
|
model.budget.dailyNeurons |
integer | Workers AI only: neurons per UTC day before answers stop (default 9,000; the free allocation is 10,000). Raise it on Workers Paid. · 0–10000000 | |
model.budget.maxInputTokens |
integer | The most tokens sent to the model per answer: prompt, passages and history, trimmed to fit. · 1000–100000 | |
model.reasoning |
"low" | "medium" | "high"
|
"medium" |
How long the model thinks before answering: low, medium or high. Deeper is better on multi-step questions, slower to start, and the thinking is billed. Models that only switch thinking on or off treat every level as on. |
model.fallbackModel |
string | Tried once when the main model fails for any reason other than the budget. · 1–200 chars | |
model.locale |
string | BCP 47, e.g. en-AU: spelling and date style of answers. · ≤ 35 chars |
|
model.timezone |
string | IANA, e.g. Australia/Melbourne: "are you open now?". · ≤ 64 chars |
|
model.maxAnswerSentences |
integer | 4 |
The longest answer, in sentences. Short answers cost less and read better in a chat. · 1–20 |
model.maxOutputTokens |
integer | 600 |
A hard cap on tokens per answer. · 64–4096 |
model.historyMessages |
integer | 6 |
Earlier messages sent with each question. The main cost lever after retrieval. · 0–24 |
model.richMessages |
boolean | true |
Let the model offer next-step chips (option buttons) after an answer. |
model.tools |
object | {} |
What the assistant can do besides answering. |
model.tools.callback |
boolean | true |
Offer to have the team call or email back — the default way to a person. |
model.tools.businessHours |
boolean | true |
Answer "are you open now?" from the business hours and time zone. |
model.tools.captureLead |
boolean | Retired. Accepted from older configs and ignored. | |
model.tools.handoff |
boolean | Retired. Accepted from older configs and ignored. | |
model.tools.booking |
boolean | Retired. Accepted from older configs and ignored. | |
model.business |
object | {} |
Details the owner confirmed. Usually left empty: the details learned from the site, and edited in the dashboard, are used. |
model.business.name |
string | The business name. · ≤ 200 chars | |
model.business.phone |
string | The main phone number. · ≤ 100 chars | |
model.business.email |
string | The contact email. · ≤ 200 chars | |
model.business.address |
string | The street address. · ≤ 400 chars | |
model.business.hours |
string[] | [] |
Opening hours, one line per day or range, e.g. "Mon-Fri 7am-5pm". · ≤ 14 items |
model.business.serviceAreas |
string[] | [] |
Suburbs or regions served. · ≤ 100 items |
model.gateway |
string | With provider: "workers-ai". An AI Gateway id: caching, logs and rate limits in front of every Workers AI call. · 1–64 chars |
|
model.preset |
"deepinfra" | "openrouter" | "deepseek" | "groq" | "together" | "mistral" | "fireworks" | "vercel-ai-gateway" | "cloudflare-ai-gateway"
|
With provider: "openai-compatible". Fills baseUrl and the key's variable name: deepinfra, openrouter, deepseek, groq, together, mistral, fireworks, vercel-ai-gateway, cloudflare-ai-gateway. |
|
model.baseUrl |
string | With provider: "openai-compatible". The API base, before /chat/completions. Needed without a preset. · URL |
|
model.apiKey |
{ env } |
With provider: "openai-compatible". The key, by environment variable name. Default: the preset's (e.g. DEEPINFRA_API_KEY). |
|
model.headers |
map of string | { env }
|
With provider: "openai-compatible". Extra headers; a value may be a secret ({ env }). |
|
model.nativeTools |
boolean | With provider: "openai-compatible". The model calls tools natively (default true). Off: tool calls it writes in its text are still read. |
|
model.accountId |
string | With provider: "openai-compatible". cloudflare-ai-gateway: the account (default: the one deployed to). |
|
model.gatewayId |
string | With provider: "openai-compatible". cloudflare-ai-gateway: the gateway (default: default). · 1–64 chars |
|
model.gatewayToken |
{ env } |
With provider: "openai-compatible". cloudflare-ai-gateway: an authenticated gateway's token, sent as cf-aig-authorization. |
|
model.apiKey |
{ env } |
{"env":"OPENAI_API_KEY"} |
With provider: "openai". Your OpenAI API key, by environment variable name. |
model.apiKey |
{ env } |
{"env":"GEMINI_API_KEY"} |
With provider: "gemini". Your Gemini API key, by environment variable name. |
model.apiKey |
{ env } |
{"env":"ANTHROPIC_API_KEY"} |
With provider: "anthropic". Your Anthropic API key, by environment variable name. |
model.module (required)
|
string | With provider: "custom". The file, relative to helppuff.json, e.g. ./llm.ts. Bundled into the Worker at deploy. |
|
model.secrets |
string[] | With provider: "custom". The environment variables your file reads (env.NAME): uploaded as Worker secrets at deploy. · ≤ 20 items |
|
prompt |
string | "prompt.md" |
Path to the system prompt, relative to helppuff.json. |
assistant |
object | {} |
How the assistant behaves: goal, tone, answer length. HelpPuff writes these around prompt.md on every answer, so prompt.md holds only what is specific to the business. |
assistant.goal |
"callbacks" | "answers" | "bookings"
|
"callbacks" |
What the assistant is for: callbacks (help, then get the team in touch), answers, or bookings (help, then send them to bookingUrl: a booking, sign-up or quote page). |
assistant.tone |
"friendly" | "professional" | "casual"
|
"friendly" |
How it sounds. |
assistant.length |
"short" | "detailed"
|
"short" |
short: a few sentences; detailed: complete answers with short lists. |
assistant.prices |
"share" | "quote"
|
"share" |
share: give prices exactly as the site and documents state them; quote: never give a price or estimate, offer a quote from the team instead. |
assistant.bookingUrl |
string | The page the bookings goal sends visitors to: booking, sign-up or a quote form. · ≤ 2000 chars, URL |
|
autoSummary |
boolean | true |
Automatically summarise and label finished conversations. Off keeps transcripts, after-chat tools and webhooks running. |
live |
object | {} |
Live chat: visitors can talk to a person on your team, who answers from the dashboard or Telegram. Off by default. helppuff live on turns it on. |
live.enabled |
boolean | false |
Let visitors talk to a person on the team. When nobody is available they get the callback form instead. |
live.waitSeconds |
integer | 120 |
How long a visitor waits for someone to take the chat before they are offered the callback form (they can keep waiting). · 15–3600 |
live.closeAfterMinutes |
integer | 60 |
A conversation with no message for this long is closed. A visitor who writes again is answered by the assistant. · 5–1440 |
live.showAgentName |
boolean | true |
Show visitors the first name of the person answering ("Sam joined"); off: "Someone from the team". |
live.aiWhileWaiting |
boolean | false |
Let the assistant keep answering until someone takes the chat. |
bookings |
object | {} |
Appointments: mode none, link (offer a booking page) or picker (a date picker in the chat; HelpPuff books, the assistant never does). Left out, a booking link found on the website is offered. With the picker, sync picks the calendar (keys in Settings → Integrations, helppuff integrations). helppuff bookings changes them. |
files |
object | {} |
Photos and files on R2: visitors sending them in the chat (chat: off, live while a person has the chat, or always) and the team attaching them to jobs (jobs). Both off by default; helppuff files turns them on. Needs R2 on for the Cloudflare account (deploy makes the bucket). |
Only for a backend that runs the whole conversation itself: retell, http (your own API), echo, or openai / gemini with their own file stores. Otherwise leave it out and use model and knowledge.retrieval.
HelpPuff's own assistant, set up by model and knowledge.retrieval. Implied when backend is left out; never written by hand.
| Field | Type | Default | Description |
|---|
Workers AI with HelpPuff's own knowledge base. The default; runs on the Workers Free plan.
| Field | Type | Default | Description |
|---|---|---|---|
backend.model |
string | "@cf/zai-org/glm-4.7-flash" |
The Workers AI model that writes answers. GLM-4.7 Flash is the best all-round on the Free plan; the wiki's AI models page compares the others for speed, quality and answers a day. · 1–200 chars |
backend.reasoning |
"low" | "medium" | "high"
|
"medium" |
How long the model thinks before answering: low, medium or high. Deeper is better on multi-step questions, slower to start, and the thinking is billed. Models that only switch thinking on or off treat every level as on. |
backend.fallbackModel |
string | Tried once when the main model fails for any reason other than the budget. · 1–200 chars | |
backend.gateway |
string | An AI Gateway id: caching, logs and rate limits in front of every model call. · 1–64 chars | |
backend.locale |
string | BCP 47, e.g. en-AU: spelling and date style of answers. · ≤ 35 chars |
|
backend.timezone |
string | IANA, e.g. Australia/Melbourne: "are you open now?". · ≤ 64 chars |
|
backend.maxAnswerSentences |
integer | 4 |
The longest answer, in sentences. Short answers cost less and read better in a chat. · 1–20 |
backend.maxOutputTokens |
integer | 600 |
A hard cap on tokens per answer. · 64–4096 |
backend.historyMessages |
integer | 6 |
Earlier messages sent with each question. The main cost lever after retrieval. · 0–24 |
backend.richMessages |
boolean | true |
Let the model offer next-step chips (option buttons) after an answer. |
backend.retrieval |
object | {} |
How answers find passages in the knowledge base. |
backend.tools |
object | {} |
What the assistant can do besides answering. |
backend.tools.callback |
boolean | true |
Offer to have the team call or email back — the default way to a person. |
backend.tools.businessHours |
boolean | true |
Answer "are you open now?" from the business hours and time zone. |
backend.tools.captureLead |
boolean | Retired. Accepted from older configs and ignored. | |
backend.tools.handoff |
boolean | Retired. Accepted from older configs and ignored. | |
backend.tools.booking |
boolean | Retired. Accepted from older configs and ignored. | |
backend.handoff |
map of any | Retired (callbacks replaced handoff). Accepted from older configs and ignored. | |
backend.business |
object | {} |
Details the owner confirmed. Usually left empty: the details learned from the site, and edited in the dashboard, are used. |
backend.business.name |
string | The business name. · ≤ 200 chars | |
backend.business.phone |
string | The main phone number. · ≤ 100 chars | |
backend.business.email |
string | The contact email. · ≤ 200 chars | |
backend.business.address |
string | The street address. · ≤ 400 chars | |
backend.business.hours |
string[] | [] |
Opening hours, one line per day or range, e.g. "Mon-Fri 7am-5pm". · ≤ 14 items |
backend.business.serviceAreas |
string[] | [] |
Suburbs or regions served. · ≤ 100 items |
backend.budget |
object | {} |
The daily Workers AI spend guard. Past it, visitors get your contact details and a callback form instead of answers. |
backend.budget.dailyNeurons |
integer | 9000 |
Neurons per UTC day before answers stop; the free allocation is 10,000. · 0–10000000 |
backend.budget.maxInputTokens |
integer | 6000 |
The most tokens sent to the model per answer — prompt, passages and history — trimmed to fit. · 1000–100000 |
How answers find passages in the knowledge base.
| Field | Type | Default | Description |
|---|---|---|---|
backend.retrieval.embeddingModel |
string | "@cf/baai/bge-m3" |
Turns pages and questions into vectors. Changing it re-learns the site (and uploaded files) on the next deploy. · 1–200 chars |
backend.retrieval.rerankerModel |
string | null | "@cf/baai/bge-reranker-base" |
Re-reads the passages search found and keeps only those that answer the question. null turns it off: about half a second faster, but more likely to answer from the wrong page. |
backend.retrieval.topKVector |
integer | 20 |
Passages taken from meaning (vector) search before re-scoring. · 1–50 |
backend.retrieval.topKKeyword |
integer | 20 |
Passages taken from keyword (full-text) search before re-scoring. · 1–50 |
backend.retrieval.finalK |
integer | 4 |
Passages the model reads for each answer. · 1–10 |
backend.retrieval.minScore |
number | 0.2 |
Passages scored below this are dropped. With none left, the assistant says it is not sure and offers a callback instead of guessing. · 0–1 |
backend.retrieval.queryRewrite |
"heuristic" | "llm" | "off"
|
"heuristic" |
heuristic: follow-ups borrow the previous question. llm: one extra small call rewrites it. |
backend.retrieval.intentModel |
string | null | null |
e.g. @cf/cloudflare/clef-flash: classify each question to steer retrieval. Off by default. |
Cloudflare AI Search: retrieval and generation managed by Cloudflare.
| Field | Type | Default | Description |
|---|---|---|---|
backend.instance |
string | Instance name on your account. Created by helppuff deploy if it does not exist. |
|
backend.endpoint |
string | Use an existing public endpoint instead of a binding, e.g. https://search.example.com · URL | |
backend.model |
string | Workers AI model id, or an AI Gateway alias. Empty uses the instance's own model. | |
backend.maxResults |
integer | Passages AI Search retrieves per question. · 1–50 |
OpenAI (Responses API), or any compatible endpoint with baseUrl.
| Field | Type | Default | Description |
|---|---|---|---|
backend.model |
string | "gpt-5-mini" |
The OpenAI model. |
backend.apiKey |
{ env } |
{"env":"OPENAI_API_KEY"} |
Your OpenAI API key, by environment variable name. |
backend.vectorStoreId |
string | Filled in by helppuff knowledge sync. |
|
backend.retrieval |
"helppuff" |
helppuff: answer from HelpPuff's own knowledge base (crawled by the Worker) instead of a vector store. |
|
backend.promptId |
string | A stored prompt in the OpenAI dashboard; overrides prompt.md. | |
backend.baseUrl |
string | Another OpenAI-compatible Responses endpoint, e.g. Azure OpenAI. · URL |
Google Gemini, with File Search.
| Field | Type | Default | Description |
|---|---|---|---|
backend.model |
string | "gemini-3-flash" |
The Gemini model. |
backend.apiKey |
{ env } |
{"env":"GEMINI_API_KEY"} |
Your Gemini API key, by environment variable name. |
backend.fileSearchStore |
string | Filled in by helppuff knowledge sync. |
|
backend.retrieval |
"helppuff" |
helppuff: answer from HelpPuff's own knowledge base instead of File Search. |
Anthropic Claude, grounded in AI Search or HelpPuff's own knowledge base.
| Field | Type | Default | Description |
|---|---|---|---|
backend.model |
string | "claude-opus-5" |
The Claude model. |
backend.apiKey |
{ env } |
{"env":"ANTHROPIC_API_KEY"} |
Your Anthropic API key, by environment variable name. |
backend.effort |
"low" | "medium" | "high" | "xhigh" | "max"
|
How hard Claude thinks before answering. Lower is faster and cheaper. | |
backend.knowledge |
boolean | Ground answers in a Cloudflare AI Search instance. On by default when there is knowledge. | |
backend.retrieval |
"helppuff" |
helppuff: ground answers in HelpPuff's own knowledge base instead of AI Search. |
|
backend.instance |
string | Instance name on your account. Created by helppuff deploy if it does not exist. |
|
backend.endpoint |
string | Use an existing public endpoint instead of a binding, e.g. https://search.example.com · URL |
Your own API.
| Field | Type | Default | Description |
|---|---|---|---|
backend.url (required)
|
string | Your API's base URL. · URL | |
backend.mode |
"helppuff" | "openai"
|
"helppuff" |
helppuff: your API speaks the HelpPuff backend protocol. openai: any /chat/completions endpoint. |
backend.model |
string |
openai mode: the model name your endpoint expects. |
|
backend.token |
{ env } |
Sent as Authorization: Bearer …. |
|
backend.signingSecret |
{ env } |
Signs every request with HMAC-SHA256 so your API can verify it. | |
backend.stream |
boolean | true |
Stream replies as they are written. |
A Retell chat agent.
| Field | Type | Default | Description |
|---|---|---|---|
backend.agentId (required)
|
string | The Retell chat agent id. | |
backend.apiKey |
{ env } |
{"env":"RETELL_API_KEY"} |
Your Retell API key, by environment variable name. |
backend.retrieval |
"helppuff" |
helppuff: crawl the site into HelpPuff's knowledge base; the agent searches it via a custom function (see helppuff status). |
Echoes what it is sent, with a demo of every widget feature. For development; needs no key.
| Field | Type | Default | Description |
|---|
What the assistant learns from: the website, and your own files.
| Field | Type | Default | Description |
|---|---|---|---|
knowledge.website |
boolean | object | true |
Learn from the website: true (the defaults), false, or which pages and how often. |
knowledge.website.maxPages |
integer | Default: 300 for workers-ai (crawled by the Worker), 50 for the others (crawled here). · 1–1000 | |
knowledge.website.include |
string[] | Only crawl URLs matching one of these: a substring, or a glob like **/services/**. |
|
knowledge.website.exclude |
string[] | Never crawl URLs matching one of these. Defaults leave out privacy, terms, tags, carts and accounts. | |
knowledge.website.renderJs |
"auto" | "always" | "never"
|
workers-ai: render pages drawn by JavaScript with Browser Rendering (auto = only when needed). |
|
knowledge.website.schedule |
"off" | "daily" | "weekly" | "monthly"
|
workers-ai: re-crawl the selected pages on this schedule. | |
knowledge.files |
string[] | [] |
Files and folders to index, relative to helppuff.json. PDF, Markdown, text, HTML, DOCX. |
knowledge.retrieval |
object | What answers come from. Left out: HelpPuff's own knowledge base. none for none. Changed only here (or with helppuff rag set), then helppuff deploy. |
|
knowledge.retrieval.type (required)
|
"helppuff" | "none" | "ai-search" | "openai-vector-store" | "http" | "custom"
|
"helppuff" HelpPuff's own knowledge base: your site and files, learned by the Worker (Vectorize + D1). · "none" No knowledge base: the prompt and the business details only. · "ai-search" Cloudflare AI Search, searched for passages; your model writes the answer. · "openai-vector-store" An OpenAI vector store you fill in OpenAI, searched for passages. · "http" Your own search over HTTP: POST url { query, question, siteId, limit } → { passages: [{ title, content, url? }] }. · "custom" Your own knowledge base: a TypeScript file whose default export is defineRetriever({ id, search }). See the wiki's Custom knowledge base page. |
|
knowledge.retrieval.embeddingModel |
string | "@cf/baai/bge-m3" |
With type: "helppuff". Turns pages and questions into vectors. Changing it re-learns the site (and uploaded files) on the next deploy. · 1–200 chars |
knowledge.retrieval.rerankerModel |
string | null | "@cf/baai/bge-reranker-base" |
With type: "helppuff". Re-reads the passages search found and keeps only those that answer the question. null turns it off: about half a second faster, but more likely to answer from the wrong page. |
knowledge.retrieval.topKVector |
integer | 20 |
With type: "helppuff". Passages taken from meaning (vector) search before re-scoring. · 1–50 |
knowledge.retrieval.topKKeyword |
integer | 20 |
With type: "helppuff". Passages taken from keyword (full-text) search before re-scoring. · 1–50 |
knowledge.retrieval.finalK |
integer | 4 |
With type: "helppuff". Passages the model reads for each answer. · 1–10 |
knowledge.retrieval.minScore |
number | 0.2 |
With type: "helppuff". Passages scored below this are dropped. With none left, the assistant says it is not sure and offers a callback instead of guessing. · 0–1 |
knowledge.retrieval.queryRewrite |
"heuristic" | "llm" | "off"
|
"heuristic" |
With type: "helppuff". heuristic: follow-ups borrow the previous question. llm: one extra small call rewrites it. |
knowledge.retrieval.intentModel |
string | null | null |
With type: "helppuff". e.g. @cf/cloudflare/clef-flash: classify each question to steer retrieval. Off by default. |
knowledge.retrieval.instance |
string | With type: "ai-search". Instance name on your account. Created by helppuff deploy if it does not exist. |
|
knowledge.retrieval.endpoint |
string | With type: "ai-search". Use an existing public endpoint instead of a binding, e.g. https://search.example.com · URL |
|
knowledge.retrieval.maxResults |
integer | With type: "ai-search". Passages per question. · 1–50 |
|
knowledge.retrieval.vectorStoreId (required)
|
string | With type: "openai-vector-store". The vector store id, e.g. vs_…. |
|
knowledge.retrieval.apiKey |
{ env } |
{"env":"OPENAI_API_KEY"} |
With type: "openai-vector-store". An OpenAI API key that can read it. |
knowledge.retrieval.url (required)
|
string | With type: "http". Your endpoint. · URL |
|
knowledge.retrieval.token |
{ env } |
With type: "http". Sent as Authorization: Bearer …. |
|
knowledge.retrieval.module (required)
|
string | With type: "custom". The file, relative to helppuff.json, e.g. ./rag.ts. |
|
knowledge.retrieval.secrets |
string[] | With type: "custom". The environment variables your file reads: uploaded as Worker secrets at deploy. · ≤ 20 items |
Rate limits, daily cap, Turnstile. The defaults are safe for a public site.
| Field | Type | Default | Description |
|---|---|---|---|
security.captcha |
object | Check new chats (and dashboard sign-ins) with Cloudflare Turnstile (invisible for most visitors). Strongly recommended: without it, a script can start chats. | |
security.captcha.provider (required)
|
"turnstile" |
Cloudflare Turnstile. | |
security.captcha.siteKey (required)
|
string | The Turnstile site key (public). · 1–200 chars | |
security.captcha.secret (required)
|
{ env } |
The Turnstile secret key, by environment variable name. | |
security.limits |
object | {} |
Per-visitor and per-site limits. The daily cap is the cost backstop. |
security.signIn |
object | {} |
Dashboard sign-in limits. |
security.signIn.attemptsPerIp |
integer | 10 |
Sign-in attempts (and one-time link checks) one IP may make per window. · 1–1000 |
security.signIn.attemptsPerAccount |
integer | 5 |
Failed sign-ins one email may have per window, from anywhere. Counts failures only. · 1–1000 |
security.signIn.windowMinutes |
integer | 15 |
The window both sign-in limits count over, in minutes. · 1–1440 |
security.signIn.captcha |
boolean | true |
Ask for Turnstile on the sign-in form when security.captcha is set. Add the dashboard's hostname to the Turnstile widget. |
security.allowIps |
string[] | [] |
IP addresses or CIDR ranges exempt from the per-visitor limits (your office, a monitor). The per-chat and daily caps still apply. · ≤ 500 items |
security.blockIps |
string[] | [] |
IP addresses or CIDR ranges refused by the chat. The dashboard is not affected. · ≤ 500 items |
security.sessionTtlHours |
number | 24 |
How long a chat can be continued (the widget keeps it across pages and reloads). · 0.25–720 |
Per-visitor and per-site limits. The daily cap is the cost backstop.
| Field | Type | Default | Description |
|---|---|---|---|
security.limits.messagesPerIpPerMinute |
integer | 10 |
Messages one visitor (IP) may send a minute. · 1–600 |
security.limits.messagesPerIpPerDay |
integer | 100 |
Messages one visitor (IP) may send a day (UTC), so one visitor cannot use up the daily cap. · 1–100000 |
security.limits.sessionsPerIpPerHour |
integer | 5 |
New chats one visitor (IP) may start an hour. · 1–1000 |
security.limits.sessionsPerIpPerDay |
integer | 20 |
New chats one visitor (IP) may start a day (UTC). · 1–10000 |
security.limits.messagesPerSession |
integer | 60 |
Messages in one chat before the visitor must start another. · 1–1000 |
security.limits.messagesPerSitePerDay |
integer | 500 |
The cost backstop. Always set this. · 1–1000000 |
security.limits.maxMessageLength |
integer | 1000 |
The longest message a visitor may send, in characters. · 1–4000 |
security.limits.maxLeadFieldLength |
integer | 200 |
The longest answer to one form field, in characters (a message box gets maxLeadMessageLength). · 20–2000 |
security.limits.maxLeadMessageLength |
integer | 2000 |
The longest answer to a message box in a form, in characters. · 20–4000 |
security.limits.feedbackPerIpPerMinute |
integer | 30 |
Thumbs up or down one visitor (IP) may give a minute. · 1–600 |
security.limits.pollsPerIpPerMinute |
integer | 120 |
Checks for new messages one visitor (IP) may make a minute (backends that reply later, like Retell). · 1–600 |
security.limits.endsPerIpPerMinute |
integer | 10 |
Chats one visitor (IP) may close a minute. · 1–600 |
security.limits.retellLookupsPerMinute |
integer | 120 |
Knowledge-base lookups a Retell agent may make a minute, for the whole site. · 1–6000 |
security.limits.apiRequestsPerKeyPerMinute |
integer | 120 |
Requests a new API key may make a minute (each key can have its own). Chat requests also count against the daily cap. · 1–6000 |
security.limits.apiKeysPerSite |
integer | 50 |
Active API keys a site may have. · 1–500 |
security.limits.handoversPerIpPerDay |
integer | 3 |
Times one visitor (IP) may ask for a person a day (live chat). · 1–1000 |
security.limits.waitingPerSite |
integer | 20 |
Live chats that may wait for a person at once; past it, visitors get the callback form. · 1–1000 |
security.limits.liveSocketsPerIp |
integer | 3 |
Live chat connections one visitor (IP) may hold open at once. · 1–100 |
security.limits.slotLookupsPerIpPerMinute |
integer | 30 |
Times one visitor (IP) may load free times in the booking calendar a minute. · 1–600 |
security.limits.bookingsPerIpPerHour |
integer | 5 |
Appointments one visitor (IP) may book, move or cancel an hour, from the chat. · 1–100 |
security.limits.uploadsPerIpPerHour |
integer | 20 |
Photos and files one visitor (IP) may send an hour, when visitors can send files (files.chat). · 1–1000 |
Where leads go besides the dashboard.
| Field | Type | Default | Description |
|---|---|---|---|
leads.webhook |
string | { env }
|
POST each lead to this URL (or to the URL in this environment variable). For every event, add webhooks in the dashboard instead. |
The dashboard at /admin: conversations, leads, knowledge, analytics and settings, stored in D1 on your account.
| Field | Type | Default | Description |
|---|---|---|---|
dashboard.enabled |
boolean | true |
Serve the dashboard at /admin. |
dashboard.adminEmail |
string | The owner's email, for backends without a setup link. workers-ai: set on the setup page instead. · email | |
dashboard.summaryModel |
string | Workers AI model used for conversation summaries. |
Written by helppuff deploy. Safe to commit; holds no secrets.
| Field | Type | Default | Description |
|---|---|---|---|
cloudflare.accountId |
string | The Cloudflare account deployed to. | |
cloudflare.workerName |
string | The Worker's name. Default: knowtific-helppuff-. | |
cloudflare.url |
string | Where the Worker answers. · URL | |
cloudflare.kvNamespaceId |
string | The KV namespace (live config, new-conversation counters). | |
cloudflare.d1DatabaseId |
string | The D1 database (conversations, leads, knowledge). | |
cloudflare.vectorizeIndex |
string | The Vectorize index (knowledge vectors). | |
cloudflare.filesBucket |
string | The R2 bucket (photos and files), when the account has R2. |
Brand, launcher, home screen, lead form, flows — see helppuff schema.
Names, colour and theme.
| Field | Type | Default | Description |
|---|---|---|---|
widget.brand.name |
string | "Chat" |
The business name in the widget. · 1–60 chars |
widget.brand.agentName |
string | "Assistant" |
The assistant's name, in its header and messages. · 1–60 chars |
widget.brand.avatar |
string | The assistant's picture: a square image URL. · 1–2048 chars | |
widget.brand.accent |
string | "#5B5BF7" |
The main colour (hex). Text on it is made readable automatically. |
widget.brand.theme |
"light" | "dark" | "auto"
|
"auto" |
auto follows the visitor's system setting. |
widget.brand.tokens |
map of string | Fine-grained styling: CSS custom properties without the --hp- prefix, e.g. { "radius-panel": "12px", "font": "Inter, sans-serif" }. |
The button that opens the chat.
| Field | Type | Default | Description |
|---|---|---|---|
widget.launcher.position |
"bottom-right" | "bottom-left"
|
"bottom-right" |
Which corner the button sits in. |
widget.launcher.offset |
object | Move the button away from the corner, e.g. above a cookie banner. | |
widget.launcher.offset.x (required)
|
number | Pixels from the side. · 0–200 | |
widget.launcher.offset.y (required)
|
number | Pixels from the bottom. · 0–200 | |
widget.launcher.label |
string | Text beside the orb, or inside it when shape is pill. For example "Chat with us". · ≤ 40 chars |
|
widget.launcher.icon |
"chat" | "phone" | "mail" | "calendar" | "quote" | "pin" | "clock" | "wrench" | "heart" | "info" | "book" | "arrow-right" | "arrow-left" | "close" | "send" | "menu" | "sound" | "sound-off" | "check" | "external" | "person"
|
"chat" |
Which of the built-in icons the launcher shows. |
widget.launcher.shape |
"orb" | "pill"
|
"orb" |
orb is the signature circle. pill widens it to sit the label inside the button, which reads as a clearer invitation on a busy page. |
widget.launcher.hideOnPaths |
string[] | Pages where the widget does not appear, e.g. /checkout/**. · ≤ 50 items |
The first screen visitors see.
| Field | Type | Default | Description |
|---|---|---|---|
widget.home.title |
string | "Hi there" |
The heading of the first screen. · ≤ 120 chars |
widget.home.subtitle |
string | "Ask anything, or pick a shortcut." |
The line under it. · ≤ 240 chars |
widget.home.shortcuts |
object[] | Buttons on the first screen: suggested questions, a call button, a form… · ≤ 8 items | |
widget.home.shortcuts[].id (required)
|
string | Unique among the shortcuts. · 1–64 chars | |
widget.home.shortcuts[].label (required)
|
string | What the shortcut says. · 1–80 chars | |
widget.home.shortcuts[].description |
string | A line under the label. · ≤ 160 chars | |
widget.home.shortcuts[].icon |
"chat" | "phone" | "mail" | "calendar" | "quote" | "pin" | "clock" | "wrench" | "heart" | "info" | "book" | "arrow-right" | "arrow-left" | "close" | "send" | "menu" | "sound" | "sound-off" | "check" | "external" | "person"
|
A built-in icon. | |
widget.home.shortcuts[].action (required)
|
object | What happens when the button is pressed. kind picks it. |
|
widget.home.shortcuts[].action.kind (required)
|
"reply" | "url" | "tel" | "email" | "flow" | "form" | "booking"
|
"reply" Send a message as the visitor. · "url" Open a page. · "tel" Call a number. · "email" Write an email. · "flow" Start one of widget.flows. · "form" Open one of widget.forms. · "booking" Open the booking calendar in the chat (Settings → Bookings: date picker in the chat). |
|
widget.home.shortcuts[].action.id (required)
|
string | An id, unique within its list. · 1–64 chars | |
widget.home.shortcuts[].action.label (required)
|
string | What the button says. · 1–120 chars | |
widget.home.shortcuts[].action.value (required)
|
string | With kind: "reply". The message sent. · 1–500 chars |
|
widget.home.shortcuts[].action.url (required)
|
string | With kind: "url". The page to open. · 1–2048 chars |
|
widget.home.shortcuts[].action.newTab |
boolean | With kind: "url". Open in a new tab. |
|
widget.home.shortcuts[].action.phone (required)
|
string | With kind: "tel". The number to call. · 1–40 chars |
|
widget.home.shortcuts[].action.email (required)
|
string | With kind: "email". The address to write to. · 3–200 chars |
|
widget.home.shortcuts[].action.flowId (required)
|
string | With kind: "flow". The flow's id. · 1–64 chars |
|
widget.home.shortcuts[].action.formId (required)
|
string | With kind: "form". The form's id. · 1–64 chars |
|
widget.home.shortcuts[].paths |
string[] | Show it only on these pages. · ≤ 20 items | |
widget.home.links |
object | A list of useful pages on the first screen. | |
widget.home.links.title (required)
|
string | The list's heading. · ≤ 120 chars | |
widget.home.links.items (required)
|
object[] | The links. · 1–10 items | |
widget.home.links.items[].label (required)
|
string | The link text. · 1–160 chars | |
widget.home.links.items[].url (required)
|
string | Where it goes (https). · 1–2048 chars | |
widget.home.links.items[].description |
string | A line under the link. · ≤ 300 chars |
The short form before the chat.
| Field | Type | Default | Description |
|---|---|---|---|
widget.leadForm.enabled |
boolean | true |
Ask for details before the chat starts. |
widget.leadForm.title |
string | The form's heading. · ≤ 120 chars | |
widget.leadForm.fields |
object[] | [{"name":"name","label":"Name","type":"text","required":true,"autocomplete":"name"},{"name":"phone","label":"Phone","type":"tel","required":true,"autocomplete":"tel"}] |
The questions, in order. Add your own; each answer is kept on the lead and shown to the assistant. · 1–12 items |
widget.leadForm.fields[].name (required)
|
string | The key the answer is stored under. name, email, phone and message mean something to the assistant (a message opens the chat). · 1–64 chars |
|
widget.leadForm.fields[].label (required)
|
string | What the visitor sees. · 1–160 chars | |
widget.leadForm.fields[].type (required)
|
"text" | "email" | "tel" | "textarea" | "select"
|
The kind of input. select needs options. |
|
widget.leadForm.fields[].required |
boolean | Must be filled in. | |
widget.leadForm.fields[].placeholder |
string | Hint text inside the empty field. · ≤ 160 chars | |
widget.leadForm.fields[].options |
string[] | The choices of a select. · ≤ 50 items |
|
widget.leadForm.fields[].pattern |
string | A regular expression the answer must match. · ≤ 200 chars | |
widget.leadForm.fields[].autocomplete |
string | The HTML autocomplete hint, e.g. email, tel. · ≤ 64 chars |
|
widget.leadForm.submitLabel |
string | The button's text. · ≤ 60 chars | |
widget.leadForm.privacy |
object | A privacy note under the form. | |
widget.leadForm.privacy.text (required)
|
string | The notice, e.g. "We only use this to reply to you." · 1–300 chars | |
widget.leadForm.privacy.url (required)
|
string | Your privacy policy. · 1–2048 chars | |
widget.leadForm.askFirstMessage |
boolean | Add a box for the visitor's first message to the form. A field named message does the same and can be labelled. |
The conversation screen.
| Field | Type | Default | Description |
|---|---|---|---|
widget.chat.placeholder |
string | Hint text in the message box. · ≤ 120 chars | |
widget.chat.initialMessages |
string[] | The assistant's greeting, before the visitor writes. · ≤ 3 items | |
widget.chat.shortcuts |
object[] | Buttons above the message box during the chat. · ≤ 8 items | |
widget.chat.shortcuts[].id (required)
|
string | Unique among the shortcuts. · 1–64 chars | |
widget.chat.shortcuts[].label (required)
|
string | What the shortcut says. · 1–80 chars | |
widget.chat.shortcuts[].description |
string | A line under the label. · ≤ 160 chars | |
widget.chat.shortcuts[].icon |
"chat" | "phone" | "mail" | "calendar" | "quote" | "pin" | "clock" | "wrench" | "heart" | "info" | "book" | "arrow-right" | "arrow-left" | "close" | "send" | "menu" | "sound" | "sound-off" | "check" | "external" | "person"
|
A built-in icon. | |
widget.chat.shortcuts[].action (required)
|
object | What happens when the button is pressed. kind picks it. |
|
widget.chat.shortcuts[].action.kind (required)
|
"reply" | "url" | "tel" | "email" | "flow" | "form" | "booking"
|
"reply" Send a message as the visitor. · "url" Open a page. · "tel" Call a number. · "email" Write an email. · "flow" Start one of widget.flows. · "form" Open one of widget.forms. · "booking" Open the booking calendar in the chat (Settings → Bookings: date picker in the chat). |
|
widget.chat.shortcuts[].action.id (required)
|
string | An id, unique within its list. · 1–64 chars | |
widget.chat.shortcuts[].action.label (required)
|
string | What the button says. · 1–120 chars | |
widget.chat.shortcuts[].action.value (required)
|
string | With kind: "reply". The message sent. · 1–500 chars |
|
widget.chat.shortcuts[].action.url (required)
|
string | With kind: "url". The page to open. · 1–2048 chars |
|
widget.chat.shortcuts[].action.newTab |
boolean | With kind: "url". Open in a new tab. |
|
widget.chat.shortcuts[].action.phone (required)
|
string | With kind: "tel". The number to call. · 1–40 chars |
|
widget.chat.shortcuts[].action.email (required)
|
string | With kind: "email". The address to write to. · 3–200 chars |
|
widget.chat.shortcuts[].action.flowId (required)
|
string | With kind: "flow". The flow's id. · 1–64 chars |
|
widget.chat.shortcuts[].action.formId (required)
|
string | With kind: "form". The form's id. · 1–64 chars |
|
widget.chat.shortcuts[].paths |
string[] | Show it only on these pages. · ≤ 20 items | |
widget.chat.fallbackContact |
object | How to reach a person when the assistant is unavailable (an outage, or the daily budget is spent). | |
widget.chat.fallbackContact.phone |
string | Shown when the assistant cannot answer. · ≤ 40 chars | |
widget.chat.fallbackContact.email |
string | Shown when the assistant cannot answer. · ≤ 200 chars |
A message that pops up beside the button to invite a chat.
| Field | Type | Default | Description |
|---|---|---|---|
widget.teaser.text (required)
|
string | The message, e.g. "Need a quote? Ask me." · 1–200 chars | |
widget.teaser.delayMs |
integer | Time on the page. Omit to rely on afterScroll alone. · 2000–120000 |
|
widget.teaser.afterScroll |
integer | Percentage of the page scrolled, 1–100. Whichever trigger fires first shows the teaser; a visitor who reads rather than waits still sees it. · 1–100 | |
widget.teaser.paths |
string[] | Show it only on these pages. · ≤ 50 items | |
widget.teaser.oncePerSession |
boolean | true |
Show it once per visit, not on every page. |
Guided questions asked in the widget (no AI), sent as one message at the end. Type: object[].
| Field | Type | Default | Description |
|---|---|---|---|
widget.flows[].id (required)
|
string | What a flow action or shortcut starts it by. · 1–64 chars |
|
widget.flows[].steps (required)
|
object[] | Questions asked one at a time, in the widget, before anything is sent. · 1–10 items | |
widget.flows[].steps[].field (required)
|
string | The name the answer is kept under, for the template. · 1–64 chars | |
widget.flows[].steps[].ask (required)
|
string | The question, shown as the assistant's message. · 1–400 chars | |
widget.flows[].steps[].input (required)
|
"text" | "choice" | "phone" | "email"
|
How the visitor answers. | |
widget.flows[].steps[].choices |
string[] | The buttons of a choice step. · ≤ 12 items |
|
widget.flows[].steps[].required |
boolean | The step cannot be skipped. | |
widget.flows[].submit (required)
|
object | What happens with the answers: message sends them as one message, job saves them as a job. |
|
widget.flows[].submit.as (required)
|
"message" | "job"
|
"message" Sent as one visitor message. · "job" Saved as a job (the site's Jobs pipeline), with the answers as its fields; the visitor gets its number. |
|
widget.flows[].submit.template (required)
|
string | The message, with {{field}} for each answer, e.g. "Quote for {{service}} in {{suburb}}". · 1–1000 chars |
Inline forms by id, opened by a form action or shortcut. Submitting one sends its answers to the assistant. Type: map of object.
| Field | Type | Default | Description |
|---|---|---|---|
widget.forms.<id>.title |
string | The form's heading. · ≤ 120 chars | |
widget.forms.<id>.fields (required)
|
object[] | The questions. · 1–12 items | |
widget.forms.<id>.fields[].name (required)
|
string | The key the answer is stored under. name, email, phone and message mean something to the assistant (a message opens the chat). · 1–64 chars |
|
widget.forms.<id>.fields[].label (required)
|
string | What the visitor sees. · 1–160 chars | |
widget.forms.<id>.fields[].type (required)
|
"text" | "email" | "tel" | "textarea" | "select"
|
The kind of input. select needs options. |
|
widget.forms.<id>.fields[].required |
boolean | Must be filled in. | |
widget.forms.<id>.fields[].placeholder |
string | Hint text inside the empty field. · ≤ 160 chars | |
widget.forms.<id>.fields[].options |
string[] | The choices of a select. · ≤ 50 items |
|
widget.forms.<id>.fields[].pattern |
string | A regular expression the answer must match. · ≤ 200 chars | |
widget.forms.<id>.fields[].autocomplete |
string | The HTML autocomplete hint, e.g. email, tel. · ≤ 64 chars |
|
widget.forms.<id>.submitLabel |
string | The button's text. · ≤ 60 chars |
A soft sound when a reply arrives.
| Field | Type | Default | Description |
|---|---|---|---|
widget.sound.enabled (required)
|
boolean | Play it. |
Set by security.captcha; you do not need to set it here.
| Field | Type | Default | Description |
|---|---|---|---|
widget.captcha.provider (required)
|
"turnstile" |
Cloudflare Turnstile. | |
widget.captcha.siteKey (required)
|
string | The Turnstile site key. · 1–200 chars |
Ignored: the widget always shows "Powered by HelpPuff". Accepted so older configs still load. Type: boolean | object.
UI string overrides. Keys are validated by the widget, not here. Type: map of string.