Symptoms
- The customer pays on Stripe's page, then sees “This site can't be reached” or “localhost refused to connect”.
- After paying they land on an old preview copy of your app, a “Deployment not found” page, or a log-in screen for your hosting account.
- They land on your real site but get a 404 (“Page not found”) on
/success. - The payment shows as paid in your Stripe Dashboard, but the customer writes to ask whether it worked.
- Pressing “back” on Stripe's checkout page has the same problem.
Why it happens
- The address was written while you were testing. Many examples, and many AI-written first versions, put
http://localhost:3000/successstraight into the code that creates the checkout. It works on your computer, because your computer is running the app. For a customer, localhost means their own computer, where nothing is running. - The live address setting is missing. Some code reads the address from a setting such as
APP_URL,SITE_URLorNEXT_PUBLIC_APP_URL, and falls back to localhost when it's empty. If you never added that setting to your live hosting, the fallback wins. - It points at a preview address. A preview or one-off deployment address (for example one ending in
.vercel.app, or a builder preview link) may be old, behind a log-in, or gone. On Vercel,VERCEL_URLis the address of that one deployment, not your main domain. - You changed domains. You moved from
yourapp.lovable.apporyourapp.vercel.appto your own domain, but the checkout code or a Payment Link still uses the old address. - The success page doesn't exist on the live site. The address is right, but
/successreturns 404. On single-page apps hosted on Netlify and similar hosts, a direct visit to a page needs a rewrite toindex.html. - A Payment Link has its own redirect. If you use a Stripe Payment Link instead of code, the address is set in the Stripe Dashboard under After the payment, not in your app.
Step-by-step fix
1. Find where the address is set
Search your project's code for success_url, cancel_url and return_url (also successUrl and cancelUrl). You'll usually find them in the server code or backend function that calls stripe.checkout.sessions.create. In apps made with Lovable or Bolt this is often a Supabase Edge Function.
If you don't find them, you probably use a Payment Link. In the Stripe Dashboard, open Payment Links, select the link, choose Edit, then open After the payment. Under Confirmation page, check the website address it redirects to.
2. Use one setting for your live address
Add a setting with your live address, for example:
SITE_URL=https://yourapp.comAdd it where your live app reads its settings: your host's production environment variables, or the secrets of your backend function. Use https://, your real domain, and no slash at the end.
Then build both addresses from it in the code that creates the checkout:
const site = process.env.SITE_URL; // https://yourapp.com on the live site
const session = await stripe.checkout.sessions.create({
mode: "payment",
line_items: [{ price: "price_...", quantity: 1 }],
success_url: `${site}/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${site}/pricing`,
});Leave {CHECKOUT_SESSION_ID} exactly as written, with the curly brackets. Stripe replaces it with the real checkout ID after the payment.
If the setting can be empty, make the code stop with a clear error instead of falling back to localhost.
On Vercel, use your own setting or VERCEL_PROJECT_PRODUCTION_URL (your main domain, without https://). Don't use VERCEL_URL: it is the address of one deployment.
3. Make sure the success page exists
Open https://yourapp.com/success in a private window. It should show a page, not a 404.
If it's a single-page app on Netlify, add a file public/_redirects with this line, then publish again:
/* /index.html 2004. Don't unlock anything on the success page alone
Stripe says you can't rely on the success page to deliver what was bought, because some customers never reach it (for example, they lose their connection right after paying). Give access, send the receipt or start the order from a Stripe webhook (checkout.session.completed). The success page should only say thank you.
5. Publish and test with a test card
Publish again so the new code and setting are live. Then, with Stripe in test mode on a test or preview copy of the app (with its own SITE_URL), go through checkout with Stripe's test card 4242 4242 4242 4242. Check that you land on that copy's success page, and that “back” from checkout lands on your pricing page.
If you also changed domains, check your sign-in settings too. Supabase and Google sign-in keep their own list of allowed addresses.
Fix prompt for your AI builder
Paste this into Lovable, Bolt, Cursor or another AI tool. Replace the parts in brackets first.
After customers pay with Stripe Checkout, they are sent to [localhost / a preview address / a 404 page]
instead of my live site. My live address is [https://yourapp.com].
Please:
1. Find every place this app sets success_url, cancel_url or return_url for Stripe
(including Supabase Edge Functions or other backend functions).
2. Build them from one setting named SITE_URL instead of a fixed address.
Use `${SITE_URL}/success?session_id={CHECKOUT_SESSION_ID}` for success_url
(keep {CHECKOUT_SESSION_ID} exactly as written) and `${SITE_URL}/pricing` for cancel_url.
3. If SITE_URL is missing, stop with a clear error. Don't fall back to localhost.
4. Make sure /success exists and opens when visited directly on the live site.
5. Make sure access or orders are given by the Stripe webhook, not by the success page.
6. Tell me exactly where I must add SITE_URL for the live app.
Don't print or log any keys. Don't make a real payment. Don't change prices or products in Stripe.How to find out sooner next time
This one is hard to catch yourself. You test on your own computer, where localhost works, and the payment still shows as paid in Stripe. Often the first sign is a customer asking whether their payment went through.
Heedza's payment page check opens your live site from the outside on a schedule and checks that your payment page opens and shows its form or Pay button, along with your home, sign-up and log-in pages. It checks that the site is online every 5 minutes on Free, every minute on Solo and Starter, and every 30 seconds on Pro and Business, and opens those pages once a day on Free and up to every 15 minutes on paid plans. If a page stops working, you get an email with a screenshot and a fix to paste into your AI tool. Heedza never logs in, fills in forms or pays, so it can't see the page customers reach after paying. Test that part yourself with a test card after each change to checkout.
Let Heedza check this for you. Heedza’s payment page check opens your live payment page on a schedule and tells you if the Pay button disappears. It never pays. It never logs in or fills in forms, so it can’t see errors that only show after that.
Made with Lovable, Bolt, Replit, Cursor or v0? Free for 1 site: start free or see prices.