Skip to main content

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 Bet Settlement Engine That Actually Works

Manual settlement breaks at scale. Every time a game ends, someone has to check results, match them against bets, handle edge cases (postponed, cancelled, retired), and process payouts. This guide shows you how to automate all of it.

What You’ll Use

SDK MethodEndpointPurpose
getSettlements()GET /v1/settlementsPoll for resolved markets
getEventResult()GET /v1/events/{id}/resultGet final score
checkBet()POST /v1/bets/checkVerify individual bet outcome
checkParlay()POST /v1/bets/check-parlayVerify parlay/accumulator outcome

Architecture

Game Ends → Poll getSettlements() every 60s → Match bets by market_id + selection_id → Resolve (won/lost/refund) → Update balance

Step 1: Set Up the SDK

import { FieldFundedSDK } from '@fieldfunded/sdk';

const client = new FieldFundedSDK({
  apiKey: process.env.FIELDFUNDED_API_KEY!,
  baseUrl: 'https://api.fieldfunded.com/v1',
});

Step 2: Poll for Settlements

FieldFunded’s settlement is gradual. Simple markets (1x2, Winner) resolve within 2-5 minutes of game end. Complex markets (player props, correct score) take 10-30 minutes.
async function pollSettlements() {
  const data = await client.getSettlements({ limit: 20 });

  for (const settlement of data.settlements) {
    console.log(
      `Event ${settlement.event_id}: ${settlement.settlement_status}`,
      `— ${settlement.resolved_markets_count}/${settlement.total_markets_count} markets`,
      `(${settlement.pending_markets_count} pending)`
    );

    // Process only when we have resolved markets
    if (settlement.resolved_markets_count > 0) {
      await processSettlement(settlement);
    }
  }
}

Step 3: Match Bets Against Resolved Markets

The key is matching by market_id and selection_id — these are unique identifiers that guarantee you’re resolving the correct bet.
async function processSettlement(settlement: any) {
  // Get all unresolved bets for this event from your database
  const pendingBets = await db.query(
    'SELECT * FROM bets WHERE event_id = $1 AND status = $2',
    [settlement.event_id, 'pending']
  );

  for (const bet of pendingBets) {
    // Use checkBet for precise resolution
    const result = await client.checkBet({
      selections: [{
        event_id: bet.event_id,
        market: bet.market_name,
        outcome: bet.outcome,
        odds: bet.odds,
        stake: bet.stake,
        market_id: bet.market_id,       // Use IDs for 100% accuracy
        selection_id: bet.selection_id,
      }],
    });

    const betResult = result.results[0];

    if (betResult.result === 'pending') continue; // Not yet resolved

    await resolveBet(bet, betResult);
  }
}

Step 4: Resolve and Pay

async function resolveBet(bet: any, result: any) {
  switch (result.result) {
    case 'won':
      // Payout = stake × odds (returned by API)
      await db.query(
        'UPDATE bets SET status = $1, payout = $2 WHERE id = $3',
        ['won', result.payout, bet.id]
      );
      await db.query(
        'UPDATE users SET balance = balance + $1 WHERE id = $2',
        [result.payout, bet.user_id]
      );
      break;

    case 'lost':
      await db.query(
        'UPDATE bets SET status = $1, payout = 0 WHERE id = $2',
        ['lost', bet.id]
      );
      break;

    case 'refund':
      // Return original stake
      await db.query(
        'UPDATE bets SET status = $1, payout = $2 WHERE id = $3',
        ['refunded', bet.stake, bet.id]
      );
      await db.query(
        'UPDATE users SET balance = balance + $1 WHERE id = $2',
        [bet.stake, bet.user_id]
      );
      break;
  }
}

Step 5: Handle Parlays

For parlays, use checkParlay(). The API handles void legs automatically (void leg = odds 1.0, recalculates combined odds).
async function resolveParlay(parlay: any) {
  const result = await client.checkParlay({
    legs: parlay.legs.map((leg: any) => ({
      event_id: leg.event_id,
      market: leg.market_name,
      outcome: leg.outcome,
      odds: leg.odds,
      market_id: leg.market_id,
      selection_id: leg.selection_id,
    })),
    stake: parlay.stake,
  });

  // result.payout contains recalculated odds if any leg was voided
  console.log(`Parlay status: ${result.payout.status}`);
  console.log(`Combined odds: ${result.payout.combined_odds}`);
  console.log(`Payout: ${result.payout.payout}`);
}

Step 6: Edge Cases

Postponed Games

FieldFunded follows the 48-hour rule: if a game is postponed but resumes within 48 hours, bets remain active. After 48 hours, all markets are refunded.

Cancelled / Walkover

Immediate refund. The getEventResult() endpoint returns status: 'walkover' or status: 'cancelled'.
const result = await client.getEventResult(eventId);
if (['walkover', 'cancelled', 'abandoned'].includes(result.status)) {
  await refundAllBets(eventId);
}

Timeout Protection

If some markets haven’t resolved after 30 minutes, they auto-refund. Your settlement loop simply picks up the refund status on the next poll.

Step 7: Run the Loop

import cron from 'node-cron';

// Poll every 60 seconds
cron.schedule('* * * * *', async () => {
  try {
    await pollSettlements();
  } catch (err) {
    console.error('Settlement poll failed:', err);
  }
});

Production Checklist

  • Use market_id + selection_id for matching (not market names)
  • Make resolution idempotent (re-processing won’t double-pay)
  • Log every resolution for audit trail
  • Handle SDK errors (RateLimitError, ServiceUnavailableError)
  • Set up alerts if settlement polling stops

Settlement API Reference

See full endpoint documentation →

Get Your Free API Key

Start building in 5 minutes — 10,000 free requests/month