Tawk.to
"Tawk.to: free live chat widget, JavaScript API, visitor monitoring, triggers, REST API, webhooks, customization"
Tawk.to is a free live chat platform focused on real-time visitor engagement. Integration should maximize the JavaScript API for frontend customization and visitor context, while using the REST API and webhooks for backend automation. Unlike paid platforms, Tawk.to provides unlimited agents and chat history at no cost, making it ideal for startups and small teams that need live chat without per-seat pricing. Design integrations that enrich visitor profiles with session data, use triggers for proactive engagement, and route chats through webhooks to external systems for CRM sync and analytics. The tradeoff is a simpler API surface compared to enterprise tools — plan accordingly. ## Key Points - Always use the `visitor.hash` property in production to prevent identity spoofing. Generate the HMAC server-side using your Tawk.to API key and the visitor's email. - Set custom attributes inside the `onLoad` callback to ensure the widget is ready before making API calls. - Use tags to categorize visitors by plan, source, or intent so agents can prioritize high-value conversations. - Store chat transcripts externally via webhooks for compliance and CRM integration since Tawk.to's built-in search is limited. - Use `hideWidget()` on pages where chat is not appropriate (checkout, legal documents) rather than removing the script entirely. - Monitor the `onStatusChange` callback to show fallback contact options when all agents go offline. - Set the pre-chat form to collect email before the conversation starts so you can follow up on dropped chats. - **Loading the widget script conditionally based on user state** — This causes the widget to flash in and out. Use `hideWidget()` and `showWidget()` to control visibility without reloading. - **Relying on Tawk.to as a primary CRM** — Tawk.to stores visitor data but has limited query and export capabilities. Sync contacts to a dedicated CRM via webhooks for segmentation and campaigns. - **Ignoring the offline state** — When no agents are online, the widget shows an offline form. If you do not monitor `ticket:create` webhooks, those offline messages are silently lost.
skilldb get customer-support-services-skills/Tawk.toFull skill: 346 linesTawk.to Integration
Core Philosophy
Tawk.to is a free live chat platform focused on real-time visitor engagement. Integration should maximize the JavaScript API for frontend customization and visitor context, while using the REST API and webhooks for backend automation. Unlike paid platforms, Tawk.to provides unlimited agents and chat history at no cost, making it ideal for startups and small teams that need live chat without per-seat pricing. Design integrations that enrich visitor profiles with session data, use triggers for proactive engagement, and route chats through webhooks to external systems for CRM sync and analytics. The tradeoff is a simpler API surface compared to enterprise tools — plan accordingly.
Setup
Embed the chat widget with the JavaScript API:
declare global {
interface Window {
Tawk_API: TawkAPI;
Tawk_LoadStart: Date;
}
}
interface TawkAPI {
onLoad: () => void;
onStatusChange: (status: string) => void;
onChatStarted: () => void;
onChatEnded: () => void;
onPrechatSubmit: (data: Record<string, string>) => void;
onOfflineSubmit: (data: Record<string, string>) => void;
visitor: { name: string; email: string; hash?: string };
setAttributes: (attrs: Record<string, string>, callback?: (error?: Error) => void) => void;
addEvent: (name: string, metadata: Record<string, string>, callback?: (error?: Error) => void) => void;
addTags: (tags: string[], callback?: (error?: Error) => void) => void;
maximize: () => void;
minimize: () => void;
toggle: () => void;
popup: () => void;
hideWidget: () => void;
showWidget: () => void;
endChat: () => void;
isChatMaximized: () => boolean;
isChatMinimized: () => boolean;
isChatHidden: () => boolean;
isChatOngoing: () => boolean;
isVisitorEngaged: () => boolean;
getWindowType: () => string;
getStatus: () => string;
}
function initTawk(propertyId: string, widgetId: string): void {
window.Tawk_API = window.Tawk_API || ({} as TawkAPI);
window.Tawk_LoadStart = new Date();
const script = document.createElement("script");
script.async = true;
script.src = `https://embed.tawk.to/${propertyId}/${widgetId}`;
script.charset = "utf-8";
script.setAttribute("crossorigin", "*");
document.head.appendChild(script);
}
function identifyVisitor(name: string, email: string, hash?: string): void {
window.Tawk_API.visitor = { name, email, hash };
}
Set up the REST API client for backend operations:
import axios, { AxiosInstance } from "axios";
function createTawkClient(apiKey: string): AxiosInstance {
return axios.create({
baseURL: "https://api.tawk.to/v1",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
timeout: 10000,
});
}
const tawkApi = createTawkClient(process.env.TAWK_API_KEY!);
const PROPERTY_ID = process.env.TAWK_PROPERTY_ID!;
Key Techniques
Visitor Context and Custom Attributes
function setVisitorContext(user: {
id: string;
plan: string;
accountAge: number;
lastLogin: string;
totalSpend: number;
}): void {
window.Tawk_API.onLoad = () => {
window.Tawk_API.setAttributes(
{
userId: user.id,
plan: user.plan,
accountAgeDays: String(user.accountAge),
lastLogin: user.lastLogin,
totalSpend: String(user.totalSpend),
},
(error) => {
if (error) console.error("Failed to set Tawk attributes:", error);
}
);
window.Tawk_API.addTags(
[user.plan, user.accountAge > 365 ? "veteran" : "newer-user"],
(error) => {
if (error) console.error("Failed to add tags:", error);
}
);
};
}
function trackVisitorEvent(eventName: string, metadata: Record<string, string>): void {
window.Tawk_API.addEvent(eventName, metadata, (error) => {
if (error) console.error(`Failed to track event ${eventName}:`, error);
});
}
// Usage: track when a user hits a paywall
trackVisitorEvent("paywall-hit", {
feature: "advanced-reports",
currentPlan: "free",
page: "/reports/custom",
});
Widget Behavior Control
function setupConditionalWidget(user: { role: string; plan: string }): void {
window.Tawk_API.onLoad = () => {
// Hide widget for admin users who use internal tools
if (user.role === "admin") {
window.Tawk_API.hideWidget();
return;
}
// Auto-open for free plan users on pricing page
if (user.plan === "free" && window.location.pathname === "/pricing") {
setTimeout(() => {
if (!window.Tawk_API.isChatOngoing()) {
window.Tawk_API.maximize();
}
}, 5000);
}
};
}
function setupChatEventHandlers(
onStart: () => void,
onEnd: (feedback?: Record<string, string>) => void
): void {
window.Tawk_API.onChatStarted = () => {
console.log("Chat started");
onStart();
};
window.Tawk_API.onChatEnded = () => {
console.log("Chat ended");
onEnd();
};
window.Tawk_API.onPrechatSubmit = (data) => {
console.log("Pre-chat form submitted:", data);
};
window.Tawk_API.onOfflineSubmit = (data) => {
console.log("Offline message submitted:", data);
};
window.Tawk_API.onStatusChange = (status) => {
console.log(`Agent status changed to: ${status}`);
if (status === "offline") {
// Show alternative contact method
document.getElementById("email-support-fallback")?.classList.remove("hidden");
}
};
}
REST API Chat Operations
interface ChatMessage {
id: string;
sender: { type: "visitor" | "agent"; name: string };
message: string;
timestamp: string;
}
async function listChats(
filters: { startDate?: string; endDate?: string; page?: number } = {}
): Promise<{ chats: unknown[]; total: number }> {
const params: Record<string, string> = {};
if (filters.startDate) params.startDate = filters.startDate;
if (filters.endDate) params.endDate = filters.endDate;
if (filters.page) params.page = String(filters.page);
const { data } = await tawkApi.get(`/property/${PROPERTY_ID}/chats`, { params });
return { chats: data.data, total: data.total };
}
async function getChatMessages(chatId: string): Promise<ChatMessage[]> {
const { data } = await tawkApi.get(`/property/${PROPERTY_ID}/chat/${chatId}/messages`);
return data.data.map((msg: Record<string, unknown>) => ({
id: msg.id,
sender: msg.sender,
message: msg.message,
timestamp: msg.time,
}));
}
async function getChatTranscript(chatId: string): Promise<string> {
const messages = await getChatMessages(chatId);
return messages
.map((m) => `[${m.timestamp}] ${m.sender.name} (${m.sender.type}): ${m.message}`)
.join("\n");
}
Webhook Handler
import type { Request, Response } from "express";
import crypto from "node:crypto";
function verifyTawkWebhook(req: Request, secret: string): boolean {
const signature = req.headers["x-tawk-signature"] as string;
if (!signature) return false;
const digest = crypto.createHmac("sha1", secret).update(JSON.stringify(req.body)).digest("hex");
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(digest));
}
interface TawkWebhookPayload {
event: string;
chatId: string;
time: string;
message?: { text: string; sender: { type: string } };
visitor?: { name: string; email: string; city: string; country: string };
property: { id: string; name: string };
}
function handleTawkWebhook(req: Request, res: Response): void {
if (!verifyTawkWebhook(req, process.env.TAWK_WEBHOOK_SECRET!)) {
res.status(401).json({ error: "Invalid signature" });
return;
}
const payload = req.body as TawkWebhookPayload;
switch (payload.event) {
case "chat:start":
console.log(`Chat started with ${payload.visitor?.name} from ${payload.visitor?.country}`);
// Create lead in CRM
break;
case "chat:end":
console.log(`Chat ${payload.chatId} ended`);
// Archive transcript, update CRM
break;
case "ticket:create":
console.log(`Offline ticket created by ${payload.visitor?.email}`);
// Create ticket in external system
break;
default:
console.log(`Unhandled event: ${payload.event}`);
}
res.status(200).json({ ok: true });
}
Visitor Monitoring and Proactive Engagement
async function getOnlineVisitors(): Promise<Array<{
name: string;
page: string;
city: string;
referrer: string;
}>> {
const { data } = await tawkApi.get(`/property/${PROPERTY_ID}/visitors`);
return data.data.map((v: Record<string, unknown>) => ({
name: v.name ?? "Anonymous",
page: v.currentPage,
city: v.city,
referrer: v.referrer,
}));
}
// Frontend: proactive trigger based on user behavior
function setupProactiveTriggers(): void {
let pageViewCount = 0;
const startTime = Date.now();
window.Tawk_API.onLoad = () => {
pageViewCount++;
// Trigger chat after visiting 3+ pages
if (pageViewCount >= 3 && !window.Tawk_API.isChatOngoing()) {
window.Tawk_API.maximize();
}
// Trigger chat after 60 seconds on page
setTimeout(() => {
if (!window.Tawk_API.isChatOngoing() && !window.Tawk_API.isChatHidden()) {
window.Tawk_API.maximize();
}
}, 60000);
};
}
// Secure visitor identity with HMAC
function generateVisitorHash(email: string, apiKey: string): string {
return crypto.createHmac("sha256", apiKey).update(email).digest("hex");
}
Best Practices
- Always use the
visitor.hashproperty in production to prevent identity spoofing. Generate the HMAC server-side using your Tawk.to API key and the visitor's email. - Set custom attributes inside the
onLoadcallback to ensure the widget is ready before making API calls. - Use tags to categorize visitors by plan, source, or intent so agents can prioritize high-value conversations.
- Store chat transcripts externally via webhooks for compliance and CRM integration since Tawk.to's built-in search is limited.
- Use
hideWidget()on pages where chat is not appropriate (checkout, legal documents) rather than removing the script entirely. - Monitor the
onStatusChangecallback to show fallback contact options when all agents go offline. - Set the pre-chat form to collect email before the conversation starts so you can follow up on dropped chats.
Anti-Patterns
- Loading the widget script conditionally based on user state — This causes the widget to flash in and out. Use
hideWidget()andshowWidget()to control visibility without reloading. - Calling API methods before
onLoadfires — The Tawk API is not available until the widget fully initializes. All calls tosetAttributes,addTags, andaddEventmust be inside or afteronLoad. - Relying on Tawk.to as a primary CRM — Tawk.to stores visitor data but has limited query and export capabilities. Sync contacts to a dedicated CRM via webhooks for segmentation and campaigns.
- Setting visitor identity client-side without HMAC — Without hash verification, any user can impersonate another by changing the name and email in JavaScript. Always generate and pass the hash from your server.
- Using multiple widget instances on the same page — Loading the Tawk script twice creates duplicate chat windows and race conditions. Ensure the init function runs exactly once per page lifecycle.
- Ignoring the offline state — When no agents are online, the widget shows an offline form. If you do not monitor
ticket:createwebhooks, those offline messages are silently lost.
Install this skill directly: skilldb add customer-support-services-skills
Related Skills
Crisp
"Crisp: live chat widget, chatbot scenarios, CRM contacts, campaigns, helpdesk, REST API, JavaScript SDK, webhooks"
Drift
"Drift: conversational marketing, live chat, chatbots, meeting scheduling, contact management, REST API, webhooks, JavaScript SDK"
Freshdesk
"Freshdesk: ticket management, contact CRUD, automations, canned responses, REST API v2, webhooks, satisfaction surveys"
Help Scout
"Help Scout: conversation management, mailbox API, customer profiles, Beacon widget, webhooks, Docs knowledge base, REST API v2"
Intercom
"Intercom: Messenger widget, conversations API, custom bots, product tours, help center, user/company data, events, webhooks, Node SDK"
LiveChat
"LiveChat: real-time chat, ticket system, agent management, chat archives, customer SDK, REST API v3, webhooks, rich messages"