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:
<PillarProviderproductKey="..."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
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 toolconst summary = context.messages.map(m => `${m.role === 'user' ? 'Customer' : 'Co-pilot'}: ${m.content}`).join('\n\n');// Open Intercom with the conversationwindow.Intercom('showNewMessage',`Escalated from Co-pilot:\n\n${summary}`);} else {window.Intercom('showNewMessage');}close();}return null;}
Vanilla JavaScript Example
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:
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
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
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 widgetwindow.zE('messenger', 'open');window.zE('messenger:set', 'conversationFields', [{id: 'description',value: `Escalated from Co-pilot:\n\n${summary}`,}]);}
Freshdesk
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
pillar.on('sidebar:click', ({ tabId }) => {if (tabId === 'support') {const context = pillar.getChatContext();// Navigate to your support page with contextconst 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:
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:
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:
import { PillarProvider } from '@pillar-ai/react';import { SupportTab } from './components/SupportTab';function App() {return (<PillarProviderproductKey="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:
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 historyopenLiveChat(chatContext);close();}function handleEmailSupport() {const chatContext = getChatContext();// Navigate to email form with contextwindow.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:
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:
function ContextualHelp({ errorCode }: { errorCode: string }) {const { open, sendMessage } = usePillar();async function handleGetHelp() {// Open the co-pilot and ask about the erroropen({ 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:
function SupportTab() {const { context, close } = usePillar();const { user, subscription } = context || {};// Check if user has support accessconst hasSupport = subscription?.status === 'active' &&['pro', 'enterprise'].includes(subscription?.plan);// Check if user is in trialconst 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"><buttononClick={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:
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 contextpillarConversationId: chatContext?.conversationId,conversationHistory: chatContext?.messages,// Include user contextuserId: context?.user?.id,currentPage: context?.page?.path,}),});return response.json();}
Best Practices
- Always include context — Support agents work faster with the co-pilot conversation history
- Include the conversation ID — Link tickets to Pillar for analytics and review
- Close the panel — Call
close()after opening your support tool to avoid confusion - Handle no-context gracefully — Users might click support before chatting with the co-pilot