<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Novruz Veliev</title>
    <description>The latest articles on DEV Community by Novruz Veliev (@novruz_veliev).</description>
    <link>https://dev.to/novruz_veliev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4132221%2Fe7b160c1-7e58-4687-a80b-541cfe0699f4.jpg</url>
      <title>DEV Community: Novruz Veliev</title>
      <link>https://dev.to/novruz_veliev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/novruz_veliev"/>
    <language>en</language>
    <item>
      <title>Stars, user counts and logos: a trust check for your launch page</title>
      <dc:creator>Novruz Veliev</dc:creator>
      <pubDate>Mon, 26 Oct 2026 09:04:23 +0000</pubDate>
      <link>https://dev.to/novruz_veliev/stars-user-counts-and-logos-a-trust-check-for-your-launch-page-3kdn</link>
      <guid>https://dev.to/novruz_veliev/stars-user-counts-and-logos-a-trust-check-for-your-launch-page-3kdn</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://shipclarity.app/launch-page-social-proof-check.html?r=devto" rel="noopener noreferrer"&gt;shipclarity.app&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When I read a freshly launched product the way a buyer would, the trust strip under the hero is one of the first things I check. Stars, a user count, a row of logos, "Featured on". Buyers check it too, and they often check it against the one place you don't control: the store listing, the launch page, the company's own LinkedIn. When the two don't match, the badge that was meant to build trust becomes the reason someone leaves.&lt;/p&gt;

&lt;p&gt;None of these are bugs in the code. They usually come from a template, a placeholder that nobody replaced, or a number that was true on launch day and stopped being true. Below are six versions I keep finding (product names removed), what each one costs you, and a 10-minute check for your own page.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. A star rating with no reviews behind it
&lt;/h2&gt;

&lt;p&gt;The most direct version: the hero says "5.0 rating on the App Store" or "4.9 on Shopify", and the listing it points to says "No reviews yet". I've seen this on an app launched the same week, where the badge was clearly part of the landing template. A merchant who is about to install clicks through to the listing first. That click is the last step before the decision, and it lands on a zero.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to check:&lt;/strong&gt; for every rating on your site, open the listing it refers to in a private window and compare the number and the review count. If the listing has no reviews yet, take the badge down until it does, or replace it with something you can show, like "launched this week, early users welcome".&lt;/p&gt;

&lt;h2&gt;
  
  
  2. A rating that lives only in the page code
&lt;/h2&gt;

&lt;p&gt;Some page templates ship with review markup for search engines: a block of structured data that says the product has a 5.0 rating from 48 reviews. Nobody sees it on the page, so nobody notices it. Google's structured data guidelines expect a rating in the markup to be visible on the page and to come from real reviews, and markup that misleads can get a site a manual action in Search Console. It's the kind of thing a template adds and a founder never reads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to check:&lt;/strong&gt; open your page source and search for &lt;code&gt;aggregateRating&lt;/code&gt; and &lt;code&gt;reviewCount&lt;/code&gt;. If they are there, the numbers must match reviews a visitor can actually see. If you have no reviews yet, delete the block. Google's Rich Results Test will show you what it reads from the page.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. User counts and live activity that the product can't back
&lt;/h2&gt;

&lt;p&gt;"Trusted by 10,000+ makers" on a product that launched on Monday. A live feed of "someone just signed up" that repeats the same five names. A leaderboard pre-filled by the founder so the page wouldn't look empty on day one. The intention is understandable, an empty room looks bad. But visitors notice faster than founders expect, and one public comment saying "these numbers are fake" does more damage than an empty room ever would. I watched one founder get called a scam for exactly this, and the fix that worked was the honest one: zero real users shown as zero, with a clear "be the first" message.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to check:&lt;/strong&gt; for every number on the page, ask where it comes from today. If the answer is "I typed it" or "seed data", either remove it or label it plainly as an example. A small true number ("42 people signed up this week") reads better than a big round one.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Logo walls and "used by teams at"
&lt;/h2&gt;

&lt;p&gt;A row of well-known company logos under "used by teams at", when one person from that company once signed up for the free plan, or nobody did and the row came with the template. Big logos invite exactly the people who will check: buyers at mid-size companies who can ask a colleague. Using a company's logo without its permission can also bring a polite but firm email from its legal team.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to check:&lt;/strong&gt; for each logo, can you name the person or team who uses the product and say you have permission to show it? If not, take the logo down. "Used by solo founders and small teams" with no logos at all is fine at launch.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Testimonials nobody can trace
&lt;/h2&gt;

&lt;p&gt;Three glowing quotes with first names, stock photos and job titles like "Founder, SaaS company". Sometimes they are placeholder text from the template, sometimes they came from friends during the beta. Either way, a quote nobody can trace doesn't persuade, and a stock photo that shows up on five other landing pages is easy to find with a reverse image search. In the US, the FTC's rule on fake reviews and testimonials, in force since October 2024, also covers made-up quotes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to check:&lt;/strong&gt; keep only quotes from real people who agreed to be quoted, with their real name and, ideally, a link to their product or profile. If you have none yet, leave the section out. One real quote beats three invented ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Launch badges that outlived the launch
&lt;/h2&gt;

