Fortnite Chapter 7, Season 4 (“Override”) is scheduled to end on October 31, 2026, though some trackers list November 1 because Epic’s Battle Pass timer crosses time zones. Leak sources don’t even agree on what comes after. One leaker points to November 28, 2026 for Chapter 8. Another says December 5. A third camp expects a short mini-season to bridge the gap starting November 1. If you’ve spent any time refreshing countdown sites this month, you already know the frustration: every tracker gives you a different number, and none of them tell you why.

If you’re trying to pin down the next Fortnite season with any real confidence, a countdown widget won’t get you there. This tutorial builds something different. You’ll write a Python tool called Season Watch that pulls live data from the public Fortnite-API.com endpoint, logs every leak claim you feed it with a source and timestamp, and scores each claim’s reliability against what Epic actually ships. When two leakers disagree, the tool tells you which one has a better track record instead of just showing you both numbers side by side. By the end you’ll have a working CLI, a local database, optional Discord alerts, and a scheduled job that keeps itself updated without you touching a browser.

Why Next Fortnite Season Leak Dates Keep Conflicting

Epic Games rarely confirms a chapter’s release date until days before launch. In the gap, leak communities fill in the blanks using datamined files, patch notes, and pattern-matching against past season lengths. That process produces good guesses, not facts, and different leakers often pull from different partial data sets. The result is exactly what’s happening right now with Chapter 8: two credible names in the leak community, two different dates, and no way for a casual player to know which one to trust.

Season length itself adds noise. Chapter 7 Season 1 (“Pacific Break”) ran from November 29, 2025 to March 19, 2026, a run of 110 days. Season 4 (“Override”) launched August 20, 2026 and is set to close after roughly 73 days. Seasons don’t follow a fixed cadence, so extrapolating “next season starts X days after this one” rarely holds up. A verification tool that checks claims against real signals beats a formula that guesses from history.

There’s also a technical wrinkle worth building around: Fortnite’s in-game shop refreshes daily, so any script that watches for “site changes” as a proxy for “season changed” will trigger false positives constantly. Season Watch avoids that trap by checking the news and Message of the Day (MOTD) feed instead of the shop, a distinction covered in Step 4.

History backs up the skepticism, too. Chapter 7 itself didn’t open quietly, it opened through the “Zero Hour” live event on November 29, 2025, a finale that reportedly drew 10.5 million in-game players before the Pacific Break map went live. Epic tends to mark chapter transitions with a visible, scheduled event rather than a silent server update, which means a real Chapter 8 launch should come with its own announced moment, not just a patch note buried in a datamine. That’s one more reason a hash-diff on Epic’s own news feed beats trusting a single leak post: an actual chapter change tends to leave a loud, public trail.

Source / claimReported Chapter 8 windowBasis
fortnitecountdown.gg trackerOverride ends Nov 1, 2026Live Battle Pass listing
techwiser.com trackerOverride ends Oct 31, 2026Live Battle Pass listing
Leaker “HYPEX” (per timesaver.gg)Nov 28, 2026Datamine / pattern leak
Separate leak source (per timesaver.gg)Dec 5, 2026Datamine / pattern leak
Mini-season theory (per accountshark.net, fnboosting.com)Bridge season from Nov 1, 2026Leaked update schedule

That table alone explains why a single countdown number is misleading right now. Season Watch treats each row as a claim with a source, not as ground truth, and updates its confidence score every time new data comes in.

Prerequisites: What You Need Before You Start

You don’t need a Fortnite developer account or an API key for this build. Fortnite-API.com serves its core endpoints, including the news and cosmetics feeds, without authentication, which keeps setup short. The heaviest dependency here is Python itself, and everything else in the stack (SQLite, a webhook POST, a scheduled job) is deliberately boring technology, chosen so the project keeps working without maintenance for months at a time. Here’s what to have installed first.

  • Python 3.12 or newer (the examples in this tutorial were tested on 3.12)
  • pip, bundled with modern Python installs
  • SQLite 3, bundled with Python’s standard library (no separate install needed)
  • A terminal or shell (bash, zsh, or PowerShell all work)
  • A free Discord server if you want webhook alerts (optional, Step 9)
  • A GitHub account if you want to schedule the tool with Actions instead of local cron (optional, Step 10)
  • About 90 minutes for the full build, testing included

