Learn how to build a billing system with Replit using Python. Follow step-by-step tips to create, test, and deploy your own secure payment solution.

Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
A simple and reliable way to build a billing system in Replit is to use a small backend (for example, an Express.js app running on Node.js) that integrates with a payment provider like Stripe. You’ll create API endpoints in Replit that the frontend (a simple HTML/React page or a form) can call. Payment keys and sensitive information should be stored inside Replit’s Secrets tab, not hardcoded. The backend will receive requests, communicate securely with Stripe to create checkout sessions, and handle webhooks for payment confirmations. You can do all this within one Replit project.
Start a new Replit project using the “Node.js” template. This gives you a working server.js file and a package.json ready to use.
In the Replit shell at the bottom, install express and Stripe:
npm install express stripe
These will appear automatically in your package.json once installed.
On the left sidebar in Replit, click the padlock icon (called Secrets or Environment Variables). Add:
Secrets stay hidden and are available via process.env.STRIPE_SECRET_KEY in Node.
In your main file (likely index.js or server.js), paste and customize the following:
// Import dependencies
const express = require('express')
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY)
const app = express()
const PORT = process.env.PORT || 3000
// Parse JSON request bodies
app.use(express.json())
// Example product for demo purposes
const YOUR_DOMAIN = 'https://your-replit-username.your-repl-name.repl.co' // replace with your actual Replit URL
// Route to create a checkout session
app.post('/create-checkout-session', async (req, res) => {
try {
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
mode: 'payment',
line_items: [
{
price_data: {
currency: 'usd',
product_data: {
name: 'Subscription Plan', // Change this as needed
},
unit_amount: 1000, // in cents ($10)
},
quantity: 1,
},
],
success_url: `${YOUR_DOMAIN}/success.html`,
cancel_url: `${YOUR_DOMAIN}/cancel.html`,
})
res.json({ url: session.url })
} catch (err) {
res.status(500).json({ error: err.message })
}
})
// Optional: handle Stripe webhooks (advanced)
// Create Stripe webhook endpoint in dashboard → point to /webhook
// Then validate events here for actual payment confirmation
app.listen(PORT, () => console.log(`Server running on port ${PORT}`))
This code should be inserted inside your main server file. If your Repl has no server.js or index.js, create one in the root folder and paste it there.
Create a new file in the root directory called index.html and put:
<!DOCTYPE html>
<html>
<head>
<title>Billing Example</title>
<script src="https://js.stripe.com/v3/"></script>
</head>
<body>
<h1>Buy Subscription</h1>
<button id="checkout">Checkout</button>
<script>
const checkoutButton = document.getElementById('checkout');
checkoutButton.addEventListener('click', async () => {
const response = await fetch('/create-checkout-session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
})
const session = await response.json()
window.location = session.url // Redirect user to Stripe Checkout
});
</script>
</body>
</html>
This frontend calls your backend endpoint, which creates a checkout session, and then redirects the user to Stripe’s secure payment page.
Stripe will redirect users back to your success.html. You can create this file in the root with a friendly message like:
<!DOCTYPE html>
<html>
<body>
<h1>Payment Successful!</h1>
<p>Thank you for your purchase.</p>
</body>
</html>
For more complex billing systems (like subscriptions, invoices, or customer accounts), you can expand the backend to store user data in a hosted DB (like Replit DB, Supabase, or MongoDB Atlas) and handle webhooks with verification.
This setup gives you a real, working billing system using Replit’s free environment, with Stripe taking care of the heavy lifting (secure card handling, compliance, and receipts) while your app stays simple and easy to deploy and maintain inside Replit.
import express from "express";
import bodyParser from "body-parser";
import Database from "@replit/database";
import "dotenv/config";
const app = express();
const db = new Database();
app.use(bodyParser.json());
// Automatically calculate invoice totals and persist to Replit DB
app.post("/api/invoices", async (req, res) => {
const { userId, items } = req.body;
if (!userId || !Array.isArray(items)) return res.status(400).json({ error: "Invalid data" });
const subtotal = items.reduce((sum, item) => sum + item.price \* item.qty, 0);
const taxRate = 0.07;
const total = +(subtotal \* (1 + taxRate)).toFixed(2);
const invoice = {
id: `inv_${Date.now()}`,
userId,
items,
subtotal,
taxRate,
total,
createdAt: new Date().toISOString(),
status: "unpaid"
};
await db.set(invoice.id, invoice);
res.json(invoice);
});
// Example secure endpoint to fake mark invoices as paid (simulate integrating with Stripe later)
app.post("/api/invoices/:id/pay", async (req, res) => {
const invoice = await db.get(req.params.id);
if (!invoice) return res.status(404).json({ error: "Invoice not found" });
invoice.status = "paid";
invoice.paidAt = new Date().toISOString();
await db.set(invoice.id, invoice);
res.json(invoice);
});
app.listen(3000, () => console.log("Billing API running on Replit"));
import express from "express";
import crypto from "crypto";
import "dotenv/config";
const app = express();
app.use(express.raw({ type: "application/json" }));
// Verify incoming Stripe webhooks securely on Replit
app.post("/webhook/stripe", async (req, res) => {
const signature = req.headers["stripe-signature"];
const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET;
let event;
try {
const expectedSig = crypto
.createHmac("sha256", endpointSecret)
.update(req.body)
.digest("hex");
if (expectedSig !== signature) {
return res.status(400).send("Invalid signature");
}
event = JSON.parse(req.body.toString());
} catch (err) {
console.error("⚠️ Webhook signature verification failed.", err);
return res.sendStatus(400);
}
// Handle a successful payment event
if (event.type === "checkout.session.completed") {
const session = event.data.object;
console.log(`✅ Payment received for session ${session.id}`);
// TODO: update your Replit DB invoice as paid
}
res.json({ received: true });
});
app.listen(3001, () => console.log("Stripe webhook listener running on Replit"));
import express from "express";
import Database from "@replit/database";
import "dotenv/config";
const app = express();
const db = new Database();
app.use(express.json());
// Middleware to track user operation count (for metered billing)
app.use(async (req, res, next) => {
const userId = req.headers["x-user-id"];
if (!userId) return res.status(401).json({ error: "User ID required" });
const key = `usage_${userId}`;
const usage = (await db.get(key)) || { requests: 0, lastReset: new Date().toISOString() };
usage.requests += 1;
// auto reset usage every 30 days
const last = new Date(usage.lastReset);
const now = new Date();
if (now - last > 1000 _ 60 _ 60 _ 24 _ 30) {
usage.requests = 1;
usage.lastReset = now.toISOString();
}
await db.set(key, usage);
req.userId = userId;
req.usage = usage;
next();
});
// Endpoint to get current usage and calculate due charges dynamically
app.get("/api/billing/usage", async (req, res) => {
const { userId, usage } = req;
const unitPrice = 0.002; // $0.002 per request
const totalDue = parseFloat((usage.requests \* unitPrice).toFixed(4));
const invoice = {
userId,
requests: usage.requests,
totalDue,
lastReset: usage.lastReset,
};
res.json(invoice);
});
app.listen(3000, () => console.log("Usage-based billing API running on Replit"));

Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
To build a billing system on Replit, use a secure backend (like Node.js with Express), store API keys in Replit Secrets, and integrate a real payment provider such as Stripe. Replit can fully handle the logic, routes, and webhooks you need; the only step you should move outside Replit is storing sensitive data or dealing with heavy workloads (use external DB or Stripe’s Dashboard for that). Never hardcode secrets or depend on local file storage for invoices—Replit instances aren’t persistent that way. Use a small Express server that serves routes for payments and webhook events, set your Stripe secret keys in Replit’s “Secrets” panel, and deploy it. Handle callbacks inside a dedicated webhook route, tested via Replit’s web preview URL.
Create these files in your Replit project (if not already existing):
npm init -y
npm install express stripe body-parser
Go to the padlock icon (Secrets panel) in Replit sidebar and add:
Your API keys will be stored safely by Replit; they will not appear in version control.
This file initializes Express, sets up Stripe, creates a payment route, and listens for webhooks. It’s simple, reliable, and works fine on Replit’s always-on web server.
// index.js
import express from "express"
import Stripe from "stripe"
import bodyParser from "body-parser"
const app = express()
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY) // access the secret in Replit
app.use(bodyParser.json())
// Create a checkout session for payment
app.post("/create-checkout-session", async (req, res) => {
try {
const session = await stripe.checkout.sessions.create({
payment_method_types: ["card"],
line_items: [
{
price_data: {
currency: "usd",
product_data: { name: "Monthly Subscription" },
unit_amount: 1000, // $10.00
},
quantity: 1,
},
],
mode: "subscription",
success_url: `${req.headers.origin}/success`,
cancel_url: `${req.headers.origin}/cancel`,
})
res.json({ url: session.url })
} catch (e) {
res.status(500).json({ error: e.message })
}
})
// Stripe webhook for events like successful payments
app.post("/webhook", bodyParser.raw({ type: "application/json" }), (req, res) => {
const sig = req.headers["stripe-signature"]
let event
try {
event = stripe.webhooks.constructEvent(req.body, sig, process.env.STRIPE_WEBHOOK_SECRET)
} catch (err) {
console.log("Webhook signature verification failed.", err.message)
return res.sendStatus(400)
}
if (event.type === "checkout.session.completed") {
const session = event.data.object
console.log("Payment success:", session.id)
// here you can update user record in DB or send confirmation email
}
res.send()
})
app.listen(3000, () => console.log("Server running on port 3000"))
Click the “Run” button. Replit starts your web server and gives you a preview URL (something like https://your-repl-name.username.repl.co). In Stripe Dashboard, configure this URL plus /webhook as your endpoint.
If you want to test payments without going live, enable Stripe test mode and use their test cards (they always start with 4242…).
Replit is great for quick deployments, small SaaS tools, or startups validating billing flows. But remember that Replit servers sleep when inactive (unless you’re on Hacker/Pro plan or deploy as a Replit “Deployment”). If your billing system needs guaranteed uptime or webhook reliability, use Replit Deployments or proxy webhooks through a reliable server.
This setup will run perfectly for development, testing, and small production-scale payments. Keep your code modular and secrets isolated — and Replit will handle the rest nicely.
When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.