&lt;p&gt;"#1 Product of the Day" when the product finished fifth. "Featured on" with the logo of a site that only listed it in a directory. A launch badge with a live widget that now shows a low upvote count from months ago. These are smaller than the other five, but they sit right next to the price, and a visitor who catches one stretched claim starts reading every other line with suspicion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to check:&lt;/strong&gt; click every badge on your page and check that the page behind it says what the badge says. Replace "#1" with the place you actually got, or with a plain "Launched on" link.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 10-minute check
&lt;/h2&gt;

&lt;p&gt;Open your landing page in a private window. Next to it, open every outside page your trust strip refers to: the store listing, the launch page, the review site. Go through the strip left to right and, for each item, find the same fact on the outside page. Then view the page source and search for &lt;code&gt;aggregateRating&lt;/code&gt;. Anything you can't match, remove or relabel today. Repeat after every redesign, because new templates bring their own badges.&lt;/p&gt;




&lt;p&gt;The checks above are all visible from outside. What isn't: whether the signup email arrives, whether one user can see another user's data, and whether the payment and the receipt match what the pricing page promised. That part is what I check by hand at &lt;a href="https://shipclarity.app/?r=devto" rel="noopener noreferrer"&gt;ShipClarity&lt;/a&gt;, with real test accounts and screen recordings of every step.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post was written with AI assistance.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>startup</category>
      <category>saas</category>
      <category>webdev</category>
      <category>marketing</category>
    </item>
    <item>
      <title>Pricing page vs terms: six mismatches I keep finding on launch day</title>
      <dc:creator>Novruz Veliev</dc:creator>
      <pubDate>Mon, 19 Oct 2026 08:30:10 +0000</pubDate>
      <link>https://dev.to/novruz_veliev/pricing-page-vs-terms-six-mismatches-i-keep-finding-on-launch-day-59li</link>
      <guid>https://dev.to/novruz_veliev/pricing-page-vs-terms-six-mismatches-i-keep-finding-on-launch-day-59li</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://shipclarity.app/pricing-page-vs-terms.html?r=devto" rel="noopener noreferrer"&gt;shipclarity.app&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For the last month I've been reading the pricing pages of freshly launched products the way a person about to pay would: the plan card, the toggle, the FAQ, and then the terms and the refund policy. The most common money problem I find is not a broken checkout. It's two pages of the same site describing the same plan differently.&lt;/p&gt;

&lt;p&gt;None of this crashes the app, so nobody reports it. A careful buyer just stops at the moment you want them to pay and wonders which line is true. And if there is ever a dispute, the line that counts is usually the one in the terms, not the friendly one on the card. Below are the six mismatches I see most, with real examples (product names removed), and a 10-minute check you can run on your own site.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. "Unlimited" and "lifetime" on the card, a limit in the terms
&lt;/h2&gt;

&lt;p&gt;This is the one I meet most. The top plan lists "unlimited" something, and the terms say every plan has strict limits, with the top plan named in the same sentence. Or the FAQ promises unlimited use of a feature and the plan table caps it at 1,000 a month. Or the card says "lifetime" and the terms say updates stop after one year.&lt;/p&gt;

&lt;p&gt;The cause is almost always timing. The terms were written once, often from a template or by an AI builder, when the product had different plans. The pricing page has been edited ten times since.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to check:&lt;/strong&gt; search your pricing page and FAQ for &lt;code&gt;unlimited&lt;/code&gt;, &lt;code&gt;lifetime&lt;/code&gt;, &lt;code&gt;forever&lt;/code&gt; and &lt;code&gt;every plan&lt;/code&gt;. For each hit, search the terms for the same feature. If the terms mention a limit, a fair-use rule or an end date, either the card or the terms has to change.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. One-time on the card, auto-renew in the terms
&lt;/h2&gt;

&lt;p&gt;A pass sold as "one-time, no auto-renew", and terms that say all paid tiers are billed on a recurring basis until cancelled. The founder knows the pass is one-time. The buyer who reads the terms before paying does not, and the bank that handles a chargeback reads the terms too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to check:&lt;/strong&gt; list every way to pay you: monthly, yearly, one-time pass, credits, lifetime deal. Then read the billing section of your terms and ask of each one: does this sentence describe it correctly? A single added line ("the $X pass is a one-time payment and does not renew") usually fixes it.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. A refund promise that the refund policy doesn't back
&lt;/h2&gt;