Confirm Python is ready before moving on:

python3 --version
# Expected output: Python 3.12.x or later
pip3 --version

Step 1: Scope the Project and Pick Your Data Sources

Before writing code, decide what “verification” means for this project. Season Watch checks two things: whether Epic’s own API shows a real content change (the strongest possible signal), and whether multiple independent leak sources converge on the same date within a small tolerance window, say 48 hours. A claim backed by three sources within two days of each other earns a higher confidence score than a single outlier claim.

Create your working directory and a place to keep the pieces organized:

mkdir season-watch && cd season-watch
mkdir -p data logs
touch season_watch.py db.py api_client.py scoring.py alerts.py

Keep each file focused: db.py handles SQLite, api_client.py wraps Fortnite-API.com calls, scoring.py holds the confidence math, alerts.py sends Discord messages, and season_watch.py is the CLI entry point that ties them together.

Step 2: Set Up the Python Project and Virtual Environment

Isolate dependencies in a virtual environment so this project doesn’t collide with anything else on your machine:

python3 -m venv .venv
source .venv/bin/activate   # Windows: .venv\Scripts\activate
pip install --upgrade pip
pip install requests python-dotenv tabulate

requests handles the HTTP calls to Fortnite-API.com, python-dotenv lets you keep a Discord webhook URL out of source control, and tabulate formats the CLI report into a readable table. Freeze the versions once everything works so reruns stay reproducible:

pip freeze > requirements.txt

Create a .env file for secrets and add it to .gitignore immediately, before you forget:

echo "DISCORD_WEBHOOK_URL=" > .env
echo ".env" >> .gitignore
echo ".venv/" >> .gitignore
echo "data/*.db" >> .gitignore

Step 3: Build the SQLite Schema for Leaks and Snapshots

Two tables cover everything the tool needs: one for leak claims, one for API snapshots that record what Epic’s feed actually showed at a given time. Python’s built-in sqlite3 module handles all of it without a separate database server. Open db.py and add the schema:

import sqlite3
from pathlib import Path

DB_PATH = Path("data/season_watch.db")

def get_connection():
    conn = sqlite3.connect(DB_PATH)
    conn.row_factory = sqlite3.Row
    return conn

def init_db():
    conn = get_connection()
    conn.executescript("""
    CREATE TABLE IF NOT EXISTS leaks (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        source TEXT NOT NULL,
        claimed_date TEXT NOT NULL,
        reported_at TEXT NOT NULL,
        chapter_label TEXT,
        notes TEXT
    );

    CREATE TABLE IF NOT EXISTS snapshots (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        checked_at TEXT NOT NULL,
        news_hash TEXT,
        motd_title TEXT,
        changed_from_previous INTEGER DEFAULT 0
    );

    CREATE TABLE IF NOT EXISTS source_accuracy (
        source TEXT PRIMARY KEY,
        correct_calls INTEGER DEFAULT 0,
        total_calls INTEGER DEFAULT 0
    );
    """)
    conn.commit()
    conn.close()

if __name__ == "__main__":
    init_db()
    print("Database initialized at", DB_PATH)

Run it once to create the file:

python3 db.py
# Output: Database initialized at data/season_watch.db

The source_accuracy table is what lets the tool learn over time. Every time a season actually changes, you’ll update this table with which leak sources called it correctly, which feeds directly into the confidence scoring in Step 6.

Step 4: Write a Fortnite-API.com Client Wrapper

Fortnite-API.com exposes a news endpoint at /v2/news/br that returns the current Message of the Day content and a hash tied to that content. That hash is the signal you want, not the shop endpoint, which rotates daily regardless of season status and will flood your logs with noise if you watch it instead.

