Build "tell me when Charizard drops below €600" features in any language. Two patterns: pull via REST cron, or push via WebSocket / Business+ webhooks.
| Pattern | Latency | Tier |
|---|---|---|
| REST poll (cron / queue) | ~6 h | Free+ |
WebSocket card:{id} stream | < 5 s after scrape | Starter+ |
| Webhook callback (HTTPS POST) | < 5 s | Business+ |
curl -H "Authorization: Bearer $CSM_KEY" \
"https://collectorstashmarket.com/api/cards/3782/prices"
import os, time, requests, smtplib
from email.message import EmailMessage
CSM = "https://collectorstashmarket.com"
KEY = os.environ["CSM_KEY"]
HDR = {"Authorization": f"Bearer {KEY}"}
# (card_id, target_eur, email)
WATCH = [(3782, 600.0, "buyer@example.com"), (11172, 50.0, "buyer@example.com")]
def cheapest(card_id):
rows = requests.get(f"{CSM}/api/cards/{card_id}/prices", headers=HDR, timeout=10).json().get("prices", [])
eur = [r["market_price"] for r in rows if r.get("currency") == "EUR" and r.get("market_price")]
return min(eur) if eur else None
def notify(to, card_id, price, target):
msg = EmailMessage()
msg["From"], msg["To"] = "alerts@yourdomain.com", to
msg["Subject"] = f"Card {card_id} dropped to €{price:.2f}"
msg.set_content(f"Target was €{target:.2f}.\nhttps://collectorstashmarket.com/cards/{card_id}")
with smtplib.SMTP("localhost") as s: s.send_message(msg)
for cid, target, email in WATCH:
p = cheapest(cid)
if p is not None and p <= target:
notify(email, cid, p, target)
print(f"[ALERT] {cid} {p} <= {target}")
time.sleep(0.5) # respect 60 req/min on Free
import WebSocket from "ws";
const watch = new Map<number, number>([[3782, 600], [11172, 50]]); // cardId -> target EUR
const ws = new WebSocket(
`wss://collectorstashmarket.com/api/v1/stream?api_key=${process.env.CSM_KEY}`,
);
ws.on("open", () => {
ws.send(JSON.stringify({
type: "subscribe",
topics: [...watch.keys()].map((id) => `card:${id}`),
}));
});
ws.on("message", (raw) => {
const f = JSON.parse(raw.toString());
if (f.type === "ping") return ws.send(JSON.stringify({ type: "pong" }));
if (f.type !== "price_update") return;
const { card_id, market_price, currency } = f.data;
const target = watch.get(card_id);
if (target && currency === "EUR" && market_price <= target) {
console.log(`[ALERT] ${card_id} → ${market_price} EUR (target ${target})`);
// POST to your own notify/email/SMS service here
}
});
ws.on("close", () => setTimeout(() => process.exit(1), 5_000)); // let systemd restart
<?php
// /webhooks/csm-price.php — register this URL via the developer dashboard.
// CSM signs every webhook with X-CSM-Signature: hex(hmac_sha256(secret, body)).
$secret = getenv("CSM_WEBHOOK_SECRET");
$body = file_get_contents("php://input");
$sig = $_SERVER["HTTP_X_CSM_SIGNATURE"] ?? "";
if (!hash_equals(hash_hmac("sha256", $body, $secret), $sig)) {
http_response_code(401);
exit("bad signature");
}
$event = json_decode($body, true);
if ($event["type"] === "price.alert.triggered") {
$card = $event["data"]["card_id"];
$price = $event["data"]["market_price"];
// ship to user — your existing alert table lookup goes here
error_log("price alert: card=$card price=$price");
}
http_response_code(200);
import crypto from "node:crypto";
import express from "express";
const app = express();
app.post("/webhooks/csm-price",
express.raw({ type: "application/json" }),
(req, res) => {
const sig = req.header("x-csm-signature") ?? "";
const expected = crypto
.createHmac("sha256", process.env.CSM_WEBHOOK_SECRET!)
.update(req.body).digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
return res.status(401).end("bad signature");
}
const event = JSON.parse(req.body.toString());
if (event.type === "price.alert.triggered") {
// ship to user
}
res.status(200).end();
});
app.listen(8080);