&lt;p&gt;Three versions of this come up. The pricing page says "7-day money back" and the refund policy says payments are non-refundable. The refund policy describes plans the site no longer sells, or a downgrade to a plan that isn't on the pricing page. Or the terms say nothing at all about cancelling and refunds, while the site takes a monthly subscription.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The silent version matters as much as the contradicting one. If the terms of a subscription product don't say how to cancel and whether a month is ever refunded, the first angry customer gets to decide what was promised.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;How to check:&lt;/strong&gt; search the whole site for &lt;code&gt;refund&lt;/code&gt;, &lt;code&gt;money back&lt;/code&gt;, &lt;code&gt;cancel&lt;/code&gt; and &lt;code&gt;guarantee&lt;/code&gt;. Every place that mentions them should say the same number of days and the same conditions, and the plan names in the refund policy should be the ones you sell today.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The page's own math
&lt;/h2&gt;

&lt;p&gt;Here the pricing page disagrees with itself. A yearly toggle that says "save up to 35%" while the best plan works out to 32%. "2 months free" on a yearly price that actually gives away almost four. A plan card promising 250 images next to a credit table that charges 2 credits per image, which makes it 125. The same plan shown at two different prices on two pages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to check:&lt;/strong&gt; take a calculator and redo every percentage, every "N months free" and every "about N items" on the page. Then open each page that shows a price (home, pricing, checkout, FAQ, terms) and compare the numbers and the currency. Euros on the homepage and dollars in the terms is more common than you'd think.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Features on the plan card that say "coming soon" elsewhere
&lt;/h2&gt;

&lt;p&gt;The trial promises a result "in minutes", and the feature that produces that result has a "coming soon" badge on the features page. The homepage says a database comes with every plan, and the cheapest plan in the table doesn't include one. The home page sells a service from a plan that is itself marked as not available yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to check:&lt;/strong&gt; read your plan cards feature by feature and click through to where each feature is described. Anything with "soon", "beta" or "waitlist" next to it should not be listed as included in a plan people can pay for today, or it should carry the same label on the card.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Speed and guarantee words
&lt;/h2&gt;

&lt;p&gt;"Instant", "real-time", "the moment it happens", "guaranteed for 10 years". The headline uses them, and further down the FAQ says the check runs once every 24 hours, or the terms say there is no guarantee of availability. Good terms often admit the limit honestly. The problem is only that the headline promises more than the terms are willing to stand behind.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to check:&lt;/strong&gt; find every time-and-certainty word in your hero and plan cards, and look for the real number in the FAQ, docs and terms. If the real number is "daily", say "daily" at the top, or show the faster interval on the plan that has it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 10-minute check
&lt;/h2&gt;

&lt;p&gt;Open four tabs: pricing page, FAQ, terms, refund policy. Read them in that order, straight after each other, as if you were about to enter a card number. For every plan, write down four things from each page: what it costs, how it renews, what it includes, and how to get money back. Where two pages give different answers, fix the one that is out of date. Then set a reminder to repeat this whenever you change a price or add a plan, because that's when the pages drift apart again.&lt;/p&gt;




&lt;p&gt;Everything above can be read from outside, without an account. What can't: what the checkout actually charges, what the receipt and emails say, whether a cancelled plan really stops billing, and whether a refund removes access. That part is what I check by hand at &lt;a href="https://shipclarity.app/?r=devto" rel="noopener noreferrer"&gt;ShipClarity&lt;/a&gt;, with real test accounts and screen recordings of every step.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post was written with AI assistance.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>saas</category>
      <category>startup</category>
      <category>webdev</category>
      <category>testing</category>
    </item>
    <item>
      <title>Lovable Deep Scan vs a manual check: what the scan finds and what it can't see</title>
      <dc:creator>Novruz Veliev</dc:creator>
      <pubDate>Mon, 12 Oct 2026 09:15:29 +0000</pubDate>
      <link>https://dev.to/novruz_veliev/lovable-deep-scan-vs-a-manual-check-what-the-scan-finds-and-what-it-cant-see-18a0</link>
      <guid>https://dev.to/novruz_veliev/lovable-deep-scan-vs-a-manual-check-what-the-scan-finds-and-what-it-cant-see-18a0</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://shipclarity.app/lovable-deep-scan-vs-manual-check.html?r=devto" rel="noopener noreferrer"&gt;shipclarity.app&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Lovable now ships with its own security scanner. A quick scan runs on every publish, and a Deep scan reviews your whole codebase when you ask for it. Founders often ask me whether that makes a manual check pointless. Short answer: run the scan first, it's free and it's good. Then look at the list below of what it was never built to see.&lt;/p&gt;

&lt;p&gt;Everything I say about Lovable's feature here comes from its own documentation and blog, as of October 2026. Lovable changes fast, so if a detail below looks out of date, the docs win.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Lovable's scans are
&lt;/h2&gt;

&lt;p&gt;There are two built-in scans. Both are free to run and don't use credits.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Quick scan&lt;/strong&gt; runs by itself every time you publish and takes about 10 to 15 seconds. It reads your database access rules and settings, including Row Level Security, and checks your npm packages against known vulnerabilities. Only packages rated critical become findings.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Deep scan&lt;/strong&gt; does not run by itself. You start it from the security view, and it takes a few minutes. It is an AI review of all your application code. Lovable's docs list what it looks at: access control and authorization, unauthenticated endpoints, unsafe input and injection, leaked secrets, payments and billing, authentication and account security, and exposed personal data. Lovable suggests running it before a public launch, when you handle sensitive data, or after big changes.&lt;/p&gt;

