Field Stories10 min read

The Vibe-Coded App That Just Got Its First Paying Customer

By Ergini, Software & AI Developer

A composite story. The company and the people in it are invented. The problem, the rules and the system are real, and the full blueprint is in the use case library.

TL;DR

A composite story: an invented founder in Ghent built a pet-sitting app in Lovable, on Supabase and Stripe, and her first paying customer pays but still sees the trial banner. The audit I would run finds more: access granted by the success page, a policy that lets any signed-in user read every company's alarm codes and away dates, and an OpenAI key in the browser. Data and keys are fixed the same day, billing moves to signed Stripe webhooks, and tests, staging and CI go in before the second customer's data does. The founder keeps prompting screens.

Trial: 3 days left

The screenshot Ilse sends is from her phone: Noor's app, the list of today's visits, and across the top a yellow banner that says Trial: 3 days left. Under it she has typed: "Paid this morning. Did it go through?"

It did. Stripe shows the payment, the first money the app has made from a business that is not Noor's own. Noor runs a pet-sitting business in Ghent: four sitters, about ninety households, mostly cats and one tortoise whose heat lamp needs checking twice a day. Over one winter she built the app she wanted in Lovable, with Supabase behind it and Stripe Checkout selling a monthly plan. She is not a developer, and until this week she has not needed one.

Every household in the app has an address, a key-safe code, an alarm code, a feeding plan and the dates the owners are away. Sitters see their round for the day, tick off each visit and type a line ("fed, litter done, Pip under the bed again"), and the app turns it into a friendly update with a photo for the owner. Ilse's company, six sitters in Antwerp, imported its 120 households from a spreadsheet last week.

Noor fixes the banner the way she fixes most things now. She opens the Supabase table editor, finds Ilse's row and changes plan from trial to pro by hand. Then she prompts for paying users to always be marked as pro, and gets exactly what she asked for: a success page that upgrades whoever loads it. Question closed.

The question she cannot close arrives by email two days later, from a pet-sitting business in Brussels with twenty sitters and a demo booked for the week after next: before they move their households in, who exactly can see their clients' alarm codes? Noor knows the answer she wants to give. She has never seen the code that decides it.

Type /success after the domain

She books a call. First, I ask her to share her screen, open a private window, sign up with an email address she has never used, and type /success after the app's domain.

The yellow banner disappears. The new account is on the paid plan, and nobody has paid.

The success page is where Stripe Checkout sends a customer after payment, and in Noor's app, loading it is what makes an account paid. Ilse most likely closed the tab before it loaded, so she paid and got nothing; anyone who types the address gets the plan free. Stripe already tells an app who has paid, through signed webhook events such as checkout.session.completed and customer.subscription.deleted. This app has never listened.

Noor asks whether she has to start over. Probably not. The screens Lovable generated are good; her sitters use them on doorsteps with a cat winding round their legs. What a builder cannot know is her rules: which company may read which rows, and who has paid. Those live where a demo never looks, in the Supabase policies, the success page and the server functions, and nobody has read them.

Noor and both businesses are invented; what follows is what I would do with any app in her position. I ask for read-only access first: the GitHub repository Lovable syncs to, Supabase and Vercel as a team member, and a Stripe restricted key that can only read. One rule is not negotiable. A coding agent will read her code and draft fixes, but it never holds production credentials, and nothing it writes reaches a user without tests and my review. In 2025, Replit's AI agent deleted a live production database during a code freeze.

Two test sitters and one policy

The first afternoon is checks that produce evidence, not opinions. Claude Code reads a local clone and drafts a map: eleven tables, which screens query Supabase straight from the browser, where addresses, codes and money move. I check the map against the running app, because a map of generated code is itself generated.

Then the probes. Supabase serves the tables through a public API, and the anon key that opens it ships in every copy of the front end, by design. Row-level security policies are all that stands between that key and the data. I create two test companies with a sitter each, sign in as the second sitter and ask for households.

The API returns 214 rows: Noor's 92 households, Ilse's 120 and the two test ones, with addresses, key-safe codes, alarm codes and away dates. The households table does have a policy. Its condition is that the user is signed in, and since anyone can sign up, that means anyone. In a pet-sitting app, that list says which homes are empty this week and how to get in.

The built JavaScript contains the OpenAI key, set through VITE_OPENAI_KEY so the visit updates would work, and anything with that prefix is readable in a browser's developer tools. A gitleaks scan of the git history finds a Stripe secret key deleted from the code in February and still sitting in the history.

Two findings are fixed that evening, and nothing else is touched. A new policy ties each household to the signed-in user's company through a membership table users cannot edit, and a probe test must get none of the other company's rows. At six, Noor and I rotate both keys together, and the visit updates move into a Supabase edge function that checks who is calling and caps each company's use.

I also export the API logs before they age out. Whether anyone outside the two companies read those rows, and whether that must be reported, is a legal judgment under GDPR with a clock on it. It belongs to Noor and a lawyer, and so does what she tells Ilse. My part is keeping the evidence.

What the screen says, and what the check finds

The written report follows two days later: each finding with its evidence, fix and effort, and a severity set by fixed rules. Exposed personal data or secrets are fixed the same day, money at risk goes into the first week, and whatever only slows change goes on a keep, refactor or rebuild list.

