← Documentation to help get you started

DemoBuilds

Updated October 29, 2026

Demo builds

A demo build is a throwaway assistant built from a prospect's public website, so an agency can show a practice what the product would do for it before anything is installed. The agency starts one from the Agency Console or through the partner API; a few minutes later there is an unlisted test page with the prospect's homepage behind a live widget, plus a list of common questions the assistant could not answer — the gaps the agency offers to fill.

  • API contract and GoHighLevel recipe: Partner API
  • Design decisions and slice history: docs/plans/ProvisioningAPIPlan.md
  • Test tools: tools/partner-api/ (see Testing below)

Who can do what

Action Who
See the Demo builds list Any agency member
Start or delete a demo, edit the page wording Agency managers
Create, edit or revoke API keys Agency managers
Start, read or delete demos over the API Anyone holding an active key
See demo workspaces in the central console Central admins, with "Show demo workspaces" ticked

What a build does

A build is a crawl_jobs job of kind demo, run by cmd/renderer (the only process with a headless browser). The renderer scales to zero, so the first build after an idle period waits about a minute for it to boot; the console shows this as "Waiting for a build worker to start".

  1. Create (chatbot-server, synchronous): validate the URL (public http(s) only), check the limits, create a demo tenant (tenants.is_demo=1, plan pro, pre-chat form off, AI answers immediately) and a demo_builds row, mint the test-page token, enqueue.
  2. Configure: apply the industry playbook (therapy by default), then switch the crisis gate on with the region's lines — 911 and 988 for both US and CA.
  3. Crawl at most 15 pages. robots.txt is respected. This is the one crawl in the product that does not require a verified domain; the page cap is the mitigation.
  4. Ingest the pages (plus the site footer) as one knowledge document.
  5. Screenshot the homepage at 1280×800, stored with the tenant's uploads.
  6. Ask the fixed questions cold, with no tools: an empty grounded answer is "not answered". Therapy asks about insurance, openings, ages, telehealth and fees; other industries ask about hours, location, services, pricing and contact.
  7. Finish: ready with results, or error with a reason code.

Failures nobody can fix by retrying — the site blocks us, has no readable text, or will not render a screenshot — are final at once. Retrying would only crawl somebody else's site twice more. Infrastructure failures are retried by the queue, and every step is safe to repeat.

Reason Meaning
crawl_blocked robots.txt, 403s or a bot wall on every page
no_content Pages loaded but had no readable text
screenshot_failed The homepage would not render
ingest_failed The knowledge base could not be built
internal / timeout Our side; start a new build

The demo page

Same /test-page/{token} link as the ordinary test page; a workspace with a demo_builds row gets the demo layout instead (handlers/testpage_demo.go):

  • A "Preview" banner — the agency's wording or "Test page for {business}. Built from your public website. Not live."
  • The homepage screenshot in a browser frame, with the real widget on top. The widget claims the prospect's homepage URL, so page-aware answers match.
  • Up to three starter questions, taken from the ones it could answer, so the first click works. They send through ChatWidgetTest.ask() (widget 1.0.66+).
  • "What it couldn't answer yet" with the unanswered questions and, if the agency set one, a booking button.
  • Copy in EN/FR/NL/DE by the visitor's browser language (or ?lang=).

The page returns 404 while the build is running, after a failure, and from the moment it expires. It sends X-Robots-Tag: noindex, nofollow.

Agencies set their own wording under Demo builds → Page wording: banner, the text above the unanswered questions, button label and link. {business} is replaced per demo; a blank field uses the default.

Guardrails

  • No contact details. The pre-chat form is off, lead capture is off, the AI is not offered remember_contact_details, and the visitor transcript email is disabled (TenantHub.isDemo).
  • No alerts. Chats are Test chats (is_test), so SMS escalation and lead routing are already suppressed; a demo has no operators, devices or lead destinations, so push, missed-chat email and webhooks have no one to reach.
  • Crisis safety stays on. Every demo gets the crisis gate, whatever its industry. The message lists 911 and 988, says nobody is watching the chat, and promises no follow-up (safety.Unmonitored).
  • Not a customer. Demo tenants are not agency subaccounts: they never touch the site pool, consolidated billing, white-label or staff access, and the central console hides them by default.

