Steam Ban Monitor Documentation
Python β€’ Steam Web API β€’ Discord Webhooks

Steam Ban Monitor

A lightweight monitoring tool that checks Steam accounts for ban status changes via the official Steam Web API and sends formatted Discord embed notifications. Designed for unattended operation on Windows using Task Scheduler.

What it does

Detects new bans, ban changes, and unbans across multiple SteamID64 accounts.

How you get alerts

Notifications are posted to a Discord channel using a webhook (embed format, color-coded).

Overview
What the tool does and which ban types it can detect

The Steam Ban Monitor is a Python script that monitors a list of Steam accounts (SteamID64) for changes in ban status using the official Steam Web API. When a ban status changes, the script posts a Discord embed notification to a configured webhook.

Supported ban types

Detected via Steam API
  • VAC bans
  • Game bans (Steam-visible β€œGame ban on record”)
  • Community bans
  • Economy / Trade restrictions
Not detectable (common confusion)
  • In-game bans that are not reflected on Steam profile
  • Server-only bans on community servers
  • Any internal game ban system not exposed via Steam Web API
Requirements
Software, access keys, and network requirements

Software

  • Windows 10 / Windows 11
  • Python 3.9+ (recommended: latest stable)
  • Python package: requests

Accounts / Keys

  • Steam Web API Key
  • Discord Webhook URL (channel where notifications should be posted)

Security: Never publish your Steam API key or Discord webhook URL. If exposed, rotate both immediately.

Folder structure
Recommended layout for script, config and state storage
SteamBanMonitor/
β”œβ”€β”€ steam_ban_monitor.py
β”œβ”€β”€ config.json
β”œβ”€β”€ ban_state.json        (auto-created / updated)
└── start_monitor.bat     (recommended for Task Scheduler)

The file ban_state.json is created automatically and stores the last known ban state per SteamID. This enables change detection (new ban vs. unban vs. ban update).

Installation
Create folder, install dependencies, verify Python

1) Create a directory

C:\SteamBanMonitor\

2) Place files

Copy the following into the folder:

  • steam_ban_monitor.py
  • config.json

3) Install Python dependency

pip install requests
Configuration
How to configure API keys, webhook, and SteamID64 list

The tool is configured via config.json. All monitored accounts must be provided as SteamID64 (17-digit numeric IDs).

Example config.json

{
  "steam_api_key": "YOUR_STEAM_API_KEY",
  "discord_webhook_url": "YOUR_DISCORD_WEBHOOK_URL",
  "steam_ids": [
    "76561198084321450",
    "76561197998666186"
  ],
  "state_file": "ban_state.json"
}

Fields

Field Description
steam_api_keySteam Web API key used to query ban and profile data.
discord_webhook_urlDiscord webhook endpoint to receive embed notifications.
steam_idsArray of SteamID64 values to monitor.
state_fileLocal JSON file storing last known ban status per SteamID.

Tip: If you previously used a wrong SteamID64, the API may return an empty list. Always verify SteamIDs before adding them.

Main Script – steam_ban_monitor.py
Complete Python source code (copy, modify, run)

Create a file named steam_ban_monitor.py in the same folder as config.json, then paste the code below. The script checks Steam ban status and posts Discord embed notifications on changes (including unbans). The current Steam name (persona name) is included in the alert.

import json
import os
import time
import requests

CONFIG_FILE = "config.json"


def load_config():
    with open(CONFIG_FILE, "r", encoding="utf-8") as f:
        return json.load(f)


def load_state(state_file):
    if not os.path.exists(state_file):
        return {}
    with open(state_file, "r", encoding="utf-8") as f:
        return json.load(f)


def save_state(state_file, state):
    with open(state_file, "w", encoding="utf-8") as f:
        json.dump(state, f, indent=2)


def fetch_bans(steam_api_key, steam_ids):
    url = "https://api.steampowered.com/ISteamUser/GetPlayerBans/v1/"
    params = {
        "key": steam_api_key,
        "steamids": ",".join(steam_ids)
    }
    resp = requests.get(url, params=params, timeout=10)
    resp.raise_for_status()
    return resp.json().get("players", [])


