Skip to main content

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.

Instance Plans vs. Platform Plans

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:

  1. Go to stripe.com and click Start now
  2. Enter your email and create a password
  3. Verify your email address
  4. Complete business verification (can be done later for testing)
Test Mode

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

  1. In Stripe Dashboard, click Developers in the left sidebar
  2. Click API keys
  3. You'll see two keys:
    • Publishable key (pk_test_... or pk_live_...) - safe to expose
    • Secret key (sk_test_... or sk_live_...) - keep private!
  4. Click Reveal test key to copy your secret key
caution

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.

Default Plan

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

  1. Log into your kuploy-cloud dashboard as platform admin
  2. Go to SettingsBilling
  3. Enter your Stripe keys:
    • Stripe Secret Key: sk_test_... or sk_live_...
    • Stripe Publishable Key: pk_test_... or pk_live_...
  4. Click Save
  5. 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:

  1. In kuploy-cloud, go to SettingsPlans
  2. Verify that your plans appear with the correct names and pricing
  3. Stripe Products and Prices are auto-provisioned — no manual Price ID entry required
tip

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:

  1. Go to DevelopersWebhooks
  2. Click Add endpoint
  3. Enter your webhook URL:
    https://your-domain.com/api/webhooks/stripe
  4. Click Select events and add:
    • checkout.session.completed
    • customer.subscription.created
    • customer.subscription.updated
    • customer.subscription.deleted
    • invoice.paid
    • invoice.payment_failed
  5. Click Add endpoint
  6. On the endpoint page, click Reveal under Signing secret
  7. Copy the webhook secret (whsec_...)

In kuploy-cloud:

  1. Go to SettingsBilling
  2. Enter the Webhook Secret: whsec_...
  3. Click Save

8. Test Your Setup

Use Stripe test cards to verify everything works:

Card NumberResult
4242 4242 4242 4242Successful payment
4000 0000 0000 0002Card declined
4000 0000 0000 3220Requires 3D Secure

Use any future expiry date and any 3-digit CVC.

  1. Create a test user account
  2. Go to Billing and select a paid plan
  3. Complete checkout with a test card
  4. Verify the subscription appears in both kuploy-cloud and Stripe Dashboard

Going Live

When ready for production:

  1. Complete Stripe business verification
  2. Toggle off "Test mode" in Stripe Dashboard
  3. Update kuploy-cloud with live API keys (STRIPE_SECRET_KEY, STRIPE_PUBLISHABLE_KEY)
  4. Update webhook endpoint for live mode
  5. Trigger a license sync — kuploy-cloud auto-creates live Stripe Products/Prices from the plan catalog
  6. 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:

VariableDescription
STRIPE_SECRET_KEYStripe API secret key
STRIPE_WEBHOOK_SECRETWebhook signing secret
STRIPE_PUBLISHABLE_KEYStripe 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.

Never use db:push in production

kuploy-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:

ColumnTypeDescription
planIdtext (PK)Plan slug from kuploy.app template
nametextDisplay name
stripeProductIdtextAuto-provisioned Stripe Product ID
stripePriceIdtextAuto-provisioned Stripe Price ID
priceintegerPrice in cents
currencytextCurrency code (e.g., "cad", "usd")
billingCycletext"monthly" or "annual"
maxProjectsintegerProject limit
maxAppsintegerApp limit
maxDatabasesintegerDatabase limit
maxTeamMembersintegerTeam member limit
maxDomainsintegerTotal domain limit
maxCustomDomainsintegerCustom domain limit
maxFreeSubdomainsintegerFree subdomain limit
maxPurchasableDomainsintegerPurchasable domain limit
maxInstancesintegerInstance limit
maxStorageBytesintegerStorage limit in bytes
buildMinutesPerMonthintegerMonthly build minute limit
featuresjsonbFeature flags (whiteLabel, customDomains, domainPurchase, aiAssistant, multiServer, prioritySupport)
isDefaultbooleanWhether this is the default plan
isActivebooleanSoft-delete flag
descriptiontextPlan description

Troubleshooting

Checkout fails

  1. Verify STRIPE_SECRET_KEY is correct (test vs live mode)
  2. 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"
  3. Check logs: kubectl logs -n kuploy -l app.kubernetes.io/name=kuploy

Webhooks not received

  1. Verify webhook URL is publicly accessible
  2. Check STRIPE_WEBHOOK_SECRET matches Stripe Dashboard
  3. View webhook logs: Stripe Dashboard → Developers → Webhooks → Select endpoint

Plans not showing

  1. Verify license sync is running (plans are synced from kuploy.app):
    kubectl logs -n kuploy -l app.kubernetes.io/name=kuploy | grep "plan catalog"
  2. 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;"
  3. Verify isActive is true and at least one plan has isDefault = true
  4. If no plans exist, run the bootstrap seed: kubectl exec -it deployment/kuploy -n kuploy -- npm run db:seed