Widget Embedding Guide
The <salesbooth-deal> web component handles the complete deal flow. This guide covers all 27 attributes, theming, events, and advanced features.
Quick Embed
The minimum required setup — just your publishable API key and a product ID:
<script src="https://salesbooth.com/sdk/v1/salesbooth-widget.js"></script>
<salesbooth-deal
api-key="sb_pub_your_key"
products="prod_a1b2c3d4"
></salesbooth-deal>Keys prefixed sb_pub_ have limited read-only scope. They can list products and submit deals, but cannot read customer data or manage your account.
All Widget Attributes
The widget is configured entirely via HTML attributes. All attributes except api-key are optional.
Authentication
Product Configuration
Content & Copy
Branding & Appearance
Customer Pre-fill
Workflow & Steps
Analytics & Tracking
Performance
Theme Customization
The widget supports three levels of customization: attributes, CSS variables, and a custom CSS file.
Attribute-based theming
<salesbooth-deal
api-key="sb_pub_your_key"
products="prod_a1b2c3d4"
theme-color="#7c3aed"
dark-mode="true"
font-family="'Inter', sans-serif"
border-radius="12px"
logo-url="https://your-site.com/logo.png"
button-style="outlined"
></salesbooth-deal>Custom CSS (advanced)
For full control, link a custom CSS file. The widget uses shadow DOM, so your styles must be injected via custom-css:
/* Override widget CSS custom properties */
:host {
--sb-primary: #7c3aed;
--sb-primary-hover: #6d28d9;
--sb-radius: 12px;
--sb-font: 'Inter', sans-serif;
}
/* Override specific components */
.sb-btn-primary {
text-transform: uppercase;
letter-spacing: 0.05em;
font-weight: 700;
}
.sb-header {
border-bottom: 2px solid var(--sb-primary);
}<salesbooth-deal
api-key="sb_pub_your_key"
products="prod_a1b2c3d4"
custom-css="https://your-site.com/custom-styles.css"
></salesbooth-deal>The custom-css URL is fetched by the browser. Ensure CORS headers allow requests from your page's origin, or host it on a CDN.
Dark mode
<!-- Always dark -->
<salesbooth-deal dark-mode="true" ... />
<!-- Follow system preference (prefers-color-scheme) -->
<salesbooth-deal dark-mode="auto" ... />
<!-- Toggle dynamically with JavaScript -->
<script>
const widget = document.querySelector('salesbooth-deal');
widget.setAttribute('dark-mode', window.matchMedia('(prefers-color-scheme: dark)').matches ? 'true' : 'false');
</script>Event Handling
The widget emits DOM CustomEvents. Listen via addEventListener or the sb.on() SDK method.
Widget DOM events
const widget = document.querySelector('salesbooth-deal');
widget.addEventListener('salesbooth:deal-created', (e) => {
const { deal_id, customer, total, currency } = e.detail;
// Redirect, track, provision access...
});
widget.addEventListener('salesbooth:deal-signed', (e) => {
console.log('Contract signed for deal', e.detail.deal_id);
});
widget.addEventListener('step.payment', (e) => {
analytics.track('checkout_payment_step', { value: e.detail.total });
});All widget events
SDK event API (sb.on())
If you load the SDK separately from the widget, use the event emitter API:
// After loading salesbooth.js
const sb = Salesbooth.init({ apiKey: 'sb_pub_your_key' });
// Subscribe to an event
sb.on('deal.created', (data) => {
console.log('Deal created:', data);
});
// Subscribe to all events (wildcard)
sb.on('*', (eventName, data) => {
analytics.track(eventName, data);
});
// Unsubscribe a specific handler
const handler = (data) => { ... };
sb.on('deal.created', handler);
sb.off('deal.created', handler);
// Unsubscribe all handlers for an event
sb.off('deal.created');
// Rate limit events
sb.on('rate-limit-warning', ({ remaining, limit, reset }) => {
console.warn(`API quota low: ${remaining}/${limit} remaining`);
});
sb.on('rate-limited', ({ retryAfter }) => {
showBanner(`Rate limited. Retrying in ${retryAfter}s...`);
});Offline Mode
The widget supports offline-first operation via a persistent IndexedDB queue. Deals created while offline are stored and automatically synced when connectivity is restored.
Enable offline mode
<salesbooth-deal
api-key="sb_pub_your_key"
products="prod_a1b2c3d4"
mode="offline"
></salesbooth-deal>How the offline queue works
// The widget automatically:
// 1. Detects network loss via navigator.onLine + fetch failures
// 2. Persists API requests to IndexedDB (database: "salesbooth_offline")
// 3. Shows a queue badge: "2 deals pending sync"
// 4. Replays the queue on reconnect using Idempotency-Key headers
// 5. Removes successfully synced requests; keeps 5xx failures for retry
// Listen for offline events
const widget = document.querySelector('salesbooth-deal');
widget.addEventListener('offline.queued', (e) => {
showBanner(`Deal saved offline. ${e.detail.queue_length} pending sync.`);
});
widget.addEventListener('offline.synced', (e) => {
showSuccess(`${e.detail.synced_count} deal(s) synced!`);
});All queued requests include an Idempotency-Key header. If a request is retried after a network failure, the server deduplicates it — you'll never create a duplicate deal.
Storage quota
// The widget monitors IndexedDB storage usage.
// When usage exceeds 85% of quota, a warning is emitted:
widget.addEventListener('widget.error', (e) => {
if (e.detail.code === 'STORAGE_QUOTA_WARNING') {
console.warn('Offline storage near limit:', e.detail.message);
}
});ETag caching
The SDK uses HTTP ETags to avoid redundant data fetches. Product catalog and configuration responses are cached in memory and revalidated with If-None-Match headers.
// Control cache TTL via attribute
<salesbooth-deal cache-ttl="600000" ... /> <!-- 10 minutes -->
// Or disable caching entirely
<salesbooth-deal cache-ttl="0" ... />Mobile Responsiveness
The widget is fully responsive and touch-optimized by default. No additional configuration is required.
- Fluid layout that adapts to any container width
- Touch-friendly tap targets (minimum 44×44px)
- Native mobile keyboard types on input fields
- Swipe gestures for step navigation
- Apple Pay / Google Pay on supported devices
Recommended container sizing
/* Let the widget fill its container */
salesbooth-deal {
display: block;
width: 100%;
max-width: 480px; /* recommended max for single-column layout */
margin: 0 auto;
}
/* For modal/popup embed */
.checkout-modal salesbooth-deal {
width: 100vw;
max-width: 560px;
height: 100%;
overflow-y: auto;
}Use your browser's DevTools device emulation for initial testing, but always verify on physical iOS and Android devices before launch — especially the payment step.
ETag Caching & Performance
The widget uses HTTP ETags for efficient cache revalidation, reducing bandwidth and improving load times for returning customers.
// First load: full response
GET /api/v1/widget-config?id=wc_abc123
Authorization: Bearer sb_pub_xxx
< ETag: "abc123"
< Cache-Control: max-age=300
// Subsequent loads: conditional request
GET /api/v1/widget-config?id=wc_abc123
Authorization: Bearer sb_pub_xxx
> If-None-Match: "abc123"
< 304 Not Modified ← no body transferred, widget uses cached dataThe widget stores ETags in memory (scoped per API key) and reuses them across step transitions. The cache is reset on page reload.
Retry behavior
The SDK automatically retries failed requests with exponential backoff:
// Default retry schedule:
// Attempt 1: immediately
// Attempt 2: 1 second delay
// Attempt 3: 2 second delay
// Attempt 4: 4 second delay (max 3 retries by default)
// Listen for retry events
sb.on('retry', ({ attempt, retryAfter, status }) => {
console.log(`Retrying... attempt ${attempt}, wait ${retryAfter}s (HTTP ${status})`);
});