&lt;p&gt;Findings come in three levels: Critical, Warning and Info. Some findings can be fixed with one click. Deep scan findings are not auto-fixed, so you fix those yourself or ask Lovable in chat, which costs credits.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Lovable's own docs say these tools find common security issues but "cannot guarantee complete security". That is a fair description, and worth keeping in mind when the scan comes back clean.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What the scan is good at
&lt;/h2&gt;

&lt;p&gt;Credit where it's due. A table with RLS switched off, a policy that lets anyone read everything, a secret key pasted into frontend code, a package with a known critical hole: these are exactly the mistakes AI builders make, and the scan catches them in seconds. It also never gets tired. It reads every file, every time.&lt;/p&gt;

&lt;p&gt;The Deep scan goes further than most founders expect. It reads your code with your app in mind, so it can flag an Edge Function that anyone can call without logging in, or a price that comes from the browser and could be changed by the buyer. A human reviewer reading the same code would need hours to do that.&lt;/p&gt;

&lt;p&gt;If you have never run it, stop here and run it. Fix what it flags. Paying anyone to find what a free tool finds in four minutes is a waste of money.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it can't see some problems
&lt;/h2&gt;

&lt;p&gt;Both built-in scans read. They read code, database rules and package lists. Lovable's docs describe them as static analysis. They don't open your live app, sign up, wait for an email, or log in as anybody.&lt;/p&gt;

&lt;p&gt;That matters, because a lot of launch bugs don't live in the code at all. They live in the email provider's settings, in the Stripe dashboard, in a redirect URL saved in Supabase, in what your pricing page promises. Code can look right and the app can still fail when a real person uses it.&lt;/p&gt;

&lt;p&gt;To be fair, Lovable also lists optional third-party connectors, including an AI penetration test that sends requests to your running app and tries to break authentication and API flows. That is closer to testing, and worth a look for apps with sensitive data. It is still a security tool. It doesn't ask whether a customer can get through your product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seven things only a person with accounts finds
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The sign-up email that never arrives
&lt;/h3&gt;

&lt;p&gt;The code calls the email function correctly. The scan is happy. Meanwhile the sending domain isn't verified, or the default sender has hit its limit, and new users sit waiting for a confirmation link. You only see it by signing up with a fresh inbox and waiting. More on this in &lt;a href="https://shipclarity.app/lovable-password-reset-email-not-arriving.html?r=devto" rel="noopener noreferrer"&gt;why Lovable reset emails don't arrive&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The password reset that leads nowhere
&lt;/h3&gt;

&lt;p&gt;Even when the reset email does arrive, the link often opens an old preview address or a page that doesn't handle the token. The user clicks, sees an error, and gives up. Nothing about this is a security flaw, so no security scan will report it. It still loses you customers.&lt;/p&gt;

&lt;h3&gt;
  
  
  One user seeing another user's data
&lt;/h3&gt;

&lt;p&gt;The scan checks that RLS policies exist and aren't wide open. It can't tell you whether the policy matches what you meant. A policy can be valid and still let a team member see every team, or let a user read rows through a view nobody thought about. The way to know is practical: two accounts, one creates data, the other tries to reach it through the screens, the URL and the API. The do-it-yourself version is in &lt;a href="https://shipclarity.app/supabase-rls-anon-key-check.html?r=devto" rel="noopener noreferrer"&gt;Supabase RLS and the anon key&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test keys on the live site
&lt;/h3&gt;

&lt;p&gt;The Deep scan reviews payment logic in your code. It doesn't see which Stripe mode your live site actually runs in, whether the live webhook exists, or whether the price IDs in your secrets belong to test mode. Those settings live in dashboards, outside the codebase. The 15-minute check is in &lt;a href="https://shipclarity.app/stripe-test-mode-vs-live-mode.html?r=devto" rel="noopener noreferrer"&gt;Stripe test mode vs live mode&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The pricing page that disagrees with the terms
&lt;/h3&gt;

&lt;p&gt;Your pricing page says "cancel anytime" and the terms say annual plans aren't refundable. The plan card says 5 projects and the app stops you at 3. This is a mismatch between words on two pages and behaviour in a third, and it only shows up when someone reads them side by side and then tries the limit.&lt;/p&gt;

&lt;h3&gt;
  
  
  The layout on a real phone
&lt;/h3&gt;

&lt;p&gt;A button hidden under the keyboard, a modal you can't close on a small screen, a table that scrolls the whole page sideways. Code review can't predict how Safari on an iPhone renders your page. Someone has to hold the phone.&lt;/p&gt;

&lt;h3&gt;
  
  
  Broken paths through the app
