@askly/widget
v2.4.0
Published
Embeddable AI support chat widget for React — drop-in customer support chatbot with voice, RAG (docs-grounded) answers, and human handoff. One line to install.
Maintainers
Readme
Askly — Embeddable AI Support Chat Widget for React
Askly is an embeddable AI support chat widget for React and plain HTML. Drop it into any site to give customers an AI-powered support chatbot with voice conversations, RAG answers grounded in your own docs, and one-click handoff to a human agent — installed with a single line.
🌐 Website & live demo → askly.co.in
Features
- 🤖 AI-powered answers — resolves customer questions automatically from your knowledge base.
- 📚 RAG (docs-grounded) — replies grounded in your own documentation, not hallucinated.
- 🎙️ Voice chat — customers can talk to the assistant, not just type.
- 🙋 Human handoff — one-click escalation to a live agent when needed.
- ⚛️ React + CDN — use as an npm module, or a single
<script>tag on any site (no React required). - 🎨 Fully themeable — colors, logo, position, and copy configured in the portal.
Installation
Script tag (any website)
One line, before </body>. The bundle is self-contained — no React or other dependencies on your page:
<script src="https://unpkg.com/@askly/widget@latest/dist/widget.js" data-app-id="YOUR_APP_ID" async></script>That's the whole install. Your branding — name, logo, theme, welcome message — loads automatically from your Askly portal settings.
NPM (React / bundlers)
npm install @askly/widgetimport Askly from '@askly/widget';
Askly.init({ appId: "YOUR_APP_ID" });The npm builds (index.esm.js / index.umd.js) treat React as a peer dependency, so your bundle isn't double-shipping it.
Quick Start
All you need is your appId — copy it from Widget → Install Code in your Askly tenant portal. Everything else (name, theme, logo, welcome message, voice, etc.) is configured there and loaded automatically; the init props below are optional overrides.
The widget talks to Askly's hosted backend automatically — there is nothing else to configure.
Configuration
The Askly.init() method accepts an object with the following configuration options:
| Option | Type | Required | Default | Description |
| :--- | :--- | :--- | :--- | :--- |
| appId | string | Yes | - | Your app ID, from the tenant portal's Widget → Install Code screen. Script tag: data-app-id. |
widgetIdandorgIdare still accepted as deprecated aliases forappId, so existing embeds keep working.
Configured in the portal
The widget's appearance and behaviour — display name, logo, theme color, welcome message, position, voice, sounds, timestamps, "powered by" text, and so on — are set on the Widget screen in your Askly portal and loaded automatically by appId. Logos are uploaded right there and apply instantly. You do not pass these in code. (They remain accepted as optional init() overrides for advanced cases, but the portal is the source of truth.)
Identifying an authenticated user
Anonymous visitors are tracked automatically — the widget mints a visitor_id per browser, so
someone who returns tomorrow is recognised as the same person and their history follows them.
You don't configure anything for that.
Call Askly.identify() once your user logs in. Everything they did anonymously on this browser
is merged into their profile, so your agents see one person with one history instead of a
stranger every session.
Askly.identify({
userId: "usr_8812",
email: "[email protected]",
name: "Priya Raman",
hash: "<HMAC — see below>",
timestamp: 1735689600,
attributes: { plan: "pro", signupDate: "2024-03-11" },
});| Option | Type | Description |
| :--- | :--- | :--- |
| userId | string | Required. Your application's user ID. |
| hash | string | HMAC-SHA256 of userId + timestamp, using your Widget Secret Key. |
| timestamp | number | Unix seconds the hash was generated for. Valid for 5 minutes. |
| email / name / phone | string | Optional profile fields shown to your agents. |
| attributes | object | Any extra traits you want on the contact record. |
Generate hash on your backend. Your Widget Secret Key must never reach browser JS — anyone
holding it could impersonate any of your users and read their conversations.
// Node.js, on your server
const crypto = require("crypto");
const timestamp = Math.floor(Date.now() / 1000);
const hash = crypto
.createHmac("sha256", process.env.ASKLY_WIDGET_SECRET)
.update(`${userId}${timestamp}`)
.digest("hex");Without a valid hash the traits are still used locally, but Askly will not link the accounts.
Logging out
Askly.reset(); // forget this visitor and clear local chat history
Askly.shutdown(); // the same, and remove the widget from the pageCall one of these on logout, particularly for shared computers — otherwise the next person to use the browser inherits the previous person's conversations.
Identifying at init time
If the user is already logged in when the page loads, you can pass the same values to init()
instead of calling identify() separately:
| Option | Type | Description |
| :--- | :--- | :--- |
| userId | string | Authenticated user ID. |
| timestamp | number | Signature timestamp. |
| signature | string | HMAC request signature (same value as hash above). |
Asking visitors for their email
Anonymous visitors can't be replied to once they close the tab — the answer just waits in a widget they may never reopen. So the widget can ask for an email, and Askly delivers the reply there instead when they've gone.
Configured on the Widget screen in your portal, not in code:
| Mode | Behaviour | | :--- | :--- | | When it matters (default) | A dismissible prompt after the assistant replies, and a required one only once a conversation is waiting on your team — the point where email is the only way to reach them. Barely affects how many people start a chat. | | Before every chat | Nobody can send a first message without an email. Captures the most addresses, at a cost of roughly 30% fewer conversations — including ones the assistant would have resolved on its own. | | Never | Visitors are never asked. |
Anyone whose address you already have — from identify(), a previous chat, or because they typed
it — is never asked again.
An address given this way is not verified: it proves the person wants replies there, not who they are. It is never used to match them to an existing contact, so nobody can read someone else's conversation history by typing their address. Only addresses given through this prompt are ever emailed; one merely spotted in the text of a message is not consent to write to them.
Restricting the widget to certain pages
Control where the widget appears using URL path prefixes (matched against window.location.pathname).
| Option | Type | Description |
| :--- | :--- | :--- |
| includePaths | string[] | Allowlist — the widget renders only on paths matching one of these prefixes. |
| excludePaths | string[] | Blocklist — the widget renders everywhere except paths matching one of these prefixes. |
// Show ONLY on support & docs pages
Askly.init({ appId: "YOUR_APP_ID", includePaths: ["/support", "/docs"] });
// Show everywhere EXCEPT admin & checkout
Askly.init({ appId: "YOUR_APP_ID", excludePaths: ["/admin", "/checkout"] });Via script tag, use comma-separated values: data-include-paths="/support,/docs" or data-exclude-paths="/admin,/checkout".
Notes:
includePathswins: if it's set,excludePathsis ignored.- Prefix match, not exact —
"/docs"also matches/docs/getting-started. No glob/regex. - Optimized: on non-matching pages the path check runs before anything mounts, so there's no DOM node, no React root, and no config network request — the SDK does essentially nothing.
- The decision is evaluated once at init; it's a UX targeting tool, not a security boundary (the script can still be loaded on any page).
Lifecycle Callbacks
Callbacks are functions, so they can only be attached in code (not from the portal):
Askly.init({
appId: "YOUR_APP_ID",
onMessageSent: (message) => console.log("Sent:", message),
onMessageReceived: (reply) => console.log("Received:", reply),
onChatOpened: () => console.log("Opened"),
onChatClosed: () => console.log("Closed"),
onEscalation: (data) => console.log("Escalated to agent:", data)
});Backend Integration
None required. The widget talks to Askly's hosted backend automatically — you only provide an appId.
Development
# Install dependencies
npm install
# Compile production bundles
npm run buildReleasing
npm version <patch|minor|major>
npm publish # prepublishOnly rebuilds dist/Publishing is not the last step. The tenant portal bundles this package
(AsklyWidget.tsx and the help centre both import("@askly/widget")), and its Docker build runs
npm ci — which installs whatever package-lock.json pins and ignores the ^ range entirely. So
a new version does not reach Askly's own site until the portal's lockfile is bumped:
cd ../tenant-portal && npm install @askly/widget@latest # updates package-lock.jsonSkipping this is how 2.2.0 shipped to npm while the portal kept serving 2.1.0 for a day — visitors were unrecognisable there, and every message showed as a separate person in the Inbox.
Why Askly?
Is this an AI customer support widget I can embed in React? Yes — Askly is an embeddable AI support chat widget that installs into any React app or plain HTML page with a single line, no backend to build.
How does the AI answer questions accurately? Askly uses RAG (retrieval-augmented generation) to ground every reply in your own documentation, so answers stay accurate and on-brand instead of hallucinated.
Can customers reach a human?
Yes — the widget supports one-click escalation to a live agent via the
onEscalation callback and built-in human handoff.
License
MIT
