Turn competitor complaints into your next customers.
Website · Getting Started · Architecture · Deployment
Important
Deployment Note: The auto-generated website integrated with this repository's default CI/CD pipeline will fail to run correctly because it requires manual environment setup (API keys for Supabase, Stripe, OpenRouter, RapidAPI, etc.).
Therefore, we have fully deployed the working application to our own hosted environment: 👉 Live Demo: https://undercut-sigma.vercel.app
- Demo Video: Watch on YouTube (https://youtu.be/cA9n3KbqTFc)
- Live Application: https://undercut-sigma.vercel.app
Undercut is an AI-powered social listening and automated lead acquisition platform that monitors competitor complaints on X (Twitter) and Instagram. It automates the following workflow:
- Scrapes recent posts targeting specified competitors.
- Filters out noise and irrelevant mentions using an AI classification gate (Gate 1).
- Drafts contextual, brand-aligned pitch replies (Gate 2) using your own product profile, differentiators, and chosen tone of voice.
- Routes the user directly to a pre-filled native Twitter intent link or copies to clipboard for Instagram, allowing users to send human-approved pitches in one click.
Our primary users are:
- Indie Hackers & Solo Founders: Looking to acquire their first adopters by presenting a better alternative to frustrated competitor users.
- SaaS Growth Teams: Seeking to scale organic acquisition channels.
- Bootstrap Developers: Running on limited marketing budgets who want to acquire high-intent users directly.
Undercut targets a global market of creators and startups. However, key markets include:
- United States & Europe: High density of startups, SaaS founders, and growth hackers.
- Southeast Asia (especially Indonesia & Singapore): A rapidly expanding community of tech builders and indie hackers looking for cost-effective organic growth.
- Traditional Social Listening Suites (e.g., Brand24, Mention, Sprout Social): Powerful but expensive, complex to configure, and lack automated, product-aware AI pitch drafting.
- Twitter-only growth tools (e.g., TweetHunter): Great for scheduling and general engagement, but not specialized in intercepting and pitching competitor complaints across multiple platforms.
- Two-Gate AI Filtering: Lowers cost and noise by instantly removing junk posts (Gate 1) before generating response pitches (Gate 2).
- Anti-Bot Philosophy: Instead of automated bot spamming (which risks social account bans), Undercut generates drafts and lets the user review and reply with a single click via native web intents, maintaining account safety and trust.
- Prepaid Credit System: Easy, flexible payment top-ups with Stripe checkout and tiered bonuses, paired with free credits that reset weekly.
- Mobile-Responsive UI: Built to allow founders to monitor leads and pitch alternatives on the go.
Undercut was built and designed for the HackOnVibe hackathon as an AI-powered growth hacking tool. It enables startups and indie hackers to capture high-intent customers by monitoring competitor complaints in real-time.
Undercut is a social listening and automated lead acquisition platform. Instead of spending hours manual-searching keywords on social media, Undercut automates the entire funnel:
- Scrapes recent posts mentioning competitors on X (Twitter) and Instagram.
- Filters out noise using an AI classification gate (only showing genuine complaints or high-intent posts).
- Drafts custom, contextual, and persuasive pitch replies tailored to the user's product, core differentiators, and preferred brand voice.
- Simplifies the reply process with single-click actions (X tweet intents or copy-paste helpers).
Undercut is designed specifically for:
- Indie Hackers & Solo Founders: Acquire early adopters by directly offering your alternative to frustrated users.
- SaaS Growth Teams: Scale organic user acquisition by hijacking competitor complaints.
- Digital Marketers: Track competitor brand sentiments and capture leads in real-time.
For the HackOnVibe hackathon, we built a fully-functioning end-to-end MVP. Here are the core features implemented:
- Real-time background data fetching from X (Twitter) and Instagram via RapidAPI.
- Automatically handles normalizations and filters out duplicates.
- Gate 1 (AI Gatekeeper): Leverages OpenRouter/Nemotron to inspect the raw content and classify if it is a relevant competitor complaint. Irrelevant posts (noise) are immediately hard deleted.
- Gate 2 (AI Reply Draftsman): Leverages DeepSeek to draft personalized replies containing custom product profile differentiators matching the user's brand voice.
- Clean, responsive platform tabs (X and Instagram).
- Draft Sorting: Leads with generated drafts are prioritized and automatically sorted to the top.
- Inline Editing & Validation: Edit drafts directly with real-time character limit enforcement (free X plan auto-restricted to
262chars to prevent Twitter overflow). - Delete All: Clean up queues in one click with stylized action pills.
- Free Trial: New users start with
$0.00balance and 5 free demo credits resetting weekly. - Stripe Top-ups: Fully integrated Stripe checkout session flows with bonus reward tiers (3% bonus for $50+, 5% bonus for $100+).
- Dynamic Success Callback: Handles Stripe webhooks and callbacks, automatically refreshing balances and displaying success notifications with the exact top-up amount.
- Step-by-step onboarding flow setting up Product Name, Landing Page, Core Competitors, and Core Differentiators.
- Interactive Tone of Voice selector (Professional, Casual, Supportive, Bold) with real-time AI-generated preview.
| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router, Turbopack) |
| Language | TypeScript |
| Styling | Tailwind CSS v4 |
| UI / Animation | React 19, Framer Motion, Lucide Icons |
| Auth & Database | Supabase (Auth, PostgreSQL, Row Level Security) |
| LLM Providers | OpenRouter, DeepSeek |
| Scraping | RapidAPI (twitter-api45, instagram-scraper-stable) |
| Payments | Stripe (Checkout, Webhooks) |
src/
├── app/
│ ├── api/
│ │ ├── auth/callback/ # Supabase OAuth callback
│ │ ├── billing/
│ │ │ ├── history/ # GET — transaction history
│ │ │ ├── status/ # GET — credit balance & billing status
│ │ │ ├── topup/ # POST — create Stripe checkout session
│ │ │ └── webhook/stripe/ # POST — Stripe webhook handler
│ │ ├── competitors/ # CRUD for competitor targets
│ │ ├── ingest/scrape/ # POST — trigger scrape for a target
│ │ ├── leads/ # CRUD for leads, draft & reply actions
│ │ ├── pipeline/
│ │ │ ├── process-batch/ # POST — batch-process multiple leads
│ │ │ └── process-lead/ # POST — process single lead (Gate 1 → Gate 2)
│ │ └── profile/ # GET/PUT user profile
│ ├── billing/ # Billing & top-up page
│ ├── dashboard/ # Lead queue (X and Instagram tabs)
│ ├── privacy/ # Privacy policy
│ ├── profile/ # Profile & onboarding settings
│ ├── terms/ # Terms of service
│ ├── layout.tsx # Root layout
│ └── page.tsx # Landing page
├── components/
│ ├── dashboard/ # LeadCard, LeadQueue, MobileTabBar, etc.
│ ├── landing/ # Hero, Pricing, HowItWorks, LiveDemoWidget
│ ├── onboarding/ # OnboardingWizard, TonePreview
│ └── ui/ # Toast, shared UI primitives
├── lib/
│ ├── pipeline/
│ │ ├── concurrency.ts # Concurrency limiter utility
│ │ ├── gate1.ts # Gate 1: complaint classification
│ │ ├── gate2.ts # Gate 2: reply generation
│ │ └── helpers.ts # Pipeline orchestration helpers
│ ├── data.ts # Client-side data fetching
│ ├── fud-keywords.ts # Keyword lists for relevance filtering
│ ├── llm-client.ts # LLM provider abstraction (OpenRouter / DeepSeek)
│ ├── normalizer.ts # Raw API response normalization
│ ├── scraper.ts # X & Instagram scraper integration
│ ├── server-data.ts # Server-side Supabase data access
│ ├── stripe.ts # Stripe client initialization
│ ├── types.ts # Shared TypeScript interfaces
│ └── utils.ts # General utilities
└── proxy.ts # Next.js middleware (auth & onboarding gate)
┌─────────────┐ ┌──────────────────────────────────────────────────┐
│ Landing │ │ Dashboard │
│ Page │──────▶│ ┌────────────┐ ┌────────────┐ ┌───────────┐ │
└─────────────┘ │ │ Lead Queue │ │ Profile │ │ Billing │ │
│ └─────┬──────┘ └────────────┘ └─────┬─────┘ │
└────────┼───────────────────────────────┼────────┘
│ │
┌────────▼────────┐ ┌────────▼────────┐
│ /api/ingest │ │ /api/billing │
│ Scrape targets │ │ Stripe topup │
└────────┬────────┘ └────────┬────────┘
│ │
┌────────▼────────┐ ┌────────▼────────┐
│ RapidAPI │ │ Stripe │
│ X + Instagram │ │ Checkout + │
└────────┬────────┘ │ Webhooks │
│ └─────────────────┘
┌────────▼────────┐
│ /api/pipeline │
│ Gate 1 + 2 │
└────────┬────────┘
│
┌────────▼────────┐
│ LLM Providers │
│ OpenRouter / │
│ DeepSeek │
└────────┬────────┘
│
┌────────▼────────┐
│ Supabase │
│ PostgreSQL + │
│ Auth + RLS │
└─────────────────┘
-
Scrape — User triggers a scrape for a competitor target. The scraper fetches recent posts from X or Instagram, normalizes the data, deduplicates against existing leads, and inserts new leads with
status = PENDINGandgate_1_passed = false. -
Gate 1 (Classification) — When the user clicks "Generate Draft" on a lead, Gate 1 runs first. An LLM evaluates whether the post is a genuine complaint about the target competitor. If it passes, the lead advances to Gate 2.
-
Gate 2 (Reply Generation) — A second LLM call generates a contextual reply draft using the user's product profile, differentiators, and selected tone of voice. Credits are consumed at this stage.
-
Review & Send — The user reviews the AI draft, optionally edits it, and marks the lead as replied.
- Node.js 18+
- npm 9+
- A Supabase project
- A Stripe account (test mode is sufficient for development)
- A RapidAPI account with subscriptions to:
twitter-api45instagram-scraper-stable-api
- An OpenRouter API key
- A DeepSeek API key (used as fallback)
# Clone the repository
git clone git@github.com:chulopp/Undercut.git
cd Undercut
# Install dependencies
npm install
# Copy the environment template and fill in your credentials
cp .env.example .env.local
# Start the development server
npm run devThe application will be available at http://localhost:3000.
Create a .env.local file in the project root with the following variables:
# App
NEXT_PUBLIC_APP_URL=http://localhost:3000
# Supabase
NEXT_PUBLIC_SUPABASE_URL=<your-supabase-url>
NEXT_PUBLIC_SUPABASE_ANON_KEY=<your-anon-key>
SUPABASE_SERVICE_ROLE_KEY=<your-service-role-key>
# LLM Providers
OPENROUTER_API_KEY=<your-openrouter-key>
DEEPSEEK_API_KEY=<your-deepseek-key>
DEEPSEEK_BASE_URL=https://api.deepseek.com
# LLM Models
GATE1_MODEL_PRIMARY=nvidia/nemotron-3-super-120b-a12b:free
GATE1_MODEL_FALLBACK=<comma-separated fallback model list>
GATE2_MODEL=deepseek-chat
GATE2_MODEL_FALLBACK=<comma-separated fallback model list>
# RapidAPI
RAPIDAPI_KEY=<your-rapidapi-key>
RAPIDAPI_HOST_TWITTER=twitter-api45.p.rapidapi.com
RAPIDAPI_HOST_INSTAGRAM=instagram-scraper-stable-api.p.rapidapi.com
# Stripe
STRIPE_SECRET_KEY=<your-stripe-secret-key>
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=<your-stripe-publishable-key>
STRIPE_WEBHOOK_SECRET=<your-webhook-signing-secret>
# Business Config
TOPUP_BONUS_TIER_1_THRESHOLD=50
TOPUP_BONUS_TIER_1_PERCENT=3
TOPUP_BONUS_TIER_2_THRESHOLD=100
TOPUP_BONUS_TIER_2_PERCENT=5npm run build
npm startAfter deploying to your hosting provider, complete the following configuration steps:
Add your production callback URL to the Google Cloud Console:
https://<your-domain>/api/auth/callback
Configure the same URL in Supabase Dashboard → Authentication → Providers → Google.
Create a webhook endpoint in the Stripe Dashboard pointing to:
https://<your-domain>/api/billing/webhook/stripe
Subscribe to the following events:
checkout.session.completed
Update STRIPE_WEBHOOK_SECRET with the signing secret from the newly created endpoint.
Execute the following SQL in your Supabase SQL Editor to create the credit consumption function:
CREATE OR REPLACE FUNCTION consume_cycle_credit(
p_profile_id UUID,
p_lead_id UUID,
p_amount NUMERIC
)
RETURNS JSONB
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
DECLARE
v_profile profiles%ROWTYPE;
v_tx_type TEXT;
v_actual_amount NUMERIC;
BEGIN
SELECT * INTO v_profile
FROM profiles
WHERE id = p_profile_id
FOR UPDATE;
IF NOT FOUND THEN
RETURN jsonb_build_object('ok', false, 'error', 'profile_not_found');
END IF;
IF v_profile.free_demo_credits_remaining > 0 THEN
UPDATE profiles
SET free_demo_credits_remaining = free_demo_credits_remaining - 1
WHERE id = p_profile_id;
v_tx_type := 'FREE_DEMO';
v_actual_amount := 0;
ELSIF v_profile.credit_balance >= p_amount THEN
UPDATE profiles
SET credit_balance = credit_balance - p_amount
WHERE id = p_profile_id;
v_tx_type := 'GATE_2_GENERATION_FEE';
v_actual_amount := p_amount;
ELSE
RETURN jsonb_build_object('ok', false, 'error', 'insufficient_balance');
END IF;
INSERT INTO billing_ledger (profile_id, lead_id, amount_usd, transaction_type)
VALUES (p_profile_id, p_lead_id, v_actual_amount, v_tx_type);
RETURN jsonb_build_object('ok', true, 'type', v_tx_type, 'charged', v_actual_amount);
END;
$$;Set all variables from Environment Variables in your hosting provider's settings. Update NEXT_PUBLIC_APP_URL to your production domain.
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/profile |
Fetch current user profile |
| PUT | /api/profile |
Update profile and onboarding settings |
| GET | /api/competitors |
List all competitor targets |
| POST | /api/competitors |
Create a new competitor target |
| DELETE | /api/competitors/[id] |
Remove a competitor target |
| GET | /api/leads |
List leads (filterable by platform/status) |
| GET | /api/leads/[id] |
Fetch a single lead |
| DELETE | /api/leads/[id] |
Delete a lead |
| POST | /api/leads/[id]/draft |
Generate AI reply draft (Gate 1 + Gate 2) |
| POST | /api/leads/[id]/reply |
Mark lead as replied |
| POST | /api/ingest/scrape |
Trigger scrape for a competitor target |
| POST | /api/pipeline/process-lead |
Process a single lead through the pipeline |
| POST | /api/pipeline/process-batch |
Batch-process multiple leads |
| GET | /api/billing/status |
Fetch credit balance and billing status |
| POST | /api/billing/topup |
Create a Stripe checkout session |
| GET | /api/billing/history |
Fetch transaction history |
| POST | /api/billing/webhook/stripe |
Stripe webhook handler |
All authenticated endpoints require a valid Supabase session cookie. Row Level Security (RLS) policies enforce per-user data isolation at the database level.
| Command | Description |
|---|---|
npm run dev |
Start development server (Turbopack) |
npm run build |
Create production build |
npm start |
Start production server |
npm run lint |
Run ESLint |
This project is proprietary software. All rights reserved.