Intercom
"Intercom: Messenger widget, conversations API, custom bots, product tours, help center, user/company data, events, webhooks, Node SDK"
Intercom is a customer communications platform that unifies messaging, support, and engagement into a single system. Integration should treat Intercom as the central hub for customer interactions — connecting the Messenger widget for real-time chat, the Conversations API for programmatic ticket management, and webhooks for event-driven workflows. Design integrations that enrich user and company data continuously, automate repetitive support tasks with custom bots, and leverage product tours and help center content to reduce inbound volume. Always authenticate via OAuth or access tokens, handle rate limits gracefully, and use webhook signatures to verify payloads. ## Key Points - Store `external_id` mapping between your database and Intercom contacts so you can sync bidirectionally without duplicates. - Use conversation tags and custom attributes to route tickets automatically via Intercom's assignment rules. - Batch event tracking calls where possible — Intercom rate-limits to 500 requests per minute per app. - Always verify webhook signatures before processing payloads to prevent spoofing. - Use `updated_at` filters when polling for changes instead of fetching all records. - Set `custom_launcher_selector` to control when the Messenger opens rather than showing it on every page. - Keep custom attribute names under 190 characters and values under 255 characters. - **Polling conversations in a loop** — Use webhooks for real-time updates instead of repeatedly calling the list endpoint. Polling wastes quota and introduces latency. - **Storing Intercom IDs as your primary key** — Intercom IDs can change during merges. Always use your own `external_id` as the canonical reference. - **Sending PII in event metadata** — Event metadata is visible to all workspace admins. Never include passwords, tokens, or sensitive personal data. - **Ignoring rate limit headers** — The API returns `X-RateLimit-Remaining` and `X-RateLimit-Limit`. Implement exponential backoff when approaching the limit rather than retrying blindly. - **Creating contacts without email or external_id** — Orphaned leads clutter the workspace and cannot be merged later. Always provide at least one identifier.
skilldb get customer-support-services-skills/IntercomFull skill: 262 linesIntercom Integration
Core Philosophy
Intercom is a customer communications platform that unifies messaging, support, and engagement into a single system. Integration should treat Intercom as the central hub for customer interactions — connecting the Messenger widget for real-time chat, the Conversations API for programmatic ticket management, and webhooks for event-driven workflows. Design integrations that enrich user and company data continuously, automate repetitive support tasks with custom bots, and leverage product tours and help center content to reduce inbound volume. Always authenticate via OAuth or access tokens, handle rate limits gracefully, and use webhook signatures to verify payloads.
Setup
Install the Node SDK and configure credentials:
import Intercom from "intercom-client";
const client = new Intercom.Client({
tokenAuth: { token: process.env.INTERCOM_ACCESS_TOKEN! },
});
// Verify connection
async function verifyConnection(): Promise<void> {
const admin = await client.admins.me();
console.log(`Connected as: ${admin.name} (${admin.email})`);
}
Embed the Messenger widget in your frontend:
// Initialize Intercom Messenger on the client side
declare global {
interface Window {
Intercom: (...args: unknown[]) => void;
intercomSettings: Record<string, unknown>;
}
}
function bootIntercom(user: { id: string; name: string; email: string; createdAt: number }): void {
window.intercomSettings = {
api_base: "https://api-eus.intercom.io",
app_id: process.env.NEXT_PUBLIC_INTERCOM_APP_ID!,
user_id: user.id,
name: user.name,
email: user.email,
created_at: user.createdAt,
custom_launcher_selector: "#open-intercom",
};
window.Intercom("boot", window.intercomSettings);
}
function updateIntercomUser(attributes: Record<string, unknown>): void {
window.Intercom("update", attributes);
}
function shutdownIntercom(): void {
window.Intercom("shutdown");
}
Key Techniques
Managing Contacts and Companies
interface ContactPayload {
role: "user" | "lead";
externalId: string;
email: string;
name: string;
customAttributes?: Record<string, unknown>;
}
async function upsertContact(payload: ContactPayload): Promise<string> {
try {
const contact = await client.contacts.createUser({
externalId: payload.externalId,
email: payload.email,
name: payload.name,
customAttributes: payload.customAttributes ?? {},
});
return contact.id;
} catch (err: unknown) {
if ((err as { statusCode?: number }).statusCode === 409) {
const existing = await client.contacts.search({
data: {
query: { field: "external_id", operator: "=", value: payload.externalId },
},
});
const found = existing.data[0];
if (found) {
await client.contacts.update({ id: found.id, name: payload.name, customAttributes: payload.customAttributes });
return found.id;
}
}
throw err;
}
}
async function attachContactToCompany(contactId: string, companyName: string, companyId: string): Promise<void> {
await client.companies.create({ companyId, name: companyName, plan: "business" });
await client.contacts.attachCompany({ id: contactId, companyId });
}
Conversations API
async function createConversation(fromContactId: string, body: string): Promise<string> {
const conversation = await client.conversations.create({
from: { type: "user", id: fromContactId },
body,
});
return conversation.conversationId;
}
async function replyToConversation(
conversationId: string,
adminId: string,
message: string
): Promise<void> {
await client.conversations.replyByIdAsAdmin({
id: conversationId,
adminId,
messageType: "comment",
body: message,
});
}
async function closeConversation(conversationId: string, adminId: string): Promise<void> {
await client.conversations.close(conversationId, { adminId, body: "Resolved. Closing ticket." });
}
async function searchConversations(query: string): Promise<void> {
const results = await client.conversations.search({
data: {
query: {
operator: "AND",
value: [
{ field: "open", operator: "=", value: true },
{ field: "statistics.last_contact_reply_at", operator: ">", value: Math.floor(Date.now() / 1000) - 86400 },
],
},
},
});
for (const convo of results.data) {
console.log(`#${convo.id} — ${convo.statistics?.lastContactReplyAt}`);
}
}
Tracking Events
async function trackEvent(contactId: string, eventName: string, metadata?: Record<string, unknown>): Promise<void> {
await client.events.create({
eventName,
userId: contactId,
createdAt: Math.floor(Date.now() / 1000),
metadata,
});
}
// Example: track a purchase event
await trackEvent("user_abc", "completed-purchase", {
order_id: "ORD-12345",
amount: 99.99,
currency: "USD",
plan: "pro",
});
Webhook Handler
import crypto from "node:crypto";
import type { Request, Response } from "express";
function verifyIntercomWebhook(req: Request, secret: string): boolean {
const signature = req.headers["x-hub-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, "utf8"), Buffer.from(`sha1=${digest}`, "utf8"));
}
function handleIntercomWebhook(req: Request, res: Response): void {
if (!verifyIntercomWebhook(req, process.env.INTERCOM_WEBHOOK_SECRET!)) {
res.status(401).json({ error: "Invalid signature" });
return;
}
const topic = req.body.topic as string;
const data = req.body.data?.item;
switch (topic) {
case "conversation.user.created":
console.log(`New conversation from user ${data?.user?.id}`);
break;
case "conversation.user.replied":
console.log(`User replied to conversation ${data?.id}`);
break;
case "conversation.admin.closed":
console.log(`Conversation ${data?.id} closed`);
break;
case "user.created":
console.log(`New user: ${data?.email}`);
break;
default:
console.log(`Unhandled topic: ${topic}`);
}
res.status(200).json({ received: true });
}
Help Center Articles
async function createHelpArticle(
title: string,
body: string,
collectionId: string,
authorId: number
): Promise<string> {
const article = await client.articles.create({
title,
body,
authorId,
parentId: collectionId,
parentType: "collection",
state: "draft",
});
return article.id;
}
async function searchArticles(query: string): Promise<Array<{ id: string; title: string }>> {
const results = await client.articles.search({ phrase: query, state: "published" });
return results.data.articles.map((a) => ({ id: a.id, title: a.title }));
}
Best Practices
- Store
external_idmapping between your database and Intercom contacts so you can sync bidirectionally without duplicates. - Use conversation tags and custom attributes to route tickets automatically via Intercom's assignment rules.
- Batch event tracking calls where possible — Intercom rate-limits to 500 requests per minute per app.
- Always verify webhook signatures before processing payloads to prevent spoofing.
- Use
updated_atfilters when polling for changes instead of fetching all records. - Set
custom_launcher_selectorto control when the Messenger opens rather than showing it on every page. - Keep custom attribute names under 190 characters and values under 255 characters.
Anti-Patterns
- Polling conversations in a loop — Use webhooks for real-time updates instead of repeatedly calling the list endpoint. Polling wastes quota and introduces latency.
- Storing Intercom IDs as your primary key — Intercom IDs can change during merges. Always use your own
external_idas the canonical reference. - Sending PII in event metadata — Event metadata is visible to all workspace admins. Never include passwords, tokens, or sensitive personal data.
- Ignoring rate limit headers — The API returns
X-RateLimit-RemainingandX-RateLimit-Limit. Implement exponential backoff when approaching the limit rather than retrying blindly. - Creating contacts without email or external_id — Orphaned leads clutter the workspace and cannot be merged later. Always provide at least one identifier.
- Hardcoding admin IDs — Admin IDs change when team members leave. Fetch admin lists dynamically or use assignment rules instead.
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"
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"