import requests

BASE_URL = "https://fortnite-api.com/v2"
TIMEOUT = 10

class FortniteAPIError(Exception):
    pass

def get_news():
    resp = requests.get(f"{BASE_URL}/news/br", timeout=TIMEOUT)
    if resp.status_code == 429:
        raise FortniteAPIError("Rate limited, back off before retrying")
    if resp.status_code != 200:
        raise FortniteAPIError(f"Unexpected status {resp.status_code}")
    payload = resp.json()
    data = payload.get("data", {})
    return {
        "hash": data.get("hash"),
        "date": data.get("date"),
        "motd_title": (data.get("motds") or [{}])[0].get("title", ""),
    }

Test the client on its own before wiring it into anything else:

python3 -c "from api_client import get_news; print(get_news())"
# Example output:
# {'hash': 'adc0708e9786553a3a48e5c5a8d8631fd1d15a6c', 'date': '2026-09-29T15:31:41Z', 'motd_title': 'Fortnite: Override Is Here!'}

A changed hash and a new motd_title mean Epic pushed a real content update. That’s your strongest, most objective signal, stronger than any leak claim, because it comes straight from the source.

Step 5: Seed Your Leak Database With Current Claims

Add a small helper to insert leak claims as you find them. Treat this like a running log: every time you read a leak post, you add a row instead of trusting your memory of “someone said November-something.”

from db import get_connection
from datetime import datetime, timezone

def add_leak(source, claimed_date, chapter_label="", notes=""):
    conn = get_connection()
    conn.execute(
        "INSERT INTO leaks (source, claimed_date, reported_at, chapter_label, notes) "
        "VALUES (?, ?, ?, ?, ?)",
        (source, claimed_date, datetime.now(timezone.utc).isoformat(), chapter_label, notes),
    )
    conn.commit()
    conn.close()

if __name__ == "__main__":
    add_leak("HYPEX", "2026-11-28", "Chapter 8", "Reported via timesaver.gg roundup")
    add_leak("Unnamed leaker", "2026-12-05", "Chapter 8", "Reported via timesaver.gg roundup")
    add_leak("fortnitecountdown.gg", "2026-11-01", "Override end", "Live Battle Pass listing")
    add_leak("techwiser.com", "2026-10-31", "Override end", "Live Battle Pass listing")
    print("Seed data inserted")

Run this file once to populate the table, then add new claims as they surface. The notes field matters more than it looks: six months from now you won’t remember which roundup a date came from, and the scoring model needs the source field to stay consistent (use the same spelling every time you cite the same leaker).

Step 6: Build the Confidence-Scoring Algorithm

This is the core of the project. The scoring function clusters claims that fall within a tolerance window of each other, then ranks clusters by combined source weight. A source’s weight starts at 1.0 and adjusts based on its track record in the source_accuracy table.

from datetime import datetime
from db import get_connection

TOLERANCE_DAYS = 2

def _parse(d):
    return datetime.strptime(d, "%Y-%m-%d")

def get_source_weight(conn, source):
    row = conn.execute(
        "SELECT correct_calls, total_calls FROM source_accuracy WHERE source = ?",
        (source,),
    ).fetchone()
    if not row or row["total_calls"] == 0:
        return 1.0  # neutral weight for unproven sources
    return 0.5 + (row["correct_calls"] / row["total_calls"])

def score_claims(chapter_label):
    conn = get_connection()
    rows = conn.execute(
        "SELECT source, claimed_date FROM leaks WHERE chapter_label = ?",
        (chapter_label,),
    ).fetchall()

    clusters = []
    for row in rows:
        date = _parse(row["claimed_date"])
        placed = False
        for cluster in clusters:
            if abs((cluster["anchor"] - date).days) <= TOLERANCE_DAYS:
                cluster["members"].append(row["source"])
                placed = True
                break
        if not placed:
            clusters.append({"anchor": date, "members": [row["source"]]})

    results = []
    for cluster in clusters:
        weight = sum(get_source_weight(conn, s) for s in cluster["members"])
        results.append({
            "date": cluster["anchor"].strftime("%Y-%m-%d"),
            "sources": cluster["members"],
            "confidence": round(weight, 2),
        })
    conn.close()
    return sorted(results, key=lambda r: r["confidence"], reverse=True)

