SDK Web Documentation
Complete reference for the StreamBack browser SDK.
Installation
Hosted Script (Recommended)
<script src="https://streamback.tech/sdk/feedback-widget.min.js"></script>
<script>
FeedbackWidget.init({
envKey: 'env_your_key_here',
apiUrl: 'https://api.streamback.tech',
});
</script>
NPM / Yarn / pnpm (private registry / monorepo only)
The @streamback/sdk-web package is not currently published on the public npm registry.
Use the hosted script above unless you have been given private package access or you are
installing from the StreamBack monorepo.
npm install @streamback/sdk-web
# or
yarn add @streamback/sdk-web
# or
pnpm add @streamback/sdk-web
Script-tag usage exposes the legacy global FeedbackWidget for backward compatibility.
Module/bundler usage should use import { StreamBack } from '@streamback/sdk-web' when
the package is available through your private registry or local workspace.
Initialization
Basic Setup
import { StreamBack } from '@streamback/sdk-web';
StreamBack.init({
envKey: 'env_your_key_here',
apiUrl: 'https://api.streamback.tech',
});
Classic Configuration
StreamBack.init({
// Required
envKey: 'env_your_key_here',
// Optional
apiUrl: 'https://api.streamback.tech', // API endpoint
mode: 'always', // 'always' | 'trigger' | 'never'
position: 'bottom-right', // 'bottom-right' | 'bottom-left'
accentColor: '#0ea5e9', // Hex color for branding
debug: false, // Enable console logging
});
Full Configuration (Invisible Feedback Layer)
StreamBack.init({
envKey: 'env_your_key_here',
apiUrl: 'https://api.streamback.tech',
accentColor: '#6366f1',
debug: false,
// Display configuration
display: {
trigger: 'dot', // 'hidden' | 'edge' | 'dot' | 'button'
position: 'right-center', // 'bottom-right' | 'bottom-left' | 'right-center' | 'left-center'
autoHideTrigger: 0, // Seconds, 0 = never
panelWidth: 360,
},
// Contextual triggers
triggers: {
onError: { enabled: true, delay: 2000, maxPerSession: 3 },
onFrustration: { enabled: true, clickThreshold: 4, timeWindow: 2000, maxPerSession: 2 },
onExitIntent: { enabled: true, paths: ['/checkout*', '/billing*'], maxPerSession: 1 },
onIdle: { enabled: true, duration: 60000, maxPerSession: 1 },
onFormAbandonment: { enabled: true, formSelectors: ['form'], idleThreshold: 15000 },
onScrollDepth: { enabled: true, threshold: 80, minTimeOnPage: 10000 },
onPostAction: { enabled: true, events: ['checkout_complete'], delay: 30000 },
},
// Emotion-first flow
flow: {
startWith: 'emotion', // 'emotion' | 'text' | 'type'
requireText: false,
minTextForBonus: 50,
},
// Reward preview
rewards: {
format: 'credits', // 'credits' | 'currency'
customTemplate: '{{amount}} bonus credits',
showPreview: true,
previewRange: { min: 1, max: 5 },
},
// Smart cooldowns
cooldowns: {
afterDismiss: 300000, // 5 min per-type cooldown
afterSubmit: 86400000, // 24h global cooldown
maxDismissalsPerSession: 3,
respectNotNow: true,
},
// Custom whisper messages
whisperMessages: {
error: 'Something went wrong? Let us know and earn credits!',
frustration: 'Having trouble? We want to help.',
idle: 'Got a moment? Share your thoughts and earn rewards.',
exitIntent: 'Before you go - any feedback for us?',
},
// Micro-surveys
microSurveys: [
{
id: 'checkout-ease',
question: { id: 'q1', text: 'How easy was checkout?', type: 'stars' },
followUp: true,
followUpPrompt: 'Tell us more...',
routes: ['/checkout*'],
delay: 5000,
maxPerSession: 1,
},
],
});
Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
envKey | string | Required | Your environment API key |
apiUrl | string | Production URL | Custom API endpoint |
mode | 'always' | 'trigger' | 'never' | 'always' | Feedback layer display mode (classic) |
position | string | 'bottom-right' | Feedback layer position (classic) |
accentColor | string | '#0ea5e9' | Primary brand color |
debug | boolean | false | Enable debug logging |
display | DisplayConfig | - | Invisible feedback layer display options |
triggers | ContextualTriggers | - | Contextual trigger configuration |
flow | FlowConfig | - | Emotion-first or custom flow |
rewards | RewardDisplayConfig | - | Reward preview configuration |
cooldowns | CooldownConfig | - | Smart cooldown rules |
whisperMessages | WhisperMessages | - | Custom whisper message strings |
microSurveys | MicroSurveyConfig[] | - | Auto-scheduled micro-surveys |
Display Modes (Classic)
always: Feedback layer button is always visibletrigger: Feedback layer only opens viaStreamBack.open()never: Feedback layer is completely hidden (useful for feature flags)
Trigger Modes (Invisible Feedback Layer)
hidden: No visible trigger, open only via API or contextual triggersedge: Minimal 4px edge indicator strip that expands on hoverdot: Small 10px dot that expands to icon on hoverbutton: Traditional floating button (legacy mode)
User Identification
Identify User
StreamBack.identifyUser({
id: 'user_123', // Required: Unique user ID
email: 'user@example.com', // Optional but recommended
name: 'John Doe', // Optional
});
Set Entitlements
Entitlements help with targeting and can be used in policy rules:
StreamBack.setEntitlements({
plan: 'pro',
tier: 'business',
subscriptionId: 'sub_abc123',
customerId: 'cus_xyz789',
// Any custom key-value pairs
features: ['advanced-reporting', 'api-access'],
});
Reset User
Clear user data (e.g., on logout):
StreamBack.resetUser();
Feedback Layer Control
Open Feedback Layer
StreamBack.open();
Close Feedback Layer
StreamBack.close();
Start Element Selection
Allow users to click on a page element to highlight it:
StreamBack.startElementSelection();
Check Feedback Layer State
const isOpen = StreamBack.isOpen(); // boolean
Whispers (Contextual Prompts)
Show a non-modal whisper prompt that appears near the feedback layer trigger:
// Show a built-in whisper type
StreamBack.whisper('error'); // Error whisper
StreamBack.whisper('frustration'); // Frustration whisper
StreamBack.whisper('idle'); // Idle whisper
StreamBack.whisper('exitIntent'); // Exit intent whisper
StreamBack.whisper('success'); // Success whisper
// Custom whisper with your own message
StreamBack.whisper('custom', 'How did the export go?');
Whispers auto-dismiss after 8 seconds if ignored. Users can click the whisper to open the full feedback panel, or click "Not now" to dismiss with a longer cooldown.
Micro-Surveys
Show a 1-question quick poll:
StreamBack.showSurvey({
id: 'checkout-ease',
question: {
id: 'q1',
text: 'How easy was your checkout experience?',
type: 'stars', // 'stars' | 'yesno' | 'scale' | 'multiple-choice'
},
followUp: true, // Show follow-up text input after answer
followUpPrompt: 'Tell us more about your experience...',
});
Survey Question Types
| Type | Description | Options |
|---|---|---|
stars | 1-5 star rating | None |
yesno | Yes / No buttons | None |
scale | Numeric scale (e.g., 1-10) | scaleMin, scaleMax, scaleLabels |
multiple-choice | Selectable options list | options: string[] |
Auto-Scheduled Surveys
Surveys defined in microSurveys config are auto-scheduled based on delay, routes, and maxPerSession:
microSurveys: [
{
id: 'feature-useful',
question: { id: 'q1', text: 'Did you find this useful?', type: 'yesno' },
followUp: true,
followUpPrompt: 'What would make it better?',
routes: ['/features*', '#features'], // Path or hash-based routes
delay: 8000, // Show after 8 seconds
maxPerSession: 1, // Once per session
},
];
Pause / Resume
Temporarily disable all contextual triggers:
StreamBack.pause(); // Disable all triggers
StreamBack.resume(); // Re-enable triggers
Context & State Providers
Context and state providers capture app-specific data when feedback is submitted. This data appears in the console for debugging.
Context Provider
Captures static or semi-static app information:
StreamBack.registerContextProvider('app', () => ({
version: '2.1.0',
environment: process.env.NODE_ENV,
feature_flags: {
dark_mode: true,
new_checkout: false,
},
deployment: {
commit: 'abc123',
branch: 'main',
},
}));
State Provider
Captures dynamic application state (Redux, Zustand, etc.):
import { store } from './store';
StreamBack.registerStateProvider('redux', () => ({
// Be selective - don't capture sensitive data
cart_items: store.getState().cart.items.length,
active_filters: store.getState().filters,
ui_state: {
sidebar_open: store.getState().ui.sidebarOpen,
modal_open: store.getState().ui.modalOpen,
},
}));
Multiple Providers
You can register multiple providers:
// App context
StreamBack.registerContextProvider('app', () => ({...}));
// User preferences
StreamBack.registerContextProvider('preferences', () => ({
theme: localStorage.getItem('theme'),
language: navigator.language,
}));
// Redux state
StreamBack.registerStateProvider('redux', () => ({...}));
// React Query cache
StreamBack.registerStateProvider('query', () => ({
queries: queryClient.getQueryCache().getAll().map(q => q.queryKey),
}));
Privacy & Redaction
Screenshot Redaction
The SDK automatically redacts sensitive information in screenshots:
Auto-Detected Fields
| Field Type | Redaction |
|---|---|
| Password inputs | Replaced with •••••••• |
| Email inputs | Partial mask: j•••@domain.com |
| Phone inputs | Partial mask: 12•••••89 |
| Credit card fields | Fully redacted |
| Text inputs | Dots (based on length) |
| Textareas | [Redacted content] |
Custom Redaction Rules
StreamBack.setRedactionRules({
// Redact all form inputs (default: true)
redactInputs: true,
// Additional CSS selectors to redact
selectors: ['.sensitive-data', '[data-private]', '.account-balance'],
// Routes where feedback is disabled
blockedRoutes: ['/admin/*', '/checkout', '/payment/*'],
});
HTML Attribute
Mark specific elements for redaction:
<div data-feedback-redact="true">Sensitive content here - will be blurred in screenshots</div>
<!-- Exclude elements from screenshots entirely -->
<div data-feedback-ignore>This won't appear in screenshots</div>
Event Tracking
Automatic Events
The SDK automatically tracks:
| Event Type | Data Captured |
|---|---|
click | Element selector, text, coordinates |
route_change | URL, path |
window_error | Message, filename, line, stack |
network_error | URL, method, status, duration |
console_error | Level, message |
Custom Events
Track application-specific events:
StreamBack.trackEvent('checkout_started', {
cart_value: 99.99,
item_count: 3,
currency: 'USD',
});
StreamBack.trackEvent('feature_used', {
feature: 'export-to-csv',
context: 'dashboard',
});
StreamBack.trackEvent('error_boundary', {
error: error.message,
componentStack: errorInfo.componentStack,
});
Event Timeline
All events (automatic and custom) are captured in a timeline that's submitted with feedback. This helps developers understand what led to the issue.
Event Listeners
Available Events
// Feedback layer opened
StreamBack.on('open', () => {
analytics.track('streamback_opened');
});
// Feedback layer closed
StreamBack.on('close', () => {
console.log('Feedback layer closed');
});
// Feedback submitted (before API call)
StreamBack.on('submit', (data) => {
console.log('Submitting:', data);
});
// Feedback sent successfully
StreamBack.on('success', () => {
showNotification('Thanks for your feedback!');
});
// Feedback failed
StreamBack.on('error', (error) => {
showNotification('Failed to send feedback. Please try again.');
console.error('Feedback error:', error);
});
// Element selected via point-to-element
StreamBack.on('element-selected', (elementInfo) => {
console.log('Selected element:', elementInfo);
});
// Whisper shown
StreamBack.on('whisper-shown', (data) => {
console.log('Whisper shown:', data);
});
// Whisper dismissed
StreamBack.on('whisper-dismissed', (data) => {
console.log('Whisper dismissed:', data);
});
// Whisper clicked (user engaged)
StreamBack.on('whisper-clicked', (data) => {
console.log('Whisper clicked:', data);
});
Remove Listeners
const handler = () => console.log('Opened');
StreamBack.on('open', handler);
// Later...
StreamBack.off('open', handler);
Emotion-First Flow
When flow.startWith is set to 'emotion', the feedback panel opens with a 5-emoji picker instead of a text box:
- Step 1: User selects an emotion (Awful / Bad / Okay / Good / Great) - 1 tap
- Step 2: Category pills appear (Bug / Idea / Praise / Question / Other) + text area expands
- Step 3: User can submit with just emotion (no text required), or add details for higher rewards
flow: {
startWith: 'emotion', // Show emoji picker first
requireText: false, // Allow submit without text
minTextForBonus: 50, // Chars needed for "detailed" reward tier
}
The reward preview dynamically updates: "Quick feedback: 1 credit | With details: up to 5 credits"
Reward Preview
Show potential reward amounts before submission to increase engagement:
rewards: {
format: 'credits', // 'credits' | 'currency'
customTemplate: '{{amount}} bonus credits',
showPreview: true, // Show range in whispers + panel
previewRange: { min: 1, max: 5 }, // Min/max possible reward
}
When enabled:
- Whispers include reward hint: "Let us know and earn 1-5 credits!"
- Panel header shows: "Earn up to 1-5 credits for detailed feedback"
- Success state shows actual awarded amount
Smart Cooldowns
Prevent user annoyance with session-aware rate limiting:
cooldowns: {
afterDismiss: 300000, // 5 min cooldown per trigger type after dismiss
afterSubmit: 86400000, // 24h global cooldown after submission
maxDismissalsPerSession: 3, // Suppress all triggers after 3 dismissals
respectNotNow: true, // "Not now" = longer cooldown than X dismiss
}
Cooldown data is stored in sessionStorage (per-session) and localStorage (cross-session). Clearing storage resets cooldowns.
Mobile Bottom Sheet
On viewports under 768px, the feedback panel automatically renders as a bottom sheet:
- Full-width panel slides up from the bottom
- Rounded top corners with drag handle
- Max height 70% of viewport
- Touch-friendly targets (min 44px)
- Swipe down to dismiss
No configuration needed -- this is automatic based on screen size.
Framework Integration
React
// src/feedback.ts
import { StreamBack } from '@streamback/sdk-web';
export function initFeedback() {
StreamBack.init({
envKey: process.env.REACT_APP_FEEDBACK_KEY!,
mode: 'always',
});
}
// src/App.tsx
import { useEffect } from 'react';
import { useAuth } from './hooks/useAuth';
import { initFeedback } from './feedback';
function App() {
const { user } = useAuth();
useEffect(() => {
initFeedback();
}, []);
useEffect(() => {
if (user) {
StreamBack.identifyUser({
id: user.id,
email: user.email,
name: user.name,
});
} else {
StreamBack.resetUser();
}
}, [user]);
return <YourApp />;
}
Vue
// src/plugins/feedback.ts
import { StreamBack } from '@streamback/sdk-web';
export default {
install(app) {
StreamBack.init({
envKey: import.meta.env.VITE_FEEDBACK_KEY,
mode: 'always',
});
app.config.globalProperties.$feedback = StreamBack;
},
};
// src/main.ts
import feedbackPlugin from './plugins/feedback';
app.use(feedbackPlugin);
Next.js
// src/app/providers.tsx
'use client';
import { useEffect } from 'react';
import { StreamBack } from '@streamback/sdk-web';
export function FeedbackProvider({ children }) {
useEffect(() => {
StreamBack.init({
envKey: process.env.NEXT_PUBLIC_FEEDBACK_KEY!,
mode: 'always',
});
}, []);
return children;
}
// src/app/layout.tsx
import { FeedbackProvider } from './providers';
export default function RootLayout({ children }) {
return (
<html>
<body>
<FeedbackProvider>{children}</FeedbackProvider>
</body>
</html>
);
}
Angular
// src/app/feedback.service.ts
import { Injectable } from '@angular/core';
import { StreamBack } from '@streamback/sdk-web';
@Injectable({ providedIn: 'root' })
export class FeedbackService {
init() {
StreamBack.init({
envKey: environment.feedbackKey,
mode: 'always',
});
}
identifyUser(user: User) {
StreamBack.identifyUser({
id: user.id,
email: user.email,
});
}
open() {
StreamBack.open();
}
}
// src/app/app.component.ts
export class AppComponent implements OnInit {
constructor(private feedback: FeedbackService) {}
ngOnInit() {
this.feedback.init();
}
}
TypeScript Support
The SDK is written in TypeScript and includes full type definitions:
import {
StreamBack,
type StreamBackConfig,
type UserIdentity,
type RedactionRules,
} from '@streamback/sdk-web';
const config: StreamBackConfig = {
envKey: 'env_xxx',
mode: 'always',
position: 'bottom-right',
};
StreamBack.init(config);
Troubleshooting
Feedback Layer Not Appearing
- Check that
envKeyis correct and valid - Ensure
modeis not set to'never' - Check for CSS conflicts (z-index issues)
- Enable
debug: trueto see console logs
Screenshots Not Working
- Ensure no CORS issues with images on the page
- Check that
html2canvasis loading correctly - Some browser extensions may interfere
Events Not Tracking
- Enable
debug: trueto see event logs - Check that the SDK is initialized before user interactions
- Verify network requests in DevTools
User Not Identified
- Call
identifyUser()after user authentication - Ensure user ID is a string, not a number
- Check that
resetUser()isn't being called
Best Practices
- Initialize Early: Call
init()as early as possible in your app lifecycle - Identify Users: Always identify users when possible for better feedback quality
- Use Context Providers: Provide relevant app context for easier debugging
- Don't Over-Track: Only track meaningful events, not every click
- Test Redaction: Verify sensitive data is properly redacted in screenshots
- Handle Errors: Listen to the
errorevent and show user-friendly messages
Browser Support
| Browser | Version |
|---|---|
| Chrome | 60+ |
| Firefox | 60+ |
| Safari | 12+ |
| Edge | 79+ |
| iOS Safari | 12+ |
| Chrome Android | 60+ |
Security
- All API communication uses HTTPS
- Screenshots are uploaded directly to secure storage
- PII is automatically redacted before transmission
- User data is never logged or stored client-side