Skip to content

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:

ModuleIDDescription
ChallengeschallengesChallenge progress, purchase, history
BonusesbonusesActive wagering bonuses, progress
MissionsmissionsMission objectives and progress
TournamentstournamentsActive tournaments, leaderboard
LoyaltyloyaltyPoints, tier, cashback status
LeaderboardleaderboardGlobal challenge leaderboard
BadgesbadgesAchievement badges
RewardsrewardsPending 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 one userId. Your backend mints it with your server key via POST /v1/sse/token and injects it here. Required for anything user-specific (progress, history, and all actions). FAST enforces token.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=token

Never 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 require data-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 onTokenExpired callback for tabs that outlive that cap — see Authentication.

Display Modes ​

ModeBehaviorUse Case
fab (default)Floating button, opens card on clickOverlay on existing sportsbook
sidebarEmbedded panel, always visibleDedicated sidebar slot
fullpageFull viewport takeoverStandalone 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 ​

OptionTypeDescription
apiKeystringPublic widget key (pk_...). Tenant-level reads only.
userIdstringYour user's identifier. Must match the user token.
apiBasestringFAST API base URL.

Required for user-specific features ​

OptionTypeDescription
userTokenstringSigned 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 ​

OptionTypeDefaultDescription
theme'dark' | 'light''dark'Color scheme
position'bottom-right' | 'bottom-left''bottom-right'FAB position
mode'fab' | 'sidebar' | 'fullpage''fab'Display mode
langstring'en'Language code (en, de, hr)
showLeaderboardbooleantrueShow leaderboard module
showBadgesbooleantrueShow achievements/badges
showRewardsbooleantrueShow rewards
showHistorybooleantrueShow challenge history
showShopbooleantrueShow challenge purchase
showConfettibooleantrueConfetti on achievements
showLangPickerbooleantrueShow language selector

Branding (White-Label) ​

OptionTypeDefaultDescription
brandColorstring'#c9a227'Primary brand color (hex)
brandColorEndstringautoGradient end color
brandFontstringSystem fontCustom font family
brandLogostringFAST logoURL to your logo
fabSizenumber56FAB button size (px)
fabIconstring'bolt'FAB icon: bolt, trophy, chart, gift, target, crown, star

Advanced ​

OptionTypeDescription
useShellbooleanEnable modular Shell mode (default: false for backward compat)
userTokenstringSigned user token (X-User-Token). Preferred; see Required for user-specific features above.
sseTokenstringDeprecated alias for userToken (still accepted).
currencystringCurrency code (default: EUR)
currencySymbolstringOverride currency symbol
languagesstring[]Available languages for picker
onEventfunctionCallback for widget lifecycle events (challenge.purchased, challenge.purchase_failed, ...)
onTokenExpiredfunctionFires (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 instance

Shell 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-label on FAB and interactive elements
  • role="tooltip" on FAB hover state
  • aria-current on 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:

EventDataWhen
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 enforces token.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 action type is 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).

BLACKLINE CONSULTING d.o.o.