Monitor a store for price changes
Track a Shopify store for daily price changes — set up monitoring, configure frequency, react to webhooks, and query the changelog.
Overview
This guide walks through the full price-monitoring flow: track a store, set its scan frequency, receive webhook callbacks on every new snapshot, and query the changelog API for per-product diffs. By the end you'll have a working pipeline that pages you (or your agent) whenever a tracked store changes pricing.
Prerequisites:
- A ShopSniffer account (Pro Monthly recommended for monitoring)
- An API key
- A webhook endpoint reachable from the public internet
The pipeline
graph LR A[POST /api/shops] --> B[PATCH frequency= daily] B --> C[Daily 12 UTCMonitorShopsWorkflow] C --> D[New snapshot job] D --> E[Changelogdiff computed] E --> F[Webhook POSTto your URL] F --> G[Your handlerfetches changes] G --> H[React: Slack,DB, email, etc.]
Step 1 — track the store
bashcurl -X POST https://shopsniffer.com/api/shops \ -H "X-API-Key: ss_your_key_here" \ -H "Content-Type: application/json" \ -d '{"domain": "allbirds.com"}'
You'll get back a shop object with the slug. Save the slug — you'll use it in subsequent calls.
Step 2 — set daily frequency
By default, shops are tracked weekly. For price monitoring, you almost certainly want daily:
bashcurl -X PATCH https://shopsniffer.com/api/store-detail/allbirds-com/frequency \ -H "X-API-Key: ss_your_key_here" \ -H "Content-Type: application/json" \ -d '{"frequency": "daily"}'
The effective frequency is the most aggressive across all subscribers to this store — if any user sets daily, everyone gets daily. See monitoring workflows for the rules.
Step 3 — wire up a webhook endpoint
Create a small server that receives job.completed events and calls the changelog API to fetch diffs:
typescriptimport { Hono } from "hono"; const app = new Hono(); app.post("/webhook/shopsniffer", async (c) => { const body = await c.req.json(); if (body.event !== "job.completed") { return c.json({ ok: true }); } // Fetch the per-job changelog const changes = await fetch( `https://shopsniffer.com/api/changelog/${body.job_id}`, ).then((r) => r.json()); // Filter to price changes only const priceChanges = changes.changes?.flatMap((entry) => entry.details?.filter((d) => d.type === "price_change") ?? [], ) ?? []; if (priceChanges.length > 0) { console.log(`${priceChanges.length} price changes on ${body.domain}`); // Send to Slack, Discord, DB, email, etc. await notifySlack(body.domain, priceChanges); } return c.json({ ok: true }); }); export default app;
javascriptimport express from "express"; const app = express(); app.use(express.json()); app.post("/webhook/shopsniffer", async (req, res) => { const { event, job_id, domain } = req.body; if (event !== "job.completed") { return res.json({ ok: true }); } const changes = await fetch( `https://shopsniffer.com/api/changelog/${job_id}`, ).then((r) => r.json()); const priceChanges = (changes.changes ?? []) .flatMap((e) => e.details ?? []) .filter((d) => d.type === "price_change"); if (priceChanges.length > 0) { console.log(`${priceChanges.length} price changes on ${domain}`); await notifySlack(domain, priceChanges); } res.json({ ok: true }); }); app.listen(3000);
pythonfrom fastapi import FastAPI import httpx app = FastAPI() @app.post("/webhook/shopsniffer") async def webhook(body: dict): if body.get("event") != "job.completed": return {"ok": True} async with httpx.AsyncClient() as client: r = await client.get( f"https://shopsniffer.com/api/changelog/{body['job_id']}" ) changes = r.json() price_changes = [ d for entry in changes.get("changes", []) for d in entry.get("details", []) if d.get("type") == "price_change" ] if price_changes: print(f"{len(price_changes)} price changes on {body['domain']}") await notify_slack(body["domain"], price_changes) return {"ok": True}
Webhooks are unsigned — ShopSniffer does not sign callbacks with HMAC. Protect your endpoint by:
- Using a hard-to-guess path (e.g.
/webhook/shopsniffer/<random-token>) - Validating the
job_idagainst a job you created and stored locally - Allowlisting Cloudflare Workers egress IPs on your firewall
Because delivery is single-attempt (no retries), also build a fallback poll that reconciles any missed events.
Step 4 — register the webhook URL on the next scan
Webhooks are per-job, not per-store. To receive callbacks on every daily scan, register a callback URL when creating a job for that store — the easiest way is to create an "initial" job with the webhook and let the monitoring workflow inherit the pattern. Or, more reliably, create a small reconciliation cron on your side that polls once a day if no webhook arrived.
For an always-reliable pipeline, combine webhooks with a safety-net poll: when you create any job, register the webhook and schedule a timer that fires in 15 minutes. If the webhook hasn't arrived, poll GET /store-detail/:slug/changes?page=1&limit=1 to see if the daily scan produced new changes.
Step 5 — react to price changes
Once you have price diffs flowing into your handler, the downstream options are yours:
- Slack / Discord / Teams — paste the diff into a channel
- Database — append to a
price_historytable for trend charts - Email digest — batch daily and send one summary per recipient
- Agent trigger — kick off an LLM prompt to analyze the diff ("are these typical seasonal price moves?")
- Shopify sync — auto-match a competitor's new sale price in your own store
Querying the changelog directly
Even without webhooks, you can query the changelog at any time:
bash# Get the 50 most recent changes for a store curl "https://shopsniffer.com/api/store-detail/allbirds-com/changes?page=1&limit=50" \ -H "X-API-Key: ss_your_key_here" # Filter to only product changes curl "https://shopsniffer.com/api/store-detail/allbirds-com/changes?item_type=product" \ -H "X-API-Key: ss_your_key_here"
See the monitoring API for the full set of monitoring endpoints, including snapshot history, compare, analytics, and inventory events.