Skip to content
KnockDevelopers

SDK reference

Every @knock-ai/sdk call, the events you can listen to, queueing, server rendering, testing and the Content Security Policy sources.

Chat with a Developer

Everything below is on the knock object from @knock-ai/sdk, the same object useKnock() and inject(KNOCK) return.

Initialize

init
knock.init({ tagId: 'YOUR_PRODUCT_TAG_ID' });

Prop

Type

  • Call it once, as early as you can. A second call is ignored, with a console warning.
  • It doesn't check tagId. An empty or wrong ID requests a tag that doesn't exist.
  • Once your tag has loaded, knock.ready is true and knock.surface is 'product' or 'website' (undefined on older Knock tags).

Identify people

identify
knock.identify({ email: 'jane@acme.com', firstName: 'Jane', company: 'Acme', plan: 'enterprise' });

Prop

Type

  • Call it right after sign-in, or when the app loads with someone already signed in. Calling it again is safe.
  • modal.open() and scheduling.load() use the last identified email when you don't pass one.
  • Don't give your own fields names your website tag already sends, such as userId, sessionId or utm_source.

Track events

track
knock.track('trial_started', { plan: 'pro' });
knock.track('feature_used'); // properties are optional
  • Knock lowercases names and turns spaces, dashes and other symbols into _. Names are cut to 64 characters. A name that doesn't start with a letter (like 2fa_enabled) is dropped. Write them as object_action in the past tense: invoice_paid, demo_booked.
  • Property values take the same types as identify fields. An event over about 8 KB, 100 keys, 50 list items or 1 KB per string is dropped without an error.
  • On the product tag, only your track() and identify() calls are sent, plus clicks on Knock's widget if you show it.

Open the scheduling modal

modal
knock.modal.open(); // your default Knock modal
knock.modal.open({ magicLinkId: 'YOUR_MAGIC_LINK_ID', email: user.email });
knock.modal.close();

email pre-fills the modal so it starts finding times right away. It only applies with magicLinkId.

Load first, open later

scheduling.load() loads the modal without showing it, so times are ready when you open it, for example after your own form submits.

scheduling.load
const scheduling = knock.scheduling.load({
  magicLinkId: 'YOUR_MAGIC_LINK_ID',
  onStatusChange: ({ status, meeting }) => {
    if (status === 'booked') console.log('Booked for', meeting?.startDateTime);
  },
});

scheduling.open();

It returns { open(), close(), status }. You can call it before the SDK is ready: status is 'loading' until then, and open() waits.

statusMeaning
loadingStill loading.
email_submittedAn email was submitted.
slots_foundTimes are available. slots has their count and the first one.
no_slots_foundNo times to offer. reason is disqualified or no_slots.
bookedA meeting was booked. meeting has startDateTime and endDateTime.
pending_confirmationThe person requested a time that isn't confirmed yet. Not a booking.
errorSomething went wrong. message says what.
closedThe modal was closed.

Control the widget

If you show Knock's floating widget:

widget
knock.widget.hide(); // for example, where it covers your own UI
knock.widget.show();
knock.widget.open();
knock.widget.close();

Listen to events

on
const off = knock.on('modal:close', ({ magicLinkId }) => {
  console.log('Knock modal closed', magicLinkId);
});

off(); // stop listening
EventPayloadWhen
readynoneYour tag has loaded. Fires once, and a handler added after that still runs, once.
modal:open{ magicLinkId }A Knock modal opened. magicLinkId is null for your default modal.
modal:close{ magicLinkId }A Knock modal closed.
widget:opennoneKnock's widget opened.
widget:closenoneKnock's widget closed.
error{ message, cause? }Something went wrong. message says what.

on() works before or after the SDK is ready. In React, useKnockEvent(event, handler) listens while the component is mounted.

knock.wrapLink(url) returns url with the visitor's Knock identity added, so Knock can connect the chat to them. It adds it to any URL, so use it only for Knock chat links (start-chat.com, login.start-chat.com).

wrapLink
window.open(knock.wrapLink('https://start-chat.com/slack/acme/sales'));

For an <a> you render yourself, wrap the URL when the visitor opens the link, skip right-clicks, and put the plain URL back right after, so a copied link stays clean. In React, <KnockLink> does all of this for you.

Your own link
const url = 'https://start-chat.com/slack/acme/sales';
const link = document.querySelector<HTMLAnchorElement>('#chat-with-sales')!;

function openChat(event: MouseEvent) {
  if (event.button > 1) return; // not on a right-click
  link.href = knock.wrapLink(url); // the browser opens this one
  setTimeout(() => (link.href = url)); // plain again, so a later copy is clean
}
link.addEventListener('click', openChat); // click, Cmd/Ctrl-click, Enter
link.addEventListener('auxclick', openChat); // middle-click
  • Before your tag has loaded, on the server and on older Knock tags, it returns url unchanged. It never throws or waits.
  • Your website tag already adds the identity to Knock links on your marketing site. The product tag leaves your app's links alone.

Queueing

Calls made before your tag has loaded, even before init(), are queued and sent in order once it's ready. The queue holds 1,000 calls. Later calls are dropped, with a warning only when debug is true. on() doesn't count toward the limit, and wrapLink() is never queued.

Server rendering

On the server, every call does nothing, so you don't need typeof window guards. wrapLink() returns url and knock.ready stays false. Do check that your tag ID variable is set before you call init().

Testing

@knock-ai/sdk/testing gives you an in-memory mock that records every call instead of loading anything.

export.test.ts
import { createKnockMock, installKnockMock, uninstallKnockMock, type KnockMock } from '@knock-ai/sdk/testing';

let knock: KnockMock;

beforeEach(() => {
  knock = createKnockMock();
  installKnockMock(knock);
});

afterEach(() => uninstallKnockMock());

it('tracks the export', () => {
  exportReport(); // your code that calls knock.track('report_exported')
  expect(knock.eventsNamed('report_exported')).toHaveLength(1);
});

While it's installed, knock from @knock-ai/sdk and every framework binding use the mock. It also has calls, lastIdentify(), emit(event, payload) and clearCalls(). It works with Vitest and Jest.

Content Security Policy

If your app sends a Content-Security-Policy header, add these sources:

Content-Security-Policy
script-src 'self' https://storage.googleapis.com/knock-tag-build/ https://js.knock-ai.com https://cdn.jsdelivr.net;
connect-src 'self' https://ca.knock-ai.com https://js.knock-ai.com;
frame-src https://login.start-chat.com https://start-chat.com;
style-src 'self' 'unsafe-inline';
img-src 'self' https://js.knock-ai.com;
font-src 'self' https://js.knock-ai.com https://fonts.cdnfonts.com;
  • In img-src, also add the hosts of your widget's logo and agent photos.
  • https://cdn.jsdelivr.net is only needed for the CDN install. Its inline <script> also needs a nonce, a hash or 'unsafe-inline' in script-src.
  • If script-src allows scripts by nonce only, add 'strict-dynamic' so the scripts the SDK adds can load.
  • If style-src uses a nonce or a hash, browsers ignore 'unsafe-inline', so Knock's inline styles are blocked.

To check, open the browser console: a missing source shows as a CSP error that names the blocked URL. In the Network tab, filter by knock: your Knock tag script should load with 200. A 403, or (blocked:orb) in Chrome, means the tag ID is wrong.

Chat with a Developer

On this page