Skip to main content

Widget Installation

Audience: Sales · Support · QA · Engineering · Management · Where in app: Settings → Widget → Install tab · Plan availability: All plans

The Knock Knock widget is the customer-facing chat/call launcher that visitors see on a company's website. It is delivered as one script tag the customer pastes into their site. Once that script loads, it injects an iframe, draws the launcher (bubble, bar, smart card, etc.), opens chat and call windows, records the session, and watches every form on the page for lead capture. There is nothing to download, no plugin to maintain, and no redeploy needed when settings change — the widget re-fetches its company config on every visitor session.

What it does

On any page where the snippet is present, the widget:

  • Draws the launcher in the configured style and position.
  • Loads the company's configuration (working hours, channels, page rules, AI config, colors, copy).
  • Decides whether to show itself at all (mobile setting, page rules, visibility).
  • Records the visitor's session (rrweb) and streams it to the sessions service.
  • Watches submit events on every form on the page and feeds captured email/phone/name into the identity graph.
  • Evaluates AI/call auto-trigger eligibility for the current URL.

How it works

The script tag

The Install tab generates a snippet that sets the company id and then injects the widget loader. The canonical form produced by the dashboard is:

<script>
window.company_id = '<COMPANY_ID>'
var newScript = document.createElement('script')
newScript.src = 'https://app.knockknockapp.ai/widget/widget.js'
document.getElementsByTagName('HEAD')[0].appendChild(newScript)
</script>

A simpler data-company attribute form is also valid:

<script
src="https://app.knockknockapp.ai/widget/widget.js"
data-company="<COMPANY_ID>"
async
></script>

White-label tenants use the agency's custom domain in place of app.knockknockapp.ai. See White-label.

Place the snippet just before </body> so it does not block page render. The widget lazily loads its CSS and downstream dependencies (rrweb for recording, Twilio, and the AI voice SDKs — Ultravox / ElevenLabs) only when they are actually needed.

Companion iframe script

For pages that themselves embed a third-party iframe the customer controls (e.g. a checkout iframe), the Install tab also provides a tiny companion script:

<script src="https://app.knockknockapp.ai/widget/widget-iframe.js" defer></script>

Dropped into the iframe document, it postMessages knockknock-start-recording so session recording continues seamlessly across the iframe boundary.

Boot sequence (what happens on load)

  1. Reads or creates kk_user_session_id in localStorage (a string like user_<timestamp>_<random>). This is the cross-visit join key. A ?session_id= URL parameter, if present, overrides and is persisted; an Agent K landing (/k/<id> path or ?knock_id=) adopts that session id so the click continues the known lead.
  2. Detects identity hints: ?contact_id= (GoHighLevel) and the hubspotutk cookie (HubSpot) — either directly verifies the visitor. Saved pre-chat identity is re-asserted to POST /api/intake on every load.
  3. Fetches company config via GET /company/<COMPANY_ID> (working hours, channels, page rules, AI config, widget design, video URL).
  4. Consent gate (Consent Gating) — when consent_gating.enabled, the loader shows the banner / reads the stored kk_consent choice; with block_until_decision it holds session creation and render until the visitor answers.
  5. Computes online/offline status from working hours + timezone.
  6. Checks page visibility: widget_page_rules.show_only_on_pages[] (allow-list) then hide_on_pages[] — excluded pages skip the iframe render but keep session tracking.
  7. Picks the launcher style (desktop vs mobile), then applies chat-only and saved-type guards.
  8. Starts rrweb recording and streams NDJSON chunks to the sessions service (skipped while Behaviour consent is withheld; rrweb is lazy-loaded).
  9. Mounts the form-intercept script.
  10. Evaluates AI auto-trigger eligibility against ai_auto_trigger.url_rules[].
  11. Applies page rules once the real parent-page URL arrives via postMessage (the widget runs in an iframe, so window.location inside it is not the host page).

Configuration & options

The Install tab itself has minimal options — it mostly displays the snippet and per-platform instructions. The behaviors below are driven by company config fetched at load.