&lt;/h3&gt;

&lt;p&gt;A link to a page that was renamed two prompts ago. A Back button that drops you on a blank screen. An onboarding step that only works if you click in the expected order. Each piece of code is fine on its own. The path between them is broken, and a path only exists when someone walks it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The order that makes sense
&lt;/h2&gt;

&lt;p&gt;Run the Quick scan on every publish. It happens anyway, so read what it says instead of clicking past it. Run the Deep scan before launch and after big changes, and fix what it flags.&lt;/p&gt;

&lt;p&gt;Then spend an hour as your own first customer. Use a new email address. Sign up, reset the password, pay, and try to open data that belongs to a second account. Do it once on your phone. You'll find some of the gaps above yourself, and that hour is worth more than any tool.&lt;/p&gt;

&lt;p&gt;For a sense of what shows up from the outside alone, I scanned &lt;a href="https://shipclarity.app/lovable-apps-what-breaks.html?r=devto" rel="noopener noreferrer"&gt;190 live Lovable apps&lt;/a&gt; without logging in. Behind the login there is more, and that part needs accounts.&lt;/p&gt;

&lt;p&gt;If you'd rather have someone else be the first customer, that manual pass is what I do at &lt;a href="https://shipclarity.app/?r=devto" rel="noopener noreferrer"&gt;ShipClarity&lt;/a&gt;: real inboxes, two accounts, test payments, real phones. Run Lovable's scans first. I start where they stop.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post was written with AI assistance.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>lovable</category>
      <category>testing</category>
      <category>webdev</category>
      <category>security</category>
    </item>
    <item>
      <title>Stripe test mode vs live mode: a 15-minute check before your first real payment</title>
      <dc:creator>Novruz Veliev</dc:creator>
      <pubDate>Mon, 05 Oct 2026 09:01:15 +0000</pubDate>
      <link>https://dev.to/novruz_veliev/stripe-test-mode-vs-live-mode-a-15-minute-check-before-your-first-real-payment-p6</link>
      <guid>https://dev.to/novruz_veliev/stripe-test-mode-vs-live-mode-a-15-minute-check-before-your-first-real-payment-p6</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://shipclarity.app/stripe-test-mode-vs-live-mode.html?r=devto" rel="noopener noreferrer"&gt;shipclarity.app&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Stripe gives you two copies of everything: one for testing, one for real money. Keys, products, prices, webhooks, payment links and the customer portal all exist twice. Most launch-day payment bugs I see in Lovable and Bolt apps come from one piece still pointing at the test copy while the rest has moved to live.&lt;/p&gt;

&lt;p&gt;The symptoms are quiet. Checkout works for you, because you test with the card that always works. A real customer gets an error, or pays and never gets access, and you hear about it days later, if at all. Below is the check I run before an app takes its first real payment. It takes about 15 minutes and one small real purchase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Find out which key your live site actually uses
&lt;/h2&gt;

&lt;p&gt;Every Stripe key says which copy it belongs to. Publishable keys start with &lt;code&gt;pk_test_&lt;/code&gt; or &lt;code&gt;pk_live_&lt;/code&gt;, secret keys with &lt;code&gt;sk_test_&lt;/code&gt; or &lt;code&gt;sk_live_&lt;/code&gt;. The publishable key sits in your frontend, the secret key must only live on a server - in a Lovable or Bolt app that usually means Supabase Edge Function secrets or your hosting's environment variables.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to check
&lt;/h3&gt;

&lt;p&gt;Open your live site, then DevTools → Sources, and search the loaded JavaScript for &lt;code&gt;pk_test_&lt;/code&gt;. If it's there, your checkout still starts in test mode. While you're in there, search for &lt;code&gt;sk_&lt;/code&gt; too: a secret key in the browser is a bigger problem than test mode, and it needs rotating in the Stripe dashboard today.&lt;/p&gt;

&lt;p&gt;Then check the server side: in Supabase, Edge Functions → Secrets (or wherever your project keeps them), look at the Stripe secret. It's common to switch the publishable key and forget the secret one, or the other way round. Both have to say live.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prices and products don't move to live by themselves
&lt;/h2&gt;

&lt;p&gt;A product or price you created in test mode does not exist in live mode. Its ID (&lt;code&gt;price_...&lt;/code&gt;) is only valid in the copy where it was created. If your code or your AI builder hardcoded test price IDs, live checkout fails with an error like "No such price", even though the key is right.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to check
&lt;/h3&gt;

&lt;p&gt;Stripe dashboard, test mode switched off → Product catalog. Every plan your pricing page sells should be there with the same amount, currency and interval. Then compare the price IDs your app uses (search your code or Edge Functions for &lt;code&gt;price_&lt;/code&gt;) with the IDs shown in live mode. They will be different strings - that's expected - and every one in your code must be the live one.&lt;/p&gt;

&lt;p&gt;Check the amounts while you're there. A $19 plan in test mode and a $190 plan in live mode, or monthly in one and yearly in the other, is a mistake that nobody catches with a test card.&lt;/p&gt;

