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
submitevents 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)
- Reads or creates
kk_user_session_idinlocalStorage(a string likeuser_<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. - Detects identity hints:
?contact_id=(GoHighLevel) and thehubspotutkcookie (HubSpot) — either directly verifies the visitor. Saved pre-chat identity is re-asserted toPOST /api/intakeon every load. - Fetches company config via
GET /company/<COMPANY_ID>(working hours, channels, page rules, AI config, widget design, video URL). - Consent gate (Consent Gating) — when
consent_gating.enabled, the loader shows the banner / reads the storedkk_consentchoice; withblock_until_decisionit holds session creation and render until the visitor answers. - Computes online/offline status from working hours + timezone.
- Checks page visibility:
widget_page_rules.show_only_on_pages[](allow-list) thenhide_on_pages[]— excluded pages skip the iframe render but keep session tracking. - Picks the launcher style (desktop vs mobile), then applies chat-only and saved-type guards.
- Starts rrweb recording and streams NDJSON chunks to the sessions service (skipped while Behaviour consent is withheld; rrweb is lazy-loaded).
- Mounts the form-intercept script.
- Evaluates AI auto-trigger eligibility against
ai_auto_trigger.url_rules[]. - Applies page rules once the real parent-page URL arrives via
postMessage(the widget runs in an iframe, sowindow.locationinside 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.
| Item | Source | Notes |
|---|---|---|
| Company id | window.company_id / data-company | Required. Identifies which company config to load. |
| Widget host | app.knockknockapp.ai or agency domain | White-label tenants serve from a custom domain. |
| Iframe companion | widget-iframe.js | Optional; only for cross-iframe recording. |
| Headless SDK | kk-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 gating | Install tab panel → consent_gating | Opt-in cookie/consent banner or host-driven window.kkSetConsent(...). See Consent Gating. |
| Google Tag Manager install | Install tab GTM panel | The 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
| Platform | Method |
|---|---|
| Plain HTML / custom site | Paste the snippet before </body> |
| WordPress | Plugins like "Insert Headers and Footers" → Footer Scripts |
| Shopify | Online Store → Themes → Edit Code → theme.liquid, before </body> |
| Wix | Settings → Custom Code → "Body — end" |
| Webflow | Project Settings → Custom Code → Footer Code |
| Google Tag Manager | Custom HTML tag firing on all pages (or use the in-app GTM connector) |
| Squarespace | Settings → 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: trueand you are testing on a mobile device.- A page rule matching the current URL —
hide_on_pages[], or a non-emptyshow_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 callskkSetConsent). - 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 blockspostMessage, 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.jsloader 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) andwidget-iframe.jscompanion; served from the widget host. Legacy loader:backend/public/widget.js+widgetController.js. - Modern widget bundle:
widget/src/— Vue 3 + TypeScript. Entrywidget/src/main.ts, rootwidget/src/App.vue, main componentwidget/src/components/Widget.vue. - Init / config fetch:
widget/src/composables/useWidgetInit.tsanduseWidgetStore.fetchCompany()inwidget/src/stores/widget.ts→GET /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.