ItemSourceNotes
Company idwindow.company_id / data-companyRequired. Identifies which company config to load.
Widget hostapp.knockknockapp.ai or agency domainWhite-label tenants serve from a custom domain.
Iframe companionwidget-iframe.jsOptional; only for cross-iframe recording.
Headless SDKkk-sdk.js (window.KnockKnockSDK)Chat/voice without the widget UI — embeds the iframe with ?headless=1, knockknock:sdk_* postMessage protocol, requestCall(). See widget service.
Consent gatingInstall tab panel → consent_gatingOpt-in cookie/consent banner or host-driven window.kkSetConsent(...). See Consent Gating.
Google Tag Manager installInstall tab GTM panelThe dashboard can push the script into the customer's GTM container directly (/google/gtm/install-script) and remove it (/google/gtm/remove-script).

Per-platform install methods

PlatformMethod
Plain HTML / custom sitePaste the snippet before </body>
WordPressPlugins like "Insert Headers and Footers" → Footer Scripts
ShopifyOnline Store → Themes → Edit Code → theme.liquid, before </body>
WixSettings → Custom Code → "Body — end"
WebflowProject Settings → Custom Code → Footer Code
Google Tag ManagerCustom HTML tag firing on all pages (or use the in-app GTM connector)
SquarespaceSettings → Advanced → Code Injection → Footer

Verifying installation

In the dashboard, the Dashboard screen shows "Widget installed" once the first event arrives. In the database this corresponds to companies.widget_installed = true plus a companies.widget_installed_at timestamp.

Common reasons the widget does not appear:

  • Script tag missing, or behind an aggressive CDN/page cache.
  • A strict Content-Security-Policy that does not allow app.knockknockapp.ai.
  • mobile_disable: true and you are testing on a mobile device.
  • A page rule matching the current URL — hide_on_pages[], or a non-empty show_only_on_pages[] allow-list that doesn't include the page.
  • Consent gating with block_until_decision: the widget is held until the visitor answers the banner (or the host page calls kkSetConsent).
  • Working hours: the widget renders but in "offline" mode — it is installed; this is not a failure.

Behaviors & edge cases

  • localStorage unavailable (private mode, blocked storage): the widget degrades gracefully — getters/setters are wrapped in try/catch and fail silently, so the launcher still renders but cross-visit continuity may be lost.
  • iframe URL vs host URL: page rules and auto-triggers depend on the parent page URL delivered via postMessage, not the iframe's own location. If the host page blocks postMessage, URL-specific behavior won't apply.
  • Caching: because the config is fetched per session, setting changes are picked up on the next visitor load / hard refresh — no redeploy. But the widget.js loader file itself can be cached by the customer's CDN.
  • Uninstall: removing the script tag stops everything. Existing recordings, leads, and stats are retained. To wipe data, contact support.
  • Legacy vs modern bundle: two widget codebases exist (see Widget Types). Installation is identical from the customer's perspective.

Plan & limits

  • Installation and the widget itself are available on all plans.
  • White-label / custom-domain serving depends on plan/agency tier (verify against the agency plan matrix). See White-label.
  • The in-app GTM connector availability per plan is not confirmed here — verify.

Technical implementation

  • Loader / embed: widget/widget.js (modern bundle entry) and widget-iframe.js companion; served from the widget host. Legacy loader: backend/public/widget.js + widgetController.js.
  • Modern widget bundle: widget/src/ — Vue 3 + TypeScript. Entry widget/src/main.ts, root widget/src/App.vue, main component widget/src/components/Widget.vue.
  • Init / config fetch: widget/src/composables/useWidgetInit.ts and useWidgetStore.fetchCompany() in widget/src/stores/widget.tsGET /company/:id.
  • Cross-frame messaging: widget/src/services/postMessage.ts.
  • Install UI: frontend/src/views/settings/widget/tabs/InstallTab.vue (snippet generation, per-platform guides, GTM connector).
  • See the engineering service page: /services/widget.