SDK reference
Every @knock-ai/sdk call, the events you can listen to, queueing, server rendering, testing and the Content Security Policy sources.
Everything below is on the knock object from @knock-ai/sdk, the same object useKnock() and inject(KNOCK) return.
Initialize
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.readyistrueandknock.surfaceis'product'or'website'(undefinedon older Knock tags).
Identify people
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()andscheduling.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,sessionIdorutm_source.
Track events
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 (like2fa_enabled) is dropped. Write them asobject_actionin 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()andidentify()calls are sent, plus clicks on Knock's widget if you show it.
Open the scheduling 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.
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.
status | Meaning |
|---|---|
loading | Still loading. |
email_submitted | An email was submitted. |
slots_found | Times are available. slots has their count and the first one. |
no_slots_found | No times to offer. reason is disqualified or no_slots. |
booked | A meeting was booked. meeting has startDateTime and endDateTime. |
pending_confirmation | The person requested a time that isn't confirmed yet. Not a booking. |
error | Something went wrong. message says what. |
closed | The modal was closed. |
Control the widget
If you show Knock's floating widget:
knock.widget.hide(); // for example, where it covers your own UI
knock.widget.show();
knock.widget.open();
knock.widget.close();Listen to events
const off = knock.on('modal:close', ({ magicLinkId }) => {
console.log('Knock modal closed', magicLinkId);
});
off(); // stop listening| Event | Payload | When |
|---|---|---|
ready | none | Your 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:open | none | Knock's widget opened. |
widget:close | none | Knock'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.
Keep identity on chat links
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).
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.
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
urlunchanged. 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.
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:
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.netis only needed for the CDN install. Its inline<script>also needs a nonce, a hash or'unsafe-inline'inscript-src.- If
script-srcallows scripts by nonce only, add'strict-dynamic'so the scripts the SDK adds can load. - If
style-srcuses 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.