Build a Price Monitor with AlterLab + Supabase
A step-by-step guide to building a real-time price monitoring system using AlterLab web scraping and Supabase. Covers Edge Functions, pg_cron scheduling, structured extraction, and alert notifications, with full Python and TypeScript code.
AlterLab handles this automatically — scrape any URL with one API call. No infrastructure required.
Try it freePrice monitoring is one of the most common reasons people reach for a web scraping API. Track competitor prices, watch for deals, or keep your own pricing dashboard current. This tutorial builds a complete price monitor from scratch using AlterLab for scraping and Supabase for storage and scheduling.
What We're Building
A price monitor that:
- Scrapes product pages on a schedule using Supabase pg_cron + Edge Functions
- Extracts structured price data (price, currency, in-stock status) using AlterLab's JSON Schema extraction
- Stores price history in Supabase Postgres
- Sends a Slack alert when prices drop below a threshold
All serverless. No VMs, no containers to manage.
Prerequisites
- A Supabase project (free tier works)
- An AlterLab account with API key (get one here)
- Basic familiarity with TypeScript or Python
Step 1: Set Up the Database
Open the SQL editor in your Supabase dashboard and create two tables:
-- Tracked products
create table products (
id uuid primary key default gen_random_uuid(),
name text not null,
url text unique not null,
created_at timestamptz default now()
);
-- Price history
create table price_history (
id uuid primary key default gen_random_uuid(),
product_id uuid references products(id) on delete cascade,
price numeric(10, 2),
currency text default 'USD',
in_stock boolean,
checked_at timestamptz default now()
);
-- Fast lookups
create index on price_history (product_id, checked_at desc);
create index on products (url);Seed some products to track:
insert into products (name, url) values
('Widget Pro', 'https://shop.example.com/widget-pro'),
('Gadget X', 'https://shop.example.com/gadget-x');Step 2: Add Your AlterLab API Key
In your Supabase project: Settings → Edge Functions → Secrets
ALTERLAB_API_KEY=sk_live_your_key_here
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/... (optional)
PRICE_ALERT_THRESHOLD=50.00 (alert when price drops below this)Step 3: Create the Price Monitor Edge Function
Create supabase/functions/price-monitor/index.ts:
import { createClient } from "jsr:@supabase/supabase-js@2";
const supabase = createClient(
Deno.env.get("SUPABASE_URL")!,
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!,
);
// JSON Schema for structured price extraction
const PRICE_SCHEMA = {
type: "object",
properties: {
price: { type: "number", description: "Current price as a number, e.g. 29.99" },
currency: { type: "string", description: "ISO currency code, e.g. USD" },
in_stock: { type: "boolean", description: "Whether the product is currently in stock" },
name: { type: "string", description: "Product name or title" },
},
required: ["price"],
};
async function scrapePrice(url: string) {
const res = await fetch("https://api.alterlab.io/api/v1/scrape", {
method: "POST",
headers: {
"X-API-Key": Deno.env.get("ALTERLAB_API_KEY")!,
"Content-Type": "application/json",
},
body: JSON.stringify({
url,
extraction_schema: PRICE_SCHEMA,
}),
});
if (!res.ok) {
throw new Error(`AlterLab error ${res.status}: ${await res.text()}`);
}
const data = await res.json();
return data.extraction as {
price: number;
currency?: string;
in_stock?: boolean;
name?: string;
};
}
async function sendSlackAlert(product: string, price: number, url: string) {
const webhook = Deno.env.get("SLACK_WEBHOOK_URL");
if (!webhook) return;
await fetch(webhook, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
text: `Price alert: *${product}* dropped to *$${price}*\n${url}`,
}),
});
}
Deno.serve(async () => {
// Load all tracked products
const { data: products, error } = await supabase
.from("products")
.select("*");
if (error) {
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: { "Content-Type": "application/json" },
});
}
const threshold = parseFloat(Deno.env.get("PRICE_ALERT_THRESHOLD") ?? "0");
const results = [];
for (const product of products ?? []) {
try {
const extracted = await scrapePrice(product.url);
// Store in price_history
await supabase.from("price_history").insert({
product_id: product.id,
price: extracted.price,
currency: extracted.currency ?? "USD",
in_stock: extracted.in_stock ?? true,
checked_at: new Date().toISOString(),
});
// Alert if price dropped below threshold
if (threshold > 0 && extracted.price < threshold) {
await sendSlackAlert(product.name, extracted.price, product.url);
}
results.push({ name: product.name, price: extracted.price, ok: true });
} catch (err) {
results.push({ name: product.name, error: String(err), ok: false });
}
}
return new Response(JSON.stringify({ checked: results.length, results }), {
headers: { "Content-Type": "application/json" },
});
});Deploy it:
supabase functions deploy price-monitor --no-verify-jwtCreate tables
products and price_history in the Supabase SQL editor
Add secrets
ALTERLAB_API_KEY and optional SLACK_WEBHOOK_URL
Deploy Edge Function
supabase functions deploy price-monitor
Schedule with pg_cron
Check prices every hour automatically
Step 4: Schedule with pg_cron
Enable pg_cron and pg_net extensions in your Supabase dashboard under Database → Extensions, then run:
-- Check prices every hour
select cron.schedule(
'price-monitor-hourly',
'0 * * * *',
$$
select net.http_post(
url := current_setting('app.supabase_url')
|| '/functions/v1/price-monitor',
headers := jsonb_build_object(
'Authorization', 'Bearer '
|| current_setting('app.service_role_key'),
'Content-Type', 'application/json'
),
body := '{}'::jsonb
);
$$
);That's it. Supabase will now call your Edge Function every hour, scraping all tracked products and recording their prices.
Querying Price History
Check price trends directly in SQL:
-- Latest price for each product
select p.name, ph.price, ph.currency, ph.in_stock, ph.checked_at
from price_history ph
join products p on p.id = ph.product_id
where ph.checked_at = (
select max(checked_at)
from price_history ph2
where ph2.product_id = ph.product_id
);
-- Price history for a specific product (last 7 days)
select ph.price, ph.checked_at
from price_history ph
join products p on p.id = ph.product_id
where p.name = 'Widget Pro'
and ph.checked_at > now() - interval '7 days'
order by ph.checked_at desc;
-- Biggest price drops in the last 24 hours
with ranked as (
select
p.name,
ph.price,
lag(ph.price) over (partition by ph.product_id order by ph.checked_at) as prev_price,
ph.checked_at
from price_history ph
join products p on p.id = ph.product_id
where ph.checked_at > now() - interval '24 hours'
)
select name, prev_price, price,
round((price - prev_price) / prev_price * 100, 1) as pct_change
from ranked
where prev_price is not null
order by pct_change asc
limit 10;Python Alternative
If you prefer a Python script over a Deno Edge Function, use the AlterLab Python SDK with the Supabase Python client:
from alterlab import AlterLabSync
from supabase import create_client
from datetime import datetime, UTC
scraper = AlterLabSync(api_key="sk_live_...")
supabase = create_client(
"https://your-project.supabase.co",
"your-service-role-key",
)
PRICE_SCHEMA = {
"type": "object",
"properties": {
"price": {"type": "number", "description": "Current price"},
"currency": {"type": "string", "description": "ISO currency code"},
"in_stock": {"type": "boolean", "description": "In stock status"},
},
"required": ["price"],
}
def check_prices():
products = supabase.table("products").select("*").execute().data
with scraper:
for product in products:
result = scraper.scrape(
product["url"],
extraction_schema=PRICE_SCHEMA,
)
extracted = result.get("extraction") or {}
supabase.table("price_history").insert({
"product_id": product["id"],
"price": extracted.get("price"),
"currency": extracted.get("currency", "USD"),
"in_stock": extracted.get("in_stock", True),
"checked_at": datetime.now(UTC).isoformat(),
}).execute()
print(f"{product['name']}: ${extracted.get('price')}")
if __name__ == "__main__":
check_prices()Run this on a schedule with your own cron or a simple serverless function.
Cost Estimate
For 10 products checked hourly:
| Item | Rate | Daily cost |
|---|---|---|
| AlterLab scrapes (Tier 1, static) | $0.0002/req | $0.048/day |
| AlterLab scrapes (Tier 4, JS-heavy) | $0.004/req | $0.96/day |
| Supabase Edge Function invocations | Free tier | $0 |
| Supabase Postgres storage | Free tier | $0 |
For most e-commerce sites with basic JavaScript, Tier 1 or Tier 2 is sufficient. AlterLab automatically escalates tiers when a page needs more than a plain HTTP request to render correctly.
Next Steps
- Add a Supabase Realtime subscription to push live price updates to a dashboard
- Use pgvector to store product descriptions and find semantically similar products
- Add email alerts via Supabase Edge Functions + Resend
- Check out the AlterLab Supabase integration docs for more patterns including lead enrichment and content aggregation
Was this article helpful?
Frequently Asked Questions
Related Articles
How to Scrape Without Getting Blocked: The Complete Guide
Every web scraping project eventually hits blocks. This guide covers the complete compatibility stack: headers, proxies, browser environment management, rate limiting, and when to use a scraping API instead.
Yash Dubey