&lt;h2&gt;
  
  
  Webhooks: a separate endpoint and a separate secret
&lt;/h2&gt;

&lt;p&gt;Webhooks are how Stripe tells your app "this person paid". They're set up per mode. A webhook endpoint you added in test mode receives nothing from live payments, and its signing secret (&lt;code&gt;whsec_...&lt;/code&gt;) is different from the live one. The result is the worst kind of bug: the customer is charged, and your app never unlocks their account.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to check
&lt;/h3&gt;

&lt;p&gt;Live mode → Developers → Webhooks. There must be an endpoint pointing at your production URL, listening to the events your app uses (typically &lt;code&gt;checkout.session.completed&lt;/code&gt;, and the subscription events if you sell plans). Copy its signing secret and make sure your server uses that one, not the test one. After the real purchase below, open the endpoint and check that the event shows as delivered, not failed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Payment links and the customer portal
&lt;/h2&gt;

&lt;p&gt;If you sell through Stripe Payment Links, look at the link behind every "Buy" button. Test links contain &lt;code&gt;test_&lt;/code&gt; in the address, and the page they open shows a "Test mode" badge. A live site with a test payment link takes no money at all.&lt;/p&gt;

&lt;p&gt;The customer portal - where subscribers update a card or cancel - is also configured separately for each mode. If "Manage subscription" opens an error for a real customer, the portal was only set up in test mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  One real purchase, then a refund
&lt;/h2&gt;

&lt;p&gt;This is the step people skip, and the only one that proves the whole chain. Buy your cheapest plan with a real card, as a new user. Then check four things: the charge appears in the live dashboard, the webhook shows as delivered, your app gave the account what it paid for, and the receipt email arrived (Stripe doesn't send receipts for test payments, so this is the first time you'll see one). Refund yourself from the dashboard when you're done.&lt;/p&gt;

&lt;p&gt;One more quick test: on your live checkout, try the test card 4242 4242 4242 4242. It should be declined. If it goes through, your live checkout is still running in test mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  After the switch: keep testing without touching live
&lt;/h2&gt;

&lt;p&gt;Once you're live, keep test mode (or a Stripe sandbox) for everything you try next, with its own keys in a staging copy of the app. The mistake to avoid is the reverse of launch day: switching the live app back to test keys "for five minutes" to debug something, and forgetting. If you ever do, repeat the key check before you log off.&lt;/p&gt;

&lt;h2&gt;
  
  
  Want someone to run the money path with you?
&lt;/h2&gt;

&lt;p&gt;The checks above cover the switch itself. What they don't cover: upgrades and downgrades between plans, failed renewals, coupons, refunds that should remove access, and what a second user sees after the first one pays.&lt;/p&gt;

&lt;p&gt;The payment path, test and live, is one of the manual checks in a &lt;a href="https://shipclarity.app/?r=devto" rel="noopener noreferrer"&gt;Pre-Launch Audit&lt;/a&gt;, done with real test accounts and screen recordings of every step.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post was written with AI assistance.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>stripe</category>
      <category>testing</category>
      <category>webdev</category>
      <category>saas</category>
    </item>
    <item>
      <title>What breaks in Lovable apps: data from 190 live apps</title>
      <dc:creator>Novruz Veliev</dc:creator>
      <pubDate>Tue, 22 Sep 2026 09:19:18 +0000</pubDate>
      <link>https://dev.to/novruz_veliev/what-breaks-in-lovable-apps-data-from-190-live-apps-5825</link>
      <guid>https://dev.to/novruz_veliev/what-breaks-in-lovable-apps-data-from-190-live-apps-5825</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://shipclarity.app/lovable-apps-what-breaks.html?r=devto" rel="noopener noreferrer"&gt;shipclarity.app&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I ran the same outside-in scan against 190 live apps listed as built with Lovable. No login, nothing a normal visitor's browser could not see. 189 of them had at least one flagged issue. Here is what came up most, what almost never came up, and a two-minute self-check for each.&lt;/p&gt;

&lt;p&gt;Aggregate data only: no app names or domains are published, and none will be. The scan ran on 11 September 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;189 of 190 (99%)&lt;/strong&gt; had at least one flagged issue (average 4.1, median 4, out of 17 checks)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;179 (94%)&lt;/strong&gt; send no Content-Security-Policy header&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;172 (91%)&lt;/strong&gt; send no X-Frame-Options header&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;150 (79%)&lt;/strong&gt; answer 200 OK for a page that does not exist&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;127 (67%)&lt;/strong&gt; have no DMARC record, or one set to &lt;code&gt;p=none&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3&lt;/strong&gt; ship a Google/Firebase API key in the bundle; none shipped a Supabase &lt;code&gt;service_role&lt;/code&gt; key or a Stripe live secret key&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0&lt;/strong&gt; exposed a &lt;code&gt;.env&lt;/code&gt;, &lt;code&gt;.git&lt;/code&gt; or backup file, and &lt;strong&gt;0 of 125&lt;/strong&gt; probed Supabase projects returned their schema to the public key&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How the data was collected, and what it cannot see
&lt;/h2&gt;

