Appearance
Widget and White-Label
The FAST widget is a drop-in UI component that adds engagement features directly to your sportsbook frontend. It renders inside a Shadow DOM for complete style isolation.
Architecture
The widget supports two rendering modes:
- Legacy Widget - Single-purpose challenge progress card (default, backward-compatible)
- Shell (modular) - Multi-module orchestrator with dynamic module loading
The Shell loads only the modules enabled for your tenant via the widget_modules config. This keeps the initial bundle small and lets you activate features without code changes.
Module System
Each module provides:
- A Card view (compact, shown in the main panel)
- A Drawer view (full detail, opened from card or bottom nav)
- SSE events it subscribes to for real-time updates
Available modules:
| Module | ID | Description |
|---|---|---|
| Challenges | challenges | Challenge progress, purchase, history |
| Bonuses | bonuses | Active wagering bonuses, progress |
| Missions | missions | Mission objectives and progress |
| Tournaments | tournaments | Active tournaments, leaderboard |
| Loyalty | loyalty | Points, tier, cashback status |
| Leaderboard | leaderboard | Global challenge leaderboard |
| Badges | badges | Achievement badges |
| Rewards | rewards | Pending and claimable rewards |
Modules are configured per-tenant via the admin panel or PUT /v1/super-admin/tenants/:id/widget-modules.
Quick Start
Add one script tag to your page. The widget auto-initializes.
html
<!-- Optional, recommended: opens the API connection while the script loads. -->
<link rel="preconnect" href="https://api.fundedfast.eu" crossorigin>
<script
src="https://cdn.fundedfast.eu/fast-widget.iife.js"
data-fast-widget
data-api-key="pk_your_widget_key"
data-user-id="player-42"
data-user-token="eyJ0aWQ..."
data-api-base="https://api.fundedfast.eu/v1"
></script>The widget appears as a floating action button (FAB) in the bottom-right corner. Users click it to view their progress and interact with engagement modules.
Two credentials
The widget uses two credentials so it never trusts the browser to assert who the user is:
data-api-key— your public widget key (pk_...). Safe to embed. Grants tenant-level reads only (tiers, terms, leaderboard, loyalty tiers, widget config).data-user-token— a short-lived, HMAC-signed token that binds the widget to oneuserId. Your backend mints it with your server key viaPOST /v1/sse/tokenand injects it here. Required for anything user-specific (progress, history, and all actions). FAST enforcestoken.uid === data-user-id.
js
// Your backend, after the player is authenticated in your session:
const { token } = await fetch('https://api.fundedfast.eu/v1/sse/token', {
method: 'POST',
headers: { 'X-API-Key': process.env.FAST_API_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ userId: 'player-42' }),
}).then(r => r.json());
// render the widget with data-user-token=tokenNever put your server key (key-...) in the browser. Only the pk_... key and a minted user token belong on the frontend.
Sandbox shortcut: on staging/sandbox tenants the widget can self-mint a token with the public key, so you can demo with just
data-api-key+data-user-id. Production tenants requiredata-user-token— self-minting is refused. Wire up backend minting before going live.
Token lifetime: user tokens last 1 hour, and the widget renews the injected token automatically shortly before expiry (self-refresh is server-capped at 24h per chain). You only need the
onTokenExpiredcallback for tabs that outlive that cap — see Authentication.
Display Modes
| Mode | Behavior | Use Case |
|---|---|---|
fab (default) | Floating button, opens card on click | Overlay on existing sportsbook |
sidebar | Embedded panel, always visible | Dedicated sidebar slot |
fullpage | Full viewport takeover | Standalone engagement page |
Set via data-mode attribute or mode in programmatic config.
In fullpage mode the widget paints a seamless page-level background (deep black #050507 in dark theme, warm white #fefcfb in light), centers the content column, and centers the bottom navigation — so it reads as a native page, not an embedded panel.
Configuration Options
Required
| Option | Type | Description |
|---|---|---|
apiKey | string | Public widget key (pk_...). Tenant-level reads only. |
userId | string | Your user's identifier. Must match the user token. |
apiBase | string | FAST API base URL. |
Required for user-specific features
| Option | Type | Description |
|---|---|---|
userToken | string | Signed token from POST /v1/sse/token (minted by your backend). Sent as X-User-Token; required for all /users/:id/* reads and actions. Optional only on sandbox tenants. |
Display
| Option | Type | Default | Description |
|---|---|---|---|
theme | 'dark' | 'light' | 'dark' | Color scheme |
position | 'bottom-right' | 'bottom-left' | 'bottom-right' | FAB position |
mode | 'fab' | 'sidebar' | 'fullpage' | 'fab' | Display mode |
lang | string | 'en' | Language code (en, de, hr) |
showLeaderboard | boolean | true | Show leaderboard module |
showBadges | boolean | true | Show achievements/badges |
showRewards | boolean | true | Show rewards |
showHistory | boolean | true | Show challenge history |
showShop | boolean | true | Show challenge purchase |
showConfetti | boolean | true | Confetti on achievements |
showLangPicker | boolean | true | Show language selector |
Branding (White-Label)
| Option | Type | Default | Description |
|---|---|---|---|
brandColor | string | '#c9a227' | Primary brand color (hex) |
brandColorEnd | string | auto | Gradient end color |
brandFont | string | System font | Custom font family |
brandLogo | string | FAST logo | URL to your logo |
fabSize | number | 56 | FAB button size (px) |
fabIcon | string | 'bolt' | FAB icon: bolt, trophy, chart, gift, target, crown, star |
Advanced
| Option | Type | Description |
|---|---|---|
useShell | boolean | Enable modular Shell mode (default: false for backward compat) |
userToken | string | Signed user token (X-User-Token). Preferred; see Required for user-specific features above. |
sseToken | string | Deprecated alias for userToken (still accepted). |
currency | string | Currency code (default: EUR) |
currencySymbol | string | Override currency symbol |
languages | string[] | Available languages for picker |
onEvent | function | Callback for widget lifecycle events (challenge.purchased, challenge.purchase_failed, ...) |
onTokenExpired | function | Fires (max once per 30s) when the injected user token can no longer be renewed by the widget itself — mint a fresh token and call widget.update({ userToken }). |
Programmatic API
For full control, use the JavaScript API:
javascript
const widget = FastWidget.mount({
apiKey: 'pk_your_widget_key',
userId: 'player-42',
apiBase: 'https://api.fundedfast.eu/v1',
theme: 'dark',
mode: 'fab',
useShell: true,
brandColor: '#ff6600',
lang: 'de',
onEvent: (type, data) => {
console.log('Widget event:', type, data);
},
});
// Update config (e.g., when user changes)
widget.update({ userId: 'player-99' });
// Open the widget programmatically
widget.open();
// Remove the widget
widget.destroy();Mode change
Changing mode requires a full remount (different DOM placement). The update() method handles this automatically:
javascript
widget.update({ mode: 'sidebar' }); // returns a new widget instanceShell Mode
Enable Shell for the modular multi-module experience:
html
<!-- Optional, recommended: opens the API connection while the script loads. -->
<link rel="preconnect" href="https://api.fundedfast.eu" crossorigin>
<script
src="https://cdn.fundedfast.eu/fast-widget.iife.js"
data-fast-widget
data-api-key="pk_your_widget_key"
data-user-id="player-42"
data-api-base="https://api.fundedfast.eu/v1"
data-use-shell="true"
></script>Shell features:
- FAB with progress ring - Shows challenge completion percentage
- Days remaining badge - Countdown on the FAB
- Bottom navigation - Up to four tabs plus a More menu for additional modules
- SSE real-time updates - Auto-refreshes on relevant events
- Module-specific drawers - Full detail views per module
- History view - Summary tiles (programs / passed / paid out), Challenge vs Rewarded filters, and expandable entries with progress bars, P&L, and payout stats
- Keyboard navigation - Escape to close, Tab trap in drawers
FAB Progress Ring
The FAB displays a circular progress indicator:
- Active challenge: shows completion percentage (gold ring)
- Funded account: full ring (green)
- No active context: static icon
- Cooldown active: pause icon
Real-Time Updates
The Shell connects to the SSE stream and subscribes to events from all loaded modules. When a relevant event arrives (bet settled, reward granted, mission progress), the widget auto-refreshes the affected module without a full page reload.
Accessibility
The widget follows WCAG 2.1 AA guidelines:
- Focus trap inside card/drawer views (Tab cycles within widget)
- Escape key closes the widget
aria-labelon FAB and interactive elementsrole="tooltip"on FAB hover statearia-currenton active bottom nav item- All interactive elements are keyboard-accessible
- Reduced motion respected via CSS
Shadow DOM Isolation
The widget renders inside a Shadow DOM attached to a <div id="fast-widget-host"> element. This means:
- Widget styles do not leak into your page
- Your page styles do not affect the widget
- CSS custom properties set on the shadow host control theming
Widget Events
The onEvent callback receives lifecycle events:
| Event | Data | When |
|---|---|---|
widget.opened | {} | User opens widget |
widget.closed | {} | User closes widget |
widget.drawer_opened | { module: string } | Module drawer opened |
widget.theme_changed | { theme: string } | Theme toggled |
Security
The widget is designed so that a credential exposed in the browser can never be abused against other users:
- Public key = tenant reads only. The
pk_...key grants access to non-personal, tenant-level data (tiers, terms, leaderboard, loyalty tiers, widget config). On its own it cannot read or act on any/users/:id/*resource, cannot ingest bets, and cannot reach admin endpoints. - User token binds each request to one user. All user-specific reads and actions require a signed
userToken(X-User-Token), minted by your backend with the server key. FAST enforcestoken.uid === userId, so copying the public key from page source gets an attacker nothing — they cannot mint a token for, or act on, another user. - Money & irreversible actions are operator-approved. Buy, withdraw, refund, and cancel triggered from the widget call your backend's single
approvalUrl(the actiontypeis in the payload) and only proceed on{ approved: true }. If the URL is not configured, the widget action is refused (403 approval_required) on production tenants. Staging/sandbox tenants auto-approve when no URL is set, so demos work without an approval endpoint. - Least privilege end to end. The server key never reaches the browser; only
pk_...and a short-lived (1-hour) user token do. Tokens are user-scoped and time-limited. - Per-user rate limiting. User-token traffic is limited per user, not per IP, so one user cannot exhaust another's budget or bypass limits by rotating IPs.
- The widget never stores credentials in localStorage or cookies.
Going to production
On staging the widget may self-mint a user token with the public key (for frictionless demos). In production this is refused — your backend must mint the token and inject it as data-user-token. Complete this before flipping the tenant to production, or the widget's user features will return 403.
Bundle Size
The widget bundles to ~289KB (IIFE, minified). In Shell mode, modules are loaded dynamically based on tenant config, so only enabled modules add to the active footprint.
For maximum performance, serve the widget from a CDN with gzip/brotli compression (~80KB transferred).