Add One-Time Payments to Any AI-Built Website or Web App With Stripe
Add secure one-time Stripe Checkout payments, verify them with a webhook, and trigger the paid result inside your existing website or web app.
Roadmap & Resources
Plan the Payment
Plan the One-Time Payment
Start with what the customer is actually paying for. This guide adds one payment action to the website or web app you already have — it does not rebuild it.
Plan the One-Time Payment
Tell Claude what users are paying for, where the trusted price comes from and what should happen after payment.
I want to add a secure one-time Stripe payment to this existing website or web app. Users are paying for: {{PAYMENT_FOR}} Price source: {{PRICE_SOURCE}} After Stripe verifies payment: {{AFTER_PAYMENT}} Do not build anything yet. First inspect the existing project and understand: - the framework - the frontend and backend structure - authentication, if there is any - the database, if there is one - the current data model - existing server or API functionality - any existing Stripe setup - any existing payment-related code - the deployment architecture Reuse the correct existing infrastructure. Do not rebuild the app. Then plan the payment flow and decide: 1. PAYMENT ENTRY POINT. Where does the user click Pay? 2. PAYMENT CONTEXT. Which record, user or action is this payment associated with — a request, a booking, a report, an account, a service, an order-like record? Use the real existing app. 3. AUTHORITATIVE PRICE. Where does the trusted amount come from? Never use browser display text as payment authority. 4. CHECKOUT CREATION. Which trusted server-side path creates the Stripe Checkout Session? 5. PAYMENT RECORD. What minimal payment data must the app persist? 6. WEBHOOK. Which verified Stripe event confirms successful payment? 7. PAID RESULT. Exactly what changes after verified payment? 8. AUTHENTICATION. Does this payment belong to an authenticated user or to a guest/customer flow? Do not force authentication if the existing use case does not need it. If authentication already exists and is useful, associate the payment with the authenticated user safely. 9. PRODUCTION. Which Stripe and application URLs will need production configuration later? For the payment record, use the smallest useful model. Where appropriate that may include: - id - user_id where relevant - related_record_id where relevant - the Stripe Checkout Session identifier - the Stripe Payment Intent identifier where relevant - amount - currency - payment status - paid_at - created_at Do not automatically add every field. Never store raw card information. Return: ONE-TIME PAYMENT PLAN Payment purpose: Payment entry point: User/auth model: Payment context: Trusted price source: Currency: Checkout creation path: Payment record: Webhook event: Paid result: Duplicate-payment behavior: Production requirements: Features deliberately excluded: Then return exactly one: READY TO BUILD or: NEEDS A DECISION Do not modify any files yet.
What is the user paying for?
A consultation deposit, generated report, premium action, setup fee...
Where should the payment amount come from?
What should happen after payment is verified?
Mark the request paid, unlock the report, enable one premium action...
Review the payment plan before continuing. The browser should start the payment, but verified Stripe state must control the paid result.
Confirm the Payment Scope
One payment, one verified result. Anything wider belongs in its own guide.
One payment action
One trusted price source
Stripe Checkout
Verified webhook
Payment record
Clear paid result
No subscriptions
No unnecessary commerce features
Set Up Stripe
Create or Reuse the Stripe Setup
Build and verify everything in Stripe test mode first. No real money moves until the last section.
1. Reuse what the project already has
If this project already has the Stripe integration your plan approved, reuse it. Do not create a second Stripe client, a second webhook endpoint, a second payment table, or a second set of environment-variable names.
2. Set up the product in test mode
If Stripe is missing, set it up now. Everything in Steps 3 to 5 runs against test mode, so the whole flow can be finished without a real-money transaction.
Open Stripe
Switch to test mode, then create or select what the customer is paying for.
https://dashboard.stripe.com
Switch Stripe to test mode Create or select the product or service the customer is paying for Copy your test Secret Key from Stripe's API keys page
3. Decide where the trusted amount lives
If your plan approved a fixed price, use the simplest trusted pattern this project already supports — a Stripe Price created in the Dashboard, or a trusted server-side amount configuration. Either way, add the price as a one-off price, not a recurring one.
If your plan approved an existing app or database price, the trusted server looks the current amount up instead. The browser may identify what is being purchased; it must not decide what it costs.
IMPORTANT
Never hardcode the authoritative amount in browser code. Whatever the page displays is a label — the real amount comes from the server or from Stripe.
4. Keep the Stripe credentials server-side
Store the Stripe secret key where only the server can read it, using this project's existing naming convention. If the project has no convention yet, Stripe's own names are the safe default. The webhook signing secret does not exist yet — you create it in Step 4 and add it the same way.
Server-side only
STRIPE_SECRET_KEY STRIPE_WEBHOOK_SECRET
Match your project's existing naming if it already has one.
STRIPE_SECRET_KEY
Your Stripe TEST Secret Key
STRIPE_WEBHOOK_SECRET
Added in Step 4, after the webhook exists
IMPORTANT
Never expose the Stripe secret key or the webhook signing secret through a VITE_ or NEXT_PUBLIC_ variable, frontend source, or public config. Anything the browser can read is public. Use the test key here, not a live one.
Add Stripe Checkout
Create the Checkout Flow
Your AI implements the approved plan. The browser starts the payment; the server decides what it costs.
Add One-Time Stripe Checkout
Create Checkout from trusted payment data so the browser cannot change the real amount or payment target.
Implement the approved one-time payment flow using Stripe Checkout. Reuse this project's existing architecture. Do not redesign unrelated pages. PAY BUTTON Add the payment action at the approved existing place. Use copy that fits the real use case, for example "Pay $49", "Pay Deposit", "Buy Report", "Unlock for $29" or "Continue to Payment". Do not put a generic "Buy Now" everywhere. CHECKOUT REQUEST The browser may send only the minimum safe context needed to identify the payment, such as a related record id, a selected service id, or the authenticated context. The browser must NOT authoritatively control: - the amount - the currency - paid status - the owner or user id, where a trusted session already provides it - internal payment state TRUSTED CHECKOUT PATH The server-side or otherwise trusted function must: 1. authenticate and authorize the caller where required 2. validate the payment context 3. load or resolve the authoritative price 4. validate that this payment is allowed 5. create the Stripe Checkout Session 6. attach enough trusted metadata or reference to identify this payment later 7. return only the safe Checkout information the client actually needs SUCCESS AND CANCEL URLS Configure a success URL and a cancel URL that fit this app. Remember that reaching the success page is not payment verification. DUPLICATE PAYMENT If this thing should only ever be paid for once, check the existing trusted payment state before creating another Checkout Session, so a user cannot accidentally pay twice for a one-time entitlement. If repeated payments are legitimate for this real use case, preserve that behavior. Do not guess — follow the approved plan. Keep the Stripe secret key and every other private credential server-side. Never expose them through frontend source, public config, or a client-visible environment variable. Do not add subscriptions, a cart, a product catalog, refunds, coupons or invoices. Report every file you changed, the trusted path that creates Checkout, and exactly how the authoritative price is resolved.
Protect API Keys Before Deploying an AI-Built Website or Web App
The full workflow for keeping secret keys out of anything the browser can read.
/video/protect-api-keys-before-deploying-ai-built-app
Build the Payment Result UI
Continue in the same AI conversation and give the payment its honest states. The page can say what it knows — it must not claim what it cannot prove.
Payment pending
Payment success, verified
Payment cancelled
Payment failed or not confirmed
If webhook processing may take a moment, show a short "Confirming payment..." state and check the trusted payment state instead of guessing.
IMPORTANT
Do not display "Payment complete" merely because the browser reached the success URL. Anyone can open that address, and the redirect can be replayed. Never fake success.
Confirm the Payment
Add the Verified Stripe Webhook
This is the part that makes the payment real. Everything before it is a request; a signature-verified Stripe event is the answer.
Verify the Stripe Payment
Confirm successful payment from a verified Stripe webhook instead of trusting the browser redirect.
Implement payment confirmation for the approved one-time payment using a verified Stripe webhook. WEBHOOK SIGNATURE Verify the Stripe webhook signature before anything else happens, reading the raw request body first. Do not accept arbitrary POST requests as Stripe events. If the signature is invalid, reject the request and do nothing else. SUCCESS EVENT Handle the correct event or events for this Checkout architecture, using current Stripe behavior. Do not guess obsolete event names. PAYMENT RECONCILIATION From trusted Stripe data, identify the checkout or payment, the expected amount, the currency, the payment context, and the user or record relationship where applicable. Do not trust browser-supplied data for payment confirmation. IDEMPOTENCY Stripe may retry webhook events. The same successful payment event must not create duplicate paid records, grant access twice, trigger a one-time action more than once, or duplicate any business side effect. Use an appropriate unique Stripe event, payment or session relationship so the handler is safe to repeat. PERSIST THE PAYMENT Record only the payment information this application genuinely needs, using trusted Stripe values. Never store card data. The webhook is a server-to-server call with no browser session, so make sure any request authentication in front of it is configured for that and does not block Stripe. Report the webhook path, how the signature is verified, which events are handled, what is written, and exactly what makes the handler idempotent.
Create the webhook endpoint in Stripe
Once the endpoint exists in your project, register it in Stripe and copy the signing secret it generates.
Stripe Developers Webhooks
Stay in test mode Add an endpoint pointing at your webhook URL Select the events your implementation handles Copy the signing secret — it starts with whsec_ Store it server-side under the name your project uses
IMPORTANT
The webhook signing secret is as sensitive as the secret key. Paste it straight into your server-side configuration — never into browser code, a client-visible variable, or this guide.
Trigger the Paid Result
Connect the verified payment to the thing the user actually paid for.
Complete the Paid Action
Connect verified payment to the actual result the user paid for without letting the browser grant it directly.
After a verified successful payment, perform the approved paid result. For example: payment verified, so the request is marked paid; payment verified, so the report unlocks; payment verified, so one-time access is granted; payment verified, so the paid action becomes available. Use the actual approved behavior for this project. CRITICAL AUTHORITY RULE Do not implement "the success page loaded, therefore paid = true". Do not implement "localStorage says the payment succeeded". Client code must never grant the entitlement. Only trusted backend or database logic, triggered from verified payment state, may authorize the paid result. ONE-TIME ENTITLEMENT If payment unlocks something, persist that entitlement server-side or in the trusted database. Access must not depend on a browser session, localStorage, or a hidden route. ONE-TIME ACTION If payment triggers a one-time server action, make it idempotent. A webhook retry must not repeat the valuable side effect. AUTHENTICATED USER Where authentication exists, associate the entitlement and the payment using the trusted authenticated identity plus the Stripe mapping or context. Never trust a user id submitted by the browser. GUEST PAYMENT Where no user account exists, use the smallest safe record, token or reference model appropriate to this product. Do not add an account system just because Stripe is being added. Report where the paid result is granted, what proves the payment at that moment, and what a user without a verified payment sees instead.
IMPORTANT
A user who reaches the success page has not necessarily paid, and a user who has paid may arrive before the webhook lands. Only the verified payment record decides what they get.
Test the Payment Flow
Run the Full Stripe Test
Still in Stripe test mode, walk the whole path once, end to end, using a Stripe test card.
Open the paid action in your app Click the payment button Confirm Stripe Checkout shows the correct amount and currency Complete the test payment Watch the app show "Confirming payment..." on return Confirm the webhook was received and verified in Stripe Confirm the app now shows the payment as confirmed
Then prove the intended paid result actually happened — the request now says paid, the report unlocks, the premium result becomes available, or whatever your plan approved.
The payment record exists with the trusted amount and currency
The approved paid result happened
Stripe test cards
The card numbers Stripe accepts in test mode.
https://docs.stripe.com/testing
Verify Payment Security
Now try to break it, before a customer does.
Verify the One-Time Payment
Test price tampering, fake success pages, webhook retries and the paid result before going live.
Perform a focused security and payment verification of this one-time payment flow using Stripe Test Mode. Do not add features. PRICE TAMPERING Attempt a modified client request carrying a different amount — for example a real trusted price of $49 with the browser attempting $0.49. Expected: the trusted Checkout still uses $49, or the modified request is rejected. FAKE SUCCESS Open the success route manually without completing payment. Expected: no paid entitlement and no paid result. CANCELLED CHECKOUT Start Checkout and cancel it. Expected: no paid result. SUCCESSFUL PAYMENT Complete a Stripe Test Mode payment. Expected: the webhook verifies, the payment persists, and the paid result happens exactly as approved. WEBHOOK RETRY Repeat or replay the supported test webhook scenario. Expected: no duplicate entitlement, no duplicate payment effect, no duplicate one-time action. WRONG USER OR WRONG RECORD Where relevant, attempt to create a payment for a record the authenticated user is not allowed to control. Expected: blocked before Checkout. DIRECT PAID ACTION Attempt the paid operation without a verified payment. Expected: blocked. SECRETS Confirm that browser code does not expose the Stripe secret key, the Stripe webhook signing secret, a Supabase secret or service-role credential, or any other private third-party key involved in the paid action. Return exactly one: PAYMENT FLOW VERIFIED or: NEEDS ATTENTION Return PAYMENT FLOW VERIFIED only if authoritative pricing works, successful payment is verified, fake success grants nothing, a cancelled payment grants nothing, webhook retry is safe, the intended paid result works, and sensitive keys stay private. If NEEDS ATTENTION, list only payment-flow blockers.
Complete one final Stripe Test Mode payment and verify the paid result before configuring production.
Deploy and Go Live
Deploy the Payment Flow
If this app already runs on Hostinger, push the change to GitHub and redeploy the site you have — skip the card below and go straight to the environment variables. If it is not deployed yet, take the standard path: Claude Code to GitHub to Hostinger.
Push the project to GitHub with your normal workflow. If it already has a repository, use it — do not create a duplicate website.
Deploy on Hostinger
Recommended plan: Business
USED IN TUTORIAL
https://www.hostg.xyz/SHK1P
Hostinger Websites Add Website Node.js Web App
Choose Import Git repository Connect GitHub and select this project's repository Review the detected framework and build settings Add the server-side environment variables using your project's exact names Click Deploy and wait for the result
IMPORTANT
Add the Stripe secret key and webhook signing secret as server-side variables in Hostinger only. Never move them into a VITE_ or NEXT_PUBLIC_ variable to make a build succeed — that publishes them to every visitor.
Update what needs the production URL
Once the final Hostinger URL or domain exists, update only the things that depend on it. Leave unrelated services alone.
Stripe success URL
Stripe cancel URL
The Stripe webhook endpoint
Supabase Auth URLs, where authentication exists
Allowed origins and callbacks, where relevant
Switch Stripe From Test to Live
Verify the production app with your test configuration first, where that is practical. Only then turn on real money.
Stripe test mode accepts fake test payments only. Stripe live mode accepts real customer payments. Until live credentials and live configuration are actually active, the app does not take real money, however finished it looks.
Switch Stripe to live mode Use the live secret key, and the live publishable configuration if your architecture needs one Create the live product and price where your architecture uses them Add a webhook endpoint for the production URL in live mode Copy the live signing secret and store it server-side Update the production environment variables in Hostinger
IMPORTANT
Test and live identifiers are not interchangeable. A test price id, webhook secret or key reused in live mode will fail or, worse, silently keep you in test mode while the page looks live.
NOTE
Stripe availability, verification and payout requirements vary by account, country and business. Complete whatever your own Stripe Dashboard asks of you before accepting real customer payments.
Verify the Live One-Time Payment
Check the production payment path on Hostinger before sending real customers to it.
Perform a focused production verification of this payment flow. Do not add features. GITHUB - the approved code is current - no Stripe or other private secrets are committed HOSTINGER - the deployment or update succeeded - the correct repository and branch are connected - every required environment variable is configured CHECKOUT - the payment CTA works - the trusted amount is correct - Stripe Checkout opens correctly - the production success and cancel URLs are correct WEBHOOK - the production endpoint is reachable - Stripe signature verification works - which Stripe mode this is running in is unambiguous PAYMENT RESULT - a successful verified payment produces the intended app state - opening the success page directly does not - unpaid users cannot bypass the paid action IDEMPOTENCY - duplicate webhook processing does not duplicate the paid result SECURITY - private Stripe credentials remain server-side Return exactly one: PAYMENT LIVE or: NEEDS ATTENTION If Stripe is still configured in Test Mode, do not present PAYMENT LIVE as proof that real money is enabled. Still return exactly one of the two verdicts above, and state STRIPE TEST MODE clearly in the summary. If NEEDS ATTENTION, list only launch blockers.
Use Stripe's appropriate final verification process before sending real customers to the payment button.