def fetch_persona_names(steam_api_key, steam_ids):
    # Returns dict: { steamid: personaname }
    url = "https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v2/"
    params = {
        "key": steam_api_key,
        "steamids": ",".join(steam_ids)
    }
    resp = requests.get(url, params=params, timeout=10)
    resp.raise_for_status()
    data = resp.json()
    result = {}
    players = data.get("response", {}).get("players", [])
    for p in players:
        sid = p.get("steamid")
        if sid:
            result[sid] = p.get("personaname", "")
    return result


def send_discord_embed(webhook_url, embed):
    payload = {"embeds": [embed]}
    resp = requests.post(webhook_url, json=payload, timeout=10)
    resp.raise_for_status()


def player_state_from_api(player):
    return {
        "VACBanned": bool(player.get("VACBanned")),
        "NumberOfVACBans": int(player.get("NumberOfVACBans", 0)),
        "NumberOfGameBans": int(player.get("NumberOfGameBans", 0)),
        "CommunityBanned": bool(player.get("CommunityBanned")),
        "EconomyBan": player.get("EconomyBan", "none")
    }


def has_any_ban(state):
    if not state:
        return False
    return (
        state.get("VACBanned", False)
        or state.get("NumberOfVACBans", 0) > 0
        or state.get("NumberOfGameBans", 0) > 0
        or state.get("CommunityBanned", False)
        or (state.get("EconomyBan", "none") != "none")
    )


def pick_color_for_state(state):
    # VAC = red, Game ban (no VAC) = orange, Community/Economy only = yellow, No bans = green
    if not has_any_ban(state):
        return 0x00FF00
    if state.get("VACBanned", False) or state.get("NumberOfVACBans", 0) > 0:
        return 0xFF0000
    if state.get("NumberOfGameBans", 0) > 0:
        return 0xFFA500
    return 0xFFFF00


def build_embed(steam_id, persona_name, old_state, new_state):
    profile_url = f"https://steamcommunity.com/profiles/{steam_id}"

    old_has = has_any_ban(old_state)
    new_has = has_any_ban(new_state)

    if old_has and not new_has:
        title = f"βœ… Unban detected – {persona_name}"
        desc = "A monitored Steam account appears to have no active bans anymore."
    elif not old_has and new_has:
        title = f"🚨 New ban detected – {persona_name}"
        desc = "A monitored Steam account has received a new ban or restriction."
    else:
        title = f"⚠️ Ban status changed – {persona_name}"
        desc = "The ban status of a monitored Steam account has changed."

    vac_banned = new_state.get("VACBanned", False) or new_state.get("NumberOfVACBans", 0) > 0
    vac_status = "BANNED" if vac_banned else "Not banned"

    fields = [
        {"name": "Steam Name", "value": persona_name or "(unknown)", "inline": False},
        {"name": "Steam ID", "value": steam_id, "inline": False},
        {"name": "VAC Status", "value": f"{vac_status} (Total VAC bans: {new_state.get('NumberOfVACBans', 0)})", "inline": True},
        {"name": "Game Bans", "value": str(new_state.get("NumberOfGameBans", 0)), "inline": True},
        {"name": "Community Ban", "value": "Yes" if new_state.get("CommunityBanned", False) else "No", "inline": True},
        {"name": "Economy / Trade Ban", "value": new_state.get("EconomyBan", "none"), "inline": True},
        {"name": "Profile", "value": profile_url, "inline": False},
    ]

    return {
        "title": title,
        "description": desc,
        "color": pick_color_for_state(new_state),
        "fields": fields
    }


def main():
    cfg = load_config()
    api_key = cfg["steam_api_key"]
    webhook_url = cfg["discord_webhook_url"]
    steam_ids = cfg["steam_ids"]
    state_file = cfg.get("state_file", "ban_state.json")

    state = load_state(state_file)

    print("Starting Steam Ban Monitor...")

    while True:
        try:
            bans = fetch_bans(api_key, steam_ids)
            names = fetch_persona_names(api_key, steam_ids)

            updated = False

            for player in bans:
                steam_id = player["SteamId"]
                persona_name = names.get(steam_id, "(unknown)")

                new_state = player_state_from_api(player)
                old_state = state.get(steam_id)

                if old_state is None:
                    # First time seeing this SteamID: store baseline
                    state[steam_id] = new_state
                    updated = True
                    print(f"[Init] Saved initial state for {steam_id} ({persona_name}).")

                    # If already banned on first run, send one initial alert
                    if has_any_ban(new_state):
                        fake_old = {
                            "VACBanned": False,
                            "NumberOfVACBans": 0,
                            "NumberOfGameBans": 0,
                            "CommunityBanned": False,
                            "EconomyBan": "none"
                        }
                        embed = build_embed(steam_id, persona_name, fake_old, new_state)
                        send_discord_embed(webhook_url, embed)
                        print(f"[Discord] Initial ban notification sent for {steam_id} ({persona_name}).")
                    continue

                if new_state != old_state:
                    state[steam_id] = new_state
                    updated = True

                    embed = build_embed(steam_id, persona_name, old_state, new_state)
                    send_discord_embed(webhook_url, embed)
                    print(f"[Discord] Notification sent for {steam_id} ({persona_name}).")

            if updated:
                save_state(state_file, state)

        except Exception as e:
            print(f"[Error] {e}")

        # Check interval: 300 seconds = 5 minutes
        time.sleep(300)