&lt;p&gt;The starting point was a public directory of projects built with Lovable: 512 listings. 428 of them were still live, and 395 of those were in English. After removing portfolios, games, agency pages and pages with no product surface (no pricing, payments, or signup), 190 apps were left, all scanned without errors. 155 run on their own domain; 35 are on a platform subdomain such as &lt;code&gt;*.lovable.app&lt;/code&gt;. Some have since moved to Vercel or Netlify, so read this as "built with Lovable", not "hosted by Lovable".&lt;/p&gt;

&lt;p&gt;Each app got the same 17 automatic checks that &lt;a href="https://scan.shipclarity.app/?r=devto" rel="noopener noreferrer"&gt;the free ShipClarity scanner&lt;/a&gt; runs: exposed config and repo files, secret keys in the JS bundle, Supabase schema exposure to the public key, source maps, SPF, DKIM and DMARC, HTTPS and the HTTP redirect, four security headers, the viewport tag, robots.txt and noindex, the status of a missing page, and first-response time.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this data cannot tell you
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The scanner never logs in. Signup, password reset, one user reading another user's rows, checkout and refunds are not measured here.&lt;/li&gt;
&lt;li&gt;It does not run JavaScript. It reads the server HTML and up to 8 script files referenced from the home page, so a key in a lazily loaded chunk would be missed.&lt;/li&gt;
&lt;li&gt;Headers are read from the home page only. A CSP set through a &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; tag is counted as missing.&lt;/li&gt;
&lt;li&gt;DKIM cannot be confirmed from outside (the selector is provider-specific), so there is no DKIM number here.&lt;/li&gt;
&lt;li&gt;The sample is a directory of projects whose makers chose to list them, not a random sample of every Lovable app.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  1. 189 of 190 had at least one flagged issue
&lt;/h2&gt;

&lt;p&gt;Only one app out of 190 came back clean. The average was 4.1 flagged checks out of 17, the median 4, the worst 8. Most of that count comes from the four items below: configuration defaults nobody revisited, not code bugs. "99% have issues" does not mean 99% are broken for users; it means almost nobody went back over the defaults.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. 94% send no Content-Security-Policy
&lt;/h2&gt;

&lt;p&gt;179 of 190 apps send no Content-Security-Policy header. CSP tells the browser which scripts may run on your page. Without it, any injected script (a compromised third-party widget, or unescaped user content) runs with full access to the page, including whatever the logged-in user can see.&lt;/p&gt;

&lt;p&gt;It is a hosting or proxy setting, not app code, which is why it is missing almost everywhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check yours in 2 minutes&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sI&lt;/span&gt; https://yourapp.com | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; content-security-policy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No output means no header (or check DevTools, Network tab, first request, Response Headers). If your host does not let you set headers, a proxy in front of the domain (Cloudflare, or Netlify/Vercel header rules) can add them. Start with &lt;code&gt;Content-Security-Policy-Report-Only&lt;/code&gt; so a strict policy does not break your own scripts on day one.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. 91% send no X-Frame-Options
&lt;/h2&gt;

&lt;p&gt;172 of 190 apps send no X-Frame-Options header. 171 of them send no CSP either, so there is no &lt;code&gt;frame-ancestors&lt;/code&gt; rule covering the gap. Another site can load the app in an invisible iframe and trick a logged-in user into clicking something real: a delete button, a plan change, a share toggle.&lt;/p&gt;

&lt;p&gt;From the same family: 44 of 190 (23%) lack &lt;code&gt;X-Content-Type-Options: nosniff&lt;/code&gt;, and 23 of 190 (12%) lack HSTS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check yours in 2 minutes&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sI&lt;/span&gt; https://yourapp.com | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"x-frame-options|frame-ancestors"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You want &lt;code&gt;X-Frame-Options: DENY&lt;/code&gt; (or &lt;code&gt;SAMEORIGIN&lt;/code&gt;) or a CSP with &lt;code&gt;frame-ancestors 'none'&lt;/code&gt;. &lt;code&gt;frame-ancestors&lt;/code&gt; is ignored in a &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; CSP tag; it only works as a response header.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. 79% return 200 for a missing page
&lt;/h2&gt;

&lt;p&gt;150 of 190 apps answered 200 OK when asked for a random URL that does not exist. It is the single-page-app default: the host serves &lt;code&gt;index.html&lt;/code&gt; for every path and the front-end router decides what to show. Visitors may see "not found", but search engines and uptime monitors see a healthy page.&lt;/p&gt;

