Pricendex
SaaS that alerts Shopify brands the moment a competitor undercuts their price. Low-noise alerting fires only on the undercut transition, not every price change.
- Role
- Founder & Full-Stack Engineer
- Period
- Apr 2025 – Present
- Stack
- Next.js
- TypeScript
- Supabase
- Stripe
- Shopify API
- Browserbase

Problem
Shopify brands lose sales when a competitor quietly drops below their price, and they usually find out too late. Most price trackers make that worse: they flag every price move, so the one change that matters gets lost. Pricendex is a SaaS I'm building to fix that. It's currently in private beta.
Goals
- Alert Shopify brands the moment a competitor undercuts their price.
- Keep alerts low-noise, so brands aren't flooded every time a price moves.
- Do one platform well: Shopify only, not a generic price tracker.
Before building, I ran customer research with Shopify founders. The private beta is where I get feedback on the product.
Architecture
Pricendex is one Next.js App Router application on Vercel. It has Supabase Postgres behind it and a few external services at the edges.
- Web app. Server Components for the dashboard and product pages, Server Actions for mutations, Route Handlers for OAuth callbacks, webhooks and cron jobs.
- Data. Supabase Postgres with SQL migrations and Row Level Security. The core model is stores → tracked products → competitor URLs → price snapshots → undercut alerts.
- Price checks. A daily Vercel Cron job calls a secret-protected route. Each competitor page loads in a Browserbase headless browser session, driven by
playwright-core. - Integrations. Shopify Admin API (OAuth + GraphQL) for importing products and writing prices back. Stripe for subscriptions. Resend for email. PostHog for analytics and error tracking.
Technical decisions
- Alert on the transition, not the price. An alert fires only when a competitor goes from "not undercutting" to "undercutting". A competitor who is already below you, or drops further, doesn't re-fire. That rule is a small pure function, so it's easy to test and hard to break.
- Shopify only. One platform means one product model, one OAuth flow and one write-back API. Products can also be added manually with a typed-in price, so a brand can try Pricendex before connecting a store.
- Auto-adjust is opt-in and bounded. A brand can set a rule per product: cut the price by a fixed amount or a percentage, but never below a floor they choose. It's deliberately fixed-rule, not AI pricing. The floor is a hard limit, even when that means the rule can't fully beat the competitor.
- Tenant from the URL, not the token. Each organization lives on its own subdomain. On every request the app re-derives the account from the host and checks membership, instead of trusting an account ID stored in the session.
- Stripe webhooks own billing state. The app never guesses subscription status from redirects. Access is gated on what the webhook last recorded.
Implementation
Price-check pipeline. The daily job collects every active competitor URL and scrapes each distinct URL once, even when several accounts track the same page. Before loading a page it checks the site's robots.txt and skips disallowed paths, and it identifies itself with its own User-Agent. Every URL runs in its own error boundary, so one bad page doesn't fail the batch. Snapshots are kept for 90 days.
Price extraction. Competitor pages have no known selector, so extraction is a fallback chain: structured data (JSON-LD Offer.price) first, then product price meta tags, then a currency regex. It's a pure function with unit tests for each path.
Alerts and auto-adjust. For each snapshot, the undercut check decides whether this is a new undercut. If it is, Pricendex records the alert, sends the email (unless the brand has muted it), and, if a rule is set, calculates the new price and writes it back to Shopify with a GraphQL mutation. Each step has its own error handling, so a failed price update never blocks the alert.
Accounts and billing. Auth.js handles email/password (with email verification) and Google sign-in. Organizations have owner, admin and member roles, email invites, and per-store access for invited members. Billing uses Stripe subscriptions with a Stripe-native trial, and a single environment flag switches on private-beta mode (invite codes, no subscription required).
Dashboard. An alerts feed, a positioning table (your price, lowest and average competitor, % difference), a price-history chart with range controls, CSV export, and an optional daily or weekly summary email.
Challenges
I verified each slice against a real Shopify dev store, a real Supabase project and Stripe test mode, not just mocks. That surfaced bugs unit tests couldn't catch:
- OAuth redirect mismatch. The Shopify client defaulted to
httpsfor the redirect URI while local development ran onhttp, so Shopify rejected the callback. I now derive the scheme from the app URL. - Subdomain-dropping redirects. Redirects built from
request.urldidn't reflect the real host, which sent users to the root domain and lost their session. I added a helper that builds absolute URLs from theHostheader and used it in every Route Handler redirect. - An API that didn't exist. Account deletion was meant to revoke the store's Shopify token. Testing showed the endpoint didn't work against a real store, so I removed the dead call and added a clear instruction to uninstall the app from Shopify Admin instead.
- Webhook retry loop. Deleting a Stripe customer triggers a later "subscription deleted" webhook for a customer the app no longer knows. The handler threw, and Stripe kept retrying. The sync now treats an unknown customer as a no-op.
Results
Every feature in the planned MVP is built and running in private beta: Shopify connection, competitor tracking, daily price checks, undercut alerts, rule-based auto-adjust, and the dashboard.
End-to-end tests against real services confirmed the alert rule behaves as intended. There was no alert while the brand held the lowest price, exactly one when a competitor undercut, and no duplicate when the undercut continued or the price recovered.
Beta feedback is shaping what comes next. Prospects warned that competing only on lowest price is a race to the bottom, and that an automatic adjuster needs a maximum as well as a floor. That kept auto-adjust opt-in and rule-bounded, and put a price ceiling on the public roadmap.
Tech highlights
- Next.js App Router, React 19 and TypeScript, with Server Components and Server Actions
- Supabase Postgres with migrations and Row Level Security
- Auth.js, subdomain-based multi-tenancy, and role-based access
- Stripe subscriptions driven by webhooks
- Shopify Admin API: OAuth, GraphQL product import and price write-back
- Browserbase and Playwright scraping on Vercel Cron, with robots.txt checks
- Vitest unit tests on the pure pricing logic, Playwright end-to-end tests, and GitHub Actions CI