Run it against the seed data from Step 5:

python3 -c "from scoring import score_claims; print(score_claims('Chapter 8'))"
# Example output:
# [{'date': '2026-11-28', 'sources': ['HYPEX'], 'confidence': 1.0},
#  {'date': '2026-12-05', 'sources': ['Unnamed leaker'], 'confidence': 1.0}]

With only two claims and no overlap, both land as separate clusters with equal, unproven confidence. That's the honest answer right now: nobody has converged on a single Chapter 8 date yet. As more sources report in, or as one leaker builds a track record of being right, the scores will separate and give you an actual signal instead of a coin flip.

Reading the Confidence Score: A Worked Example

The two-claim output above isn't very interesting on its own, so it helps to see what happens once more data arrives. Say a third, independent leak account posts a Chapter 8 date of November 27, one day off HYPEX's November 28 claim, and well within the two-day TOLERANCE_DAYS window set in Step 6. Add it the same way you added the seed rows:

from db import get_connection
from datetime import datetime, timezone

conn = get_connection()
conn.execute(
    "INSERT INTO leaks (source, claimed_date, reported_at, chapter_label, notes) "
    "VALUES (?, ?, ?, ?, ?)",
    ("Third leak account", "2026-11-27", datetime.now(timezone.utc).isoformat(),
     "Chapter 8", "Independent post, one day off HYPEX"),
)
conn.commit()
conn.close()

Run score_claims() again and the November 27-28 cluster now merges into a single entry with a combined confidence of roughly 2.0 (two neutral-weight sources added together), clearly ahead of the lone December 5 claim sitting at 1.0. That gap is the entire point of the exercise: instead of a coin flip between two dates, you now have a number showing which claim has independent corroboration and which one is still standing alone. This is also exactly how the tool should behave when a genuinely credible date starts gathering agreement from unrelated accounts over the next few weeks.

Step 7: Detect Real Epic Updates With Hash Snapshots

Every time Season Watch runs, it should record the current news hash and compare it to the last one saved. A changed hash paired with a new motd_title is the closest thing to ground truth you'll get before Epic posts an official announcement.

from datetime import datetime, timezone
from db import get_connection
from api_client import get_news

def take_snapshot():
    conn = get_connection()
    last = conn.execute(
        "SELECT news_hash FROM snapshots ORDER BY id DESC LIMIT 1"
    ).fetchone()
    last_hash = last["news_hash"] if last else None

    news = get_news()
    changed = 1 if (last_hash and news["hash"] != last_hash) else 0

    conn.execute(
        "INSERT INTO snapshots (checked_at, news_hash, motd_title, changed_from_previous) "
        "VALUES (?, ?, ?, ?)",
        (datetime.now(timezone.utc).isoformat(), news["hash"], news["motd_title"], changed),
    )
    conn.commit()
    conn.close()
    return changed, news

if __name__ == "__main__":
    changed, news = take_snapshot()
    label = "CHANGED" if changed else "no change"
    print(f"[{label}] {news['motd_title']}")

The first run will never show "CHANGED" since there's no prior snapshot to compare against. That's expected. The value shows up on the second and later runs, especially once you've scheduled the script (Step 10) to check every few hours through late October and November.

Step 8: Build the CLI Report Command

Tie the pieces together in season_watch.py so a single command gives you a readable status report:

import argparse
from tabulate import tabulate
from scoring import score_claims
from snapshot import take_snapshot

