Selling it as SaaS
Plans, credits and top-ups, Stripe Checkout and the customer portal, trial credits, plan feature switches, and which licence you need to charge customers.
9 min readUpdated Oct 17, 2026Applies to Adcraft 1.0.0
On this page
Adcraft is multi-tenant: every customer gets their own workspace with its own brands, team and credit balance, and you can bill them through Stripe. Everything on this page is optional if you only run it for yourself.
Important
Check your licence first. On CodeCanyon, the Regular License covers one end product whose end users are not charged. If your customers pay to use what you build with Adcraft (a subscription, credit packs, a setup fee, any paid plan), you need the Extended License. Read Envato's licence terms for the authoritative wording; this note is a summary, not legal advice.
How credits work
Every workspace has a credit balance. Generations spend credits; plans and top-ups add them. The balance is a ledger: every grant, spend and refund is a row you can see under Settings → Billing → Credit history.
| Event | Credits |
|---|---|
| New workspace (trial) | +10 by default, set under Admin → Settings |
| Plan payment (each paid Stripe invoice) | + the plan's credits |
| Top-up | +100 |
| Admin adjustment | Any amount, positive or negative, from Admin → Organisations |
| A generation | − its cost |
Credits do not expire, and cancelling a plan does not remove them.
Most generations (concepts, static ads, product videos and presenter videos started from a brief) are charged after they succeed, so a failure costs nothing. Character Studio work (portraits, looks, avatars, twins and character ads) reserves credits when it starts and refunds them if it fails, in part when only some looks fail.
What each generation costs
| Generation | Credits | Set in |
|---|---|---|
| A set of ad concepts for a brief | 1 | apps/app/src/pipelines/concepts.ts |
| Static ad, editable layout (every size) | 2 | STATIC_SCENE_CREDITS in apps/app/src/pipelines/static.ts |
| Static ad, AI-designed | The image model's credits, once per size generated | Admin → Models |
| Product video (about 15 seconds) | 20 for a full run, whichever video model is chosen. Regenerating some scenes costs a share of 20. | CREDIT_COSTS.productVideo15s |
| Presenter (UGC) video (about 30 seconds) | 40 for a full run. Regenerating only B-roll clips costs a share of a quarter of that. | CREDIT_COSTS.ugcVideo30s |
| A generated character portrait | The image model's credits per image | Admin → Models |
| HeyGen look | 3 per look | CREDIT_COSTS.heygenLook |
| HeyGen avatar from a photo or description | 5 | CREDIT_COSTS.heygenAvatar |
| Digital twin from footage | 25 | CREDIT_COSTS.digitalTwin |
| Brief help (Suggest and Improve), resizing and re-rendering an edited layout | Free |
CREDIT_COSTS lives in apps/app/src/server/billing.ts. Image model prices are per model and can be changed without a redeploy under Admin → Models (fractions are rounded up).
Note
Two entries in CREDIT_COSTS, concepts and staticAd, are only used for the price list shown on the billing page. The amounts actually charged for those two are the constants in concepts.ts and static.ts listed above, so change both places together. Video models also show a per-second credit figure in the catalogue, but product videos are charged the flat 20 above.
Tip
Credits are your price list, not your costs. Compare them with what each provider charges you (see AI models and providers) before you set plan prices.
Plans
Three plans and one top-up are defined in code, in PLANS and TOP_UP in apps/app/src/server/billing.ts:
| Plan | Display price | Credits per paid invoice | Brands (display text) |
|---|---|---|---|
| Starter | $29 / month | 150 | 1 |
| Studio | $79 / month | 500 | 3 |
| Agency | $199 / month | 1,500 | 10 |
| Top-up | $10 | 100 |
These are the defaults that ship with the code. To change a plan's name, display price, credits or bullet points, edit PLANS and rebuild. What a customer actually pays is the price you create in Stripe, so keep the two in step.
Note
Only the plan's credits and its feature switches (below) change what a workspace can do. The brand counts, ad-account counts and items such as "Priority generation queue" are shown on the plan cards and the marketing site, but they are not enforced in version 1.0.0.
Plan feature switches
Under Admin → Settings, each plan has three switches, all on by default:
| Switch | When off |
|---|---|
| Video | Product videos cannot be created |
| UGC | Presenter videos and Character Studio are unavailable |
| Publishing | The campaign builder is unavailable |
A workspace that has never bought a plan follows the Starter switches. A workspace's plan is its most recent subscription record, whatever that subscription's status.
Stripe setup
Stripe is optional. Without STRIPE_SECRET_KEY, see Running without Stripe below before you go live.
Create the products and prices in your Stripe account:
- a monthly recurring price for Starter, Studio and Agency;
- a one-time price for the credit top-up.
The amounts are yours to choose. Adcraft does not read product or price metadata: the plan and the number of credits come from the code.
Set the price IDs in the environment:
STRIPE_SECRET_KEY=sk_live_... STRIPE_PRICE_STARTER_MONTH=price_... STRIPE_PRICE_STUDIO_MONTH=price_... STRIPE_PRICE_AGENCY_MONTH=price_... STRIPE_PRICE_TOPUP=price_...Add a webhook endpoint in Stripe for
https://<your-domain>/api/stripe/webhookwith these events:checkout.session.completedcustomer.subscription.createdcustomer.subscription.updatedcustomer.subscription.deletedinvoice.paid
Set
STRIPE_WEBHOOK_SECRETto the endpoint's signing secret (whsec_…).Configure the customer portal in Stripe (Settings → Billing → Customer portal), so Manage payment method and invoices works. Only workspace owners can open it.
Test the whole flow with test keys (
sk_test_…) first. Stripe's test card4242 4242 4242 4242completes a test checkout.
For local testing, forward webhooks with the Stripe CLI:
stripe listen --api-key $STRIPE_SECRET_KEY --forward-to localhost:3000/api/stripe/webhookWhat happens after checkout
- Subscribing. An owner chooses a plan under Settings → Billing and pays on Stripe Checkout. The webhook records the subscription. Only owners can buy a plan or a top-up or open the portal; editors and viewers are told so.
- Credits arrive with
invoice.paid. Each paid invoice adds the plan's credits once (the invoice ID makes it idempotent). They do not arrive the moment checkout finishes; they arrive when Stripe confirms payment. - Top-ups add 100 credits when
checkout.session.completedarrives for the payment. - Cancelling in the portal marks the subscription cancelled. No more invoices, so no more credits; credits already granted stay.
- Failed payments mark the subscription past due (through
customer.subscription.updated). Nothing is restricted automatically, so follow up from Stripe. - Plan status does not gate features. A workspace keeps its last plan's feature switches after cancelling or falling past due; it simply stops receiving credits.
- Signatures. The webhook verifies Stripe's signature. Without
STRIPE_WEBHOOK_SECRETit answers501. - Only subscriptions started from the app count. Checkout tags the subscription with the workspace ID; a subscription created by hand in the Stripe dashboard has no such tag and is ignored.
- Return address. Checkout and the portal send customers back to the address they came from, worked out from the
HostandX-Forwarded-Protoheaders, so your reverse proxy must pass them through.
Checkout does not enable promotion codes or Stripe Tax. If you need either, add the options to the checkout.sessions.create calls in apps/app/src/server/billing-actions.ts.
Tip
If a plan payment succeeds but no credits appear, check the webhook deliveries in Stripe. Credits are granted when invoice.paid finds the subscription already recorded; if that event was processed before customer.subscription.created, grant the credits from Admin → Organisations.
Warning
Only monthly plans can be bought from the app in 1.0.0. The code can look up STRIPE_PRICE_<PLAN>_YEAR prices, but the billing page always starts a monthly checkout, and the "Yearly, save 20%" toggle on the marketing pricing page only changes the displayed numbers. If you do sell a yearly price some other way, note that credits are granted once per paid invoice, so a yearly invoice adds one plan allowance, not twelve.
Note
Choosing a different plan starts a new Stripe Checkout and creates a new subscription; it does not change the existing one. Ask customers to cancel the old plan in the portal, or cancel it for them in Stripe.
Running without Stripe
With STRIPE_SECRET_KEY unset, the plan cards and the top-up button stay visible. What happens when an owner clicks them depends on the environment:
- In development, the purchase is simulated: the workspace gets the plan and its credits immediately, with a note that the purchase was simulated.
- In production (
NODE_ENV=production, which the Docker image always uses), the purchase is refused with "Payments are not set up on this site yet, so plans and credits can't be bought right now." No plan or credits are granted. - In production with
ALLOW_SIMULATED_BILLING=true, purchases are simulated as in development.
Warning
ALLOW_SIMULATED_BILLING=true lets every workspace owner give themselves a plan and credits for free. Use it only for a demo site that should hand out free credits. If you are not charging, leave it unset and hand out credits from Admin → Organisations; the billing page remains visible either way.
Granting credits and plans by hand
From Admin → Organisations → (a workspace) a platform admin can:
- Adjust credits by any positive or negative amount, with a reason. It is written to the credit history and the audit log.
- Change plan to Starter, Studio or Agency, optionally granting that plan's credits at the same time.
Neither touches Stripe. Use them for free accounts, refunds and custom deals, not for customers who pay through Stripe.
Guardrails against runaway spend
Credits limit what a customer can generate, but your provider bills are in dollars. Adcraft also has guardrails. Platform defaults are set under Admin → Settings; some can be overridden per workspace by its owner under Settings → Guardrails:
| Guardrail | Default | Per-workspace override | Effect |
|---|---|---|---|
| Monthly provider cost cap | $200 per workspace per calendar month (UTC) | No | New generations are refused once the workspace's recorded provider cost reaches it. Leave blank to turn off. |
| Monthly credit limit | Off | Yes | Caps how many credits the workspace spends a month |
| Spend approval threshold | 100 (daily budget, in the ad account's currency) | Yes, higher, lower, or 0 for off |
Non-owners can only publish campaigns above it as paused |
| HeyGen custom voices per workspace | 2 | No | Limits cloned and designed voices, which use slots on your HeyGen account |
| Actions per minute | 120 | No | Sets the generation rate limit: a workspace may start a quarter of this number (at least 5) of each kind of generation per minute |
The cost cap uses the estimated cost recorded for each generation, so treat it as a safety net rather than an exact bill. The rate limit is kept in memory per process.
Costs and margins
- Admin → Generations & costs lists every AI call with its model, status, credits and an estimated provider cost, with cost per model.
- Admin → Models & pricing shows each model's credits next to its measured cost and margin.
- Admin → Billing shows subscriptions with links to Stripe, top-ups, plan distribution and an MRR figure. MRR is worked out from the plan list prices in
PLANS, not from Stripe. - Admin → Overview shows a gross margin estimate.
Estimated costs come from each model's approximate price in the catalogue; check them against your provider invoices.
Before you launch a SaaS
- Buy the right licence (see the top of this page).
- Configure Stripe. Without it, the billing page refuses purchases in production (or gives credits away if you set
ALLOW_SIMULATED_BILLING=true). - Decide on trial credits and the plan feature switches under Admin → Settings.
- Set spending limits with your AI providers, and review the guardrail defaults.
- Replace the prices and claims on the marketing pricing page with your own; see Rebranding.
- Publish your own terms, privacy policy and DPA; the included pages are templates.
- Read Known limitations so your sales pages promise only what the product does.