Search documentation

Search for docs or ask AI

Human Escalation

This guide walks through implementing human escalation in your application. For a conceptual overview of why escalation matters and integration patterns, see Human Escalation.

Adding a Support Tab

Configure sidebar tabs to include a support option:

examples/guides/human-escalation/sidebar-tabs-config.tsx
<PillarProvider
productKey="..."
publicKey="..."
config={{
sidebarTabs: [
{ id: 'assistant', label: 'Co-pilot', icon: 'sparkle' },
{ id: 'support', label: 'Talk to Human', icon: 'headset' },
],
}}
>

The assistant tab opens the co-pilot chat. Any other tab emits an event for your code to handle.

Handling Support Requests

When a user clicks a non-assistant tab, Pillar emits the sidebar:click event instead of opening the panel. Your code decides what happens next.

React Example

examples/guides/human-escalation/support-handler-react.tsx
import { usePillar } from '@pillar-ai/react';
import { useEffect } from 'react';
function SupportHandler() {
const { on, getChatContext, close } = usePillar();
useEffect(() => {
const unsubscribe = on('sidebar:click', ({ tabId }) => {
if (tabId === 'support') {
handleEscalation();
}
});
return unsubscribe;
}, []);
function handleEscalation() {
const context = getChatContext();
if (context) {
// Format the conversation for your support tool
const summary = context.messages
.map(m => `${m.role === 'user' ? 'Customer' : 'Co-pilot'}: ${m.content}`)
.join('\n\n');
// Open Intercom with the conversation
window.Intercom('showNewMessage',
`Escalated from Co-pilot:\n\n${summary}`
);
} else {
window.Intercom('showNewMessage');
}
close();
}
return null;
}

Vanilla JavaScript Example

examples/guides/human-escalation/support-handler-vanilla.js
const pillar = await Pillar.init({
productKey: 'your-product-key',
publicKey: 'pk_...',
sidebarTabs: [
{ id: 'assistant', label: 'Co-pilot', icon: 'sparkle' },
{ id: 'support', label: 'Talk to Human', icon: 'headset' },
],
});
pillar.on('sidebar:click', ({ tabId }) => {
if (tabId === 'support') {
const context = pillar.getChatContext();
if (context) {
const summary = context.messages
.map(m => `${m.role === 'user' ? 'Customer' : 'Co-pilot'}: ${m.content}`)
.join('\n\n');
window.Intercom('showNewMessage',
`Escalated from Co-pilot:\n\n${summary}`
);
} else {
window.Intercom('showNewMessage');
}
pillar.close();
}
});

Chat Context API

The getChatContext() method returns the full conversation:

examples/guides/human-escalation/chat-context-interface.tsx
interface ChatContext {
/** Server-assigned conversation ID */
conversationId: string | null;
/** All messages in the conversation */
messages: Array<{
role: 'user' | 'assistant';
content: string;
}>;
}

Use conversationId to link support tickets back to Pillar conversations in your dashboard.

Integration Examples

Intercom

examples/guides/human-escalation/intercom-integration.js
const context = pillar.getChatContext();
if (context) {
const summary = context.messages
.map(m => `${m.role === 'user' ? 'Customer' : 'Co-pilot'}: ${m.content}`)
.join('\n\n');
window.Intercom('showNewMessage',
`Escalated from Co-pilot:\n\n${summary}`
);
}

Zendesk

examples/guides/human-escalation/zendesk-integration.js
const context = pillar.getChatContext();
if (context && window.zE) {
const summary = context.messages
.map(m => `${m.role === 'user' ? 'Customer' : 'Co-pilot'}: ${m.content}`)
.join('\n\n');
// Pre-fill the Zendesk widget
window.zE('messenger', 'open');
window.zE('messenger:set', 'conversationFields', [{
id: 'description',
value: `Escalated from Co-pilot:\n\n${summary}`,
}]);
}

Freshdesk

examples/guides/human-escalation/freshdesk-integration.js
const context = pillar.getChatContext();
if (context && window.FreshworksWidget) {
const summary = context.messages
.map(m => `${m.role === 'user' ? 'Customer' : 'Co-pilot'}: ${m.content}`)
.join('\n\n');
window.FreshworksWidget('open');
window.FreshworksWidget('prefill', 'ticketForm', {
description: `Escalated from Co-pilot:\n\n${summary}`,
});
}

Custom Support Form

examples/guides/human-escalation/custom-support-form.js
pillar.on('sidebar:click', ({ tabId }) => {
if (tabId === 'support') {
const context = pillar.getChatContext();
// Navigate to your support page with context
const params = new URLSearchParams();
if (context?.conversationId) {
params.set('pillar_conversation', context.conversationId);
}
window.location.href = `/support?${params.toString()}`;
}
});

Multiple Support Options

Add multiple tabs for different support channels:

examples/guides/human-escalation/multiple-tabs-config.tsx
config={{
sidebarTabs: [
{ id: 'assistant', label: 'Co-pilot', icon: 'sparkle' },
{ id: 'chat', label: 'Live Chat', icon: 'chat' },
{ id: 'email', label: 'Email Us', icon: 'mail' },
],
}}

Handle each differently:

examples/guides/human-escalation/multiple-tabs-handler.js
pillar.on('sidebar:click', ({ tabId }) => {
const context = pillar.getChatContext();
switch (tabId) {
case 'chat':
window.Intercom('showNewMessage');
break;
case 'email':
window.location.href = `/contact?context=${context?.conversationId || ''}`;
break;
}
pillar.close();
});

