Stripe checkout success_url still set to localhost or a preview address
If your Stripe Checkout success_url or cancel_url still says http://localhost:3000/... or a builder preview address, customers who finish paying are sent to their own computer, or to a preview copy, instead of your thank-you page. Build both URLs from your live domain, kept in a setting such as SITE_URL, and redeploy.
Why it happens
Stripe redirects the customer to the address you pass when you create the Checkout Session. Its docs: "Stripe redirects your customer to a success page that you create and host on your site." Code written and tried on a laptop often hard-codes http://localhost:3000/success. It keeps working for you, because your computer is running the app, and fails for every real customer, who sees "This site can't be reached."
A preview address (for example a *.vercel.app or *.lovable.app deployment URL) fails differently: the customer lands on an old copy, often behind a sign-in or a "Deployment not found" page.
Fix it
- Search your code for
success_url,cancel_urlandreturn_url(andsuccessUrl,redirect_urlfor other providers). - Add a setting for your live address, for example
SITE_URL=https://yourapp.comin your host's production environment variables (and a preview value for preview builds). - Build the URLs from it in the server code that creates the session:
const site = process.env.SITE_URL; // https://yourapp.com in production
const session = await stripe.checkout.sessions.create({
mode: "payment",
line_items: [{ price: "price_123", quantity: 1 }],
success_url: `${site}/thanks?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${site}/pricing`,
});
Keep {CHECKOUT_SESSION_ID} exactly as written. Stripe: "This is a literal string and you must add it exactly as you see it here." Stripe fills in the real ID after payment.
- Redeploy. On a preview deployment with sandbox keys and its own
SITE_URL, run one checkout with a Stripe test card and confirm you land on that deployment's thank-you page.
Test this on a preview deployment before relying on it.
Don't rely on the success page for fulfillment
Stripe: "You can't rely on triggering fulfillment only from your checkout landing page", because some customers never reach it, for example when they "lose their connection to the internet before your landing page loads." Grant access, send the receipt or ship the order from a webhook handler; the success page only shows the confirmation.
What Pewterkit Check looks at
The check reads your home page and the scripts it loads for checkout return settings (success_url, cancel_url, return_url, returnUrl, successUrl, redirect_url and similar names) written as a fixed address. It reports one that points at localhost or 127.0.0.1, or at a builder preview host (such as *.vercel.app, *.netlify.app, *.lovable.app, *.pages.dev, *.replit.app), when that isn't your site's own address. URLs your server builds at run time aren't visible to it, so also check server code.
FAQ
Why does checkout work for me but not my customers?
Because localhost means "this computer". On your machine the app is running there; on a customer's machine nothing is.
Can I use a relative URL like /thanks?
Use a full https:// address. Stripe describes success_url as "The URL to which Stripe should send customers when payment or setup is complete", its examples are full addresses, and the customer is coming back from Stripe's own domain.
Does the same problem affect sign-in redirects?
Yes. Sign-in settings such as redirectTo or emailRedirectTo pointing at localhost break in the same way, and the check reports those too.
Sources
How this page was made: drafted with AI from the sources listed, reviewed by Pewterkit. Docs checked Oct 27, 2026. Spotted something out of date? Tell us.
Related guides
- Stripe test mode on a live site: switch from pk_test to your live keys
- Stripe secret key exposed in your frontend code: rotate it, then move it to the server
- OpenAI API key in your React or Vite frontend: rotate it and move calls to a backend
- Supabase service_role key in your frontend bundle: replace it and switch the old key off