In the appWhat the check findsFixed
Each company sees its own householdsA signed-in sitter from any company reads all 214, codes includedSame evening, behind a probe test
Visit updates written by AIThe OpenAI key is in the browser bundle, with no limit per companySame evening: key rotated, call moved to an edge function
A Pro badge after paymentThe success page sets the plan, so typing its address is enoughFirst week: access follows Stripe webhooks
Preview links for new featuresVercel previews write to the production databaseBefore Brussels: a staging project
Backups: onNo restore has ever been triedBefore handover: one real restore

What surprises me is how small the dangerous part is. The screens, a few hundred generated components, can stay. What exposed every alarm code in the database is a few lines of SQL the builder wrote to make one screen work, and nobody has read them since. Noor decides, part by part, what is kept or rebuilt, and she can stop after the report. The full checklist is in the prototype-to-production blueprint.

The pet photos that should have failed the build

Stabilizing comes before anything else changes. Playwright tests cover the three paths that earn money or hold data: signing up, paying through Checkout, and a sitter's round from first visit to owner's update. The probes run in CI on every pull request. The Supabase CLI pulls a baseline migration, so for the first time a file describes the schema. Vercel previews move to a staging project with seeded test companies, Sentry watches the three paths, and one real restore from backup goes into a scratch project.

The coding agent drafts much of this, and review earns its place early: when the first webhook test fails, the agent's proposed fix is to loosen the test until it passes. That draft goes back.

In the second week, Noor prompts a photo gallery onto each pet's page. Lovable syncs with GitHub in both directions, so the change arrives as a pull request, with a new table for the photos and no row-level security policy, because nobody asked for one. Every check passes, because the probes were written by hand for the tables that already existed. I catch it in review: the table would have served every company's pet photos to anyone holding the public key.

The rule that closes the gap is mechanical. One check lists every table in the public schema and fails if any has row-level security off or no probe of its own. The gallery ships a day later, with a policy.

That settles who owns what. The builder keeps screens and components, where prompting is fast and mistakes are visible. Policies, migrations, webhooks and edge functions sit behind tests that fail if a regenerated file weakens them, and branch protection asks for a developer's review on those paths. Noor keeps prompting; she just cannot break a rule without a red check telling her.

Paid means Stripe says paid

Money is the first week. The success page now only says the payment is being confirmed; access follows Stripe's webhooks, and the handler checks each event's signature before believing it.

Stripe can deliver the same event more than once, and in live mode it retries a failed delivery for up to three days. So every event ID goes into a table with a unique constraint: the handler inserts first and acts only if the insert succeeded, in one transaction. Events can also arrive out of order, so the handler reads the current subscription from Stripe instead of trusting the copy inside the event.

A nightly reconciliation compares who has paid with who has access, in both directions. Its first run finds Ilse, who pays in Stripe but was set to paid by hand and is linked to no subscription, and the throwaway account from our first call, still on the paid plan. The next mismatch reaches Noor as a line in a nightly email, not as a customer's screenshot.

What Noor can show Brussels

The Brussels demo happens on the day it was booked. When the owner asks who can see her clients' alarm codes, Noor can show the answer instead of promising it: her own staff, and a probe on every table that fails the build if another company's row ever comes back.

Her mornings look much as they did. She still prompts Lovable for screens over coffee; each change becomes a pull request with a preview on staging, and when it touches only screens and the checks are green, she merges it herself. Errors on the money paths reach Sentry before anyone writes in, the reconciliation email arrives every night with nothing in it, and a bad release is one rollback away in Vercel. The repository, a runbook for incidents and restores, and every credential sit in her accounts, not mine.

Before your second payment

If your app has a handful of users, no payments and no data worth stealing, keep prompting. A founder who can get screens into shape should not pay a developer to do it more slowly. Once money changes hands, or a second company's data sits in the same tables as the first, the two-account test is worth running today.

An audit is fixed in scope. It fixes the dangerous findings first and turns "is my app okay?" into a list with evidence, severity and effort, which you can hand to me, an agency or a future hire. Stabilization and hardening usually sit at the lean end of MVP development, because the product already exists. The full blueprint has the checks and the keep-or-rebuild table, builders and custom code are compared in no-code vs custom MVP, and the stack I harden towards is the SaaS MVP tech stack.

Frequently asked questions

Is my Lovable app safe for paying customers?

You only know once someone checks. The screens a builder generates are usually fine; your rules live in Supabase policies, edge functions and the billing code. A quick first test: create a second test account, sign in as it and try to read the first account's rows with the public anon key. Supabase's security advisor flags tables with row-level security off, but only a probe per table proves a policy is correct.

What should a founder fix first in a vibe-coded app?

Whatever exposes data or money. Personal data readable across accounts, and secret keys in the browser bundle or the git history, get fixed the same day, with the keys rotated. Billing comes next: access should follow Stripe's signed webhooks, each event processed once, not a visit to the success page. Then tests on signup, checkout and the core workflow, a baseline migration, a staging project, error tracking and one real restore from backup.

Can I keep building in Lovable after a developer hardens the code?

Yes, with a split. Lovable syncs with GitHub in both directions, so a prompt can rewrite a file a developer fixed. The builder keeps screens and components, while policies, migrations, webhooks and edge functions sit behind CI tests that fail if a regenerated file weakens them. Every change arrives as a pull request with a preview on a staging database, so prompting stays fast and cannot quietly break the rules.

How much does it cost to make a vibe-coded app production ready?

It depends on what the audit finds, which is why the audit comes first and is fixed in scope. Stabilization and hardening usually sit at the lean end of MVP development, because the product already exists. Redesigning the data model, or billing with seats, trials and EU VAT, costs more. Running costs are mostly the platforms you already pay for, plus a staging project on Supabase.