Documentation Index Fetch the complete documentation index at: https://docs.fieldfunded.com/llms.txt
Use this file to discover all available pages before exploring further.
Build a Real-Time Odds Dashboard with Live Updates
Display live odds that update automatically without page refreshes. This guide builds a server-side polling layer that fetches odds from the FieldFunded API and pushes changes to connected browsers via WebSocket — giving your users a real-time experience.
Architecture
Your server polls FieldFunded at a controlled rate, diffs the odds against a local cache, and broadcasts only the changes to connected clients. This approach is more efficient than having every browser poll the API directly.
What You’ll Use
SDK Method Endpoint Purpose getEventOdds()GET /v1/events/{id}/oddsGet current odds for an event getLive()GET /v1/liveList in-play events getScores()GET /v1/scoresLive scores
Step 1: Server-Side Polling Engine
// server/poller.ts
import { FieldFundedSDK } from '@fieldfunded/sdk' ;
const ff = new FieldFundedSDK ({
apiKey: process . env . FIELDFUNDED_API_KEY ! ,
baseUrl: 'https://api.fieldfunded.com/v1' ,
});
interface OddsSnapshot {
eventId : string ;
markets : Map < string , Map < string , number >>; // market_id -> selection_id -> odds
updatedAt : number ;
}
const oddsCache = new Map < string , OddsSnapshot >();
export interface OddsChange {
eventId : string ;
marketId : string ;
marketName : string ;
selectionId : string ;
selectionName : string ;
oldOdds : number ;
newOdds : number ;
}
export async function pollEventOdds ( eventId : string ) : Promise < OddsChange []> {
const data = await ff . getEventOdds ( eventId );
const changes : OddsChange [] = [];
const previous = oddsCache . get ( eventId );
const newSnapshot : OddsSnapshot = {
eventId ,
markets: new Map (),
updatedAt: Date . now (),
};
for ( const market of data . markets || []) {
const selMap = new Map < string , number >();
for ( const sel of market . selections ) {
selMap . set ( sel . id , sel . odds );
// Detect changes
if ( previous ) {
const prevMarket = previous . markets . get ( market . id );
const prevOdds = prevMarket ?. get ( sel . id );
if ( prevOdds !== undefined && prevOdds !== sel . odds ) {
changes . push ({
eventId ,
marketId: market . id ,
marketName: market . name ,
selectionId: sel . id ,
selectionName: sel . name ,
oldOdds: prevOdds ,
newOdds: sel . odds ,
});
}
}
}
newSnapshot . markets . set ( market . id , selMap );
}
oddsCache . set ( eventId , newSnapshot );
return changes ;
}
export function getCachedOdds ( eventId : string ) : OddsSnapshot | undefined {
return oddsCache . get ( eventId );
}
Step 2: WebSocket Server
Use ws or Socket.IO to broadcast changes:
// server/ws.ts
import { WebSocketServer , WebSocket } from 'ws' ;
import { pollEventOdds , OddsChange } from './poller' ;
const wss = new WebSocketServer ({ port: 8080 });
// Track which events each client is watching
const subscriptions = new Map < WebSocket , Set < string >>();
wss . on ( 'connection' , ( ws ) => {
subscriptions . set ( ws , new Set ());
ws . on ( 'message' , ( raw ) => {
const msg = JSON . parse ( raw . toString ());
if ( msg . type === 'subscribe' ) {
subscriptions . get ( ws )?. add ( msg . eventId );
}
if ( msg . type === 'unsubscribe' ) {
subscriptions . get ( ws )?. delete ( msg . eventId );
}
});
ws . on ( 'close' , () => {
subscriptions . delete ( ws );
});
});
function broadcast ( eventId : string , changes : OddsChange []) {
for ( const [ ws , subs ] of subscriptions ) {
if ( subs . has ( eventId ) && ws . readyState === WebSocket . OPEN ) {
ws . send ( JSON . stringify ({ type: 'odds_update' , eventId , changes }));
}
}
}
// Poll tracked events every 15 seconds
setInterval ( async () => {
const trackedEvents = new Set < string >();
for ( const subs of subscriptions . values ()) {
for ( const id of subs ) trackedEvents . add ( id );
}
for ( const eventId of trackedEvents ) {
try {
const changes = await pollEventOdds ( eventId );
if ( changes . length > 0 ) {
broadcast ( eventId , changes );
}
} catch ( err ) {
console . error ( `Poll failed for ${ eventId } :` , err );
}
}
}, 15_000 );
Step 3: Frontend Client
// client/useOddsStream.ts
import { useEffect , useRef , useState , useCallback } from 'react' ;
interface OddsChange {
marketId : string ;
selectionId : string ;
oldOdds : number ;
newOdds : number ;
}
export function useOddsStream ( eventId : string ) {
const wsRef = useRef < WebSocket | null >( null );
const [ changes , setChanges ] = useState < OddsChange []>([]);
const connect = useCallback (() => {
const ws = new WebSocket ( 'ws://localhost:8080' );
ws . onopen = () => {
ws . send ( JSON . stringify ({ type: 'subscribe' , eventId }));
};
ws . onmessage = ( event ) => {
const msg = JSON . parse ( event . data );
if ( msg . type === 'odds_update' && msg . eventId === eventId ) {
setChanges ( msg . changes );
}
};
ws . onclose = () => {
// Reconnect after 3 seconds
setTimeout ( connect , 3000 );
};
wsRef . current = ws ;
}, [ eventId ]);
useEffect (() => {
connect ();
return () => wsRef . current ?. close ();
}, [ connect ]);
return changes ;
}
Step 4: Visual Odds Flashing
Make odds changes visible with a brief flash animation:
/* Flash green when odds increase, red when they decrease */
.odds-up {
animation : flash-green 1 s ease-out ;
}
.odds-down {
animation : flash-red 1 s ease-out ;
}
@keyframes flash-green {
0% { background-color : rgba ( 34 , 197 , 94 , 0.4 ); }
100% { background-color : transparent ; }
}
@keyframes flash-red {
0% { background-color : rgba ( 239 , 68 , 68 , 0.4 ); }
100% { background-color : transparent ; }
}
// OddsCell component
function OddsCell ({ odds , previousOdds } : { odds : number ; previousOdds ?: number }) {
const direction = previousOdds
? odds > previousOdds ? 'odds-up' : odds < previousOdds ? 'odds-down' : ''
: '' ;
return (
< span key = { odds } className = { direction } >
{ odds . toFixed (2)}
</ span >
);
}
Rate Limit Math
The server polls FieldFunded, not the browsers. Request volume depends on how many events you track:
Events tracked Poll interval Requests/day Monthly Plan 5 events 15s 28,800 864,000 Pro ($79) 5 events 30s 14,400 432,000 Pro ($79) 10 events 30s 28,800 864,000 Ultra ($149) 3 events 60s 4,320 129,600 Starter ($29)
The key optimization: only poll events that have at least one connected viewer. When no one is watching an event, stop polling it entirely. This can reduce API usage by 80% during off-peak hours.
Production Checklist
Get Your Free API Key Start building in 5 minutes — 10,000 free requests/month
See Pricing All plans compared side by side