> ## 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 WhatsApp Sports Alerts

> Automate sports alerts to your WhatsApp group: game starts, odds drops, final scores. Twilio + FieldFunded + node-cron.

# Build a WhatsApp Sports Alert System

Automate sports alerts for your friend group: game kickoffs, significant odds shifts, and final scores with settlement results.

## What You'll Use

| SDK Method         | Endpoint                     | Purpose             |
| ------------------ | ---------------------------- | ------------------- |
| `getEvents()`      | `GET /v1/events`             | Find upcoming games |
| `getEventOdds()`   | `GET /v1/events/{id}/odds`   | Track odds changes  |
| `getScores()`      | `GET /v1/scores`             | Get live scores     |
| `getEventResult()` | `GET /v1/events/{id}/result` | Get final result    |

## Prerequisites

* [Twilio account](https://www.twilio.com/) (free trial works)
* Twilio WhatsApp Sandbox activated
* Node.js 18+

```bash theme={null}
npm install @fieldfunded/sdk twilio node-cron
```

## Step 1: Initialize

```typescript theme={null}
import { FieldFundedSDK } from '@fieldfunded/sdk';
import twilio from 'twilio';
import cron from 'node-cron';

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

const sms = twilio(process.env.TWILIO_SID!, process.env.TWILIO_AUTH!);
const WHATSAPP_FROM = 'whatsapp:+14155238886'; // Twilio sandbox
const GROUP_NUMBERS = ['whatsapp:+351912345678']; // Your friends
```

## Step 2: Alert Type 1 — Game Starting Soon

```typescript theme={null}
async function checkKickoffs() {
  const events = await ff.getEvents({
    sport: 'soccer',
    starts_within: '15m', // Games starting in 15 minutes
    status: 'prematch',
  });

  for (const event of events.events) {
    const odds = await ff.getEventOdds(event.id);
    const main = odds.markets?.[0];

    let message = `⚽ *Starting Soon!*\n`;
    message += `${event.home_team} vs ${event.away_team}\n`;
    message += `🏟️ ${event.league}\n`;
    message += `⏰ ${new Date(event.start_time).toLocaleTimeString()}\n`;

    if (main) {
      const oddsStr = main.selections
        .map((s: any) => `${s.name}: ${s.odds}`)
        .join(' | ');
      message += `📊 ${oddsStr}`;
    }

    await sendToGroup(message);
  }
}
```

## Step 3: Alert Type 2 — Odds Movement

```typescript theme={null}
// Store previous odds in memory (or database for persistence)
const previousOdds = new Map<string, number>();

async function checkOddsMovement() {
  const live = await ff.getEvents({ status: 'live', sport: 'soccer' });

  for (const event of live.events.slice(0, 10)) {
    const odds = await ff.getEventOdds(event.id);
    const main = odds.markets?.[0];
    if (!main) continue;

    for (const sel of main.selections) {
      const key = `${event.id}-${sel.name}`;
      const prev = previousOdds.get(key);

      if (prev) {
        const change = Math.abs(sel.odds - prev) / prev;

        if (change > 0.1) { // 10% shift
          const direction = sel.odds > prev ? '📈' : '📉';
          await sendToGroup(
            `${direction} *Odds Alert*\n` +
            `${event.home_team} vs ${event.away_team}\n` +
            `${sel.name}: ${prev.toFixed(2)} → ${sel.odds.toFixed(2)} (${(change * 100).toFixed(0)}% shift)`
          );
        }
      }

      previousOdds.set(key, sel.odds);
    }
  }
}
```

## Step 4: Alert Type 3 — Final Score

```typescript theme={null}
async function checkFinalScores() {
  // Track events we've already alerted
  const alerted = new Set<string>();

  const events = await ff.getEvents({ status: 'ended', sport: 'soccer' });

  for (const event of events.events.slice(0, 5)) {
    if (alerted.has(event.id)) continue;

    const result = await ff.getEventResult(event.id);

    if (result.status === 'ended' && result.score) {
      await sendToGroup(
        `🏁 *Full Time*\n` +
        `${event.home_team} *${result.score.home}* - *${result.score.away}* ${event.away_team}\n` +
        `🏟️ ${event.league}`
      );
      alerted.add(event.id);
    }
  }
}
```

## Step 5: Send to Group

```typescript theme={null}
async function sendToGroup(message: string) {
  for (const number of GROUP_NUMBERS) {
    await sms.messages.create({
      from: WHATSAPP_FROM,
      to: number,
      body: message,
    });
  }
}
```

## Step 6: Schedule Everything

```typescript theme={null}
// Check for kickoffs every 5 minutes
cron.schedule('*/5 * * * *', checkKickoffs);

// Check odds movement every 2 minutes during live games
cron.schedule('*/2 * * * *', checkOddsMovement);

// Check final scores every 3 minutes
cron.schedule('*/3 * * * *', checkFinalScores);
```

## Rate Limit Math

| Check         | Frequency   | Requests/hour | Monthly  |
| ------------- | ----------- | ------------- | -------- |
| Kickoffs      | Every 5 min | 24            | \~17,280 |
| Odds movement | Every 2 min | 60            | \~43,200 |
| Final scores  | Every 3 min | 40            | \~28,800 |

Total: \~89,000/month → **Pro plan (\$59/mo)** covers it.

To fit in the free tier, reduce to checking only your favorite leagues and extend intervals.

<Card title="Events API Reference" icon="calendar" href="/api-reference/events/get-events">
  See event filters (starts\_within, status) →
</Card>

***

## Related Guides

* [Build a Telegram Bot](/guides/build-telegram-bot) — full bot with inline search
* [Build a Discord Bot](/guides/build-discord-bot) — odds via slash commands
* [Affordable Odds API](/compare/affordable-odds-api) — cost analysis per use case

<CardGroup cols={2}>
  <Card title="Get Your Free API Key" icon="key" href="/quickstart">
    Start building in 5 minutes — 10,000 free requests/month
  </Card>

  <Card title="See Pricing" icon="dollar-sign" href="/compare/pricing">
    All plans compared side by side
  </Card>
</CardGroup>
