Which attributes does the script tag take?
Copy the tag from the Installation tab for support or the Sales agent tab for sales, or have a coding agent call the get_installation MCP tool. The REST API doesn’t return it, but you can build it yourself from your workspace ID, which GET /api/manage/workspace returns as id.
- src: https://app.kawlnow.com/widget.js. The script sends its requests to the origin it was loaded from, so load it from this address. A copy served from your own domain would send them to your domain, where they fail.
- data-helpline: your public workspace ID, which starts with hl_. Both agents use the same ID, and the attribute’s name comes from an earlier name for the product. Without it, the script does nothing.
- data-channel: support or sales. Leave it out for support. With any other value the script does nothing, so check the spelling.
- async: lets the page carry on loading while the script downloads. The dashboard’s snippets include it.
<script
src="https://app.kawlnow.com/widget.js"
data-helpline="YOUR_WORKSPACE_ID"
async>
</script><script
src="https://app.kawlnow.com/widget.js"
data-helpline="YOUR_WORKSPACE_ID"
data-channel="sales"
async
></script>Where should you paste it?
The dashboard suggests pasting it just before the closing </body> tag of each page where visitors should see the button. If the script runs before the page body exists, it waits for the DOMContentLoaded event and then adds the button, so a tag in the <head> works too.
Keep it a classic script. The widget finds its own tag through document.currentScript, which is null in a module script, so a tag with type="module" adds nothing. A tag manager or script loader that runs the code without the original tag has the same problem, so check that the button appears.
You can put the support and sales tags on the same page, and the second button sits 62px above the first. A repeated tag for the same workspace and channel is ignored, so a template that includes it twice still shows one button.
What does the script load?
The script has no dependencies and makes three kinds of request to the app’s origin, all visible in your browser’s network panel. They serve the widget and aren’t a public API, so don’t build on them.
- When the page loads, it reads the agent’s public display settings from https://app.kawlnow.com/api/widget/config, such as the button label and colour. That response never includes instructions, knowledge or tokens.
- When a visitor opens the panel, it loads the call page in an iframe from https://app.kawlnow.com/call, with microphone and autoplay access allowed.
- When the visitor starts a call, it asks https://app.kawlnow.com/api/widget/ticket for a one-use call ticket. That request comes from your page, so the browser sends your page’s origin, which is what the allowed-origins check reads.
How does the widget look and behave on the page?
The widget sits in a closed shadow root, so your page’s CSS can’t restyle it and its styles don’t leak into your page. This is what it adds:
- A button fixed to the bottom-right corner. Its label and colour come from the agent’s saved settings, and the default labels are Talk to us for support and Talk to sales for sales.
- A panel that opens above the button, 380px wide and up to 640px tall. On screens up to 440px wide it spans the screen, with a 12px margin each side.
- A very high z-index, so it sits above most of your page. If a cookie banner or chat button shares the corner, check that both can still be reached.
- If the call page takes longer than 12 seconds to load, the panel says “This is taking longer than usual. Try loading it again.” and shows a Try again button.
- The × button closes the panel and removes the call page, which ends a call in progress. Pressing the launch button again only hides the panel.
- Position, size, fonts and the “Powered by Kawl Now” footer are fixed, and there’s no JavaScript API to open the panel or listen for events.
What do Content Security Policy and Permissions-Policy need?
If your site sends a Content-Security-Policy header, allow https://app.kawlnow.com for scripts, connections and frames. The widget adds its styles in a <style> element inside its shadow root, so style-src must allow inline styles or the button appears unstyled, and a policy that requires Trusted Types stops the widget appearing at all.
If your site sends a Permissions-Policy header, allow the microphone for https://app.kawlnow.com as well as your own origin, and autoplay too if you restrict it. Without the microphone the call can’t start, and without autoplay the visitor may have to press Enable audio to hear the agent.
The call itself runs inside the iframe, under the call page’s own policy, so your headers don’t need to allow the voice service. Test on a staging page that sends your real headers before you launch.
Content-Security-Policy: script-src 'self' https://app.kawlnow.com; connect-src 'self' https://app.kawlnow.com; frame-src https://app.kawlnow.com; style-src 'self' 'unsafe-inline'
Permissions-Policy: microphone=(self "https://app.kawlnow.com"), autoplay=(self "https://app.kawlnow.com")