if __name__ == "__main__":
    main()

If you remove a ban and restart the script after deleting or resetting ban_state.json, the script will treat the current state as the baseline. Unban notifications require a previous β€œbanned” state to be stored in ban_state.json.

Startup Script – start_monitor.bat
Starts the Python script from a fixed folder

Create a file named start_monitor.bat in your monitor folder and adjust the path if needed. This is the recommended entry point for Windows Task Scheduler.

@echo off
cd /d C:\SteamBanMonitor
python steam_ban_monitor.py
How it works
Polling loop, state comparison and notification rules

The script runs in a loop. Every interval (default: 5 minutes), it calls: ISteamUser/GetPlayerBans (ban status) and ISteamUser/GetPlayerSummaries (current Steam persona name). It then compares the result with the previously stored state in ban_state.json.

Change detection rules

  • If any ban-related field changes, a Discord embed is posted.
  • Unbans are detected when a previously banned account has no bans/restrictions anymore.
  • On first run, the state is saved. If an account is already banned, an initial alert can be sent (depending on script version).
Discord notifications
Embed layout, fields, and color-coding

Notifications are delivered as Discord embeds. Each embed includes Steam Name, SteamID64, ban status summary and a profile link.

Color-coding

Condition Color
VAC ban presentRed
Game ban present (without VAC)Orange
Community/Economy restriction onlyYellow
No bans (unban detected)Green

Tip: Keeping the CMD window visible is useful for real-time debugging. You will see [Init], [Change] and [Discord] logs.

Windows Task Scheduler
Run automatically on startup and keep the script running

Recommended start file

Create start_monitor.bat inside your monitor directory:

@echo off
cd /d C:\SteamBanMonitor
python steam_ban_monitor.py

Create a scheduled task

  1. Open Task Scheduler β†’ click Create Task…
  2. Name: Steam Ban Monitor
  3. Enable: Run with highest privileges
  4. Trigger: At startup
  5. Action: Start a program β†’ Program: C:\SteamBanMonitor\start_monitor.bat
  6. Start in: C:\SteamBanMonitor
  7. Save task β†’ optionally run it manually once for validation
API limitations
What Steam does not provide (and update delays)

Steam’s Web API does not provide the specific game title for which a game ban was issued. It typically only provides counts and boolean flags. Additionally, some profile changes may appear on the Steam profile page before the API returns the updated values.

Important: The Steam profile page may show a ban immediately while the API may update later. The monitor can only react to what the API returns.

Troubleshooting
Common issues and how to resolve them quickly

No Discord notification received

  • Verify the webhook URL is correct and still valid.
  • Confirm the SteamID64 is correct (wrong IDs can lead to an empty API response).
  • Check the CMD window for [Error] entries.
  • Ensure the monitored ban type is visible via Steam Web API (not only in-game).

API returns empty players list

If the API output looks like {"players": []}, the SteamID64 is usually invalid or formatted incorrectly. Re-check the SteamID64 value (17 digits, numeric).

Unban not detected

  • API updates can be delayed; wait for the next polling cycles.
  • If you restart the script after deleting the state file, the tool will treat the current state as baseline.
Security notes
Protect API keys and webhook URLs

Do not store secrets in public repositories. Treat steam_api_key and discord_webhook_url as sensitive credentials. Rotate them immediately if leaked.

Recommended practice: keep config.json out of public source control or use environment variables for secrets (advanced).