Every two weeks, the same search spikes on Google: “fortnite server status.” It happened again on October 1, 2026, when Epic Games pushed update v42.30 and brought Fortnitemares 2026 into Chapter 7, Season 4 (“Override”). Players hit refresh on status pages, scrolled X for the @FortniteStatus account, and waited. Most of them had no idea Epic publishes a free, structured JSON feed that answers the question in under 200 milliseconds.
This tutorial builds a small, self-hosted bot that polls that official feed, remembers the last known state in a local database, and only pings you when something actually changes. No browser tab. No refreshing Twitter during a patch window. By the end you will have a working Python project that watches Epic’s status API, logs every transition to SQLite, pushes alerts to Discord or your phone via ntfy.sh, and drops a calendar reminder for the next predicted maintenance window. Everything here runs on tools you likely already have installed, costs nothing to operate, and takes about an hour to get from a blank folder to a bot quietly watching Fortnite’s backend in the background.
Why “Is Fortnite Down” Searches Spike Every Patch Day
Fortnite’s current build cycle runs on a roughly biweekly cadence. Chapter 7, Season 4 launched August 20, 2026 as version v42.00. Epic followed with v42.10 on September 3, v42.20 on September 17, and v42.30 on October 1, the patch that shipped Fortnitemares under the “The Game Is Cursed” theme. Each of those updates triggered the same ritual: matchmaking gets disabled roughly 30 minutes ahead of the full maintenance window, then the servers go dark for a stretch that, going by recent uptime tracking, runs about 2.5 to 3 hours.
The October 1 window reportedly opened around 4:00 a.m. Eastern Time, or 8:00 a.m. UTC, which lines up with the early-morning slot Epic has favored for most of this season. That timing is brutal for anyone outside the US East Coast trying to guess whether a 2 a.m. local crash is their internet or an actual patch. A script that reads the authoritative source and translates it into your own timezone removes the guesswork entirely.
As of October 6, 2026, Epic’s public status page reports “All Systems Operational” with no open incidents, and the last major version on record is still v42.30. That is useful context for testing: you will mostly be watching a green, quiet feed, so the tutorial includes a way to simulate an outage locally before you trust the bot to wake you up for a real one.
Keyword research backs up just how common this habit is. “Fortnite server status” alone pulls tens of thousands of monthly searches in the US, and related phrases like “when will Fortnite servers be back up” sit right behind it. Most of those searches land on fan sites that are, themselves, just manually refreshing status.epicgames.com and reposting what they see. There is nothing wrong with that approach for a one-off check, but it does not scale to “wake me up the instant it changes,” which is the actual job this tutorial solves. A bot that reads the same source those fan sites read, minus the manual refreshing, gets you the identical answer seconds after it changes rather than whenever a human remembers to check.
What This Tutorial Builds
The finished project is a single Python package with four moving parts. A poller function hits status.epicgames.com’s JSON summary endpoint on a schedule. A SQLite table stores every status snapshot so the bot can diff “now” against “last check” instead of shouting on every poll. A notifier module pushes a message to a Discord webhook and, optionally, to an ntfy.sh topic when the diff shows a real change. A small calendar helper writes an .ics file so the next predicted maintenance window shows up in Google Calendar or Apple Calendar automatically.
Unlike a countdown widget or a patch-notes scraper, this bot does not care what changed in the patch, only whether the servers are up, degraded, or down, and it keeps a permanent, queryable log of exactly when each state started and ended. That log is the part most community trackers skip, and it is the part that lets you answer “how long was the last downtime, really” without trusting a forum post.
The project also stays small on purpose. Every piece uses something already sitting in Python’s standard library or a single well-known package, so there is no framework to learn and nothing exotic to maintain. You could extend it into a full dashboard later, but the version in this tutorial does its one job (watch, log, alert) and stops there, which is exactly why it is realistic to finish in an afternoon rather than a weekend.
Prerequisites and Versions
You do not need a Fortnite account, an Epic developer key, or any paid API plan for this build. Epic’s status feed is public and unauthenticated. Here is what to install before Step 1.
| Requirement | Version used in this tutorial | Why you need it |
|---|---|---|
| Python | 3.12 or newer | Native zoneinfo module, modern typing |
| requests | 2.32.x | HTTP calls to the status API and webhooks |
| icalendar | 6.1.x | Generates valid .ics calendar files |
| SQLite | Bundled with Python (3.45+ engine) | Local state-history log, zero setup |
| Discord server (optional) | Any, with webhook permissions | Free push-style alert channel |
| ntfy.sh account (optional) | Free tier | Phone push notifications without an app install |
| cron or systemd timers | Any modern Linux distro | Runs the poller on a schedule |
A plain text editor and a terminal cover everything else. The whole build runs comfortably on a $5-a-month VPS, a Raspberry Pi, or your own laptop with cron disabled during sleep (just know you will miss alerts while the laptop is off). None of the steps below depend on a specific Linux distribution, so Ubuntu, Debian, and Fedora all work identically for this project, and the same Python code runs unmodified on macOS if you would rather test locally before deploying to a server.
Step 1: Set Up the Project
Create a project folder, a virtual environment, and install the three dependencies. Keeping this isolated matters more than it sounds, since a system-wide requests upgrade six months from now should not be able to break a bot that runs unattended on a cron job.
mkdir fortnite-status-bot && cd fortnite-status-bot
python3 -m venv venv
source venv/bin/activate
pip install requests==2.32.3 icalendar==6.1.0
mkdir -p data logs
The data folder will hold the SQLite file, and logs will hold the bot’s own run log, separate from the status history you are tracking. Separating “what the bot observed” from “what the bot did” makes debugging far easier once this thing has been running for a few weeks.
Step 2: Read Epic’s Official Status API
Epic hosts its status page on Statuspage.io infrastructure, which means a clean JSON summary sits at a predictable URL: https://status.epicgames.com/api/v2/summary.json. No API key, no rate-limit documentation published, so be a good citizen and poll it no more than once every few minutes. The payload includes a top-level page status plus a components array, where “Fortnite” is one of several grouped services alongside things like Epic Games Store and Login.
import requests
STATUS_URL = "https://status.epicgames.com/api/v2/summary.json"
def fetch_status() -> dict:
response = requests.get(STATUS_URL, timeout=10)
response.raise_for_status()
payload = response.json()
fortnite = next(
c for c in payload["components"] if c["name"] == "Fortnite"
)
return {
"overall": payload["status"]["indicator"],
"fortnite_status": fortnite["status"],
"fetched_at": payload["page"]["updated_at"],
}
if __name__ == "__main__":
print(fetch_status())
Run that file and, on a quiet day, you should see something like {'overall': 'none', 'fortnite_status': 'operational', 'fetched_at': '2026-10-06T17:14:26.682Z'}. The indicator field is the one worth watching closely, since Statuspage.io uses a fixed vocabulary across every company that hosts a page on it: none, minor, major, and critical. That consistency is what makes this approach more reliable than scraping a forum thread for the words “servers down.”
Reading the Raw JSON Payload
It helps to see the full shape of the response once before you start trusting a script to parse it. Beyond the components array, the summary endpoint also returns an incidents list (populated only while something is actively broken) and a scheduled_maintenances list, which is where Epic sometimes posts an upcoming window ahead of time instead of just flipping the indicator when it starts.
{
"page": {
"id": "ft308v428dv3",
"name": "Epic Games Public",
"url": "https://status.epicgames.com",
"updated_at": "2026-10-06T17:14:26.682Z"
},
"status": { "indicator": "none", "description": "All Systems Operational" },
"components": [
{ "id": "wf1ys2kx4pxc", "name": "Fortnite", "status": "operational" }
],
"incidents": [],
"scheduled_maintenances": []
}
Checking scheduled_maintenances is worth adding as a second, lighter-weight poll, since it is the closest thing Epic publishes to an official “this is when we are taking the game down” announcement, rather than the cadence guess this tutorial’s calendar step relies on. When that array is non-empty, pull the scheduled_for and scheduled_until fields directly instead of estimating them, and your calendar reminder stops being a guess and becomes a fact straight from Epic.
On the question of polling etiquette, five minutes is a reasonable default because a Statuspage.io-hosted feed like this one is built to serve thousands of automated dashboards at once, not just your personal bot, and the data itself only changes a handful of times per patch cycle. Polling once a minute will not get you blocked for a personal project at this volume, but it will not get you a faster alert either, since Epic updates the page in response to real incidents, not on a fixed internal clock you can outrace by polling harder.
Step 3: Design the SQLite State-History Table
A status check that does not remember the past is just a slower version of refreshing the page yourself. The schema below stores one row per poll, which sounds wasteful until you realize a few thousand rows of text take up less space than a single screenshot, and the history is what lets you compute real downtime duration later.
import sqlite3
DB_PATH = "data/status_history.db"
SCHEMA = """
CREATE TABLE IF NOT EXISTS status_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
checked_at TEXT NOT NULL,
overall_indicator TEXT NOT NULL,
fortnite_status TEXT NOT NULL,
changed_from_previous INTEGER NOT NULL DEFAULT 0
);
"""
def init_db():
conn = sqlite3.connect(DB_PATH)
conn.execute(SCHEMA)
conn.commit()
return conn
The changed_from_previous flag does the heavy lifting in the next step. Instead of running a separate query every time you want to know “was this a transition,” you stamp it once at write time and every later report just filters on that column.
Step 4: Write the Poller and Diff Logic
This is the core loop: fetch the current status, compare it against the last row in the database, write a new row either way, and return whether this poll represents an actual change. Everything downstream, Discord, ntfy, the calendar file, only fires when this function says something moved.
from datetime import datetime, timezone
def poll_and_diff(conn) -> dict | None:
current = fetch_status()
row = conn.execute(
"SELECT fortnite_status FROM status_log ORDER BY id DESC LIMIT 1"
).fetchone()
previous_status = row[0] if row else None
changed = previous_status is not None and previous_status != current["fortnite_status"]
conn.execute(
"""INSERT INTO status_log
(checked_at, overall_indicator, fortnite_status, changed_from_previous)
VALUES (?, ?, ?, ?)""",
(
datetime.now(timezone.utc).isoformat(),
current["overall"],
current["fortnite_status"],
int(changed),
),
)
conn.commit()
if changed:
return {"from": previous_status, "to": current["fortnite_status"]}
return None
Notice the first poll never counts as a change, since there is nothing to compare against yet. That one line (previous_status is not None) prevents the classic bug where a bot fires a false “server status changed” alert the moment you deploy it, which is a fast way to get your webhook muted by whoever is on the receiving channel.
Reading the previous state back from SQLite instead of holding it in a Python variable is a deliberate choice, not an accident. A global variable resets to empty every time the process restarts, which happens constantly on a cron job since cron spins up a fresh interpreter on every run. Storing the last known value in the database means the bot survives reboots, crashes, and VPS migrations without losing track of where it left off. Full documentation for the module behind this, including transaction behavior and connection handling, lives in Python’s own sqlite3 reference.
Step 5: Send Alerts to Discord and ntfy.sh
Discord webhooks need no app review and no bot token, just a URL you generate from a channel’s integration settings. ntfy.sh is the faster path to a phone notification if you would rather not keep Discord open. Both take a plain POST request, so you can wire up either one or both without extra dependencies.
import requests
DISCORD_WEBHOOK_URL = "https://discord.com/api/webhooks/XXXX/YYYY"
NTFY_TOPIC_URL = "https://ntfy.sh/fortnite-status-yourname"
def notify_discord(change: dict):
message = f"Fortnite status changed: {change['from']} -> {change['to']}"
requests.post(DISCORD_WEBHOOK_URL, json={"content": message}, timeout=10)
def notify_ntfy(change: dict):
message = f"Fortnite status changed: {change['from']} -> {change['to']}"
requests.post(
NTFY_TOPIC_URL,
data=message.encode("utf-8"),
headers={"Title": "Fortnite Server Status", "Priority": "high"},
timeout=10,
)
Pick a random, hard-to-guess ntfy topic name. Public ntfy topics are exactly that, public, and anyone who learns the topic string can read your notifications or post fake ones into it. If that is a concern, ntfy.sh also supports self-hosting and access tokens, documented on their site, which closes that gap entirely. Discord’s own webhook format is documented in its developer reference, including the full set of optional fields like embeds and usernames if you want a richer message than plain text.
Testing Webhooks Without Spamming a Real Channel
Create a throwaway private Discord channel just for bot testing before pointing the webhook at a channel other people actually read. Discord lets you generate as many webhook URLs as you want per server, so there is no reason to debug against the same channel your friends or guild are watching. Once you have confirmed a message lands correctly in the test channel, swap in the real webhook URL and leave the test one in place for the next time you change the code.
Step 6: Convert Maintenance Windows to Local Time
Epic’s status timestamps come back in UTC. Nobody actually thinks in UTC at 3 a.m., so convert before you alert, not after. Python’s built-in zoneinfo module handles this without a third-party package, and critically, it accounts for daylight saving automatically, which matters because the US and the EU do not flip their clocks on the same date.
from datetime import datetime
from zoneinfo import ZoneInfo
def to_local(utc_iso: str, zone_name: str) -> str:
dt_utc = datetime.fromisoformat(utc_iso.replace("Z", "+00:00"))
local = dt_utc.astimezone(ZoneInfo(zone_name))
return local.strftime("%Y-%m-%d %I:%M %p %Z")
# Example: Epic's Oct 1, 2026 maintenance started around 08:00 UTC
print(to_local("2026-10-01T08:00:00Z", "America/New_York"))
print(to_local("2026-10-01T08:00:00Z", "Europe/Berlin"))
print(to_local("2026-10-01T08:00:00Z", "Asia/Tokyo"))
That script prints three correct local times for the same instant, so the Discord message you send can read “maintenance window starts 4:00 AM EDT” for your US audience instead of a UTC timestamp nobody will do mental math on during a server outage. The table below shows the pattern this season has followed, which is worth logging yourself since Epic does not publish a fixed recurring schedule.
| Version | Release date | Notable change | Reported downtime |
|---|---|---|---|
| v42.00 | August 20, 2026 | Chapter 7, Season 4 “Override” launch | Season-launch window, longer than usual |
| v42.10 | September 3, 2026 | Mid-season content update | ~2.5-3 hours |
| v42.20 | September 17, 2026 | Mid-season content update | ~2.5-3 hours |
| v42.30 | October 1, 2026 | Fortnitemares 2026 event launch | ~2.5 hours, started ~4:00 AM ET / 8:00 AM UTC |
Step 7: Generate an .ics Calendar Reminder
Since Epic has run this season on a roughly 14-day cadence, you can generate a tentative calendar event for the next likely patch the moment a confirmed one lands, then correct it once Epic confirms a real date. The icalendar package builds a standards-compliant .ics file that Google Calendar, Outlook, and Apple Calendar all import without complaint, since they all follow the RFC 5545 iCalendar spec.
from datetime import datetime, timedelta, timezone
from icalendar import Calendar, Event
def build_next_patch_reminder(last_patch_utc: datetime, cadence_days: int = 14):
cal = Calendar()
cal.add("prodid", "-//Fortnite Status Bot//shattered.io//")
cal.add("version", "2.0")
event = Event()
predicted = last_patch_utc + timedelta(days=cadence_days)
event.add("summary", "Likely Fortnite maintenance window (unconfirmed)")
event.add("dtstart", predicted)
event.add("dtend", predicted + timedelta(hours=3))
event.add("description", "Auto-predicted from recent patch cadence. Verify against status.epicgames.com before relying on it.")
cal.add_component(event)
with open("data/next_patch_reminder.ics", "wb") as f:
f.write(cal.to_ical())
build_next_patch_reminder(datetime(2026, 10, 1, 8, 0, tzinfo=timezone.utc))
Label it “unconfirmed” in the description field, because it is a guess, not a scraped fact. Epic has shifted cadence before, and a calendar invite that looks authoritative but turns out wrong does more harm than a missing reminder. Treat it as a nudge to go check the real feed, not a replacement for it.
Step 8: Schedule, Log, and Harden the Bot
With the poller, diff, and notifiers written, wire them together in a single entry-point script and schedule it. Every unattended network job needs three things it did not need as a manual script: a timeout, a retry limit, and a log line for every run, successful or not. Skip any of those three and the first time Epic’s status page has a hiccup of its own, your bot either crashes silently or spams retries into a webhook rate limit.
import logging
logging.basicConfig(
filename="logs/bot.log",
level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s",
)
def main():
conn = init_db()
try:
change = poll_and_diff(conn)
except requests.exceptions.RequestException as exc:
logging.warning("Status fetch failed: %s", exc)
return
if change:
logging.info("Status changed: %s", change)
notify_discord(change)
notify_ntfy(change)
else:
logging.info("No change detected")
if __name__ == "__main__":
main()
Add it to cron with a five-minute interval, which is frequent enough to catch a maintenance window quickly without hammering a free public endpoint:
# crontab -e
*/5 * * * * cd /home/you/fortnite-status-bot && venv/bin/python main.py >> logs/cron.log 2>&1
If you prefer systemd timers over cron, the same command slots into a .service plus .timer pair with OnCalendar=*:0/5, which gets you proper journal logging for free. If the cron syntax above looks unfamiliar, a tool like crontab.guru lets you paste a schedule expression and see exactly when it will fire in plain English before you trust it on a production box.
Step 9: Test and Deploy
Do not wait for a real outage to find out your webhook URL has a typo. Fake a state change locally by manually inserting a row with a different fortnite_status value, then run main.py once and confirm a Discord or ntfy message actually lands. This single dry run catches the vast majority of deployment mistakes, from an expired webhook to a firewall blocking outbound HTTPS on your VPS.
python3 -c "
import sqlite3
conn = sqlite3.connect('data/status_history.db')
conn.execute(
\"INSERT INTO status_log (checked_at, overall_indicator, fortnite_status, changed_from_previous) VALUES (datetime('now'), 'none', 'degraded_performance', 0)\"
)
conn.commit()
"
python3 main.py
Once that test alert arrives, let the bot run for a full patch cycle before trusting it fully. The next scheduled window is the real test, since it is the first time the bot sees a live transition from operational to degraded or down rather than a value you typed in yourself. Keep an eye on logs/cron.log during that first live window too, since it will show you whether the five-minute polling interval actually caught the transition promptly or whether you need to tighten it to two or three minutes for faster detection next time.
Querying Your Own Downtime History
After a few weeks of logging, the status_log table becomes more interesting than the live feed itself, since it is the only place that holds a complete, timestamped record of exactly when Fortnite went down and exactly when it came back for your own monitoring window. Epic’s live API only ever shows the current state, not the history, so this table is the part nobody else can hand you.
import sqlite3
conn = sqlite3.connect("data/status_history.db")
rows = conn.execute(
"""SELECT checked_at, fortnite_status FROM status_log
WHERE changed_from_previous = 1
ORDER BY id ASC"""
).fetchall()
for checked_at, status in rows:
print(f"{checked_at} -> {status}")
Pair consecutive rows from that query and you get a start and end timestamp for every outage the bot ever observed, which is enough to compute an average downtime figure for your own reporting instead of citing a one-off number from a single patch day. If you host a Discord community around a game, that kind of self-sourced reliability record carries more weight with your own members than a screenshot of someone else’s dashboard. Over a full season you will likely end up with four or five major maintenance windows plus the occasional short unscheduled blip, and having both logged separately in the same table makes it easy to tell which outages were planned patches and which were something going wrong on Epic’s side between releases.
How Epic’s Status Sources Compare
The JSON feed this tutorial uses is not the only option, and it is worth knowing where it sits relative to the alternatives, since each one answers a slightly different question.
| Source | Best for | Latency | Limitation |
|---|---|---|---|
| status.epicgames.com API | Programmatic polling, this tutorial | Near real-time, no stated rate limit | Covers Epic’s own reported incidents only |
| @FortniteStatus on X | Human-readable updates during big outages | Manual, posted by Epic staff | No structured data to parse |
| Official Fortnite Trello board | Known bugs, not live uptime | Updated periodically | Not designed for maintenance timing |
| Third-party API status pages (e.g. fortniteapi.statuspage.io) | Monitoring unofficial API wrappers, not the game itself | Varies by provider | Reflects a third-party service, not Epic’s servers |
Treat Epic’s own feed as ground truth and the rest as corroboration. If the official API says operational but players in one region still report issues, that is usually a regional network or ISP problem rather than a Fortnite-wide outage, and no status page, official or third-party, will catch that for you. During the biggest outages, Epic staff also post directly to the official @FortniteStatus account on X, which is worth following as a human sanity check even though it has no structured data a script can parse.
Common Pitfalls
Most of these mistakes show up only after the bot has been running quietly for a while, which is exactly when you stop watching it closely. Build the habit of checking logs/bot.log once a week for the first month, even when everything seems fine.
- Polling too aggressively. A 30-second interval feels responsive but adds load to a free public endpoint for no real benefit, since Epic’s maintenance windows last hours, not seconds. Five minutes is plenty.
- Alerting on the first poll. Without the previous_status guard from Step 4, every fresh deployment fires a false alarm the moment it starts.
- Hardcoding one timezone. A bot built for yourself tends to assume everyone is in your timezone. If you ever share it with a Discord server, that assumption breaks for half the members.
- Treating the predicted calendar event as confirmed. The 14-day cadence has held for this season, but Epic has changed patch rhythm before without notice.
- Skipping request timeouts. A hung HTTP call with no timeout can block a cron job indefinitely, which quietly stops all future alerts until you notice the process is stuck.
- Public ntfy topics. An easily guessable topic name is readable, and postable, by anyone who finds it.
- No backup notification channel. If Discord has its own outage at the same moment Fortnite does (it has happened before to other games during shared infrastructure incidents), a single-channel bot goes silent exactly when you need it most. Running both the Discord and ntfy notifiers from Step 5 costs nothing and removes that single point of failure.
- Forgetting to rotate or back up the SQLite file. A corrupted or accidentally deleted database file throws away weeks of downtime history in one command. A weekly cron-triggered copy to a second folder costs one line and saves the whole point of keeping history in the first place.
Troubleshooting Guide
Nine times out of ten, a problem with this bot traces back to one of the items below rather than anything wrong with Epic’s API itself, which has stayed stable through every patch this season. Work through the list roughly top to bottom, since the first two or three items cover the overwhelming majority of real-world reports from anyone who has run a bot like this one for more than a few weeks.
- Bot never sends an alert: Confirm the cron job is actually running with
grep CRON /var/log/syslogor check the systemd timer status withsystemctl list-timers. - KeyError on the Fortnite lookup: Epic occasionally renames or regroups components. Print the raw payload and update the component-name match in Step 2.
- Discord webhook returns 404: The webhook was deleted or the channel it points to was removed. Regenerate it from Discord’s integration settings.
- ntfy notifications arrive late: Free ntfy.sh can queue under load during major shared outages across many apps. Self-hosting removes that variability.
- SQLite reports the database is locked: Usually means two instances of the bot are running at once. Check for overlapping cron entries or a stuck previous process.
- Timestamps look off by an hour: A daylight-saving transition happened between when you tested and when you checked again. The zoneinfo module handles this automatically going forward, so re-run the conversion rather than hardcoding an offset.
- Bot reports operational while friends say the game is down: Check for a regional or platform-specific issue, since Epic’s top-level component can read operational while a sub-component like matchmaking is degraded.
- The .ics file won’t import into Google Calendar: Confirm the file ends in .ics and that
cal.to_ical()was written in binary mode, not text mode, which corrupts the line endings. - Cron job runs but logs show nothing: The script is likely failing before the logging import executes. Run it manually in the foreground first to see the raw traceback.
Advanced Tips and the Complete Project
Once the basic loop works, a few upgrades make it genuinely useful instead of just a neat side project.
Tracking the Daily Item Shop Reset Too
Since the item shop flips on a fixed schedule (midnight UTC daily, which lands at 8:00 p.m. Eastern Time during US daylight saving) you do not need to poll anything extra to track it. Just reuse the to_local() helper from Step 6 against a static midnight-UTC timestamp and fire a separate, much simpler notification a few minutes before the reset. Unlike server status, this one never needs a diff check, since it happens every single day without exception.
Running the Poller as a Serverless Function
A cron job on a single VPS has one obvious weakness: if that VPS goes down, so does your monitoring for the thing you were trying to monitor. Both fetch_status() and poll_and_diff() are plain functions with no server state beyond the SQLite file, so they port cleanly into an AWS Lambda function on a scheduled EventBridge trigger, or a Cloudflare Worker with a cron trigger, as long as you swap the local SQLite file for a small hosted database like Cloudflare D1 or a managed Postgres instance. Add a simple Flask or FastAPI endpoint on top of whichever storage you pick and you have the start of a public uptime dashboard, built entirely from data you own instead of a third party’s tracker.
The full project, once you have followed all nine steps, looks like this on disk:
fortnite-status-bot/
├── venv/
├── data/
│ ├── status_history.db
│ └── next_patch_reminder.ics
├── logs/
│ ├── bot.log
│ └── cron.log
├── main.py
├── status.py # fetch_status()
├── db.py # init_db(), poll_and_diff()
├── notify.py # notify_discord(), notify_ntfy()
├── timezones.py # to_local()
└── calendar_gen.py # build_next_patch_reminder()
That is a complete, working monitor built from one free public API, one built-in database engine, and two free notification channels, with no paid service anywhere in the stack. Running cost on a $5 VPS is effectively zero beyond the hosting fee you were likely already paying for something else, and the entire project fits comfortably under 300 lines of Python split across six small files, which is short enough to read end to end in one sitting if you want to understand every piece before you deploy it.
Frequently Asked Questions
Is there an official Fortnite server status API?
Yes. Epic Games runs its public status page on Statuspage.io infrastructure, which exposes a free JSON endpoint at status.epicgames.com/api/v2/summary.json covering Fortnite alongside other Epic services.
How long does Fortnite maintenance usually last?
Recent scheduled updates in Chapter 7, Season 4, including the October 1, 2026 v42.30 patch, have run roughly 2.5 to 3 hours from the start of the maintenance window to restored service, though actual availability can vary by platform and region. A season-launch update, like v42.00 on August 20, 2026, has historically run longer than a routine mid-season patch, so budget extra time around those dates.
What time does the Fortnite item shop reset?
The item shop refreshes daily at midnight UTC, which corresponds to 8:00 p.m. Eastern Time during US daylight saving, including the period this article was published in.
Do I need an Epic developer account to use the status API?
No. The summary.json endpoint behind status.epicgames.com is public and unauthenticated, unlike Epic’s full developer and game-data APIs, which do require registration.
Why not just scrape the status page instead of using the JSON feed?
Scraping HTML breaks every time Epic redesigns the page, while the JSON endpoint is the same structured data the page itself renders from, so it is far more stable for an automated bot.
Can this bot predict exactly when the next update will happen?
It can only estimate based on the recent roughly 14-day cadence between v42.00, v42.10, v42.20, and v42.30. Treat any generated calendar event as a rough guess, not a confirmed Epic announcement, and prefer the scheduled_maintenances field from Step 2 whenever Epic actually populates it, since that comes straight from Epic rather than from a pattern you extrapolated yourself.
Does this work for platforms other than PC, like PlayStation or Xbox?
The Fortnite component on Epic’s status page reflects the backend service shared across platforms, so a backend outage shows up for everyone, but platform-specific issues, like a console network service outage, are separate and worth checking against that platform’s own status page too.
Is it safe to share my Discord webhook URL if I publish this code on GitHub?
No. A webhook URL grants posting rights to that channel, so keep it in an environment variable or a local config file excluded by .gitignore rather than hardcoding it in code you might publish.




