Install the widget
One script tag, no dependencies, no build step. It is the same file for every project — what makes it yours is the project key in the tag.
The snippet
Paste it before the closing </body> tag of every page that should offer chat. async matters: the widget is never on the critical path, and nothing on your page should wait for it.
<script
async
src="https://api.helpwing.app/widget.js"
data-project="pk_your_project_key">
</script>Where the project key comes from
Open Chat widget in your workspace. The screen shows the exact snippet for your project, with the key filled in and the right script URL for the deployment you are on — copy it from there rather than from this page. Keys start with pk_ and are designed to be public: one names a project, and grants nothing beyond starting a conversation with it.
Single-page apps
A router that never reloads the document is exactly what the widget wants: the tag stays in the shell, the script boots once, and the conversation survives every route change. Nothing framework-specific is required.
<!-- index.html — a Vite, Angular or plain SPA shell -->
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
<script async src="https://api.helpwing.app/widget.js" data-project="pk_your_project_key"></script>
</body>Loading it from application code instead — so you can identify the signed-in user, or keep the key in your environment config — is a few lines more. The JavaScript, React and Vue & Nuxt guides have the version worth copying.
Allow your origins
The widget only loads its configuration on origins you list under Chat widget → Security. Add every host you serve from, including the one you develop on:
- https://example.com
- https://app.example.com
- http://localhost:5173
An empty list means any origin, which is the right setting while you are still wiring things up. A blocked origin is not an error your users will ever see — the widget simply does not appear.
Check that it loaded
Every page load reports itself, whether or not the origin was allowed, so the Chat widget screen can tell "never installed" from "installed and blocked". Load a page with the snippet on it, then use Check for a recent load. It reports the origin it saw, which is usually enough to spot a typo in the allow list.
Two things stop the launcher from rendering even when the script is on the page: the widget being switched off in settings, and the visitor being on a device the widget is configured to skip. Both are on the same screen.
The browser API
Once the script boots it puts a single global on the page. Everything it exposes:
- Support.identify(identity)
- Say who the visitor is. Pass null to forget them again — there is no separate reset.
- Support.open()
- Open the panel. Ignored until the widget has loaded its configuration.
- Support.close()
- Close the panel back to the launcher.
- Support.show()
- Show the launcher again after hide().
- Support.hide()
- Take the launcher off the page. The conversation underneath is kept.
Because the tag is async, window.Support may not exist yet when your code first wants it. Rather than waiting on onload, push calls onto window.Support.q — an array of [method, argument] pairs that the widget replays the moment it boots. The next guide wraps that up so you never think about it again.
NextJavaScript →Load the widget from application code, with a typed wrapper and no framework.