Page SDK
The widget's window.showpilot: hear milestones on the page, tell the agent who the visitor is, hand it context, open a tour.
The embed script installs window.showpilot the moment it runs. It is small, additive, and does nothing until you call it, so an existing embed is unchanged.
<script>
// Optional: queue calls before the script loads.
window.showpilotq = window.showpilotq || [];
window.showpilotq.push(['on', 'meeting.booked', (e) => console.log('booked', e)]);
</script>Hear milestones
showpilot.on('tour.completed', ({ publicId }) => { /* … */ });
const off = showpilot.on('*', (e) => analytics.track(e.event, e)); // every milestone
off();Events use the same names as the webhook catalog: conversation.started, lead.identified, meeting.booked, tour.played, tour.step_viewed, tour.cta_clicked, tour.completed, content.viewed, content.completed, voice.started, visitor.identified. Payloads never carry personal data; the same milestones also land on window.dataLayer as showpilot_<name>.
Tell the agent who the visitor is
showpilot.identify({
userId: 'u_42', // or email
email: 'sam@acme.example',
name: 'Sam Rivera',
traits: { plan: 'team', seats: '40' },
signature: '<from your server>',
timestamp: 1758800000,
});Unsigned calls are accepted and the visitor is marked claimed. To make it verified, your server signs the subject (the userId, else the email) with the identity secret from Settings → API keys:
// Node, on your server, per page load
const ts = Math.floor(Date.now() / 1000);
const signature = 'sha256=' + crypto.createHmac('sha256', IDENTITY_SECRET).update(`${userId}.${ts}`).digest('hex');Signatures are good for ten minutes. A bad or stale signature is refused with 400, never silently downgraded. The call resolves { ok, visitorId, identity: 'verified' | 'claimed' } and fires visitor.identified on the page.
Hand it context
showpilot.context({ plan: 'enterprise', stage: 'renewal', account: 'Acme' });Up to twenty string values, kept for the page. They ride every chat turn and the session's launch context, so the agent answers as if it already knew.
Open something
showpilot.open(); // the panel
showpilot.open({ tour: '<tour public id>' }); // a tour, in the agent's stage
showpilot.open({ tour: '<id>', step: 3 });
showpilot.ready(() => { /* the widget has mounted */ });Calls made before the widget mounts wait for it.