Limits

  • 50 builds per agency per rolling 24 hours.
  • One build of the same site (registrable domain) per agency per 10 minutes.
  • 60 API requests per minute per key.
  • 15 pages per build.

Expiry and deletion

Demos live 14 days. The link 404s at expiry; within the hour, cmd/worker's sweep (DemoHandler.PurgeExpired) permanently deletes the tenant: knowledge base, every Qdrant collection, chats and uploaded files. The same sweep removes demo tenants whose build row has gone (what deleting an agency leaves behind). Deleting a demo from the console or with DELETE /v1/demos/{id} does the same immediately.

Partner API and callbacks

Summary only — see Partner API. Keys are created by agency managers, expire within 6 months, and carry a webhook signing secret. Builds started with a key and a callback URL are POSTed back by cmd/worker when they finish, signed X-ChatOps-Signature: t=…,v1=…, retried for about 10 hours. Each build in the console shows "callback pending / sent / failed".

Where things live

Piece Location
Tables demo_builds, partner_api_keys, tenants.is_demo, organizations.demo_* (migrations 154, 155)
Create, run, delete, expiry internal/handlers/demo.go
Demo page internal/handlers/testpage_demo.go, assets/testpage_demo.html, demo_page_copy.go
API + key management internal/handlers/partner_api.go, internal/partnerapi
Callback delivery internal/handlers/demo_callback.go (run by cmd/worker every 10 s)
Screenshot, page ingest internal/poller/demo.go
Console UI dashboard/src/pages/AgencyDemos.jsx, AgencyApiKeys.jsx

Testing

API, scripted

.\tools\partner-api\test-demos.ps1 -ApiKey cop_live_… -WebsiteUrl https://www.some-practice.com `
    -CallbackUrl https://webhook.site/<your-id> -Delete

Runs the refusals first (no key, bad key, http callback, private address, unknown region, unknown id), then a real build: idempotency on externalRef, polling to completion, the result, lookup by reference, the X-API-Key header, and — with -Delete — that deletion makes the link 404. Exits non-zero on any unexpected response. A build that ends failed with a reason is reported, not counted as a test failure: plenty of real sites block crawlers.

API, by hand

tools/partner-api/demos.http has every request, positive and negative, for the VS Code REST Client extension. Paste a key into @apiKey (and don't commit it).

Callback signature

Save the callback body byte-for-byte from webhook.site, then:

node tools/partner-api/verify-signature.js --secret=whsec_… \
     --header="t=…,v1=…" --body=callback.json --tolerance=0

--sign produces a valid header for a body, to test your own receiver.

Console and page

  1. Agency Console → Demo builds → New demo on a real practice site. Watch it go waiting → reading pages → ready.
  2. Open the test page. Check the banner, the screenshot, the starter questions (no form appears), the gaps panel and button, and the phone layout.
  3. In the chat: a crisis phrase ("I don't want to be alive anymore") must list 911 and 988 and say nobody is watching; "My name is Test, test@example.com" must not be saved (the visitor row stays without a name or email).
  4. A site that blocks crawlers must end crawl_blocked, with no link.

Expiry, without waiting 14 days

UPDATE demo_builds SET expires_at = NOW() - INTERVAL 1 MINUTE WHERE external_ref = '<ref>';

The link 404s at once. Restart cmd/worker (the sweep runs at start-up, then hourly) and check its log for demo expiry: deleted N demo(s), that the tenant row is gone, that no kb_<tenantId> collection remains (central console → Ops), and that <tenantId>/ is empty in the uploads bucket.

Troubleshooting

Symptom Likely cause
Stuck on "Waiting for a build worker" for 10+ min Renderer not starting; the job is marked abandoned at 10 min. Check renderctl in the worker log.
Every agency endpoint returns "db error" A migration added a column to organizations without a field on models.Organization (it is loaded with SELECT *). TestOrganizationStructCoversEveryColumn catches this.
"API keys are not available on this server" INTEGRATION_ENCRYPTION_KEY is not set on chatbot-server.
Callback status "failed" The receiver did not return 2xx within ~10 h of retries; the last error is in the badge's tooltip and demo_builds.callback_last_error.
Callbacks never sent INTEGRATION_ENCRYPTION_KEY missing on cmd/worker (it signs them), or the build was not made with a key.
Starter questions do nothing Widget older than 1.0.66 (no ChatWidgetTest.ask).