arXiv Data API: Extract Structured JSON in 2026
Learn how to build high-performance data pipelines using an arXiv data API to retrieve structured JSON metadata like titles, authors, and abstracts automatically.
Herald Blog Service

PubMed Data API: Extract Structured JSON in 2026
Learn how to extract structured JSON from PubMed using AlterLab's data API — schema-driven, accurate, and ready for AI pipelines in 2026.
Herald Blog Service
Popular Posts
Recommended

How to Scrape AliExpress: Complete Guide for 2026

Why Your Headless Browser Gets Detected (and How to Fix It)

AlterLab vs Firecrawl: In-Depth Review with Benchmarks & Code Examples

How to Scrape Twitter/X Data: Complete Guide for 2026

How to Scrape Cloudflare-Protected Sites in 2026
Newsletter
Scraping insights and API tips. No spam.
Recommended Reading

How to Scrape AliExpress: Complete Guide for 2026

Why Your Headless Browser Gets Detected (and How to Fix It)

AlterLab vs Firecrawl: In-Depth Review with Benchmarks & Code Examples

How to Scrape Twitter/X Data: Complete Guide for 2026

How to Scrape Cloudflare-Protected Sites in 2026
Stay in the Loop
Get scraping insights, API tips, and platform updates. No spam — we only send when we have something worth reading.
Explore AlterLab
Web Scraping API Resources
Part of the Web Scraping API Documentation cluster
Complete API reference with 5-tier auto-escalation — Curl to challenge resolution.
Pillar pageConfigure Tier 4 browser rendering for SPAs and dynamic content.
Scrape pages behind login using session management.
Real success rates and cost data across all 5 tiers.
MCP Server, Python SDK, and Firecrawl-compatible API for AI agent workflows.