def report(chapter_label):
    changed, news = take_snapshot()
    print(f"Live MOTD: {news['motd_title']}")
    print(f"Content changed since last check: {'yes' if changed else 'no'}\n")

    results = score_claims(chapter_label)
    table = [[r["date"], ", ".join(r["sources"]), r["confidence"]] for r in results]
    print(tabulate(table, headers=["Claimed date", "Sources", "Confidence"], tablefmt="github"))

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--chapter", default="Chapter 8")
    args = parser.parse_args()
    report(args.chapter)

Run it:

python3 season_watch.py --chapter "Chapter 8"

# Example output:
# Live MOTD: Fortnite: Override Is Here!
# Content changed since last check: no
#
# | Claimed date | Sources        | Confidence |
# |---------------|-----------------|------------|
# | 2026-11-28    | HYPEX           | 1.0        |
# | 2026-12-05    | Unnamed leaker  | 1.0        |

That's a working verification report in under 100 lines of code. Everything from here is about making it check itself automatically and tell you when something's actually worth paying attention to.

Step 9: Add Discord Webhook Alerts for Conflicts

Create a webhook in your Discord server (Server Settings, Integrations, Webhooks, New Webhook) and paste the URL into your .env file from Step 2. Then wire up alerts.py:

import os
import requests
from dotenv import load_dotenv

load_dotenv()
WEBHOOK_URL = os.getenv("DISCORD_WEBHOOK_URL")

def send_alert(message):
    if not WEBHOOK_URL:
        print("No webhook configured, skipping alert:", message)
        return
    resp = requests.post(WEBHOOK_URL, json={"content": message}, timeout=10)
    if resp.status_code not in (200, 204):
        print(f"Webhook failed with status {resp.status_code}: {resp.text}")

Then trigger it from the report function whenever a real content change appears, or when the top two confidence scores are within 0.3 of each other (a genuine conflict, not a settled answer):

from alerts import send_alert

def check_and_alert(chapter_label):
    changed, news = take_snapshot()
    if changed:
        send_alert(f"Epic pushed a content update: {news['motd_title']}")

    results = score_claims(chapter_label)
    if len(results) >= 2 and abs(results[0]["confidence"] - results[1]["confidence"]) < 0.3:
        send_alert(
            f"{chapter_label} leak dates still conflict: "
            f"{results[0]['date']} ({results[0]['confidence']}) vs "
            f"{results[1]['date']} ({results[1]['confidence']})"
        )

Right now, with Nov. 28 and Dec. 5 tied at equal confidence, this would fire immediately, which is correct: that's a real, current conflict worth flagging.

Keep Your Webhook URL and API Usage Secure

A Discord webhook URL is a bearer credential. Anyone who gets hold of it can post messages into your channel as "Season Watch," and there's no username or password check standing in the way. Treat it exactly like an API key: keep it in .env, keep .env out of version control (Step 2 already added it to .gitignore, double-check that before your first commit), and never paste it into a public gist, a Discord message, or a screenshot while writing up your own notes on the project.

If you ever suspect the URL leaked, regenerate it immediately from Discord's Integrations settings. The old one stops working the moment you do, which is the entire point of rotating a credential instead of trying to "just be more careful" with it. The same logic applies if you move alerts to GitHub Actions in Step 10: store the URL as a repository secret, never as a plain environment variable in the workflow file itself, since anyone who can read the YAML can read a hardcoded value.

On the API side, Season Watch only ever reads public, non-personal game data (news text, cosmetic names, MOTD titles). It never touches an Epic account, a player's friends list, or anything requiring login. That keeps the project outside the kind of data-handling questions that come up with tools that scrape player-identifiable information, and it's worth keeping that boundary in place if you extend the project later. Respect the rate limits from the troubleshooting table below, too: hammering a free community API with requests every few seconds is how public endpoints like this one end up requiring authentication for everyone.

Step 10: Automate It With Cron or GitHub Actions

Option A: Local cron

On Linux or macOS, edit your crontab to run the check every 3 hours:

crontab -e
# Add this line:
0 */3 * * * cd /path/to/season-watch && .venv/bin/python season_watch.py --chapter "Chapter 8" >> logs/cron.log 2>&1

