Files
runtipi/apps/costco-watch/metadata/description.md
T

1.9 KiB

costco-watch

Polls a Costco tire product page on an interval and pushes an ntfy alert when the item flips from out-of-stock to in-stock.

The header gotcha

Costco's bot mitigation silently stalls requests that lack browser fetch-metadata headers - TLS completes, then nothing comes back, no 403, just a hang until timeout. Bisecting against the live site showed the gate is the Sec-Fetch-* family: any single one of Sec-Fetch-Site, Sec-Fetch-Mode or Sec-Fetch-Dest is enough to get a normal response, even with no User-Agent at all. User-Agent, Accept and Accept-Language on their own make no difference.

This service therefore sends a browser header set itself (see BROWSER_HEADERS in watch.js). Two things that do not work as fixes:

  • cloudflared cannot add headers - its originRequest block has no such option, it only forwards what the client sent.
  • Cloudflare Transform Rules cannot set Sec-* headers - they're on Cloudflare's restricted list, so the edge can't supply the one header family that matters.

TARGET_URL still points at the costco-tire.alexzaw.dev cloudflared ingress (which sets httpHostHeader: tires.costco.com), but no Cloudflare-side configuration is required.

Config

Env Purpose
TARGET_URL Product page to watch (required)
ITEM_LABEL Friendly name used in notifications
NTFY_URL / NTFY_TOPIC ntfy server and topic (topic required)
NTFY_TOKEN Bearer token, if the topic is protected
INTERVAL_MINUTES Poll interval, default 60
API_KEY Guards /status and /check

Endpoints

  • GET /health - liveness
  • GET /status - current stock state, last check, transition history
  • POST /check - force a check now

Etiquette

tires.costco.com/robots.txt allows User-agent: * on all paths, so a personal low-frequency check is within its terms. Keep the interval at an hour or more.