Setting Up Billing for Your Users
Enable subscriptions and payments for the organizations using your kuploy-cloud platform. This guide walks you through connecting Stripe and creating the plans your end users will subscribe to.
The plans you configure here are instance plans — the subscriptions your customers (organizations) pay to you. These are separate from your platform plan at kuploy.app.
You control the pricing, limits, and names of instance plans.
1. Create a Stripe Account
If you don't have a Stripe account yet:
- Go to stripe.com and click Start now
- Enter your email and create a password
- Verify your email address
- Complete business verification (can be done later for testing)
Stripe provides a Test mode for development. Toggle "Test mode" in the top-right of the Stripe Dashboard. Test mode uses fake money and test card numbers - perfect for setup and testing.
2. Get Your API Keys
- In Stripe Dashboard, click Developers in the left sidebar
- Click API keys
- You'll see two keys:
- Publishable key (
pk_test_...orpk_live_...) - safe to expose - Secret key (
sk_test_...orsk_live_...) - keep private!
- Publishable key (
- Click Reveal test key to copy your secret key
Never share your secret key. Never commit it to git. Only enter it in secure settings.
3. Stripe Products Are Created Automatically
You do not need to manually create Products or Prices in Stripe. When kuploy-cloud receives the plan catalog from kuploy.app during license sync, it automatically creates (or updates) matching Stripe Products and Prices using the pricing you defined in kuploy.app.
kuploy.app plan template kuploy-cloud (on sync)
┌───────────────────────┐ ┌─────────────────────────────┐
│ slug: "pro" │ │ upsert cloud_plans │
│ priceCents: 2000 │──sync────► │ └─► Stripe API: │
│ currency: "cad" │ │ Create/update Product │
│ billingCycle: "monthly│ │ Create/update Price │
└───────────────────────┘ └─────────────────────────────┘
If you change a plan's price in kuploy.app, the next sync updates the Stripe Price on kuploy-cloud automatically. Existing subscriptions are not retroactively changed — only new checkouts use the updated price.
Your default plan (the one marked as default in kuploy.app) does not get a Stripe Price — it's assigned automatically to new organizations without payment processing.
4. Connect Stripe in kuploy-cloud
- Log into your kuploy-cloud dashboard as platform admin
- Go to Settings → Billing
- Enter your Stripe keys:
- Stripe Secret Key:
sk_test_...orsk_live_... - Stripe Publishable Key:
pk_test_...orpk_live_...
- Stripe Secret Key:
- Click Save
- Enable the Billing Enabled toggle
5. Define Your Plans
Plans are created in kuploy.app under Customers > Plans. Each plan has explicit resource limits and feature flags — you set every value yourself.
When creating a plan, the form provides:
- Size presets (Small, Medium, Large) — pre-fill limits with sensible starting values. These are not tied to your platform subscription tier.
- Copy from plan — duplicate an existing plan's limits and features, then adjust.
- Manual entry — set each limit (projects, apps, databases, storage, etc.) and feature toggle individually.
All three approaches produce the same result: a plan with fully explicit limits. There is no hidden inheritance from platform plans.
After creating plans, they automatically sync to every kuploy-cloud instance during license sync. Pricing, limits, and features are all controlled from kuploy.app — no per-instance configuration needed.
6. Verify Your Plans
After the first sync completes:
- In kuploy-cloud, go to Settings → Plans
- Verify that your plans appear with the correct names and pricing
- Stripe Products and Prices are auto-provisioned — no manual Price ID entry required
The default plan (marked in kuploy.app) does not get a Stripe Price — it's assigned automatically to new organizations without payment.
7. Set Up Webhooks
Webhooks notify kuploy-cloud when payments succeed, fail, or subscriptions change.
In Stripe Dashboard:
- Go to Developers → Webhooks
- Click Add endpoint
- Enter your webhook URL:
https://your-domain.com/api/webhooks/stripe - Click Select events and add:
checkout.session.completedcustomer.subscription.createdcustomer.subscription.updatedcustomer.subscription.deletedinvoice.paidinvoice.payment_failed
- Click Add endpoint
- On the endpoint page, click Reveal under Signing secret
- Copy the webhook secret (
whsec_...)
In kuploy-cloud:
- Go to Settings → Billing
- Enter the Webhook Secret:
whsec_... - Click Save
8. Test Your Setup
Use Stripe test cards to verify everything works:
| Card Number | Result |
|---|---|
4242 4242 4242 4242 | Successful payment |
4000 0000 0000 0002 | Card declined |
4000 0000 0000 3220 | Requires 3D Secure |
Use any future expiry date and any 3-digit CVC.
- Create a test user account
- Go to Billing and select a paid plan
- Complete checkout with a test card
- Verify the subscription appears in both kuploy-cloud and Stripe Dashboard
Going Live
When ready for production:
- Complete Stripe business verification
- Toggle off "Test mode" in Stripe Dashboard
- Update kuploy-cloud with live API keys (
STRIPE_SECRET_KEY,STRIPE_PUBLISHABLE_KEY) - Update webhook endpoint for live mode
- Trigger a license sync — kuploy-cloud auto-creates live Stripe Products/Prices from the plan catalog
- Test with a real card (you can refund immediately)
Technical Reference
For Kubernetes deployments where UI configuration isn't available, use environment variables and database seeding.
Environment Variables
Set these in your Kubernetes secrets:
| Variable | Description |
|---|---|
STRIPE_SECRET_KEY | Stripe API secret key |
STRIPE_WEBHOOK_SECRET | Webhook signing secret |
STRIPE_PUBLISHABLE_KEY | Stripe publishable key |
# Encode values
echo -n "sk_live_your_key" | base64
# Update secret
kubectl patch secret kuploy-secrets -n kuploy \
-p '{"data":{"STRIPE_SECRET_KEY":"<base64-value>"}}'
# Restart to apply
kubectl rollout restart deployment/kuploy -n kuploy
Bootstrap Seeding
Plans are automatically synced from kuploy.app during license sync. The seed is only needed for first boot before the first sync completes:
# Run seed script (creates bootstrap plans that will be replaced on first sync)
kubectl exec -it deployment/kuploy -n kuploy -- npm run db:seed
After the first license sync, the plan catalog from kuploy.app replaces the bootstrap plans. Plan names, limits, pricing, and Stripe Products/Prices are all managed automatically — no manual Stripe configuration per instance.
Database Migrations
kuploy-cloud uses sequential Drizzle migrations (not drizzle-kit push). Migrations run automatically on every container startup via the Docker entrypoint — no manual steps needed.
db:push in productionkuploy-cloud shares the same PostgreSQL database as kuploy core. drizzle-kit push only knows about cloud_* tables and would delete all kuploy core tables. Always let the automatic migration runner handle schema changes.
cloud_plans Schema Reference
After plan sync, each plan in kuploy-cloud has the following columns:
| Column | Type | Description |
|---|---|---|
planId | text (PK) | Plan slug from kuploy.app template |
name | text | Display name |
stripeProductId | text | Auto-provisioned Stripe Product ID |
stripePriceId | text | Auto-provisioned Stripe Price ID |
price | integer | Price in cents |
currency | text | Currency code (e.g., "cad", "usd") |
billingCycle | text | "monthly" or "annual" |
maxProjects | integer | Project limit |
maxApps | integer | App limit |
maxDatabases | integer | Database limit |
maxTeamMembers | integer | Team member limit |
maxDomains | integer | Total domain limit |
maxCustomDomains | integer | Custom domain limit |
maxFreeSubdomains | integer | Free subdomain limit |
maxPurchasableDomains | integer | Purchasable domain limit |
maxInstances | integer | Instance limit |
maxStorageBytes | integer | Storage limit in bytes |
buildMinutesPerMonth | integer | Monthly build minute limit |
features | jsonb | Feature flags (whiteLabel, customDomains, domainPurchase, aiAssistant, multiServer, prioritySupport) |
isDefault | boolean | Whether this is the default plan |
isActive | boolean | Soft-delete flag |
description | text | Plan description |
Troubleshooting
Checkout fails
- Verify
STRIPE_SECRET_KEYis correct (test vs live mode) - Check that plan sync completed and Stripe Prices were auto-provisioned:
kubectl logs -n kuploy -l app.kubernetes.io/name=kuploy | grep -i "stripe.*price" - Check logs:
kubectl logs -n kuploy -l app.kubernetes.io/name=kuploy
Webhooks not received
- Verify webhook URL is publicly accessible
- Check
STRIPE_WEBHOOK_SECRETmatches Stripe Dashboard - View webhook logs: Stripe Dashboard → Developers → Webhooks → Select endpoint
Plans not showing
- Verify license sync is running (plans are synced from kuploy.app):
kubectl logs -n kuploy -l app.kubernetes.io/name=kuploy | grep "plan catalog" - Check plans exist locally:
kubectl exec -it postgresql-0 -n database -- psql $DATABASE_URL \
-c "SELECT \"planId\", name, price, currency, \"isActive\", \"isDefault\" FROM cloud_plans;" - Verify
isActiveistrueand at least one plan hasisDefault=true - If no plans exist, run the bootstrap seed:
kubectl exec -it deployment/kuploy -n kuploy -- npm run db:seed