Steam goes down more often than most players realize, and when it does, the first move for millions of people is the same: refresh Twitter, check a third-party site, and wait. “Is Steam down” is one of the most consistently searched gaming queries on Google, spiking hard every time a big sale or a major launch pushes concurrent traffic to new highs. You can skip that wait entirely. With a free Steam Web API key, about 150 lines of Python, and a Discord webhook, you can build a status checker that pings Steam’s core services every minute and pages you the moment something breaks, often minutes before community trackers catch up.
This tutorial walks through the whole build, from project setup to a 24/7 deployment, using the current Steam Web API, Python 3.12+, and the latest stable release of the requests library. By the end you will have a working script, a tested alerting pipeline, and a small dashboard, plus enough understanding of the underlying pattern to adapt it to any other API-backed service you care about monitoring.
Why Build Your Own Steam Status Checker
Sites like steamstat.us have covered this job for years, aggregating status for the client, store, and community into one dashboard. That works fine if you just want a glance. It falls apart the moment you want something specific: an alert the instant your region’s store front goes soft, a log of exactly when an outage started for a support ticket, or a check tied to a game you actually run a community around. A self-hosted checker gives you raw control over polling frequency, alert thresholds, and where the alert lands, whether that is Discord, Slack, email, or a text message.
There is also a reliability argument. Third-party status pages are themselves just servers that can go down, get rate-limited, or quietly stop updating. During a real Valve-side incident, traffic to every status checker spikes at once, including community ones running on modest hosting. Running your own means one less point of failure between you and the truth about whether Steam is actually broken or your own connection is the problem. It also means you are not dependent on a third party’s uptime to find out about Valve’s uptime, which is a strange but real form of circular risk once you notice it.
Building this also teaches transferable skills. The same polling-plus-alerting pattern shows up in the site’s Apex Legends Rank Tracker and CS2 Stats Tracker tutorials, just pointed at a different API. Once you understand it here, adapting it for other services is mostly a matter of swapping endpoints. Browse the site’s broader esports coverage for more of these build-it-yourself tools if this is the kind of project you enjoy.
How the Checker Fits Together
Before writing any code, it helps to see the shape of the finished tool. It is a loop, not a one-shot script: every cycle, it fires off a small set of HTTP requests, compares each result against recent history, and only speaks up when something actually changes state. Three layers do the work, and each one answers a slightly different question about Steam’s health.
| Layer | What It Checks | Question It Answers |
|---|---|---|
| Web API | ISteamWebAPIUtil/GetServerInfo | Is Valve’s backend API infrastructure responding at all? |
| Store front end | store.steampowered.com HTTP response | Can a player actually load the storefront right now? |
| Community front end | steamcommunity.com HTTP response | Are profiles, chat, and workshop pages reachable? |
Each layer runs through the same evaluation logic: a result comes back, gets compared to a rolling streak counter, and either stays quiet or triggers an alert. That separation matters because these three layers can fail independently. The Web API can be perfectly healthy while the store front end chokes under sale-day traffic, or the community pages can lag while everything else works fine. Checking all three separately, instead of collapsing them into one generic “Steam status” boolean, is what makes the tool actually useful instead of just noisy.
Steam’s 2026 Traffic Numbers Show Why Uptime Matters More
Steam’s concurrent user counts keep climbing, and that raises the stakes every time something breaks. According to research published by ShaneTheGamer, Steam crossed 40 million concurrent users for the first time on March 2, 2025, hit 41,666,455 concurrent players on October 12, 2025 during a major game launch, and set an all-time record of 42,042,778 concurrent users on January 11, 2026, a figure Valve confirmed at GDC 2026. Every one of those numbers represents a moment where even a short outage would have interrupted a huge number of active sessions at once.
Valve does not run a public incident-history page the way AWS or Cloudflare do, so there is no official archive of exact outage durations to cite. What is documented is the scale of what is at risk: tens of millions of concurrent connections, a store front handling constant transaction volume, and a community layer that includes chat, trading, and workshop traffic. A tool that watches these systems and tells you the moment something degrades is more useful now than it was a few years ago, simply because more people are online to be affected.
Scale also changes what “down” means in practice. At 42 million concurrent connections, a partial degradation, say, the store front loading slowly for one region while everything else works, affects a far larger absolute number of players than the same partial issue would have a few years earlier, even though the percentage of the player base impacted might look identical on paper. That is part of why the latency tracking built into this checker in Step 3 matters as much as the simple up-or-down boolean: a store front that responds in eight seconds instead of 200 milliseconds is a real problem worth flagging, even though a status page checking only for HTTP 200 would report everything as fine.
| Metric | Value | Source |
|---|---|---|
| All-time concurrent user record | 42,042,778 (Jan 11, 2026) | Valve, confirmed at GDC 2026 |
| First time Steam crossed 40M concurrent | 40.27M (Mar 2, 2025) | ShaneTheGamer Steam Statistics 2026 |
| Prior record tied to a major launch | 41,666,455 (Oct 12, 2025) | ShaneTheGamer Steam Statistics 2026 |
| Official Steam status API | None published | Steamworks Web API docs |
| Community status reference | steamstat.us (unofficial) | Public status aggregator |
Prerequisites: What You Need Before You Start
This build stays deliberately light on dependencies so it runs on almost anything, from a Raspberry Pi to a five-dollar cloud instance. Here is what to have ready before Step 1.
- Python 3.12 or newer installed (3.14 is the current stable release)
- pip, Python’s package manager, which ships with modern Python installs
- A free Steam account to register a Web API key at steamcommunity.com
- The
requestslibrary, version 2.34.x or newer - A Discord server where you can create a webhook (or a Slack workspace if you prefer that instead)
- Basic comfort with the command line and a text editor
- Optional: a small always-on machine (VPS, home server, or Raspberry Pi) for 24/7 operation
None of this costs money for a personal setup. The Steam Web API key is free, Discord webhooks are free, and a basic VPS suitable for this workload typically runs a few dollars a month if you don’t already have a machine that stays on. The whole project also runs comfortably on hardware you may already own; a machine that already stays on for other reasons, like a home media server or a router running custom firmware, has more than enough headroom for a script that wakes up once a minute, makes three quick HTTP requests, and goes back to sleep.
Step 1: Set Up Your Project Folder and Virtual Environment
Start by isolating the project so its dependencies don’t collide with anything else on your machine. Create a folder, spin up a virtual environment, and install the one external library this project needs.
mkdir steam-status-checker
cd steam-status-checker
python3 -m venv venv
source venv/bin/activate # on Windows: venv\Scripts\activate
pip install requests==2.34.2
Pinning the exact requests version keeps the environment reproducible if you move this to a server later. Create three empty files inside the folder now: steam_status.py for the main logic, config.py for settings, and state.json for tracking incident history. You will fill each of these in over the next several steps.
It is worth resisting the urge to skip the virtual environment step because the project only needs one dependency. Isolating it means you can copy the entire folder to a different machine later, run pip freeze > requirements.txt, and reproduce the exact same setup with pip install -r requirements.txt on the new box in seconds, without worrying about whatever else is installed system-wide.
Step 2: Get a Free Steam Web API Key
Go to steamcommunity.com/dev/apikey while logged into a Steam account, enter any domain name (localhost works fine for personal projects), and Valve issues a key instantly. This key is not strictly required for the core status check in this tutorial, since the ISteamWebAPIUtil/GetServerInfo endpoint does not need authentication, but you will want it if you extend the checker later to pull player counts or app-specific data, which do require a key.
Store the key as an environment variable instead of hardcoding it into your script. That habit matters the moment you push this code anywhere public.
export STEAM_API_KEY="your_key_here"
export DISCORD_WEBHOOK_URL="your_webhook_url_here"
Add those two lines to your shell profile or a local .env file that you keep out of version control. If you plan to publish this project on GitHub, add a .gitignore entry for that file immediately, before your first commit, not after. Secrets that make it into git history stay there even after you delete the file in a later commit, so the only reliable fix at that point is rotating the key or webhook entirely.
Step 3: Write the Core Web API Health Check
The most direct signal for whether Steam’s Web API layer is healthy comes from ISteamWebAPIUtil/GetServerInfo. It takes no parameters, needs no key, and returns the server’s current time if everything is working. A timeout, connection error, or non-200 response tells you the Web API layer is degraded or unreachable.
import time
import requests
WEBAPI_URL = "https://api.steampowered.com/ISteamWebAPIUtil/GetServerInfo/v1/"
def check_webapi(timeout=5.0):
start = time.monotonic()
try:
response = requests.get(WEBAPI_URL, timeout=timeout)
latency_ms = round((time.monotonic() - start) * 1000)
if response.status_code == 200:
return {"service": "webapi", "up": True, "latency_ms": latency_ms}
return {"service": "webapi", "up": False, "latency_ms": latency_ms,
"detail": f"HTTP {response.status_code}"}
except requests.exceptions.RequestException as exc:
return {"service": "webapi", "up": False, "latency_ms": None, "detail": str(exc)}
Running this function alone from a Python shell should return something like {'service': 'webapi', 'up': True, 'latency_ms': 142} under normal conditions. That latency number matters as much as the boolean; a service that responds with 200 but takes four seconds is technically up and practically unusable, so keep the number around for later alerting logic.
Step 4: Check the Steam Store and Community Front Ends
The Web API being healthy does not guarantee the store or community pages are loading normally for players; those run on separate infrastructure. Since Valve does not publish a dedicated JSON status endpoint for either, the practical approach is a synthetic check: request the front page and treat a fast 200 response as healthy.
def check_http_endpoint(name, url, timeout=6.0):
start = time.monotonic()
try:
response = requests.get(url, timeout=timeout, headers={
"User-Agent": "SteamStatusChecker/1.0"
})
latency_ms = round((time.monotonic() - start) * 1000)
up = response.status_code < 500
return {"service": name, "up": up, "latency_ms": latency_ms,
"detail": f"HTTP {response.status_code}"}
except requests.exceptions.RequestException as exc:
return {"service": name, "up": False, "latency_ms": None, "detail": str(exc)}
def check_store():
return check_http_endpoint("store", "https://store.steampowered.com/")
def check_community():
return check_http_endpoint("community", "https://steamcommunity.com/")
Note the up = response.status_code < 500 condition. A 4xx response usually means the request itself was malformed, not that Steam is down, so treating only 5xx codes as a genuine outage cuts down on false alarms. Set a real user-agent string too; some CDNs in front of these front ends will quietly throttle requests that look like a generic script.
Step 5: Build Incident Detection So a Single Bad Ping Doesn't Trigger a False Alarm
Networks hiccup constantly. If you alert on the very first failed check, you will get paged for your own Wi-Fi dropping a packet, not for a real Steam outage. The fix is a consecutive-failure counter: only treat a service as genuinely down after it fails a set number of checks in a row, and only treat it as recovered after the same number of consecutive successes.
FAILURE_THRESHOLD = 3 # consecutive failures before declaring an outage
RECOVERY_THRESHOLD = 2 # consecutive successes before declaring recovery
def evaluate_incident(service_name, is_up, counters):
counter = counters.setdefault(service_name, {"fail_streak": 0, "ok_streak": 0, "incident_open": False})
if is_up:
counter["ok_streak"] += 1
counter["fail_streak"] = 0
if counter["incident_open"] and counter["ok_streak"] >= RECOVERY_THRESHOLD:
counter["incident_open"] = False
return "recovered"
else:
counter["fail_streak"] += 1
counter["ok_streak"] = 0
if not counter["incident_open"] and counter["fail_streak"] >= FAILURE_THRESHOLD:
counter["incident_open"] = True
return "new_incident"
return "no_change"
With FAILURE_THRESHOLD set to 3 and a 60-second poll interval, an outage only fires an alert after roughly three minutes of sustained failure, which is a reasonable balance between catching real incidents fast and ignoring transient blips. Tune these numbers based on how twitchy your network connection is.
Step 6: Send Real-Time Alerts to Discord
Discord webhooks accept a simple JSON POST with no authentication beyond the URL itself, which makes them the fastest way to get a working alert pipeline. Create a webhook in your server under Server Settings, then Integrations, then Webhooks, copy the URL, and wire it into the checker.
import os
DISCORD_WEBHOOK_URL = os.environ["DISCORD_WEBHOOK_URL"]
def send_discord_alert(service_name, status, detail=""):
color = 15158332 if status == "new_incident" else 3066993 # red or green
title = f"Steam {service_name} is DOWN" if status == "new_incident" else f"Steam {service_name} has RECOVERED"
payload = {
"embeds": [{
"title": title,
"description": detail or "No additional detail available.",
"color": color,
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
}]
}
try:
requests.post(DISCORD_WEBHOOK_URL, json=payload, timeout=5.0)
except requests.exceptions.RequestException as exc:
print(f"Failed to send Discord alert: {exc}")
A message posted from this code looks like a small red-bordered embed titled "Steam store is DOWN" with the HTTP error or connection detail underneath, followed later by a green "Steam store has RECOVERED" embed once the incident clears. That before-and-after pairing is what makes a log of these messages genuinely useful when you're trying to reconstruct how long an outage actually lasted.
Step 7: Persist State So Restarts Don't Cause Duplicate Alerts
The counters dictionary from Step 5 lives in memory, so if your script restarts mid-incident, it forgets that an alert already fired and can send a duplicate. Saving that state to disk between runs fixes it.
import json
from pathlib import Path
STATE_FILE = Path("state.json")
def load_state():
if STATE_FILE.exists():
return json.loads(STATE_FILE.read_text())
return {}
def save_state(counters):
STATE_FILE.write_text(json.dumps(counters, indent=2))
Call load_state() once when the script starts and save_state(counters) after every check cycle. This also gives you a free audit trail: open state.json at any point and you can see exactly how long each service's current streak has been running.
Step 8: Schedule the Checker With Cron or systemd
A script that only runs when you remember to launch it is not a monitor. On Linux, the simplest option is a cron entry that runs every minute.
# crontab -e
* * * * * cd /home/you/steam-status-checker && venv/bin/python steam_status.py >> checker.log 2>&1
If you want tighter intervals than cron's one-minute floor, or you prefer proper logging and automatic restarts, a systemd timer is the better fit. Create a service file and a matching timer file, then enable the timer with systemctl enable --now steam-checker.timer. Either approach works; cron is faster to set up, systemd is more robust for a server you plan to leave running for months.
Step 9: Add a Lightweight Status Dashboard
A Discord alert tells you something broke, but a small web page tells you the current state at a glance without digging through chat history. You don't need a framework for this; a single Flask route that reads the same state.json file works fine.
from flask import Flask, jsonify
app = Flask(__name__)
@app.route("/status")
def status():
state = load_state()
return jsonify({
name: "up" if not data["incident_open"] else "down"
for name, data in state.items()
})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000)
Install Flask with pip install flask, run the script, and visiting http://localhost:8000/status returns something like {"webapi": "up", "store": "up", "community": "up"}. Point a reverse proxy at it if you want this reachable from outside your home network.
This dashboard is intentionally minimal, returning raw JSON instead of a styled page, because that keeps it easy to consume from other tools too. You could point a browser extension at it, feed it into a home dashboard app like Home Assistant, or have a second script poll it and post a daily summary to Discord instead of only real-time alerts. Once the data exists in one predictable JSON shape, reusing it elsewhere costs almost nothing.
Step 10: Test the Checker With Simulated Outages
Don't wait for a real Steam outage to find out your alerting is broken. Temporarily point one of the check functions at a URL you know will fail, such as a closed port on the store domain, and confirm three consecutive failed cycles trigger a Discord message. Then point it back at the real URL and confirm two consecutive successes trigger the recovery message.
A working test run in the terminal should look like this:
$ python steam_status.py
[2026-09-17 14:32:01] webapi: UP (142ms)
[2026-09-17 14:32:02] store: DOWN (timeout) - fail_streak=1
[2026-09-17 14:33:02] store: DOWN (timeout) - fail_streak=2
[2026-09-17 14:34:02] store: DOWN (timeout) - fail_streak=3 - ALERT SENT
[2026-09-17 14:35:02] store: UP (198ms) - ok_streak=1
[2026-09-17 14:36:02] store: UP (203ms) - ok_streak=2 - RECOVERY SENT
If your test doesn't produce this pattern, check the pitfalls and troubleshooting sections below before assuming the code is broken; the most common cause is a typo in the environment variable name for the webhook URL.
Step 11: Deploy It So It Runs Around the Clock
Running this on a laptop that sleeps at night defeats the purpose. A cheap always-on VPS, a Raspberry Pi on your home network, or a free-tier cloud instance all work. Whatever you pick, confirm three things after deployment: the cron job or systemd timer survives a reboot, the Discord webhook still fires after a full server restart, and the log file doesn't grow unbounded (add basic log rotation with logrotate if you're running this for months).
Keep polling intervals reasonable. Valve has not published a formal rate limit for ISteamWebAPIUtil, but hitting it every few seconds from a personal project is unnecessary and risks a soft throttle. A 60-second interval catches outages fast enough for personal use while staying well inside conservative, courteous usage.
One more deployment detail worth planning for upfront: what happens if the machine running this loses power or its network connection entirely. If the checker itself goes offline, it obviously cannot alert you that it went offline, which is the classic monitoring-the-monitor problem. A simple mitigation is a separate, much simpler heartbeat check, a free third-party service that expects a ping from your script every few minutes and alerts you by a different channel if that ping stops arriving. That way a total failure of your own machine gets caught too, not just a failure of Steam's services.
Common Pitfalls When Building a Status Checker
These are the mistakes that show up most often in first attempts at this kind of tool, usually within the first day of running it unattended. Most of them are easy to fix once you know to look for them, but they are also easy to miss because the script still runs without throwing an error; it just produces wrong or noisy output.
- Alerting on the first failure. Without a consecutive-failure threshold, a single dropped packet on your own connection reads as a Steam outage and trains you to ignore every alert.
- Hardcoding secrets in the script. Committing a webhook URL or API key to a public repository means someone else can spam your Discord channel or burn your API quota within hours of the repo going public.
- Treating any non-200 response as down. A 403 or 404 from a CDN edge node often means something unrelated to an outage; only 5xx codes and connection failures reliably indicate the service itself is unhealthy.
- No timeout on requests. Leaving out the timeout parameter means a hung connection can block your entire script indefinitely instead of failing fast and moving on.
- Ignoring latency. A binary up or down check misses the more common failure mode: a service that responds with 200 but takes eight seconds, which is functionally broken for anyone trying to actually use it.
- Running checks from a single location. A regional ISP or routing issue near your server can look identical to a Steam-side outage; running the same checks from a second location, even a free-tier cloud function, helps you tell the difference.
- Forgetting to handle the case where Discord itself rejects the request. Discord webhooks are rate-limited and return an HTTP 429 once you send too many requests in a short window; a bug that fires alerts in a tight loop instead of once per state change can hit that limit and silently start dropping your alerts right when you need them most.
Troubleshooting Guide
Working through these covers the issues that come up most often once the checker is deployed. Read the symptom column first rather than jumping straight to fixes; several of these look identical from the outside (no alert arrives) but trace back to entirely different root causes, and applying the wrong fix wastes more time than reading one extra column would have.
| Symptom | Likely Cause | Fix |
|---|---|---|
| Discord alerts never arrive | Webhook URL typo or wrong environment variable name | Print the resolved URL at startup and confirm it matches Discord's integration settings exactly |
| Script exits with a KeyError on the webhook variable | Environment variable not exported in the shell running cron | Cron runs a minimal environment; source your .env file explicitly inside the cron command or script |
| Constant false-positive alerts | Failure threshold set too low for a flaky home connection | Raise the threshold to 4 or 5 consecutive failures before you trust the counter |
| state.json grows corrupted after a crash | Script killed mid-write to the state file | Write to a temp file and rename it atomically instead of overwriting state.json directly |
| Requests hang instead of failing | Missing or too-generous timeout value | Set a timeout of 5 seconds or lower on every request, never leave it unset |
| Community check always shows 403 | Missing or generic User-Agent header triggering CDN filtering | Set a descriptive User-Agent string as shown in Step 4 |
| Cron job runs but nothing happens | Cron using system Python instead of the virtual environment's Python | Reference the venv's Python binary explicitly in the crontab entry, never a bare python3 call |
| Dashboard shows stale data | Flask route reading a state.json the cron job isn't actually writing to | Confirm both processes reference the exact same absolute file path, not a relative one |
If none of these match what you're seeing, the fastest debugging step is almost always adding a plain print() statement right before the line that seems to be failing and running the script manually in the foreground, outside of cron. Scheduled jobs strip away most of the context you're used to seeing in an interactive terminal, including your normal PATH and environment variables, so a script that runs perfectly when you launch it by hand but fails silently under cron is very often an environment difference rather than a code bug.
Advanced Tips: Multi-Region Polling and Historical Uptime Tracking
Once the basic checker is stable, a few upgrades make it noticeably more useful. Running the same checks from two or three geographically separate machines and only alerting when a majority agree the service is down eliminates most of the false positives caused by regional routing problems rather than genuine Steam-side incidents. A free-tier function on a different cloud provider than your main server is enough for this; you don't need expensive infrastructure to get a second vantage point.
If you outgrow synchronous requests and want to poll all three services at once instead of in sequence, swapping in httpx's async client lets you fire all the checks concurrently and shave a few hundred milliseconds off every cycle, though for a once-a-minute personal checker the difference is mostly academic. Logging every check result, not just state transitions, into a lightweight database like SQLite also opens the door to real uptime percentages over time instead of just up or down right now. A simple query against a table of timestamped results lets you calculate a rolling 30-day uptime figure for each service, which is far more informative than any single snapshot and mirrors what dedicated status-page products do behind the scenes.
Consider adding a second alert channel as a backup. If Discord itself has an outage at the same moment as a Steam incident, a fallback SMS or email path through a service with a generous free tier means you still get notified. Redundant alerting sounds like overkill until the one time your primary channel and the thing you're monitoring go down together.
You can also containerize the whole thing with Docker once you're happy with how it behaves, which makes moving it between machines, or running it alongside other small monitoring tools, much cleaner than managing a bare virtual environment on the host. A minimal Dockerfile for this project needs little more than a Python base image, a copy of the two source files, and a pip install step, since the entire dependency list is just requests and, if you built the dashboard, flask. Running it as a container also makes the systemd or cron scheduling question moot; you schedule the container restart policy instead, and Docker handles keeping the process alive across crashes on its own.
The Complete Working Project
Here is the full script with everything from the steps above combined into one file you can run directly. Save it as steam_status.py in the project folder from Step 1.
import os
import time
import json
from pathlib import Path
import requests
WEBAPI_URL = "https://api.steampowered.com/ISteamWebAPIUtil/GetServerInfo/v1/"
STATE_FILE = Path("state.json")
DISCORD_WEBHOOK_URL = os.environ["DISCORD_WEBHOOK_URL"]
FAILURE_THRESHOLD = 3
RECOVERY_THRESHOLD = 2
def check_webapi(timeout=5.0):
return check_http_endpoint("webapi", WEBAPI_URL, timeout)
def check_http_endpoint(name, url, timeout=6.0):
start = time.monotonic()
try:
response = requests.get(url, timeout=timeout, headers={"User-Agent": "SteamStatusChecker/1.0"})
latency_ms = round((time.monotonic() - start) * 1000)
return {"service": name, "up": response.status_code < 500, "latency_ms": latency_ms}
except requests.exceptions.RequestException as exc:
return {"service": name, "up": False, "latency_ms": None, "detail": str(exc)}
def load_state():
return json.loads(STATE_FILE.read_text()) if STATE_FILE.exists() else {}
def save_state(counters):
STATE_FILE.write_text(json.dumps(counters, indent=2))
def evaluate_incident(name, is_up, counters):
c = counters.setdefault(name, {"fail_streak": 0, "ok_streak": 0, "incident_open": False})
if is_up:
c["ok_streak"] += 1
c["fail_streak"] = 0
if c["incident_open"] and c["ok_streak"] >= RECOVERY_THRESHOLD:
c["incident_open"] = False
return "recovered"
else:
c["fail_streak"] += 1
c["ok_streak"] = 0
if not c["incident_open"] and c["fail_streak"] >= FAILURE_THRESHOLD:
c["incident_open"] = True
return "new_incident"
return "no_change"
def send_discord_alert(service_name, status, detail=""):
color = 15158332 if status == "new_incident" else 3066993
title = f"Steam {service_name} is DOWN" if status == "new_incident" else f"Steam {service_name} has RECOVERED"
payload = {"embeds": [{"title": title, "description": detail or "No detail available.", "color": color}]}
requests.post(DISCORD_WEBHOOK_URL, json=payload, timeout=5.0)
def main():
counters = load_state()
checks = [check_webapi(),
check_http_endpoint("store", "https://store.steampowered.com/"),
check_http_endpoint("community", "https://steamcommunity.com/")]
for result in checks:
outcome = evaluate_incident(result["service"], result["up"], counters)
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] {result['service']}: "
f"{'UP' if result['up'] else 'DOWN'} ({result.get('latency_ms')}ms)")
if outcome in ("new_incident", "recovered"):
send_discord_alert(result["service"], outcome, result.get("detail", ""))
save_state(counters)
if __name__ == "__main__":
main()
Run it once manually with python steam_status.py to confirm it prints three UP lines with no errors, then wire it into cron or systemd from Step 8 so it runs unattended. From here you can layer in the optional dashboard from Step 9, or extend the check list to cover a specific game's matchmaking servers using the same pattern shown in this site's FACEIT Level Checker and Rocket League Rank Tracker builds, both of which poll a third-party API on a schedule in essentially the same way. The same pattern, applied to a different game, also underlies the Overwatch 2 Rank Tracker if you want a third reference point before writing your own variation.
Frequently Asked Questions
Does Steam have an official status page?
No. Valve does not run a public, first-party status page comparable to AWS or Cloudflare. The commonly used community page, steamstat.us, is run independently, not by Valve, which is exactly why building your own checker gives you a more direct signal.
Do I need a Steam Web API key for this tutorial?
Not for the core checks shown here. The GetServerInfo endpoint and the store and community HTTP checks are unauthenticated. You only need a key if you extend the project to pull player counts or app-specific data from endpoints that require one.
How often should the checker poll Steam's services?
Every 60 seconds is a reasonable default for personal use. It catches most outages within a few minutes once you factor in the consecutive-failure threshold, without hammering Valve's infrastructure with unnecessary requests.
Can I use Slack or email instead of Discord for alerts?
Yes. Slack's incoming webhooks work almost identically to Discord's, just with a different JSON payload shape. Email requires a bit more setup through an SMTP library or a transactional email API, but the same trigger logic from Step 5 applies regardless of where the alert ends up.
Why does my checker report Steam as down when it's actually working fine for me?
This is almost always a false positive from a single check, a missing User-Agent header triggering CDN filtering, or a request timeout set too low. Review the pitfalls and troubleshooting sections above; raising your consecutive-failure threshold usually resolves it.
Is there a rate limit on the Steam Web API I need to worry about?
Valve has not published specific numeric rate limits for ISteamWebAPIUtil. Polling once every 60 seconds from a single personal project stays comfortably within reasonable, courteous usage and is very unlikely to trigger any throttling.
Can this checker run on a Raspberry Pi?
Yes. The entire script has minimal CPU and memory requirements, and Python 3.12 or newer runs fine on a Raspberry Pi 4 or 5. It's one of the more practical always-on uses for a Pi that would otherwise sit idle.
What should I do if I find a genuine, sustained Steam outage?
Check Valve's official support and social channels to confirm it's not isolated to your network, and consider cross-referencing with a second, independently hosted checker or community status page before assuming it's a full platform-wide incident.
Should I monitor a specific game's servers separately from Steam itself?
Yes, if that game matters enough to you to justify the extra check. A game's own matchmaking or login servers can go down independently of Steam's storefront and community layer entirely, since most multiplayer games run their backend infrastructure on their own systems, not Valve's. Add a fourth entry to the checks list in the complete project script pointed at that game's status endpoint if it publishes one, and the same incident-detection and alerting logic already handles it without any other changes.




