@skippr/live-agent-sdk
v0.92.0
Published
[](https://skippr.ai) [](https://www.npmjs.com/package/@skippr/live-agent-sdk)
Downloads
5,956
Keywords
Readme
@skippr/live-agent-sdk
Embed product specialists that see, speak, and act in real time. Configure modules — for onboarding, demos, training, support, and anything else — and let your users pick which one to talk to from a single embed.
Key Features
- Multi-module picker — the widget fetches your active modules and lets the user pick which one to start (onboarding, demo, support, etc.), so a single embed covers every use case
- Real-time voice — two-way audio with live transcription
- Capture modes —
screenshare(user shares screen) orauto(DOM capture) - Agent controls — opt-in capabilities like element highlighting and on-screen actions, configured per module (auto mode only)
- Bring your own button — start, pause, and end sessions from your own UI with the
useLiveAgenthook (see Bring your own button) - Chat + transcript — text messaging with voice transcripts merged into one thread
- Session agenda — structured phases with progress tracking
- Flexible auth — email OTP (direct auth) or backend-signed JWT (secret mode)
- Drop-in integration — one React component or script tag, no WebRTC code needed
- Host-safe styles — prefixed CSS that won't conflict with your app
Prerequisites
You need an appKey from the Skippr Platform.
- Sign up at specialist.skippr.ai
- Create your modules in the dashboard — for example an
onboardingagent to walk new users through setup and asupportmodule for help. Every active module shows up in the widget's picker automatically. - Create an App Key in Settings and copy the
appKey— one App Key works for all your modules
Optionally, you can pin the widget to a single module by passing its id as agentId (see Pinning below). Without it, the user picks from all active modules at runtime.
Installation
React
npm install @skippr/live-agent-sdkScript Tag
No install needed. Add this to any webpage:
<script src="https://unpkg.com/@skippr/live-agent-sdk/dist/skippr-sdk.js"></script>
<script>
Skippr.initialize({
appKey: 'pk_live_your_key',
});
</script>Quick Start
The widget opens with a picker of your active modules — the user chooses which one to start a session with.
React
import { LiveAgent } from '@skippr/live-agent-sdk';
import '@skippr/live-agent-sdk/styles';
function App() {
return <LiveAgent appKey="pk_live_your_key" />;
}Script Tag
<script src="https://unpkg.com/@skippr/live-agent-sdk/dist/skippr-sdk.js"></script>
<script>
Skippr.initialize({
appKey: 'pk_live_your_key',
});
</script>Pinning to a specific module
If you want to skip the picker and always open straight into one module, pass its id as agentId:
<LiveAgent appKey="pk_live_your_key" agentId="your_module_id" />Authentication
The SDK supports two identity modes, configured per App Key in the Skippr dashboard.
Direct Auth (default)
Users log in via email OTP inside the widget. No backend integration needed. The SDK handles the full OTP flow: email input, code verification, and token persistence automatically.
Best suited for development and testing. Direct Auth persists tokens in the browser and has no automatic token refresh, so it works in production but isn't what we recommend there. For production, we suggest Secret Mode — your backend vouches for the user and controls token lifetime.
React:
<LiveAgent appKey="pk_live_your_key" />Script tag:
Skippr.initialize({
appKey: 'pk_live_your_key',
});Secret Mode
Your backend signs a short-lived JWT with the App Key's identity secret. You give the SDK a getUserToken callback that returns a freshly signed JWT each time it's called; the SDK exchanges it for a bearer token server-side and skips the login form entirely.
Because it's a callback (not a static token), the SDK can keep the session alive without a page reload: it invokes getUserToken to bootstrap, again proactively shortly before the current bearer expires, and once more if a request comes back 401. Your callback should always return a current JWT from your source of truth — typically by re-minting it on your backend per call.
The bearer's lifetime is the session token lifetime you set on the App Key in the dashboard (15 minutes to 24 hours, default 1 hour).
Step 1: Generate a signed JWT on your backend
The JWT must be signed with HS256 using the identity secret you received when creating the App Key.
| Claim | Type | Required | Description |
|-------|------|----------|-------------|
| sub | string | Yes | Unique user identifier in your system |
| name | string | No | User's display name |
| email | string | No | User's email address |
| exp | number | Recommended | Expiration timestamp (Unix seconds) |
Node.js example:
import jwt from 'jsonwebtoken';
const userToken = jwt.sign(
{
sub: user.id,
name: user.name,
email: user.email,
},
process.env.SKIPPR_IDENTITY_SECRET,
{ algorithm: 'HS256', expiresIn: '1h' },
);Step 2: Give the SDK a callback that fetches a fresh token
React:
<LiveAgent
appKey="pk_live_your_key"
getUserToken={() => fetch('/api/skippr-token').then((r) => r.text())}
/>Script tag:
Skippr.initialize({
appKey: 'pk_live_your_key',
getUserToken: () => fetch('/api/skippr-token').then((r) => r.text()),
});The callback runs whenever the SDK needs a token, so each call should return a newly minted JWT (e.g. from the /api/skippr-token endpoint on your backend that signs the JWT shown in Step 1).
Static token (testing)
If you already hold a signed JWT and just want to drop it in, pass it as a static userToken string instead of a callback. The SDK exchanges it for a bearer token the same way.
<LiveAgent appKey="pk_live_your_key" userToken={signedJwt} />Best suited for quick tests. A static token can't be re-minted, so once it expires the session cannot refresh — for production use the
getUserTokencallback above. When both are provided,getUserTokenwins.
Custom Components
Any component rendered inside <LiveAgent> can use the useLiveAgent hook to access session state and controls:
import { LiveAgent, useLiveAgent } from '@skippr/live-agent-sdk';
function ConnectionStatus() {
const { isConnected } = useLiveAgent();
if (isConnected) return <p>Agent connected</p>;
return <p>Agent disconnected</p>;
}
function App() {
return (
<LiveAgent appKey="pk_live_your_key">
<ConnectionStatus />
</LiveAgent>
);
}Bring your own button
You can bring your own widget or button and start, pause, and end sessions from it — no need to rely on the built-in launcher:
import { LiveAgent, useLiveAgent } from '@skippr/live-agent-sdk';
function TalkToExpertButton() {
const { isConnected, isStarting, displayModules, selectModule, disconnect } = useLiveAgent();
if (isConnected) {
return <button onClick={() => disconnect()}>End session</button>;
}
const module = displayModules[0];
return (
<button disabled={!module || isStarting} onClick={() => module && selectModule(module.id)}>
{isStarting ? 'Connecting…' : 'Talk to an expert'}
</button>
);
}
function App() {
return (
<LiveAgent appKey="pk_live_your_key">
<TalkToExpertButton />
</LiveAgent>
);
}- Start — call
selectModule(id)with your agent's id (the same id you'd pass asagentId). Running multiple agents? Pick one fromdisplayModules. If the user has a paused session with that agent, it resumes automatically. - Close — call
disconnect()to end the current session. - Show the right label — use
isConnectedandisStartingto switch your button between start, connecting, and end states.
API Reference
<LiveAgent>
Self-contained widget component. Renders a floating button that opens a sidebar panel for real-time agent interaction.
Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| appKey | string | required | Publishable App Key from the Skippr dashboard |
| agentId | string | — | Pin the widget to a specific module (pass that module's id). Omit to let the user pick from all your active modules at runtime. |
| getUserToken | () => Promise<string> | — | Async callback returning a freshly signed JWT for secret mode. The SDK calls it to bootstrap, again proactively before the bearer expires, and once more on a 401 to re-bootstrap. |
| userToken | string | — | Static signed JWT for secret mode (testing). Exchanged for a bearer token but can't be refreshed once expired. getUserToken takes precedence when both are set. |
| userContext | Record<string, string \| number \| boolean> | — | Background facts about the current user so the agent can tailor the conversation (e.g. { name: 'Alex', plan: 'pro', signedUpDaysAgo: 3, hasCompletedSetup: false }). Must be a flat object of strings, numbers, or booleans - nested objects and arrays are not supported. |
| variant | 'floating' \| 'sidebar' | 'floating' | Widget display mode |
| minimizable | boolean | true | Whether the widget can be minimized |
| defaultOpen | boolean | false | Whether the panel starts open |
| welcomeMessage | string | — | Message shown on the minimized bubble |
| startSessionLabel | string | 'Talk to Skippr' | Label for the start-session button (only shown when agentId is pinned) |
| autoFocusChat | boolean | true | Whether the chat input auto-focuses when opened |
| captureMode | 'screenshare' \| 'auto' | 'auto' | How the agent sees the page — 'auto' uses DOM capture (no permission prompt), 'screenshare' prompts the user to share |
| animateAgentCursor | boolean | false | Animate the agent's cursor as it points to elements on the page. Primarily configured per agent in the dashboard; this prop is a fallback when the agent has no preference set. |
useLiveAgent()
Hook for accessing session state and panel controls. Must be called within <LiveAgent>.
State
| Field | Type | Description |
|-------|------|-------------|
| isConnected | boolean | Whether the agent is connected |
| isStarting | boolean | Whether a session is being created or resumed |
| isDisconnecting | boolean | Whether the session is being torn down |
| isPausing | boolean | Whether a pause request is in flight (before isPaused commits) |
| isPaused | boolean | Whether the current session is paused |
| resumableSession | { id: string; agentId: string } \| null | The operating module's paused session, or null |
| isPanelOpen | boolean | Whether the panel is currently open |
| isMinimized | boolean | Whether the widget is minimized |
| isAuthenticated | boolean | Whether the user is authenticated |
| variant | 'floating' \| 'sidebar' | Current widget display mode |
| position | 'left' \| 'right' | Current widget position |
| error | string | Error message, if any |
| displayModules | Module[] | The user's active modules, for pickers and custom start buttons |
| activeModule | Module \| null | The module the current session was started with |
| isLoadingModules | boolean | Whether the picker list is being fetched |
| modulesError | string \| null | Error from fetching the picker list, if any |
interface Module {
id: string;
name: string;
description: string | null;
type: string;
priority: number;
controls: { highlight?: boolean; actions?: boolean };
}Methods
| Method | Type | Description |
|--------|------|-------------|
| startSession | (opts: { agentId: string; agentControls?: { highlight?: boolean; actions?: boolean } }) => Promise<void> | Start a new agent session |
| pauseSession | () => Promise<void> | Pause the active session and disconnect from LiveKit; resumable later |
| resumeSession | () => Promise<void> | Resume the pinned agent's paused session, reconnecting to continue |
| disconnect | () => Promise<void> | End the current session |
| openPanel | () => void | Open the panel |
| closePanel | () => void | Close the panel |
| togglePanel | () => void | Toggle the panel open/closed |
| expandPanel | () => void | Expand from minimized state |
| minimizePanel | () => void | Minimize the widget |
| setPosition | (position: 'left' \| 'right') => void | Change widget position |
| selectModule | (id: string) => void | Start a session with the module of the given id |
| refetchModules | () => Promise<void> | Refetch the picker list |
Additional Hooks
All hooks must be called within <LiveAgent>.
| Hook | Returns | Description |
|------|---------|-------------|
| useMediaControls() | { isMuted, isScreenSharing, toggleMute, toggleScreenShare } | Mic and screen share state and toggles |
| useAgentVoiceState() | { state, isSpeaking, isListening } | Agent voice activity state — state is the full agent state ('listening' \| 'thinking' \| 'speaking' \| ...) |
| useIsLocalSpeaking() | boolean | Whether the local user is currently speaking |
| useIsSessionHeld() | boolean | true while the user has paused the live session; false when active or when there is no session. |
| useElapsedSeconds(isRunning) | number | Drift-safe elapsed seconds since the flag flipped to true |
Utilities
| Export | Signature | Description |
|--------|-----------|-------------|
| formatTime | (seconds: number) => string | Format seconds as mm:ss |
Global API (Script Tag)
Available on window.Skippr when using the script tag bundle.
| Method | Description |
|--------|-------------|
| Skippr.initialize(config) | Mount the widget. Accepts appKey (required), agentId (optional — omit for picker), getUserToken, userToken, variant, minimizable, captureMode. |
| Skippr.logout() | Revoke the current session server-side, clear stored auth tokens, and show the login form (direct auth mode) |
| Skippr.destroy() | Remove the widget from the page and clear auth tokens |
On-screen actions
When an agent has the actions control enabled (auto mode only), it can act on the page for the user, not just guide them. A small on-screen indicator shows each action as it happens.
Actions are guardrailed: the SDK refuses to act on masked fields (passwords, secrets), sensitive iframes (Stripe, Plaid, captchas), or disabled controls. Use data-skippr-private (below) to put anything else off limits.
Hiding elements from the agent
To hide an element (and everything inside it) from the agent in captureMode: 'auto', add the data-skippr-private attribute:
<div data-skippr-private>
<SensitiveContent />
</div><section data-skippr-private>
<input type="password" />
</section>Use this for sensitive content or anything you don't want the agent to see or reference.
Support
For questions, technical support, or feedback:
- Email: [email protected]
- Website: https://skippr.ai
© 2026 Skippr