Option B: GitHub Actions

If you'd rather not leave a machine running, schedule it in a repo instead using GitHub Actions. Create .github/workflows/season-watch.yml:

name: Season Watch
on:
  schedule:
    - cron: "0 */3 * * *"
  workflow_dispatch: {}

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install -r requirements.txt
      - run: python season_watch.py --chapter "Chapter 8"
        env:
          DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}

Add DISCORD_WEBHOOK_URL as a repository secret under Settings, Secrets and variables, Actions. Note the SQLite database won't persist between Actions runs unless you commit it back to the repo or push it to external storage, which is worth knowing before you rely on this path for long-term scoring history.

Step 11: Build a Leak-Source Accuracy Scorecard

This is what separates a real verification tool from a glorified spreadsheet: once a season actually launches, you go back and grade every source that made a claim about it. Add a grading function:

from datetime import datetime
from db import get_connection

def grade_chapter(chapter_label, actual_date, tolerance_days=2):
    conn = get_connection()
    actual = datetime.strptime(actual_date, "%Y-%m-%d")
    rows = conn.execute(
        "SELECT source, claimed_date FROM leaks WHERE chapter_label = ?",
        (chapter_label,),
    ).fetchall()

    for row in rows:
        claimed = datetime.strptime(row["claimed_date"], "%Y-%m-%d")
        correct = 1 if abs((claimed - actual).days) <= tolerance_days else 0
        conn.execute(
            "INSERT INTO source_accuracy (source, correct_calls, total_calls) "
            "VALUES (?, ?, 1) "
            "ON CONFLICT(source) DO UPDATE SET "
            "correct_calls = correct_calls + ?, total_calls = total_calls + 1",
            (row["source"], correct, correct),
        )
    conn.commit()
    conn.close()
    print(f"Graded {len(rows)} claims against actual date {actual_date}")

Run this once Chapter 8's real date is confirmed, and every future score_claims() call will weight that source's future guesses accordingly. After two or three seasons, you'll have a genuinely useful reliability ranking instead of a gut feeling about which leaker to trust.

Closing the Loop When Epic Confirms a Date

The grading function from Step 11 only pays off if you actually run it, and it's easy to build a tool like this, get it working, then forget the follow-up step once the excitement of launch day takes over. Treat it as a short checklist the moment Epic's news feed hash changes and a new chapter goes live: confirm the real launch date from Epic's own post rather than a tracker site, run grade_chapter() with that date against every claim tagged for the chapter, then check the updated source_accuracy table to see which leakers actually called it.

From there, reset for the next cycle. Update chapter_label in your seed script to the next chapter or season name, and the same database keeps accumulating history instead of starting from zero. Over a year of Fortnite seasons, that turns Season Watch from a one-off script into a standing record of which sources are worth reading closely the next time a release date is up for debate, which is a more durable payoff than any single correct guess.

Step 12: Test the Full Pipeline End to End

Before trusting scheduled runs, walk through the whole flow manually once:

  1. Run python3 db.py and confirm the database file appears in data/
  2. Run python3 seed_leaks.py (or your seed script) and check row counts with a quick SELECT
  3. Run python3 season_watch.py --chapter "Chapter 8" twice in a row. The second run should show "no change" unless Epic pushed something in between
  4. Temporarily set DISCORD_WEBHOOK_URL and confirm a test message lands in your channel
  5. Simulate a grading pass with a fake past date to confirm source_accuracy updates correctly

If all five checks pass, the scheduled job from Step 10 is safe to leave running unattended.

Common Pitfalls When Tracking the Next Fortnite Season

