Crisp
"Crisp: live chat widget, chatbot scenarios, CRM contacts, campaigns, helpdesk, REST API, JavaScript SDK, webhooks"
Crisp is a business messaging platform combining live chat, CRM, and a helpdesk into one unified inbox. Integration should leverage Crisp as both a real-time communication channel and a lightweight CRM — syncing contacts, automating responses with chatbot scenarios, and triggering campaigns based on user behavior. The REST API provides full programmatic control over conversations, contacts, and helpdesk articles. Design integrations that push context into Crisp (user plan, recent activity, error logs) so agents have full visibility without switching tools. Use webhooks to react to conversation events in real time, and the JavaScript SDK to control widget behavior on the frontend. ## Key Points - Push session data (plan tier, account age, recent errors) into Crisp so agents see full context without leaving the inbox. - Use conversation segments to categorize tickets (billing, technical, onboarding) for reporting and routing. - Implement webhook retry handling — Crisp retries failed deliveries up to 5 times with exponential backoff. - Prefer the plugin authentication tier over basic token auth for production apps; it supports multi-website access. - Use `message:send` webhooks (outbound from user) for auto-responses and `message:received` (from operator) for delivery tracking. - Rate limits are 30 requests per minute for most endpoints — batch operations and cache contact lookups. - Store `people_id` alongside your user records to avoid repeated email lookups. - **Initializing the widget on every route change in SPAs** — The Crisp loader script should be loaded once. Calling `initCrisp()` repeatedly creates duplicate iframes and memory leaks. - **Using REST API for real-time messaging** — The REST API has latency and rate limits. Use the RTM (real-time messaging) WebSocket API or webhooks for live interactions. - **Overwriting session data on every page load** — Use `session:data` merges, not full replacements. Overwriting clears context that agents may have manually added. - **Ignoring conversation state transitions** — Always check current state before changing it. Resolving an already-resolved conversation or reopening a closed one creates confusing audit trails. - **Sending automated messages without throttling** — Crisp flags accounts that send high volumes of operator messages programmatically. Use campaigns or chatbot scenarios for bulk outreach instead.
skilldb get customer-support-services-skills/CrispFull skill: 305 linesCrisp Integration
Core Philosophy
Crisp is a business messaging platform combining live chat, CRM, and a helpdesk into one unified inbox. Integration should leverage Crisp as both a real-time communication channel and a lightweight CRM — syncing contacts, automating responses with chatbot scenarios, and triggering campaigns based on user behavior. The REST API provides full programmatic control over conversations, contacts, and helpdesk articles. Design integrations that push context into Crisp (user plan, recent activity, error logs) so agents have full visibility without switching tools. Use webhooks to react to conversation events in real time, and the JavaScript SDK to control widget behavior on the frontend.
Setup
Install the Node SDK and configure authentication:
import Crisp from "crisp-api";
const crispClient = new Crisp();
crispClient.authenticateTier(
"plugin",
process.env.CRISP_PLUGIN_ID!,
process.env.CRISP_PLUGIN_KEY!
);
const WEBSITE_ID = process.env.CRISP_WEBSITE_ID!;
// Verify connection
async function verifyConnection(): Promise<void> {
const website = await crispClient.website.getWebsite(WEBSITE_ID);
console.log(`Connected to: ${website.name} (${website.domain})`);
}
Embed the chat widget on your frontend:
declare global {
interface Window {
$crisp: unknown[];
CRISP_WEBSITE_ID: string;
}
}
function initCrisp(websiteId: string): void {
window.$crisp = [];
window.CRISP_WEBSITE_ID = websiteId;
const script = document.createElement("script");
script.src = "https://client.crisp.chat/l.js";
script.async = true;
document.head.appendChild(script);
}
function setCrispUser(email: string, nickname: string, data: Record<string, string>): void {
window.$crisp.push(["set", "user:email", [email]]);
window.$crisp.push(["set", "user:nickname", [nickname]]);
for (const [key, value] of Object.entries(data)) {
window.$crisp.push(["set", "session:data", [[key, value]]]);
}
}
function openCrispChat(): void {
window.$crisp.push(["do", "chat:open"]);
}
function sendCrispMessage(text: string): void {
window.$crisp.push(["do", "message:send", ["text", text]]);
}
function hideCrisp(): void {
window.$crisp.push(["do", "chat:hide"]);
}
function onCrispMessageReceived(callback: (message: { content: string }) => void): void {
window.$crisp.push(["on", "message:received", callback]);
}
Key Techniques
Managing Conversations
interface ConversationFilters {
status?: "pending" | "unresolved" | "resolved";
pageNumber?: number;
}
async function listConversations(filters: ConversationFilters = {}): Promise<unknown[]> {
const conversations = await crispClient.website.listConversations(
WEBSITE_ID,
filters.pageNumber ?? 1
);
if (filters.status) {
return conversations.filter((c: { state: string }) => c.state === filters.status);
}
return conversations;
}
async function createConversation(userEmail: string, initialMessage: string): Promise<string> {
const conversation = await crispClient.website.createNewConversation(WEBSITE_ID);
const sessionId = conversation.session_id;
await crispClient.website.updateConversationMetas(WEBSITE_ID, sessionId, {
email: userEmail,
segments: ["api-created"],
});
await crispClient.website.sendMessageInConversation(WEBSITE_ID, sessionId, {
type: "text",
from: "operator",
origin: "chat",
content: initialMessage,
});
return sessionId;
}
async function resolveConversation(sessionId: string): Promise<void> {
await crispClient.website.changeConversationState(WEBSITE_ID, sessionId, "resolved");
}
async function addNoteToConversation(sessionId: string, note: string): Promise<void> {
await crispClient.website.sendMessageInConversation(WEBSITE_ID, sessionId, {
type: "note",
from: "operator",
origin: "chat",
content: note,
});
}
CRM Contact Management
interface CrispContact {
email: string;
nickname?: string;
avatar?: string;
company?: { name: string; url?: string };
segments?: string[];
data?: Record<string, string>;
}
async function upsertContact(contact: CrispContact): Promise<string> {
const people = await crispClient.website.findPeopleWithEmail(WEBSITE_ID, contact.email);
if (people && people.length > 0) {
const personId = people[0].people_id;
await crispClient.website.updatePeopleProfile(WEBSITE_ID, personId, {
email: contact.email,
person: { nickname: contact.nickname },
company: contact.company,
});
if (contact.segments) {
await crispClient.website.updatePeopleData(WEBSITE_ID, personId, {
segments: contact.segments,
});
}
return personId;
}
const created = await crispClient.website.addNewPeopleProfile(WEBSITE_ID, {
email: contact.email,
person: { nickname: contact.nickname },
company: contact.company,
});
return created.people_id;
}
async function getContactConversations(email: string): Promise<unknown[]> {
const people = await crispClient.website.findPeopleWithEmail(WEBSITE_ID, email);
if (!people || people.length === 0) return [];
return crispClient.website.listPeopleConversations(WEBSITE_ID, people[0].people_id, 1);
}
async function addContactEvent(email: string, eventText: string, eventData?: Record<string, unknown>): Promise<void> {
const people = await crispClient.website.findPeopleWithEmail(WEBSITE_ID, email);
if (!people || people.length === 0) return;
await crispClient.website.addPeopleEvent(WEBSITE_ID, people[0].people_id, {
text: eventText,
data: eventData ?? {},
color: "blue",
});
}
Webhook Handler
import type { Request, Response } from "express";
import crypto from "node:crypto";
function verifyCrispSignature(req: Request, secret: string): boolean {
const timestamp = req.headers["x-crisp-request-timestamp"] as string;
const signature = req.headers["x-crisp-signature"] as string;
if (!timestamp || !signature) return false;
const body = JSON.stringify(req.body);
const payload = `${timestamp}${body}`;
const expected = crypto.createHmac("sha256", secret).update(payload).digest("hex");
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
interface CrispWebhookPayload {
event: string;
data: {
website_id: string;
session_id?: string;
content?: string;
from?: string;
user?: { email?: string; nickname?: string };
};
timestamp: number;
}
function handleCrispWebhook(req: Request, res: Response): void {
if (!verifyCrispSignature(req, process.env.CRISP_WEBHOOK_SECRET!)) {
res.status(403).json({ error: "Invalid signature" });
return;
}
const payload = req.body as CrispWebhookPayload;
switch (payload.event) {
case "message:send":
if (payload.data.from === "user") {
console.log(`User message in ${payload.data.session_id}: ${payload.data.content}`);
}
break;
case "message:received":
console.log(`Operator replied in ${payload.data.session_id}`);
break;
case "session:set_state":
console.log(`Conversation ${payload.data.session_id} state changed`);
break;
case "session:set_data":
console.log(`Session data updated for ${payload.data.session_id}`);
break;
default:
console.log(`Unhandled event: ${payload.event}`);
}
res.status(200).json({ ok: true });
}
Helpdesk Articles
async function createHelpdeskArticle(
title: string,
content: string,
category: string,
locale: string = "en"
): Promise<string> {
const article = await crispClient.website.createHelpdeskArticle(WEBSITE_ID, {
title,
content,
category,
status: "draft",
order: 1,
locale,
});
return article.article_id;
}
async function publishArticle(articleId: string): Promise<void> {
await crispClient.website.updateHelpdeskArticle(WEBSITE_ID, articleId, {
status: "published",
});
}
async function searchHelpdesk(query: string): Promise<Array<{ id: string; title: string }>> {
const results = await crispClient.website.searchHelpdeskArticles(WEBSITE_ID, {
query,
locale: "en",
});
return results.map((a: { article_id: string; title: string }) => ({
id: a.article_id,
title: a.title,
}));
}
Best Practices
- Push session data (plan tier, account age, recent errors) into Crisp so agents see full context without leaving the inbox.
- Use conversation segments to categorize tickets (billing, technical, onboarding) for reporting and routing.
- Implement webhook retry handling — Crisp retries failed deliveries up to 5 times with exponential backoff.
- Prefer the plugin authentication tier over basic token auth for production apps; it supports multi-website access.
- Use
message:sendwebhooks (outbound from user) for auto-responses andmessage:received(from operator) for delivery tracking. - Rate limits are 30 requests per minute for most endpoints — batch operations and cache contact lookups.
- Store
people_idalongside your user records to avoid repeated email lookups.
Anti-Patterns
- Initializing the widget on every route change in SPAs — The Crisp loader script should be loaded once. Calling
initCrisp()repeatedly creates duplicate iframes and memory leaks. - Using REST API for real-time messaging — The REST API has latency and rate limits. Use the RTM (real-time messaging) WebSocket API or webhooks for live interactions.
- Overwriting session data on every page load — Use
session:datamerges, not full replacements. Overwriting clears context that agents may have manually added. - Ignoring conversation state transitions — Always check current state before changing it. Resolving an already-resolved conversation or reopening a closed one creates confusing audit trails.
- Sending automated messages without throttling — Crisp flags accounts that send high volumes of operator messages programmatically. Use campaigns or chatbot scenarios for bulk outreach instead.
- Hardcoding website IDs in client-side code — Website IDs are not secret, but embedding them in multiple places creates maintenance burden. Use environment variables consistently.
Install this skill directly: skilldb add customer-support-services-skills
Related Skills
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"
Tawk.to
"Tawk.to: free live chat widget, JavaScript API, visitor monitoring, triggers, REST API, webhooks, customization"