Charging for Access
An app with branded sign-in can charge its users, with your own Stripe account. Payments go straight to you; the platform stores your keys encrypted, takes no fee and never sees card details. Users choose a plan on the app's own sign-in pages, pay on Stripe's checkout page and land in the app; nothing about Stripe appears inside the app.
This is your Stripe account charging your app's users. It has nothing to do with your Strongly subscription, credits or FinOps: those are how Strongly bills you, and they are unaffected by anything here.
The two kinds of plan
| Single-user plan | Team plan | |
|---|---|---|
| Who pays | Each user pays for themself | One person, the team owner, pays per seat |
| Who gets in | That user | The owner and the people they invite, up to the number of seats; members pay nothing |
| Stripe price | A flat recurring price (for example $9 / month) | A recurring price per unit (for example $5 / month per seat) |
| Good for | Individual customers | Companies, departments, client teams |
An app can offer both kinds at once; the Plans page lists every plan you add.
Before you start
- Switch on branded sign-in for the app (the top of the app's Auth tab). The plan and billing pages live on the app's own sign-in pages, so paid access needs it.
- Have a Stripe account. Use Stripe's test mode while you set things up, then repeat the setup with live keys.
- Make sure the platform has email configured if you will sell team plans: team invitations go out by email.
Step 1: Connect your Stripe account
- Open the app, go to the Auth tab, and switch on Charge for access to this app with your own Stripe account. The payment section opens below the switch.
- Paste your Stripe secret key (
sk_live_..., orsk_test_...while testing). Click Save. The key is stored encrypted; afterwards the section only shows its mode and last four characters. - Copy the Webhook endpoint URL the section shows. It looks like
https://<your-platform>/api/stripe/webhook/app/<app-id>. - In Stripe, open Developers and then Webhooks, add an endpoint with that URL, and select these events:
checkout.session.completedcustomer.subscription.created,customer.subscription.updated,customer.subscription.deleted,customer.subscription.paused,customer.subscription.resumedinvoice.paidinvoice.payment_failed
- Open the new endpoint in Stripe and reveal its signing secret (
whsec_...). Paste it into Webhook signing secret in the payment section and click Save.
Without the webhook, payments succeed in Stripe but the platform never hears about them, so nobody gets access. Steps 3 to 5 are not optional.
Grace days (default 3) is how long a user keeps access after a payment fails while Stripe retries the card. After that, access pauses until the payment goes through; it resumes on its own when Stripe reports it paid.
Step 2a: Set up a single-user plan
-
In Stripe, under Product catalog, add a product (for example "Pro") with a recurring price, flat amount, for example
$9.00 / monthor$90.00 / year. Copy the price id (price_...). -
In the payment section, click Add plan and fill in:
Field Value Name What users see, for example "Pro" Stripe price id The price_...from step 1Description What the plan includes Kind Individual Trial days A free trial at checkout, or 0 -
Click Save plan.
That is it. With the switch on and this plan saved, every user of the app is asked to choose it.
What a user sees. They sign up (or sign in) on the app's sign-in page and are taken to the Plans page (https://<your-platform>/<url-name>/plans). They click Continue to payment, pay on Stripe's checkout page (card details never touch the platform), return to the app's sign-in page, and are in. On their Billing page (https://<your-platform>/<url-name>/billing) they see their plan and open Stripe's customer portal to change the card, see invoices or cancel.
Step 2b: Set up a team plan
-
In Stripe, under Product catalog, add a product (for example "Team seat") with a recurring price that is charged per unit, for example
$5.00 / month. In Stripe's price editor this is the default "Flat rate" pricing model with the price per unit; the platform passes the number of seats as the quantity. Do not use a flat amount meant for the whole team. Copy the price id. -
In the payment section, click Add plan and fill in:
Field Value Name For example "Team" Stripe price id The per-seat price_...from step 1Description For example "Per seat; invite your team" Kind Team Minimum seats The fewest seats a team may buy, for example 2 Trial days A free trial at checkout, or 0 -
Click Save plan.
What the team owner sees. They sign up, open the Plans page, choose the team plan, enter the number of seats including themself, and pay on Stripe for seats × price. Their Billing page then has a Your team panel:
- Seats: change the number at any time; Stripe prorates the difference on the next invoice. Seats cannot drop below the plan's minimum or below the people already in them.
- Members: who holds a seat, with a remove button for each (the owner cannot remove themself; they cancel in the Stripe portal instead).
- Invite someone: enter an email address. The invite takes a seat; when all seats are taken, add seats first. An open invite can be withdrawn.
What a team member sees. They receive an email with a link to https://<your-platform>/<url-name>/join/<token>. Opening it, they create an account (or sign in) with that same email address and are in the team and in the app. They are never asked to pay. If the app approves new accounts, they wait for approval first and then follow the link again. Their Billing page says their access is paid for by the team owner.
Managing subscriptions
The payment section lists every subscription: who pays, which plan, its status, seats used for a team, the renewal date and the Stripe subscription id. Statuses:
| Status | Meaning |
|---|---|
| Active | Paid up; the user (or team) is served |
| Payment failed (grace) | A payment failed; still served while Stripe retries, for the grace days |
| Suspended | Grace ran out; paused until Stripe reports a payment |
| Canceled | Ended, by the payer in the Stripe portal, by you removing the user, or by Stripe |
Refunds, invoices and disputes are handled in your Stripe dashboard.
Ending subscriptions. Removing a user from the app (the Users table on the Auth tab) cancels the subscription they pay for at once and frees any team seat they held, so nobody keeps paying for an app they cannot use. Removing a team owner therefore ends their whole team's subscription and the members' access with it. Deleting the app cancels every live subscription first. Both stop with Stripe's reason if Stripe refuses a cancellation.
Deactivating a user. Deactivating a user (the Users table on the Auth tab) pauses collection on every subscription they pay for: Stripe charges nothing while the account is deactivated, and nobody on that subscription is served (for a team, its members included). The subscription, its period and its seats are kept. Activating the account again resumes collection and access. The subscriptions table marks such a subscription Payer deactivated.
Stopping charging. Switch the toggle off: users are no longer asked for a plan. Existing subscriptions keep running in Stripe until they are canceled. While the toggle is on, branded sign-in cannot be switched off (the plan and billing pages live on it).
Who never needs a plan
You, the app's collaborators, its app admins and platform administrators always get in. Only the app's users (people who signed up through the branded pages, were added as app users, or reach a public app) are asked for a plan.
To let a particular user in without a plan (a comped customer, a tester), open the Users table on the Auth tab and click the gift button on their row: their Access column reads Free and they are never sent to the Plans page. Click it again to ask them for a plan like everyone else.
Links inside your app
A home app has no platform chrome, so the app provides its own links, each with target="_top" so the whole page navigates:
_strongly/billingopens the user's Billing page._strongly/plansopens the Plans page._strongly/sign-outsigns the user out (see Signing out).
Testing before going live
Use a Stripe test-mode secret key, test prices and a test webhook endpoint, and complete the checkout with Stripe's test card 4242 4242 4242 4242 (any future expiry, any CVC). Stripe's 4000 0000 0000 0341 card attaches but fails to charge, which exercises the grace period. When everything works, replace the key, the webhook endpoint and the price ids with their live-mode equivalents.
For your app's code
The signed-in user's token carries user.plan (id, kind of individual or team, and status) while a plan covers them, so the app can show or hide features by plan without calling anything. See User Identity Headers.
Over the API: GET/PUT /apps/:id/paid-access, PUT/DELETE /apps/:id/paid-access/plans/:planId and GET /apps/:id/paid-access/subscriptions in the Apps API.