Smileline
Lead capture

Website tracking

Install one snippet so Smileline finds your website's forms, captures their submissions and attributes every enquiry.

By the end of this page the Smileline script will be live on your website, the setup checklist will be green, and enquiries will arrive with their marketing attribution attached — without changing any of your existing forms.

What the script does

  • Finds your forms. It scans each page for enquiry forms and lists them under Settings → Website with their field names and labels, ready to confirm. See Website forms.
  • Captures submissions. Once a detected form is confirmed, every submission becomes a lead — your form keeps working exactly as before; Smileline listens alongside it.
  • Attributes enquiries. UTM parameters, ad click IDs, the landing page and the referrer travel with each submission, so attribution works without any extra wiring.
  • Swaps in tracking numbers. With call tracking set up, the script replaces your published phone number with the visitor's rotating tracking number — every phone-shaped number on the page, the specific numbers a pool lists, or elements you mark with class="sl-phone" — so calls get the same attribution as form submissions.
  • Counts page views. Each page view, and how long the page stayed on screen, feeds website analytics under Ads → Analytics. Only the page's path is recorded, never its query string or fragment, and a view carries the visitor ID only when your consent setting allows it.
  • Runs page tests. On an Astro site with the Astro integration, it marks which version of a page a visitor saw, for A/B page tests.
  • Records visits for heatmaps and replays. Unless you switch it off, it records how each page is used — what's on screen, scrolling and clicks — for heatmaps and session replays, under your consent setting and with everything typed or chosen in a form masked. See Heatmaps and session recordings.
  • Sends nothing else. Beyond those and the one number-assignment request call tracking needs, the only other thing it loads from outside Smileline is consent-gated: OpenAI's measurement pixel while ChatGPT Ads conversion feedback is live — see Privacy and consent.

Only owners and managers can see Settings → Website and manage tracking.

Install the snippet

Until the script is installed, the tracking page is the install flow: a status band at the top, then an Install card with three numbered steps.

Go to Settings → Website. If tracking isn't set up yet, the status card reads Tracking not set up, so click Install. (Later, the same card links to Tracking settings.)

Your website address. Save it and Smileline checks which platform the site runs on, so step 3 shows instructions for WordPress, Wix, Squarespace, Webflow, Shopify or a custom build. If it can't tell, you get generic steps and a Check again link.

Copy the install code. Three tabs, and you only need one of them. Snippet is the default and suits every website; npm SDK is for custom sites and single-page apps, and is covered under Developer SDK; Astro is for sites built with Astro, and is the only way to run A/B page tests — see Astro integration. The snippet looks like this, but copy yours from Smileline, because it carries your practice's site key and regional hostname:

<script async src="https://widget-eu.smileline.io/sl.js" data-site="pk_YOUR_SITE_KEY"></script>

That is an EU example; US practices receive a widget-us.smileline.io address. Keep the complete data-site value, including pk_. Configuration, form discovery and tracking-number requests use that same prefixed key. A missing prefix produces a 404 and leaves Snippet installed unchecked.

Copy for AI under any tab puts a ready-made brief on your clipboard for ChatGPT, Claude, Cursor or your developer: the exact tag (or SDK call, or Astro setup), where it goes, what the script does, your consent setting and the Content Security Policy entries a strict site needs. It stays available beside the tabs once the script is live.

Paste it into your platform. Add it to every page of your website, ideally in a site-wide header. If someone else looks after the site, use Email these steps to a developer: Smileline emails them the snippet with the platform-specific steps. One send per hour.

Once the script has loaded from your website, the numbered steps drop away and the Install card keeps the three tabs and Copy for AI, so you can still move an Astro site to the integration later.

The script updates itself (it is cached for about five minutes), so you install it once. If your platform requires pinned assets, download sl.js and serve it yourself under Advanced → Self-hosted script, and you then own updates.

Watch the setup checklist

Under the status band, four steps confirm the setup with real events from your website. The page refreshes itself while you install, and only the next outstanding step shows its explanation, so the list stays short. Once all four are green the list disappears and the band alone reports that tracking is live.

StepTurns green when
Snippet installedThe script has phoned home from your website in the last day.
Forms detectedThe script found at least one form to capture.
Test enquiry receivedAny form on your site was submitted, and the submission arrives even before mapping.
Mapping confirmedYou confirmed a detected form's field mapping so its submissions open journeys.

