Overview
Bellhop rewrites your existing website — in place — to match what each visitor actually came for. When someone arrives from a Google Ads search for “social scheduling for agencies,” Bellhop swaps your generic hero for copy that speaks to agencies, grounded in your own knowledge base so every claim stays true. One site, rewritten per visitor — not a hundred landing pages to build and maintain.
Setting it up is four moving parts:
- The script — one line in your site’s
<head>that loads the Bellhop runtime. - Zones — the elements you let Bellhop personalize, tagged with a simple attribute.
- Intent — where Bellhop learns why a visitor came (your Google Ads account, or rules you define).
- Content — the variations Bellhop applies, written by the AI from your knowledge base or by you.
Before you begin
You’ll move fastest if you have these ready:
- A live website you can edit. Bellhop works on any site where you can add a script tag and custom attributes — Webflow, WordPress, Framer, Shopify, or hand-coded HTML.
- Editor access to that site. You’ll add one script tag and a few attributes, then publish.
- Your Bellhop site key (looks like
pub_8s1f3k9a). It’s on your site’s settings page in the dashboard. - Optional — a Google Ads account. This is where Bellhop reads visitor intent automatically. You can also define intent manually to start.
- Optional — a CRM (HubSpot). Lets Bellhop greet known accounts and sync results back.
Everything except the live website and site key is optional — Bellhop degrades gracefully and you can add sources later without touching your site again.
How Bellhop works (60-second version)
It helps to picture the round trip before you wire it up:
- A visitor lands on your page. The Bellhop script reads the page’s zones and the URL (including any Google Ads click parameters).
- It asks Bellhop’s edge: “Given this visitor’s intent, what should these zones say?” The answer comes back in well under 150 ms, from Cloudflare’s edge, with zero third-party calls.
- If a matching variation exists, the script applies it — swapping text or attributes on just the zones you tagged. Everything else on your page is untouched.
- The result is recorded so your experiments learn which variation performs best.
Because the runtime is ~3.6 KB and reads from the edge, visitors don’t see a flash or a slowdown. You stay in control of exactly which elements can change.
Create your workspace & add your site
Sign in to the dashboard at app.bellhop.marketing with the Google account
the team invited. Inside your workspace:
- Go to Sites → Add site.
- Give it a name (e.g. Marketing site) and enter your primary domain (e.g.
www.example.com). - Save. The dashboard generates your site key — a public identifier like
pub_8s1f3k9a.
Install the Bellhop script
Add this single tag to the <head> of every page you want to personalize.
Replace the key with your own from Step 1.
<script
src="https://cdn.bellhop.marketing/runtime.js"
data-bellhop-key="pub_YOUR_SITE_KEY"
data-bellhop-api="https://api.bellhop.marketing"
defer
></script> Where to paste it, by platform
- Webflow — Project Settings → Custom Code → Head Code, then republish.
- WordPress — your theme’s header, or a “header scripts” plugin (e.g. WPCode), in the
<head>slot. - Framer — Site Settings → General → Custom Code → Start of <head> tag.
- Shopify —
theme.liquid, just before</head>. - Hand-coded HTML — paste directly into your
<head>. - Google Tag Manager — a Custom HTML tag firing on All Pages, set to fire as early as possible.
Script attributes
| Attribute | Required | What it does |
|---|---|---|
src | Yes | Loads the Bellhop runtime bundle. |
data-bellhop-key | Yes | Your site key from the dashboard. |
data-bellhop-api | Yes | The Bellhop edge endpoint. Use the value shown on your site’s settings page. |
data-bellhop-debug | No | Add it (no value) to log Bellhop’s decisions to the browser console. Leave it off in production. |
defer | Recommended | Lets the page render first; Bellhop applies as soon as the DOM is ready. |
Publish your site after adding the tag. In the next step you’ll tell Bellhop which elements it’s allowed to touch — until you do, the script loads but changes nothing.
Mark up your zones
A zone is any element you allow Bellhop to personalize. You opt elements
in explicitly by adding a data-bellhop-zone attribute with a name you choose.
Nothing without this attribute is ever changed.
<h1 data-bellhop-zone="hero.h1">Greet every visitor by name</h1>
<p data-bellhop-zone="hero.subhead">One site, rewritten per visitor.</p>
<a data-bellhop-zone="hero.cta" href="/signup">Get started</a> Naming your zones
- Use lowercase, dot-separated names that describe the spot:
hero.h1,hero.subhead,hero.cta,pricing.h1,proof.headline. - Keep names stable — they’re how your dashboard content maps to the page. Renaming a zone orphans its content.
- Start small. The hero headline, sub-headline, and primary CTA are usually the highest-impact three.
Adding the attribute in a visual builder
In Webflow, for example:
- Select the element in the Designer.
- Open Element Settings (the gear icon) and scroll to Custom Attributes.
- Click + and add name
data-bellhop-zone, valuehero.h1(or your chosen name). - Repeat for each element, then republish.
Most builders (Framer, WordPress block editor, Shopify) have an equivalent “custom attribute” or HTML-attributes field. In hand-coded HTML you just add the attribute inline.
Connect your intent sources
Intent is why a visitor came. Bellhop reads it primarily from your Google Ads
traffic — the campaign, ad group, and keyword behind each click — and clusters those into
named intents like agency-scheduling or
small-biz-social.
Link Google Ads (recommended)
- In the dashboard, go to Integrations → Google Ads and connect your account.
- Bellhop reads your campaign and keyword structure and proposes intent clusters. Review, rename, and approve them.
- Make sure your Ads final URLs or tracking template pass the standard click parameters (
gclid, and ideallycampaignid,adgroupid). Bellhop uses these to resolve intent on arrival.
Or define intent manually
No Google Ads, or want to test first? Create intents by hand under Intents → New intent and trigger them with a URL parameter:
https://www.example.com/?_intent=agency-scheduling This forces a specific intent for that visit — perfect for previewing personalization before your live ad traffic flows. You can also use UTM-based rules to map campaigns to intents.
Ground the AI in your knowledge
Bellhop’s copy isn’t made up — it’s retrieval-grounded. Before it writes a single variation, it reads a knowledge base built from your own material, and every claim it generates traces back to a source you provided.
- Go to Knowledge → Sources.
- Add your website (Bellhop can crawl it), plus any product docs, help center, case studies, or a brand/voice document.
- Let it index. From then on, generated copy draws only from these sources — so it stays accurate and on-brand.
Create your first personalization
A variation (internally, a “patch”) is the set of changes Bellhop applies for
a given intent — e.g. for agency-scheduling visitors, set
hero.h1 to “The scheduling platform agencies rely on.”
Let Bellhop draft them
- Open your site and go to Content → Generate.
- Pick an intent and the zones to personalize.
- Bellhop drafts grounded copy for each zone. Review, edit any wording, and approve.
Or write them yourself
Under Content → Variations → New, choose an intent and add an operation per zone:
| Operation | Use it for |
|---|---|
setText | Replace the visible text of a zone (headlines, sub-heads, button labels). |
setAttr | Change an attribute — e.g. a CTA’s href to an intent-specific signup link. |
Choose how changes apply
Each page (route) has an apply mode that controls how personalization reaches the visitor. Set it under Sites → Routes.
| Mode | Behavior | When to use |
|---|---|---|
silent | Applies instantly, invisibly. | Production — the default once you trust your content. |
prompt | Shows a small consent popover before applying. | When you want explicit visitor opt-in. |
manual | Computes the change but doesn’t apply it. | QA — inspect what would happen without affecting visitors. |
Start a new page in manual while you check your zones and content, then switch to silent when you’re happy.
Run experiments (optional)
Not sure which headline wins? Let Bellhop test them. Experiments use a multi-armed bandit (Thompson sampling), so traffic shifts toward the best-performing variation automatically — you don’t have to call a winner by hand.
- Go to Experiments → New experiment.
- Pick a zone (e.g.
hero.h1) and, optionally, an intent to scope it to. - Add two or more arms — your control plus one or more variants.
- Set it Active. Each visitor is assigned an arm, and the dashboard reports how each is performing.
Verify it’s working
Add ?bellhop_debug=1 to any page URL to watch Bellhop work in your browser console — no site changes required.
https://www.example.com/?_intent=agency-scheduling&bellhop_debug=1 Open DevTools → Console and you’ll see entries like:
[Bellhop] initialized {siteKey: "pub_xxx"}
[Bellhop] discovered 3 zones ["hero.h1","hero.subhead","hero.cta"]
[Bellhop] personalize responded in 87ms
[Bellhop] applied setText to zone hero.h1 You can also confirm the round trip in DevTools → Network:
runtime.jsloads with status 200.- A
personalizerequest fires on page load and returns your variation. - An
eventsrequest follows once a change is applied.
data-bellhop-zone attribute is missing or misspelled.
Connect analytics
Bellhop pushes events to your window.dataLayer on every personalized view, so you
can measure impact in Google Analytics 4 through your existing Google Tag Manager setup.
bellhop_personalization— fired when a variation is applied (includes the intent and zones).bellhop_experiment— fired when a visitor is assigned an experiment arm.
To surface these in GA4:
- In GTM, add a Custom Event trigger matching
bellhop_personalization(and one forbellhop_experiment). - Add a GA4 event tag firing on those triggers.
- Publish your container — the events now appear in GA4 reports alongside conversions.
Prefer to check quickly? Run window.dataLayer.filter(e => e.event?.startsWith('bellhop'))
in the console to see the events directly.
Go live
When your zones, content, and tests check out:
- Switch each page’s apply mode from
manualtosilent. - Make sure the script tag is live on your published site (re-publish if you changed anything).
- Remove
data-bellhop-debugfrom your script tag if you added it — debug logging is for development only. - Watch the dashboard. Personalized views, experiment results, and conversions start flowing in real time.
Optional integrations
Each of these adds capability and is safe to skip — Bellhop works without them and you can connect them any time under Integrations.
| Integration | What it unlocks |
|---|---|
| Google Ads | Automatic intent from real ad traffic (campaign, ad group, keyword). |
| HubSpot | Recognize known accounts and sync personalization results back to your CRM. |
| Google Analytics 4 | Measure personalization and experiment impact next to your other metrics. |
| Company identification | Greet visitors by company where it can be resolved from network signals. |
Troubleshooting
The page doesn’t change
- Add
?bellhop_debug=1and check the console. No[Bellhop] initializedline means the script isn’t loading — confirm the tag is in<head>and you re-published. - If it initializes but says 0 zones, your
data-bellhop-zoneattributes are missing — re-check Step 3. - If zones are found but no variation applies, no approved content exists for that intent + zone yet — create one in Step 6.
Console: “no site key found”
The data-bellhop-key attribute is empty or missing. Paste your pub_… key from the dashboard.
Console: “zone not found: hero.h1”
The named element doesn’t carry that attribute. Re-open the element’s settings and confirm data-bellhop-zone is present, lowercase, and spelled exactly as in your dashboard. Also check the element isn’t hidden by a visibility rule.
Personalization is slow or times out
Confirm data-bellhop-api matches the endpoint on your site’s settings page. If it’s correct and still slow, contact support — it shouldn’t exceed ~150 ms in normal operation.
GA4 events aren’t showing
- Run
window.dataLayer.filter(e => e.event?.startsWith('bellhop'))— if entries appear here but not in GA4, your GTM container is missing a trigger for thebellhop_*events (Step 10). - If
dataLayerhas no Bellhop entries, no variation was applied on that view — verify with debug mode first.
Reference
Debug URL parameters
| Parameter | Effect |
|---|---|
?bellhop_debug=1 | Verbose [Bellhop] logging in the browser console. |
?_intent=NAME | Force a specific intent for this visit (great for previewing). |
?gclid=… | Real Google Ads click ID — intent resolved from your linked account. |
Glossary
| Term | Meaning |
|---|---|
| Site key | Your site’s public identifier (pub_…), used in the script tag. |
| Zone | An element you’ve opted in to personalization with data-bellhop-zone. |
| Intent | The reason a visitor came, resolved from Google Ads or your rules. |
| Variation | The set of changes applied for an intent (a “patch”). |
| Apply mode | How a variation reaches the visitor: silent, prompt, or manual. |
| Knowledge base | Your sources the AI is grounded in, so copy stays accurate. |
Get help
Stuck on a step, or want us to walk through your first setup live? We’re hands-on with every early-access team.