A handful of mistakes show up repeatedly in projects like this. Watch for these before they cost you a false alert or a corrupted dataset.

  • Watching the shop endpoint instead of news. The shop rotates every day regardless of season status, so it will report "change" constantly and drown out the real signal.
  • Treating one leak source as confirmed. Even well-known leakers get contradicted by other well-known leakers, as the current Nov. 28 vs. Dec. 5 split shows. Weight, don't trust blindly.
  • Ignoring time zones. Battle Pass end times are set against a specific time zone internally, and trackers that display local time without conversion will show dates a day apart for the same underlying timestamp.
  • Hardcoding dates in source. If you bake "Oct 31, 2026" directly into your script instead of pulling it from a claim or the live API, the tool stops being a verifier and becomes another static countdown page.
  • Skipping the tolerance window. Comparing dates for exact equality instead of a range (Step 6's TOLERANCE_DAYS) will split genuinely-agreeing sources into separate, artificially weaker clusters.
  • Not persisting snapshots. Without a history table, you can't tell whether a hash "changed" compared to yesterday or five minutes ago, which matters when you're debugging a false alert.
  • Forgetting to grade past seasons. Step 11's scorecard only works if you actually run it after each launch. Skip it for a few cycles and every source stays stuck at the same neutral starting weight, and the whole point of tracking accuracy over time quietly stops happening.

Troubleshooting Guide

Here's what tends to break, and the fix for each. Most of these show up in the first week of running the tool, usually during initial setup or the first unattended cron cycle, rather than months into steady use, so don't be surprised if you hit two or three of these on day one.

SymptomLikely causeFix
FortniteAPIError: Unexpected status 403Some hosting environments get blocked by upstream Cloudflare rulesAdd a standard User-Agent header to your requests.get call, and check Epic's status page in case the API backend itself is degraded
Rate limited, back off before retryingPolling too frequently, or many parallel jobs hitting the same IPIncrease your cron interval to at least every 2-3 hours
sqlite3.OperationalError: database is lockedTwo processes writing to the DB at once (cron overlap)Add a lock file check or reduce overlapping job frequency
JSONDecodeError on get_news()API returned an HTML error page instead of JSON, often during an outageWrap resp.json() in a try/except and log the raw response text
Discord webhook returns 401Webhook URL was regenerated or deleted in Discord's settingsCreate a new webhook and update .env
Discord webhook returns 404URL was copied incorrectly, often missing the token portionRe-copy the full URL from Discord's webhook settings page
Cron job never runsCrontab uses a different PATH than your interactive shellUse full absolute paths to python and your script, as shown in Step 10
GitHub Actions run succeeds but sends no alertRepository secret name doesn't match the env var in the workflow fileConfirm the secret is named exactly DISCORD_WEBHOOK_URL
Confidence scores stay at 1.0 foreverNo grading has run yet, so every source still has neutral weightRun grade_chapter() as soon as a season's real date is confirmed

Advanced Tips for Scaling Your Season Watcher

Once the base tool works, a few upgrades make it more resilient without much extra code. Cache the last successful API response to disk so a brief outage doesn't blank your report. Add exponential backoff around the requests.get call instead of a flat timeout, since Fortnite-API.com occasionally throttles during major content drops when traffic spikes. If you're tracking more than one chapter's worth of claims at a time, add a status column to the leaks table (active, resolved, wrong) so old noise doesn't pollute new scoring runs.

For alerting, Discord isn't your only option. The same send_alert pattern works for Slack incoming webhooks or even email through a transactional API, since all three accept a simple POST with a message body. If you want a lightweight dashboard instead of a chat alert, dump score_claims() output to a static JSON file on every cron run and point a small HTML page at it, no server required.

Consider also tracking Fortnitemares-style event windows the same way. Fortnitemares 2026 is confirmed for October 1, and mid-season events like this often get their own conflicting leak timelines, so the same scoring model applies without modification, just a different chapter_label.

A few smaller upgrades pay off once you're running this for more than one season cycle. Write a handful of pytest cases around score_claims() using fixed, fake dates so a future refactor doesn't silently break the clustering math. Add a --dry-run flag to season_watch.py that prints what an alert would say without actually posting it, useful when you're tuning the 0.3 confidence-gap threshold and don't want to spam your own channel while testing. And if you're tracking a game with regional launch staggering, store timestamps in UTC everywhere internally and only convert to a local display zone at the very last print statement, which avoids the exact time zone bug listed in the pitfalls section above.

Fortnite Season History: How Long Seasons Actually Run

Season length data is useful context for judging whether a leaked date is even plausible. Recent Chapter 7 seasons show real variation, which is exactly why pattern-based guessing fails as often as it succeeds.

Chapter / SeasonNameStart dateScheduled end dateApprox. length
Chapter 7, Season 1Pacific BreakNov 29, 2025Mar 19, 2026110 days
Chapter 7, Season 4OverrideAug 20, 2026Oct 31, 202673 days

A 37-day gap between the longest and shortest season in the same chapter means any leak claiming "the next season always runs X weeks" should be treated with the same skepticism as an unverified release date. Feed both data points into your own judgment, not a fixed formula.

Complete Project Structure

Once every step above is done, your directory should look like this:

season-watch/
├── .env
├── .gitignore
├── requirements.txt
├── db.py
├── api_client.py
├── snapshot.py
├── scoring.py
├── alerts.py
├── season_watch.py
├── seed_leaks.py
├── data/
│   └── season_watch.db
└── logs/
    └── cron.log

That's a full, working verification pipeline for tracking the next Fortnite season: a local database, a real API client, a scoring model that improves with every graded season, optional chat alerts, and a scheduled job that checks itself every few hours without any manual refreshing. None of it depends on a third-party tracker site staying online or a leaker staying active. The data lives in your own SQLite file, the scoring logic is plain Python you can read end to end, and the only external dependency that matters is Fortnite-API.com's public news endpoint, which has stayed free and unauthenticated through every chapter covered in this tutorial.

Frequently Asked Questions

When does Fortnite Chapter 7 Season 4 (Override) end?
Current tracker listings put the end date at October 31, 2026, though a few sites show November 1 due to time zone differences in how the Battle Pass countdown displays.

Is Fortnite Chapter 8 officially confirmed?
Not as of September 30, 2026. Epic hasn't posted an official date. Everything circulating, including the Nov. 28 and Dec. 5, 2026 windows, comes from leak sources, not Epic's own channels.

Why do leak sources disagree on the next Fortnite season's release date?
Leakers work from partial datamined files and pattern-matching against past seasons rather than a confirmed schedule, so two reputable sources can reach different conclusions from different fragments of evidence.

Do I need an API key for Fortnite-API.com?
No, the core news and cosmetics endpoints used in this tutorial are open without authentication. Heavier, personalized endpoints may require a key, but this project doesn't touch those.

How often should Season Watch poll the API?
Every 2-3 hours is enough for a season-change signal and stays well under any reasonable rate limit. Polling every few minutes adds load without adding useful information, since Epic doesn't push content that often.

Can I get in trouble for using the Fortnite API this way?
Fortnite-API.com is a community-run, publicly documented API built specifically for reading public game data like news and cosmetics. This tutorial only reads public information and doesn't touch your Epic account, automate gameplay, or interact with Epic's own private systems.

What's the difference between a mini-season and a full chapter?
A mini-season is a short, often thematically light bridge season Epic sometimes runs between two full chapters. Leak sources currently expect a possible mini-season starting around November 1, 2026, ahead of whatever full Chapter 8 launch date turns out to be accurate.

Can I adapt this tool for other games with similar leak-date confusion?
Yes. The scoring and snapshot logic doesn't reference anything Fortnite-specific beyond the API client in Step 4. Swap in a different game's public API or RSS feed and the rest of the pipeline works the same way.

What happens if Epic delays Chapter 8 past both leaked dates?
Nothing breaks. Season Watch doesn't assume either claim is correct, it just tracks them. If the real launch lands on a third date entirely, grade_chapter() in Step 11 marks both existing claims as misses and logs the actual date once you enter it, which is exactly the outcome the accuracy scorecard is built to handle.