For the last two steps, submit one of your own forms as a test, then confirm its mapping. See Confirm a detected form.

The script is deliberately quiet. It makes exactly four kinds of request, plus one more when call tracking is on and one more while heatmaps and session recordings are on, and loads one third-party script only while ChatGPT Ads conversion feedback is live:

  1. Loading its configuration when a page opens.
  2. Reporting a page view when a page opens (or a single-page app moves to a new page), and a short follow-up when the visitor leaves or hides the page, with how long it was on screen. It sends the page's path (never its query string or fragment), the referring website's domain, campaign parameters and, only when your consent setting allows it (below), the visitor ID. Known crawlers are skipped. Smileline adds the visitor's country, city and device type from the request and does not store their IP address or browser user agent with the view.
  3. Reporting a newly found form — field names, labels and input types only. Nothing a visitor typed is ever included. A form Smileline already knows is reported again at most once per browser tab, and only once the visitor ID can be stored.
  4. Submitting a form's contents when the visitor presses submit — the only moment enquiry data leaves the browser.
  5. Asking for a tracking number when a call-tracking pool is live. It sends the visitor ID, the page address and the visitor's attribution (campaign parameters, referrer, landing page, the marketing cookies listed under Attribution, screen size and time zone) — nothing the visitor typed. It runs once per browser tab per 30-minute window, and only once the visitor ID can be stored: immediately under the default consent setting, after a grant under either waiting option, never before. Known crawlers are skipped.
  6. Loading OpenAI's measurement pixel (https://bzrcdn.openai.com/sdk/oaiq.min.js) once ChatGPT Ads conversion feedback is connected, enabled and provisioned. It sends OpenAI one page view per page so ChatGPT ads can be attributed. It follows the same consent gate as the tracking number — loaded only once the visitor ID can be stored, never for known crawlers — a later denial is passed to OpenAI, and it disappears automatically when feedback is disabled or the account is disconnected. A strict Content Security Policy must allow script-src https://bzrcdn.openai.com, connect-src https://bzr.openai.com https://bzrcdn.openai.com and img-src https://bzr.openai.com. The tracking page shows ChatGPT pixel: configured or not configured.
  7. Uploading a recording of the page for heatmaps and session replays, while Heatmaps and session recordings is on: every 5 seconds while the page is open, and a last time when the visitor leaves it. Only the page's path is kept; query strings and fragments are removed before upload. A second Smileline script, sl-insights.js, does the recording; it loads only for visitors whose browser stores the visitor ID under the analytics rule below, and what they type or choose in forms never leaves their browser. See Heatmaps and session recordings.

