Polls the Steam Web API for chosen friends and sends a notification when one of them starts playing a game — either any game, or a specific one you name.
Supported destinations: ntfy, Discord webhooks, generic JSON webhooks, Pushover, and Gotify. Enable as many as you want; each alert is sent to all of them.
Do the setup steps once, then run locally or deploy to the cloud.
Go to https://steamcommunity.com/dev/apikey, sign in, and register a key (any domain name works, e.g. localhost). Free, no approval wait.
For each friend: open their Steam profile and look up their SteamID64 (17-digit number) using https://steamid.io — paste their profile URL in and copy the "steamID64" value.
A friend's game activity is only visible through the API if their profile's "Game details" privacy setting is Public (or Friends Only, if you're actually friends with them on the account tied to your API key). If it's set to Private, you won't get anything back for them.
Build a watches array:
[
{ "steamId": "76561197960287930", "label": "Charlie" },
{
"steamId": "76561197960287930",
"label": "Charlie",
"gameNames": ["Counter-Strike 2", "Dota 2"]
}
]- Omit
gameNames(or use[]) to get alerted whenever that friend starts any game. - Set
gameNamesto one or more titles if you only care about those games (substring match, case-insensitive — any match alerts). labelis optional — falls back to their Steam display name if omitted.- You can watch as many friends as you like; the script batches them into one API call per poll. IDs removed from
watchesare dropped from stored state automatically.
Build a notifications array with one object per destination.
ntfy needs no account — a "topic" is just a private-ish channel name. Pick something unpredictable (e.g. steam-alerts-8f2k1), since anyone who knows the exact name can subscribe to it.
Then either:
- Install the ntfy app (iOS / Android) and subscribe to your topic, or
- Just open
https://ntfy.sh/your-topic-namein a browser to watch it there.
If you self-host ntfy, set url to that origin and token if the topic is protected.
{ "type": "ntfy", "topic": "steam-alerts-8f2k1", "url": "https://ntfy.sh", "token": "" }In a Discord channel: Edit Channel → Integrations → Webhooks → New Webhook. Copy the URL.
{ "type": "discord", "webhookUrl": "https://discord.com/api/webhooks/ID/TOKEN" }POSTs JSON to any URL. Use this for Apprise API, Home Assistant, or a custom endpoint. authorization is sent as the Authorization header (include Bearer if needed).
{ "type": "webhook", "url": "https://example.com/hooks/steam", "authorization": "" }Body:
{
"title": "Charlie is now playing",
"message": "Counter-Strike 2",
"label": "Charlie",
"game": "Counter-Strike 2",
"steamId": "76561197960287930",
"profileUrl": "https://steamcommunity.com/profiles/76561197960287930"
}Create an application at https://pushover.net/apps/build to get an API token. Your user key is on the Pushover dashboard.
{ "type": "pushover", "userKey": "USER_KEY", "apiToken": "APP_TOKEN" }Create an application in your Gotify server and copy its token.
{ "type": "gotify", "url": "https://gotify.example.com", "token": "APP_TOKEN", "priority": 5 }Requires Node.js 24+ and pnpm (or npm).
Copy config.example.json to config.json and fill in your Steam API key plus the watches and notifications arrays from setup:
{
"steamApiKey": "YOUR_STEAM_API_KEY",
"notifications": [
{ "type": "ntfy", "topic": "steam-alerts-8f2k1" },
{ "type": "discord", "webhookUrl": "https://discord.com/api/webhooks/ID/TOKEN" }
],
"pollIntervalMinutes": 5,
"staleAfterMinutes": 720,
"watches": [
{ "steamId": "76561197960287930", "label": "Charlie" }
]
}staleAfterMinutes (optional, default 720 / 12h): if the last successful poll is older than this, prior games are forgotten and anyone still playing will alert again (covers “process was off overnight”).
pnpm install
pnpm startpnpm start stays running: it polls on the interval you set and prints a log line for each alert it sends. Closing the terminal stops it.
pnpm start:once runs once and then closes
To keep it running in the background or across reboots, use whatever process manager you prefer (PM2, systemd, launchd, Task Scheduler, etc.).
State is stored in state.json next to the project (or STATE_PATH).
Skip config.json. Use the same watches and notifications JSON as environment variables WATCHES and NOTIFICATIONS. Put the Steam API key, tokens, and webhook URLs in secrets, not in committed files.
Cloudflare Workers is the best free option: a cron every 5 minutes, KV for last-seen games, no always-on VM. Waiting on Steam and notification APIs does not count toward the 10ms free-plan CPU limit.
Fly.io runs the same Node process as local, with a volume for state. Use it if you already have Fly or want a long-running process.
-
Install deps and copy local Worker secrets:
pnpm install cp .dev.vars.example .dev.vars
-
Edit
wrangler.jsoncvars:WATCHESand a placeholderNOTIFICATIONSif you want. Change the cron if you want a different interval (*/5 * * * *is every 5 minutes, UTC). -
Put
STEAM_API_KEYand the realNOTIFICATIONSJSON in.dev.vars, then:pnpm dev:worker curl "http://localhost:8787/cdn-cgi/handler/scheduled?format=json" -
Deploy, then set secrets (this overwrites
NOTIFICATIONSfromwrangler.jsoncif you set it here):pnpm deploy:worker
then run either
pnpm wrangler secret bulk .dev.vars
OR manually set each secret:
pnpm wrangler secret put STEAM_API_KEY pnpm wrangler secret put NOTIFICATIONS
KV is created automatically on first deploy.
-
Confirm the Worker is live (HTTP returns
steam-friend-alert) and check Cron Events in the Cloudflare dashboard after a few minutes.
State lives in KV. If lastCheckedAt is older than STALE_AFTER_MINUTES (default 720), the next poll treats it as a new session.
-
Install the Fly CLI and log in.
-
Edit
fly.toml: changeappto a unique name, and setWATCHES. PutNOTIFICATIONSin a Fly secret if it includes tokens or webhook URLs. -
Launch (skip generating a new config if it asks):
fly launch --copy-config --name YOUR-APP-NAME fly volumes create steam_alert_state --size 1 fly secrets set STEAM_API_KEY=your-key fly deploy
The process listens on :8080 for Fly health checks and polls in the background. State is stored on the volume so restarts do not re-alert people already in-game.
You can also point Fly at a config.json with CONFIG_PATH / STATE_PATH instead of env vars.
- Steam's default API rate limit (100k calls/day) is far more than this needs even at a 1-minute poll interval with dozens of friends.
- There's no push mechanism from Steam itself — this only works by polling, so alerts land up to one poll interval late.
config.json,.dev.vars, andstate.jsonare gitignored — never commit API keys, webhook URLs, or tokens.