&lt;p&gt;Search engines may index junk URLs as soft 404s, and monitors keep reporting "up" for deleted routes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check yours in 2 minutes&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; https://yourapp.com/this-page-does-not-exist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it prints 200, open the URL in a browser too: a blank page or the home page is worse than a "not found" screen. The minimum fix is a catch-all route that shows a clear not-found page and adds &lt;code&gt;&amp;lt;meta name="robots" content="noindex"&amp;gt;&lt;/code&gt;. A real 404 status needs a host-level rule.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. 67% have no enforcing DMARC
&lt;/h2&gt;

&lt;p&gt;127 of 190 apps have either no DMARC record at all (50) or one set to &lt;code&gt;p=none&lt;/code&gt; (77), which blocks nothing. All 127 are on their own domain: 82% of the 155 custom domains. The 35 on a platform subdomain inherit its record. And 52 custom domains (27% of all apps) have no SPF record either.&lt;/p&gt;

&lt;p&gt;Without an enforcing policy, anyone can send email as your domain, and your own signup and password-reset emails are more likely to land in spam. For the second part there is a separate guide: &lt;a href="https://shipclarity.app/lovable-password-reset-email-not-arriving.html?r=devto" rel="noopener noreferrer"&gt;Lovable password reset email not arriving&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check yours in 2 minutes&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dig TXT _dmarc.yourdomain.com +short          &lt;span class="c"&gt;# macOS / Linux&lt;/span&gt;
nslookup &lt;span class="nt"&gt;-type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;TXT _dmarc.yourdomain.com      &lt;span class="c"&gt;# Windows&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No answer means no record; &lt;code&gt;p=none&lt;/code&gt; is monitor-only. The target is &lt;code&gt;p=quarantine&lt;/code&gt; or &lt;code&gt;p=reject&lt;/code&gt;, but only after SPF and DKIM are set up for the provider that sends your mail, or you will block your own emails.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. 3 apps ship an API key in the bundle
&lt;/h2&gt;

&lt;p&gt;3 of 190 apps had a key matching a known secret pattern in the JavaScript sent to every visitor. All three were Google/Firebase API keys (&lt;code&gt;AIza...&lt;/code&gt;). This type of key is designed to sit in client code and is fine if restricted by HTTP referrer and by API in the Google Cloud console. An unrestricted one can be used on someone else's site at your expense.&lt;/p&gt;

&lt;p&gt;The more useful number is the zero: none of the 190 shipped a Supabase &lt;code&gt;service_role&lt;/code&gt; key, a Stripe live secret key (&lt;code&gt;sk_live_&lt;/code&gt;), or an AWS access key in the scanned bundles, the keys that would hand over the database or the payment account. The Supabase anon key is public by design and was not counted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check yours in 2 minutes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In DevTools on your live app, press Ctrl+Shift+F (Cmd+Option+F on a Mac) and search all loaded files for &lt;code&gt;service_role&lt;/code&gt;, &lt;code&gt;sk_live_&lt;/code&gt;, &lt;code&gt;AKIA&lt;/code&gt; and &lt;code&gt;AIza&lt;/code&gt;. A hit on the first three means rotate the key now and move the call server-side. A hit on &lt;code&gt;AIza&lt;/code&gt; means confirm in Google Cloud Console, Credentials, that the key has both a referrer and an API restriction.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. What held up
&lt;/h2&gt;

&lt;p&gt;The scary items were rare. Out of 190 apps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;0 exposed a &lt;code&gt;.env&lt;/code&gt;, &lt;code&gt;.git/config&lt;/code&gt;, config or backup file at a guessable path.&lt;/li&gt;
&lt;li&gt;190 load over HTTPS. 3 also still answer on plain HTTP without redirecting.&lt;/li&gt;
&lt;li&gt;140 (74%) use Supabase. For 125 of them the scanner could test the public key against the database's REST root, and none returned the schema. The other 15 could not be probed.&lt;/li&gt;
&lt;li&gt;3 publish source maps in production, 3 have the home page set to noindex, and 19 (10%) serve no robots.txt.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A closed schema endpoint is a good sign, not a clean bill of health. Whether user A can read user B's rows depends on each table's row-level security, testable only with two real accounts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check yours in 2 minutes&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; https://yourapp.com/.env
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; http://yourapp.com/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first should not be 200 with real file contents (a 200 with your normal app page is the soft 404 from point 4, not a leak). The second should be a 301 or 308 redirect to HTTPS.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to take from this
&lt;/h2&gt;

&lt;p&gt;Almost everything above is configuration: headers, DNS records, a catch-all route. An afternoon covers it. None of it says whether the app works for a paying user. The failures that cost customers live behind the login: a signup that silently fails, a reset link pointing at the old preview domain, one account seeing another's data, a checkout that charges twice. This dataset cannot say how common those are.&lt;/p&gt;

&lt;p&gt;If you want to see where your own app stands, &lt;a href="https://scan.shipclarity.app/?r=devto" rel="noopener noreferrer"&gt;the free outside scanner&lt;/a&gt; runs the same 17 checks on your URL in about 20 seconds, with no signup.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>security</category>
      <category>testing</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