It also honours consent, according to Visitor id and consent under Advanced on the tracking page:

  • Store the visitor id immediately (the default, and what call-tracking services generally do): the visitor ID and attribution are stored as soon as the script loads. Your cookie banner decides whether the script loads at all.
  • Wait for a consent-mode / TCF grant or Always wait for consent: it reads Google Consent Mode and IAB TCF signals from your cookie banner automatically, and your own code can signal consent explicitly through the SDK. Until storage consent is granted, the visitor's attribution lives in memory only — nothing is written to cookies, local storage or session storage, and no tracking number is requested. A later grant upgrades it.
  • Under every setting, an explicit denial through the SDK wipes anything previously stored — the cookie, the attribution record, the cached tracking number and the form-discovery marks below — and tells OpenAI's pixel, when loaded, to stop.
  • Website analytics follows the same setting, with one difference: a page view always counts towards your anonymous totals, but carries the visitor ID only when analytics is allowed too. Under Store the visitor id immediately that is unless the visitor refuses analytics, on your cookie banner or through your own code; under either waiting option, only once analytics storage is granted (a grant for advertising alone isn't enough). A view without the ID counts on the Overview tab of Ads → Analytics but never shows on its Visitors tab, and is never counted in an A/B page test. If consent is given later on the page, the view is sent again with the ID.
  • Heatmaps and session recordings follow that analytics rule exactly: a page is recorded only while the visitor ID can be stored for analytics. If consent is given later on the page, recording starts then; a later refusal stops it at once.
  • A browser sending the Global Privacy Control signal (navigator.globalPrivacyControl, required by California and a dozen other US states) is treated as that same explicit denial, ahead of the consent setting — no site setting can opt out of a legally mandated opt-out. The script runs in memory only for that visitor: nothing is written to cookies or storage, no tracking number is requested (they see your published number), nothing is recorded for heatmaps or replays, and their form submissions still arrive with the page they were on but without a stored visitor id or first touch.
  • Each form submission carries the visitor's own consent choices for conversion feedback: a Reject on your cookie banner, an explicit SDK denial or a Global Privacy Control signal arrives as a refusal, and that enquiry is never shared with your ad platforms. Your banner's starting state, before the visitor chooses anything, is not sent as a refusal.
  • When storage is allowed, Smileline keeps a first-party visitor ID cookie (_sl_vid, 400 days, SameSite=Lax), an attribution record in local storage (_sl:v1, expiring after 180 days of inactivity), and, for the life of the browser tab only, the visitor's assigned tracking number (_sl:dni:v1:<site key>, 30 minutes) and marks for forms already reported (_sl:fd:…) in session storage.
  • A visit's first touch is recorded once and never replaced; the last touch is replaced only when a new session (after 30 minutes of inactivity) arrives with campaign parameters or an external referrer.

Visit-level analytics (each visitor's page views, and which version of a tested page they saw) are kept for 13 months in your practice's region; after 12 months each day is folded into daily totals that name no visitor, and those are kept. Query strings and fragments are never recorded. See Website analytics. Recordings for heatmaps and session replays are kept for 13 months in your practice's region, then deleted.

Heatmaps and session recordings

The script also records how visitors use each page of your website, for heatmaps and session replays, on the Heatmaps and Visitors tabs of Ads → Analytics. It is on by default. To stop it for the whole website, turn off Heatmaps and session recordings on the tracking page; only owners and managers can.

  • Who is recorded follows your Visitor id and consent setting, as above: only visitors whose browser stores the visitor ID for analytics. Browsers sending Global Privacy Control and known crawlers are never recorded.
  • Every form control is masked in the visitor's browser, before anything is sent: text boxes, text areas, drop-down lists, checkboxes, radio buttons and sliders, and any editable area (contenteditable) with everything inside it.
  • Mask more yourself. Add data-clarity-mask="true" to any element to mask it and everything inside it. data-clarity-unmask is ignored, so nothing on your website can switch masking off.
  • Only your own website is recorded. Smileline's hosted forms, online booking page and other Smileline addresses never are.
  • Page addresses. Only the page's path is kept; query strings and fragments are removed before upload.
  • Browser support. Recording needs the CompressionStream feature of current browsers. An older browser isn't recorded, and its page views still count.
  • Uploads happen every 5 seconds while a page is open, so a visit shorter than about 5 seconds may have no replay; its clicks and scrolling still count. Each website can upload up to 2 GB of recordings a day, far above real traffic. Past that, recording pauses until midnight UTC.
  • Retention. Recordings are kept for 13 months in your practice's region. Erasing a patient deletes the recordings made under their visitor ID.

The data-clarity-* attributes keep the names used by Microsoft Clarity, whose open-source (MIT) recording code Smileline builds on. Nothing is sent to Microsoft, and a Microsoft Clarity tag you run yourself keeps working separately. Mention session recording in your website's privacy notice.

If Smileline is briefly unavailable

The script keeps working from the last configuration it loaded, stored in the visitor's browser (_sl:cfg:<site key>, no expiry). It is used only while Smileline cannot answer, and cleared as soon as Smileline says tracking is switched off or the key no longer exists. A form submission the script cannot deliver — a dropped connection, a busy or unavailable server — is held in that visitor's browser storage (_sl:outbox) and sent again on their next page view on your site until Smileline accepts it. Each submission carries a stable event id, so delivering it twice never creates two leads.

Both entries hold the visitor's own submission on their own device and are written under every consent setting — they are not tracking, and nothing is read from them elsewhere. Mention them in your privacy notice alongside the cookies listed above.

Domain pinning

With domains pinned, detected forms only accept submissions from those hostnames — anything else parks for review instead of becoming a lead. Built forms are exempt: they submit from Smileline's own hosted-form origin and are protected by their bot check instead. Custom integrations are exempt too, because they post server-to-server.

On the tracking page, open Advanced and use Domain pinning to Pin up to ten hostnames. Pinning practice.com also covers www.practice.com and other subdomains. An empty list means pinning is off.

A submission from an unpinned domain still receives a success response — the sender learns nothing — and the parked submission appears in the review queue so a legitimate enquiry from a forgotten domain is never lost.

Form detection follows the same boundary: the script only registers forms found on pages belonging to your configured website address, its www twin and subdomains, or a pinned domain. Reports from any other host are ignored.

Pause tracking or rotate the key

Both live under Advanced on the tracking page.

  • The Tracking live switch pauses tracking everywhere: installed scripts stop loading their configuration and go inert. While tracking is paused the status band says so and offers Resume tracking.
  • Regenerate key mints a new site key. Snippets carrying the current key stop working immediately, so update the website straight afterwards.
  • Configuration changes reach installed scripts within about a minute.

Astro integration

If your website is built with Astro, install the Smileline Astro integration instead of the snippet. It adds the same tracking script to every page, serves the script and its requests from your own domain (under /_sl/), and runs A/B page tests. Install one of the three — snippet, SDK or integration — never two.

Open the Astro tab in step 02 of the install card (it stays on the card once the script is live). It prints your setup with your site key and region filled in:

npm install @smileline/astro
// astro.config.mjs
import { defineConfig } from "astro/config";
import smileline from "@smileline/astro";

export default defineConfig({
  integrations: [smileline({ siteKey: "pk_YOUR_SITE_KEY", region: "eu" })],
});

region is the Smileline region your practice's data lives in: eu for UK and EU practices, us for US practices. Copy it from the tab rather than typing it.

The server key

The integration also needs a server key, which lets it read the page tests you start and vouch for the visitors it forwards from your own domain.

On the Astro tab, click Create server key. Only owners and managers can.

Copy the key from the dialog. It's shown once and can't be recovered; if you lose it, click Rotate key for a new one.

Set it as SMILELINE_SERVER_KEY in the environment where your site runs — a secret in your hosting's settings, never in your code or anywhere a browser can read it — and deploy.

The tab then shows when the integration last checked in. Rotate key replaces the key; the previous one stops being accepted within two minutes, so set the new one straight away.

Render tested pages on demand

  • A/B page tests split visitors on your site's server, so the project needs a server adapter (for example Cloudflare, Netlify, Vercel or Node), and both pages of every test must render on demand: add export const prerender = false to each. A fully static site still gets the tracking script and analytics, and the build warns that page tests are off.
  • A page under test is sent as a private, uncached response, and the integration removes CDN caching headers from it. Don't add CDN or edge caching rules for tested pages, or every visitor would get the same version.
  • Leave the /_sl/ path free on your site: the integration's route lives there. If it is ever unavailable, the script talks to Smileline directly instead, so a strict Content Security Policy still needs the Smileline origins the Copy for AI brief lists.

Developer SDK

Building a custom site or single-page app? The npm package replaces the snippet rather than joining it, so install one or the other, never both. It carries the same tracking core (visitor identity, attribution, consent) and turns Snippet installed green on its own, but it leaves the two things the snippet does by touching your pages to you:

  • It does not find your forms, so nothing appears under Website forms by itself. Build each form's capture hook yourself and post to it with captureLead.
  • It does not swap in call tracking numbers.

Pick it from the npm SDK tab in step 02 of the install card, which prints the snippet below with your own site key and region already filled in:

npm install @smileline/browser
import { init, captureLead, consent } from "@smileline/browser";

init({ siteKey: "pk_YOUR_SITE_KEY" });

init defaults to the EU cell, so US practices pass their regional API base: init({ siteKey: "pk_…", apiBase: "https://api-us.smileline.io" }). The tracking page prints yours, so copying from there is always right.

  • init(options) — call once; later calls are ignored. Safe to import during server-side rendering (it becomes a no-op).
  • captureLead(token, fields) — submits an enquiry to a form's capture endpoint with the visitor's attribution attached. It never throws; it resolves to { ok, status }.
  • consent(state) — "granted", "denied", or per-flag, e.g. { adStorage: true }.
  • visitor() — the visitor ID, or null before init completes.

The snippet installs the same API as a global command queue, safe to call before the script loads:

sl("consent", "granted");
sl("captureLead", "YOUR_FORM_TOKEN", { email: "amelia@example.co.uk" });

On this page