Docs
Install the widget and connect it to your application safely.
JavaScript SDK 0.4.0. Website updates and npm upgrades are separate.
Install on your website
Create a site in the dashboard, open its settings and copy its site ID. Anonymous visitors can chat without creating an account.
Add the script before </body>:
<script src="https://humanfirst.chat/widget.js" data-site-id="YOUR_SITE_ID"></script>After the script has loaded, window.HFChat queues commands until the widget is ready. Without a site ID, production can resolve a site from its configured domain; always pass a site ID on localhost.
For React or Next.js, install the npm SDK:
pnpm add @humanfirst-chat/js@^0.4.0"use client";
import { useEffect } from "react";
import HFChat from "@humanfirst-chat/js";
export function SupportChat() {
useEffect(() => {
HFChat.init({ siteId: "YOUR_SITE_ID" }).catch(() => {
// Keep the page usable. Offer a support fallback or retry explicitly.
});
}, []);
return <button onClick={() => HFChat.open({ focus: true })}>Chat</button>;
}The SDK loads HumanFirst's widget remotely. Updating the website delivers widget/server fixes; changing the installed SDK version delivers SDK methods, types and loading fixes. An existing ^0.3.0 dependency does not accept 0.4.0.
Verify a visitor identity
HFChat.identify({ email, name }) declares fields from the browser. It does not prove who the visitor is and never retrieves history by e-mail.
To resume a user's history across browsers, enable identity verification in the site settings. Keep the site identity secret on your server. Derive the subject from your authenticated user session, never from an arbitrary browser-supplied user ID.
// Server only: call with the user from your authenticated session.
import { SignJWT } from "jose";
export async function supportIdentity(user: {
id: string; email?: string; name?: string;
}) {
const secret = process.env.HUMANFIRST_IDENTITY_SECRET;
if (!secret) throw new Error("Missing HumanFirst identity secret");
return new SignJWT({ email: user.email, name: user.name })
.setProtectedHeader({ alg: "HS256" })
.setSubject(user.id)
.setIssuedAt()
.setExpirationTime("1h")
.sign(new TextEncoder().encode(secret));
}// Browser: obtain the token from your authenticated server endpoint.
HFChat.identify({ identityToken: tokenFromYourServer, plan: "pro" });The secret is used as the displayed UTF-8 string. Sign with HS256 and include sub, iat and exp. Subjects are at most 128 characters; tokens expire within seven days of issue. A short lifetime such as one hour is recommended.
Only signed email and name populate the verified profile. Missing claims clear the corresponding previous fields. Custom metadata remains declared. The site setting that refuses unsigned identities also protects name/e-mail enrichment through session and conversation creation; anonymous chat remains available.
Rotating the site secret allows the previous secret for seven days. It does not revoke chat sessions already issued. Never include the secret in a public environment variable, browser code or logs.
Handle logout and account changes
Reset the chat when your application logs out, before another account uses the same browser. This API is available from SDK 0.4.0.
// In your application's logout handler:
HFChat.reset();
// After the next account signs in, using its own server-issued token:
HFChat.identify({ identityToken: tokenForTheNewAccount });reset() starts a new anonymous chat session and drops the old browser credentials, conversation and pending state. It does not log out your application or delete conversations on the server.
The latest identity request wins when responses arrive out of order. The old history is hidden and sending is disabled while verification runs. If signed verification fails, the previous session is abandoned. A refused unsigned declaration leaves the current session in place and is not a substitute for logout.
identify() returns void. Neither awaiting widget initialization nor onReady() is a confirmation that the visitor's identity has been verified.
Widget commands
Import HFChat from the npm package, or use window.HFChat after loading the script. For manual script initialization, HumanFirstChat.initAsync(options) lets you await preparation or observe an error.
- init(options)
- Await widget preparation; catch rejection. A later explicit call can retry.
- show() / hide()
- Show or hide the widget, including its launcher.
- open(options?) / close() / toggle()
- Control the panel. open({ focus: true }) focuses the input.
- isOpen()
- Read the current panel state.
- identify(meta)
- Declare visitor fields or submit a server-signed identityToken. Returns void.
- reset()
- Forget this browser's chat credentials, history and pending state on logout. Does not delete server history.
- configure(settings)
- Update supported appearance settings such as widgetColor, widgetPosition and widgetSize.
- onReady(callback)
- Register a bridge-readiness callback. This does not confirm identity verification.
- onOpen(callback) / onClose(callback)
- Register panel lifecycle callbacks.
- ready
- Read whether the widget bridge is ready.
HFChat.configure({ widgetColor: "#2563eb", widgetPosition: "left" });
HFChat.onOpen(() => { /* panel opened */ });
HFChat.onClose(() => { /* panel closed */ });For a page-specific call to action, hide the launcher with hide(), then call open({ focus: true }). Restore it with show() when leaving that page. hideOnClose controls whether closing also hides the launcher.
Offline replies and resume links
When the operator is offline, a visitor who has written can leave an address for reply notifications. That address is stored separately from a signed identity. An operator reply that remains unread can trigger an e-mail after a short delay, if the site owner has enabled follow-ups.
The e-mail opens the site with a credential in the URL fragment. The loader removes it and exchanges it once for access to the indicated conversation. It does not grant access to the visitor's other conversations. A new visitor reply reopens the same thread if it was closed.
The link is valid for seven days and cannot be exchanged again after use. The successful browser keeps a session limited to that thread. Older links without the notification identifier are no longer accepted.
The unsubscribe link disables follow-up e-mails. Leaving the address again does not currently re-enable them. The chat remains usable. Do not forward resume links: before their first use, they grant access to the indicated conversation.
Loading, CSP and troubleshooting
- Check the site ID, domain, enabled setting and script URL. On localhost, use an explicit site ID.
- Catch
init()rejection and retry explicitly after recovery. The initial SDK script and bridge-readiness waits have timeouts; this is not one global timeout for all network operations. - For CSP, allow the API origin under
script-srcandconnect-srcfor the loader/configuration, and the actual standalone widget origin underframe-src. The iframe has its own policy for API and WebSocket connections. Self-hosted origins may differ: allowing only the API origin for frames is insufficient. - If signed identity is refused, check the server session, secret encoding, HS256, issue/expiry dates and the site whose secret signs the token. Never log the secret or token to debug it.
- If another account's chat appears, verify the logout/reset integration and use a fresh server token for the new account. An e-mail declaration cannot authenticate a user.
- Attachment uploads validate access and file contents. Their returned storage URLs are public; do not treat them as role-protected download links.
Use logs: "minimal" or "full" only when needed, and keep credentials and conversation contents out of shared diagnostics. Service availability is shown on the status page.