Building a Custom Support UI

Instead of using third-party tools, you can build your own support experience. This gives you full control over what options users see based on their plan, account status, or any other criteria.

Rendering Custom Content in the Sidebar

Use the renderTab option to render your own React component when a tab is selected:

examples/guides/human-escalation/render-tab-example.tsx
import { PillarProvider } from '@pillar-ai/react';
import { SupportTab } from './components/SupportTab';
function App() {
return (
<PillarProvider
productKey="your-product-key"
publicKey="pk_..."
config={{
sidebarTabs: [
{ id: 'assistant', label: 'Co-pilot', icon: 'sparkle' },
{
id: 'support',
label: 'Support',
icon: 'headset',
renderTab: () => <SupportTab />
},
],
}}
>
{/* Your app */}
</PillarProvider>
);
}

Showing Options Based on User Status

Build a support tab that shows different options depending on whether the user has a paid plan:

examples/guides/human-escalation/support-tab-paid-plan.tsx
import { usePillar } from '@pillar-ai/react';
function SupportTab() {
const { context, getChatContext, close } = usePillar();
const hasPaidPlan = context?.user?.plan === 'pro' || context?.user?.plan === 'enterprise';
function handleLiveChat() {
const chatContext = getChatContext();
// Open your live chat with conversation history
openLiveChat(chatContext);
close();
}
function handleEmailSupport() {
const chatContext = getChatContext();
// Navigate to email form with context
window.location.href = `/support/email?conversation=${chatContext?.conversationId || ''}`;
}
function handleCommunity() {
window.open('https://community.yourapp.com', '_blank');
}
return (
<div className="support-options">
<h3>Get Help</h3>
{hasPaidPlan ? (
<>
<button onClick={handleLiveChat}>
<HeadsetIcon />
<span>Live Chat</span>
<span className="badge">Priority</span>
</button>
<button onClick={handleEmailSupport}>
<MailIcon />
<span>Email Support</span>
<span className="response-time">~2 hour response</span>
</button>
</>
) : (
<>
<button onClick={handleEmailSupport}>
<MailIcon />
<span>Email Support</span>
<span className="response-time">~24 hour response</span>
</button>
<div className="upgrade-prompt">
<p>Upgrade to Pro for priority live chat support</p>
<a href="/pricing">View Plans</a>
</div>
</>
)}
<button onClick={handleCommunity}>
<UsersIcon />
<span>Community Forum</span>
</button>
</div>
);
}

Opening Support Programmatically

You can open specific tabs from anywhere in your app:

examples/guides/human-escalation/help-button.tsx
import { usePillar } from '@pillar-ai/react';
function HelpButton() {
const { open } = usePillar();
return (
<button onClick={() => open({ tab: 'support' })}>
Contact Support
</button>
);
}

Open to the co-pilot first, then let users switch to support:

examples/guides/human-escalation/contextual-help.tsx
function ContextualHelp({ errorCode }: { errorCode: string }) {
const { open, sendMessage } = usePillar();
async function handleGetHelp() {
// Open the co-pilot and ask about the error
open({ tab: 'assistant' });
await sendMessage(`I'm seeing error ${errorCode}. What does this mean?`);
}
return (
<button onClick={handleGetHelp}>
Get help with this error
</button>
);
}

Gating Support Access

Control who can access support based on your business logic:

examples/guides/human-escalation/gated-support.tsx
function SupportTab() {
const { context, close } = usePillar();
const { user, subscription } = context || {};
// Check if user has support access
const hasSupport = subscription?.status === 'active' &&
['pro', 'enterprise'].includes(subscription?.plan);
// Check if user is in trial
const isInTrial = subscription?.status === 'trialing';
// Check support hours (example: 9am-5pm PST)
const now = new Date();
const pstHour = now.getUTCHours() - 8;
const isBusinessHours = pstHour >= 9 && pstHour < 17;
if (!hasSupport && !isInTrial) {
return (
<div className="upgrade-required">
<LockIcon />
<h3>Support requires a paid plan</h3>
<p>Upgrade to Pro to get access to live chat and priority email support.</p>
<a href="/pricing" onClick={() => close()}>
View Plans
</a>
</div>
);
}
return (
<div className="support-options">
<button
onClick={handleLiveChat}
disabled={!isBusinessHours}
>
<HeadsetIcon />
<span>Live Chat</span>
{!isBusinessHours && (
<span className="status">Available 9am-5pm PST</span>
)}
</button>
{/* ... other options */}
</div>
);
}

Tracking Support Requests

Include conversation context when creating support tickets:

examples/guides/human-escalation/create-support-ticket.tsx
async function createSupportTicket(issue: string) {
const { getChatContext, context } = usePillar();
const chatContext = getChatContext();
const response = await fetch('/api/support/tickets', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
issue,
// Include Pillar conversation for context
pillarConversationId: chatContext?.conversationId,
conversationHistory: chatContext?.messages,
// Include user context
userId: context?.user?.id,
currentPage: context?.page?.path,
}),
});
return response.json();
}

Best Practices

  1. Always include context — Support agents work faster with the co-pilot conversation history
  2. Include the conversation ID — Link tickets to Pillar for analytics and review
  3. Close the panel — Call close() after opening your support tool to avoid confusion
  4. Handle no-context gracefully — Users might click support before chatting with the co-pilot